view release on metacpan or search on metacpan
Revision history for Perl extension Amazon-S3-Thin
0.32 2022-08-24T00:32:45Z
- [core] Fetch credentials from ECS task role (#43 @masawada++)
0.31 2022-08-11T17:20:41Z
- [bugfix] Fix a behavior of creating a session token with IMDSv2 (#41 @masawada++)
0.30 2020-08-28T11:53:24Z
- [core] support extra credential providers
0.29 2020-05-08T11:20:28Z
- [Signature v4] Make it work with signature v4. (use path style addressing for v4) (@showaltb)
- [Signature v4] Add `region` option. It is required by default (break B.C.) (@showaltb)
- [Signature v4] Add `signature_version` option. (@showaltb)
- [Core] Remove `host` option (break B.C.)
- [S3 api]Add API calls of `put_bucket` and `delete_bucket`
- [Core] Suppresses uri encoding of the bucket key. (@showaltb)
- [Environment] make it work with Carton on travis
- [Dev] Add `debug` option
- Internal design
- Add Credentials class to contain aws credentials
- Add Resource class to express URI
0.16 2015-05-08T04:51:36Z
- get_object() takes optinal headers
0.15 2015-04-26T06:44:48Z
- add garu to contributors
0.14 2015-04-25T19:36:32Z
eg/s3
lib/Amazon/S3/Thin.pm
lib/Amazon/S3/Thin/Credentials.pm
lib/Amazon/S3/Thin/Resource.pm
lib/Amazon/S3/Thin/Signer/V2.pm
lib/Amazon/S3/Thin/Signer/V4.pm
minil.toml
t/00_compile.t
t/01_accessors.t
t/01_new.t
t/02_credentials_ecs_container.t
t/02_credentials_metadata.t
t/02_signer_v2.t
t/02_signer_v4.t
t/03_request.t
t/04_request_v2.t
t/05_presigned_post.t
t/06_request_virtual_host.t
t/07_copy_200_error.t
xt/90_functional.t
xt/91_functional.t
xt/92_presigned_post.t
[](https://github.com/DQNEO/Amazon-S3-Thin/actions)
# NAME
Amazon::S3::Thin - A thin, lightweight, low-level Amazon S3 client
# SYNOPSIS
use Amazon::S3::Thin;
# Pass in explicit credentials
my $s3client = Amazon::S3::Thin->new({
aws_access_key_id => $aws_access_key_id,
aws_secret_access_key => $aws_secret_access_key,
aws_session_token => $aws_session_token, # optional
region => $region, # e.g. 'ap-northeast-1'
});
# Get credentials from environment
my $s3client = Amazon::S3::Thin->new({region => $region, credential_provider => 'env'});
# Get credentials from instance metadata
my $s3client = Amazon::S3::Thin->new({
region => $region,
credential_provider => 'metadata',
version => 2, # optional (default 2)
role => 'my-role', # optional
});
# Get credentials from ECS task role
my $s3client = Amazon::S3::Thin->new({
region => $region,
credential_provider => 'ecs_container',
});
my $bucket = "mybucket";
my $key = "dir/file.txt";
my $response;
$response = $s3client->put_bucket($bucket);
# CONSTRUCTOR
## new( \\%params )
**Receives:** hashref with options.
**Returns:** Amazon::S3::Thin object
It can receive the following arguments:
- `credential_provider` (**default: credentials**) - specify where to source credentials from. Options are:
- `credentials` - existing behaviour, pass in credentials via `aws_access_key_id` and `aws_secret_access_key`
- `env` - fetch credentials from environment variables
- `metadata` - fetch credentials from EC2 instance metadata service
- `ecs_container` - fetch credentials from ECS task role
- `region` - (**REQUIRED**) region of your buckets you access- (currently used only when signature version is 4)
- `aws_access_key_id` (**REQUIRED \[provider: credentials\]**) - an access key id
of your credentials.
- `aws_secret_access_key` (**REQUIRED \[provider: credentials\]**) - an secret access key
of your credentials.
- `version` (**OPTIONAL \[provider: metadata\]**) - version of metadata service to use, either 1 or 2.
[read more](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)
- `role` (**OPTIONAL \[provider: metadata\]**) - IAM instance role to use, otherwise the first is selected
- `secure` - whether to use https or not. Default is 0 (http).
- `ua` - a user agent object, compatible with LWP::UserAgent.
Default is an instance of [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent).
- `signature_version` - AWS signature version to use. Supported values
are 2 and 4. Default is 4.
- `debug` - debug option. Default is 0 (false).
If set 1, contents of HTTP request and response are shown on stderr
if ($version) {
printf "s3 %s\n", $VERSION;
exit 0;
}
if ($help) {
$self->help();
exit 0;
}
# https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
my $cred_file = $ENV{HOME} . "/.aws/credentials";
my $config_file = $ENV{HOME} . "/.aws/config";
if (-f $config_file and !defined $region) {
my $config = Config::Tiny->read($config_file)->{$profile};
$region = $config->{region};
}
if (-f $cred_file) {
my $crd = Config::Tiny->read($cred_file)->{$profile};
lib/Amazon/S3/Thin.pm view on Meta::CPAN
my $METADATA_PREFIX = 'x-amz-meta-';
my $MAIN_HOST = 's3.amazonaws.com';
sub new {
my $class = shift;
my $self = shift;
# If we have an explicitly-configured credential provider then use that here, otherwise
# existing behaviour will be followed
if ($self->{credential_provider} and $self->{credential_provider} eq 'env') {
$self->{credentials} = Amazon::S3::Thin::Credentials->from_env;
}
elsif ($self->{credential_provider} and $self->{credential_provider} eq 'metadata') {
$self->{credentials} = Amazon::S3::Thin::Credentials->from_metadata($self);
}
elsif ($self->{credential_provider} and $self->{credential_provider} eq 'ecs_container') {
$self->{credentials} = Amazon::S3::Thin::Credentials->from_ecs_container($self);
}
else {
# check existence of credentials
croak "No aws_access_key_id" unless $self->{aws_access_key_id};
croak "No aws_secret_access_key" unless $self->{aws_secret_access_key};
# wrap credentials
$self->{credentials} = Amazon::S3::Thin::Credentials->new(
$self->{aws_access_key_id},
$self->{aws_secret_access_key},
$self->{aws_session_token},
);
delete $self->{aws_access_key_id};
delete $self->{aws_secret_access_key};
delete $self->{aws_session_token};
}
delete $self->{credential_provider};
lib/Amazon/S3/Thin.pm view on Meta::CPAN
return $self;
}
sub _load_signer {
my $self = shift;
my $version = shift;
my $signer_class = "Amazon::S3::Thin::Signer::V$version";
eval "require $signer_class" or die $@;
if ($version == 2) {
return $signer_class->new($self->{credentials}, $MAIN_HOST);
} elsif ($version == 4) {
return $signer_class->new($self->{credentials}, $self->{region});
}
}
sub _default_ua {
my $self = shift;
my $ua = LWP::UserAgent->new(
keep_alive => 10,
requests_redirectable => [qw(GET HEAD DELETE PUT)],
lib/Amazon/S3/Thin.pm view on Meta::CPAN
__END__
=head1 NAME
Amazon::S3::Thin - A thin, lightweight, low-level Amazon S3 client
=head1 SYNOPSIS
use Amazon::S3::Thin;
# Pass in explicit credentials
my $s3client = Amazon::S3::Thin->new({
aws_access_key_id => $aws_access_key_id,
aws_secret_access_key => $aws_secret_access_key,
aws_session_token => $aws_session_token, # optional
region => $region, # e.g. 'ap-northeast-1'
});
# Get credentials from environment
my $s3client = Amazon::S3::Thin->new({region => $region, credential_provider => 'env'});
# Get credentials from instance metadata
my $s3client = Amazon::S3::Thin->new({
region => $region,
credential_provider => 'metadata',
version => 2, # optional (default 2)
role => 'my-role', # optional
});
# Get credentials from ECS task role
my $s3client = Amazon::S3::Thin->new({
region => $region,
credential_provider => 'ecs_container',
});
my $bucket = "mybucket";
my $key = "dir/file.txt";
my $response;
$response = $s3client->put_bucket($bucket);
lib/Amazon/S3/Thin.pm view on Meta::CPAN
=head2 new( \%params )
B<Receives:> hashref with options.
B<Returns:> Amazon::S3::Thin object
It can receive the following arguments:
=over 4
=item * C<credential_provider> (B<default: credentials>) - specify where to source credentials from. Options are:
=over 2
=item * C<credentials> - existing behaviour, pass in credentials via C<aws_access_key_id> and C<aws_secret_access_key>
=item * C<env> - fetch credentials from environment variables
=item * C<metadata> - fetch credentials from EC2 instance metadata service
=item * C<ecs_container> - fetch credentials from ECS task role
=back
=item * C<region> - (B<REQUIRED>) region of your buckets you access- (currently used only when signature version is 4)
=item * C<aws_access_key_id> (B<REQUIRED [provider: credentials]>) - an access key id
of your credentials.
=item * C<aws_secret_access_key> (B<REQUIRED [provider: credentials]>) - an secret access key
of your credentials.
=item * C<version> (B<OPTIONAL [provider: metadata]>) - version of metadata service to use, either 1 or 2.
L<read more|https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html>
=item * C<role> (B<OPTIONAL [provider: metadata]>) - IAM instance role to use, otherwise the first is selected
=item * C<secure> - whether to use https or not. Default is 0 (http).
=item * C<ua> - a user agent object, compatible with LWP::UserAgent.
Default is an instance of L<LWP::UserAgent>.
lib/Amazon/S3/Thin/Credentials.pm view on Meta::CPAN
package Amazon::S3::Thin::Credentials;
=head1 NAME
Amazon::S3::Thin::Credentials - AWS credentials data container
=head1 SYNOPSIS
my $credentials = Amazon::S3::Thin::Credentials->new(
$aws_access_key_id, $aws_secret_access_key,
# optional:
$aws_session_token
);
my $key = $credentials->access_key_id();
my $secret = $credentials->secret_access_key();
my $session_token = $credentials->session_token();
1;
=head1 DESCRIPTION
This module contains AWS credentials and provide getters to the data.
# Load from arguments
my $creds = Amazon::S3::Thin::Credentials->new($access_key, $secret_key, $session_token);
# Load from environment
my $creds = Amazon::S3::Thin::Credentials->from_env;
# Load from instance profile
my $creds = Amazon::S3::Thin::Credentials->from_metadata(role => 'foo', version => 2);
lib/Amazon/S3/Thin/Credentials.pm view on Meta::CPAN
my $self = {
key => $key,
secret => $secret,
session_token => $session_token,
};
return bless $self, $class;
}
=head2 from_env()
Instantiate C<Amazon::S3::Thin::Credentials> and attempts to populate the credentials from
current environment.
Croaks if either AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are not set but supports the
optional AWS_SESSION_TOKEN variable.
my $creds = Amazon::S3::Thin::Credentials->from_env;
=cut
sub from_env {
lib/Amazon/S3/Thin/Credentials.pm view on Meta::CPAN
my $self = {
key => $ENV{AWS_ACCESS_KEY_ID},
secret => $ENV{AWS_SECRET_ACCESS_KEY},
session_token => $ENV{AWS_SESSION_TOKEN}
};
return bless $self, $class;
}
=head2 from_metadata()
Instantiate C<Amazon::S3::Thin::Credentials> and attempts to populate the credentials from
the L<EC2 metadata service|https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html>. An instance can have multiple IAM
roles applied so you may optionally specify a role, otherwise the first one will be used.
In November 2019 AWS released L<version 2|https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/> of the instance metadata service which
is more secure against Server Side Request Forgery attacks. Using v2 is highly recommended thus
it is the default here.
my $creds = Amazon::S3::Thin::Credentials->from_metadata(
role => 'foo', # The name of the IAM role on the instance
version => 2 # Metadata service version - either 1 or 2
lib/Amazon/S3/Thin/Credentials.pm view on Meta::CPAN
$ua->default_header('X-aws-ec2-metadata-token' => $res->decoded_content);
}
return _instance_metadata($ua, $args->{role});
}
sub _instance_metadata {
my ($ua, $role) = @_;
my $res = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials');
croak 'Error querying metadata service for roles: ' . $res->decoded_content unless $res->is_success;
my @roles = split /\n/, $res->decoded_content;
return unless @roles > 0;
my $target_role = (defined $role and grep { $role eq $_ } @roles)
? $role
: $roles[0];
my $cred = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $target_role);
croak 'Error querying metadata service for credentials: ' . $cred->decoded_content unless $cred->is_success;
my $obj = eval { $JSON->decode($cred->decoded_content) };
croak "Invalid data returned from metadata service: $@" if $@;
return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
}
=head2 from_ecs_container()
Instantiate C<Amazon::S3::Thin::Credentials> and attempts to populate the credentials from
the L<ECS task role|https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html>.
my $creds = Amazon::S3::Thin::Credentials->from_ecs_container;
=cut
sub from_ecs_container {
my ($class, $args) = @_;
my $ua = $args->{ua} // LWP::UserAgent->new;
my $relative_uri = $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI};
croak 'The environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is not set' unless defined $relative_uri;
my $cred = $ua->get('http://169.254.170.2' . $relative_uri);
croak 'Error retrieving container credentials' unless $cred->is_success;
my $obj = eval { $JSON->decode($cred->decoded_content) };
croak "Invalid data returned: $@" if $@;
return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
}
=head2 access_key_id()
Returns access_key_id
lib/Amazon/S3/Thin/Signer/V2.pm view on Meta::CPAN
my $AMAZON_HEADER_PREFIX = 'x-amz-';
# reserved subresources such as acl or torrent
our @ordered_subresources = qw(
acl delete lifecycle location logging notification partNumber policy
requestPayment torrent uploadId uploads versionId versioning versions
website
);
sub new {
my ($class, $credentials, $host) = @_;
if (ref($credentials) ne 'Amazon::S3::Thin::Credentials') {
croak "credentials object is not given."
}
my $self = {
credentials => $credentials,
host => $host,
};
bless $self, $class;
}
sub sign
{
my ($self, $request) = @_;
$request->header(Date => HTTP::Date::time2str(time)) unless $request->header('Date');
if (defined $self->{credentials}->session_token) {
$request->header('X-Amz-Security-Token', $self->{credentials}->session_token);
}
my $host = $request->uri->host;
my $bucket = substr($host, 0, length($host) - length($self->{host}) - 1);
my $path = $bucket . $request->uri->path;
my $signature = $self->calculate_signature( $request->method, $path, $request->headers );
$request->header(
Authorization => sprintf("AWS %s:%s"
, $self->{credentials}->access_key_id,
, $signature));
}
# generate a canonical string for the given parameters. expires is optional and is
# only used by query string authentication.
sub calculate_signature {
my ($self, $method, $path, $headers, $expires) = @_;
my $string_to_sign = $self->string_to_sign( $method, $path, $headers, $expires );
my $hmac = Digest::HMAC_SHA1->new($self->{credentials}->secret_access_key);
$hmac->add($string_to_sign);
return MIME::Base64::encode_base64($hmac->digest, '');
}
sub string_to_sign {
my ($self, $method, $path, $headers, $expires) = @_;
my %interesting_headers = ();
while (my ($key, $value) = each %$headers) {
my $lk = lc $key;
lib/Amazon/S3/Thin/Signer/V4.pm view on Meta::CPAN
use strict;
use warnings;
use AWS::Signature4;
use Digest::SHA ();
use JSON::PP ();
use MIME::Base64 ();
use POSIX 'strftime';
sub new {
my ($class, $credentials, $region) = @_;
my $self = {
credentials => $credentials,
region => $region,
};
bless $self, $class;
}
=head1 METHODS
=head2 sign($request)
Signs supplied L<HTTP::Request> object, adding required AWS headers.
=cut
sub sign
{
my ($self, $request) = @_;
my $signer = $self->signer;
if (defined $self->{credentials}->session_token) {
$request->header('X-Amz-Security-Token', $self->{credentials}->session_token);
}
my $digest = Digest::SHA::sha256_hex($request->content);
$request->header('X-Amz-Content-SHA256', $digest);
$signer->sign($request, $self->{region}, $digest);
$request;
}
=head2 signer
Returns an L<AWS::Signature4> object for signing requests
=cut
sub signer
{
my $self = shift;
AWS::Signature4->new(
-access_key => $self->{credentials}->access_key_id,
-secret_key => $self->{credentials}->secret_access_key,
);
}
# This method is written referencing these botocore's implementations:
# https://github.com/boto/botocore/blob/00c4cadcf0996ef77a3a01b158f15c8fced9909b/botocore/signers.py#L602-L714
# https://github.com/boto/botocore/blob/00c4cadcf0996ef77a3a01b158f15c8fced9909b/botocore/signers.py#L459-L528
# https://github.com/boto/botocore/blob/00c4cadcf0996ef77a3a01b158f15c8fced9909b/botocore/auth.py#L585-L628
sub _generate_presigned_post {
my ($self, $bucket, $key, $fields, $conditions, $expires_in) = @_;
lib/Amazon/S3/Thin/Signer/V4.pm view on Meta::CPAN
}
push @$fields, 'x-amz-algorithm' => 'AWS4-HMAC-SHA256';
push @$fields, 'x-amz-credential' => $credential;
push @$fields, 'x-amz-date' => $datetime;
push @$conditions, {'x-amz-algorithm' => 'AWS4-HMAC-SHA256'};
push @$conditions, {'x-amz-credential' => $credential};
push @$conditions, {'x-amz-date' => $datetime};
my $session_token = $self->{credentials}->session_token;
if (defined $session_token) {
push @$fields, 'x-amz-security-token' => $session_token;
push @$conditions, {'x-amz-security-token' => $session_token};
}
my $policy = $self->_encode_policy({
expiration => $expiration,
conditions => $conditions,
});
push @$fields, policy => $policy;
t/02_credentials_ecs_container.t view on Meta::CPAN
use warnings;
use Amazon::S3::Thin::Credentials;
use Test::More;
my $arg = +{
credential_provider => 'ecs_container',
region => 'ap-northeast-1',
};
{
diag "retrieve credentials from the ECS task role";
local $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} = '/foobar';
my $ua = MockUA->new;
my $credentials = Amazon::S3::Thin::Credentials->from_ecs_container(+{ ua => $ua });
is_deeply $ua->requests, [
{
method => 'GET',
uri => 'http://169.254.170.2/foobar',
},
];
is $credentials->access_key_id, 'DUMMY-ACCESS-KEY';
is $credentials->secret_access_key, 'DUMMY-SECRET-ACCESS-KEY';
is $credentials->session_token, 'DUMMY-TOKEN';
}
{
diag "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is not set";
my $ua = MockUA->new;
eval {
my $credentials = Amazon::S3::Thin::Credentials->from_ecs_container(+{ ua => $ua });
};
like $@, qr/The environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is not set/;
}
{
diag "request failed";
local $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} = '/internal_server_error';
my $ua = MockUA->new;
eval {
my $credentials = Amazon::S3::Thin::Credentials->from_ecs_container(+{ ua => $ua });
};
like $@, qr/Error retrieving container credentials/;
}
{
diag "returned content is not JSON";
local $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} = '/not_json';
my $ua = MockUA->new;
eval {
my $credentials = Amazon::S3::Thin::Credentials->from_ecs_container(+{ ua => $ua });
};
like $@, qr/Invalid data returned: /;
}
done_testing;
package MockUA;
sub new {
t/02_credentials_metadata.t view on Meta::CPAN
my $arg = +{
credential_provider => 'metadata',
region => 'ap-northeast-1',
};
{
diag "IMDSv1";
my $ua = MockUA->new;
my $credentials = Amazon::S3::Thin::Credentials->from_metadata(+{
%$arg,
ua => $ua,
version => 1,
});
is_deeply $ua->requests, [
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials',
headers => {},
},
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials/DUMMY-INSTANCE-PROFILE-1',
headers => {},
},
];
is $credentials->access_key_id, 'DUMMY-ACCESS-KEY';
is $credentials->secret_access_key, 'DUMMY-SECRET-ACCESS-KEY';
is $credentials->session_token, 'DUMMY-TOKEN';
}
{
diag "IMDSv2";
my $ua = MockUA->new;
my $credentials = Amazon::S3::Thin::Credentials->from_metadata(+{
%$arg,
ua => $ua,
});
is_deeply $ua->requests, [
{
method => 'PUT',
uri => 'http://169.254.169.254/latest/api/token',
headers => { 'X-aws-ec2-metadata-token-ttl-seconds' => 90 },
},
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials',
headers => { 'X-aws-ec2-metadata-token' => 'DUMMY-METADATA-TOKEN' },
},
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials/DUMMY-INSTANCE-PROFILE-1',
headers => { 'X-aws-ec2-metadata-token' => 'DUMMY-METADATA-TOKEN' },
},
];
is $credentials->access_key_id, 'DUMMY-ACCESS-KEY';
is $credentials->secret_access_key, 'DUMMY-SECRET-ACCESS-KEY';
is $credentials->session_token, 'DUMMY-TOKEN';
}
{
diag "test when a role name is specified";
my $ua = MockUA->new;
my $credentials = Amazon::S3::Thin::Credentials->from_metadata(+{
%$arg,
ua => $ua,
role => 'DUMMY-INSTANCE-PROFILE-3',
version => 1,
});
is_deeply $ua->requests, [
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials',
headers => {},
},
{
method => 'GET',
uri => 'http://169.254.169.254/latest/meta-data/iam/security-credentials/DUMMY-INSTANCE-PROFILE-3',
headers => {},
},
];
is $credentials->access_key_id, 'DUMMY-ACCESS-KEY';
is $credentials->secret_access_key, 'DUMMY-SECRET-ACCESS-KEY';
is $credentials->session_token, 'DUMMY-TOKEN';
}
done_testing;
package MockUA;
sub new {
my $class = shift;
bless { requests => [], default_headers => {} }, $class;
}
t/02_credentials_metadata.t view on Meta::CPAN
sub is_success { !!1; }
sub decoded_content {
my $self = shift;
my $latest_uri = $self->{request}->{uri};
if ($latest_uri =~ qr{/latest/api/token$}) {
return 'DUMMY-METADATA-TOKEN';
} elsif ($latest_uri =~ qr{/latest/meta-data/iam/security-credentials$}) {
return <<'TEXT';
DUMMY-INSTANCE-PROFILE-1
DUMMY-INSTANCE-PROFILE-2
DUMMY-INSTANCE-PROFILE-3
TEXT
} elsif ($latest_uri =~ qr{/latest/meta-data/iam/security-credentials/.+$}) {
return <<'JSON';
{
"Code" : "Success",
"LastUpdated" : "2022-08-01T00:00:00Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "DUMMY-ACCESS-KEY",
"SecretAccessKey" : "DUMMY-SECRET-ACCESS-KEY",
"Token" : "DUMMY-TOKEN",
"Expiration" : "2022-08-01T12:00:00Z"
}
t/02_signer_v2.t view on Meta::CPAN
# no HTTP communication.
{
diag "test PUT request";
my $secret = "secretfoobar";
my $verb = "PUT";
my $path = "example/file.txt";
my $hdr = HTTP::Headers->new;
$hdr->header("content-length", 15);
$hdr->header("date", 'Sun, 01 Mar 2015 15:11:25 GMT');
my $credentials = Amazon::S3::Thin::Credentials->new('', $secret);
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $sig = $signer->calculate_signature($verb,$path,$hdr);
is $sig, "/WcvruHFtEoxcEMmdsfLJ6iZClw=";
}
{
diag "test GET request with single subresource";
my $secret = "somesecret";
my $verb = "GET";
my $path = "example/?delete";
my $date = 'Sun, 01 Mar 2015 15:11:25 GMT';
my $string_to_sign = "$verb\n\n\n$date\n/$path";
my $credentials = Amazon::S3::Thin::Credentials->new('', $secret);
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'IM6VtFJwF3z+lulFGux8tlU4N8Q=', "get with subresource";
}
{
diag "test GET request with single subresource";
my $secret = "somesecret";
my $verb = "GET";
my $path = 'example/?delete&versionId=4&invalid&acl&location="foo"';
my $date = 'Sun, 01 Mar 2015 15:11:25 GMT';
my $string_to_sign = "$verb\n\n\n$date\n/example/?acl&delete&location&versionId";
my $credentials = Amazon::S3::Thin::Credentials->new('', $secret);
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'OztMp7iNgvQVKXZQhXeIBz9UHnU=', "get with many subresources";
}
# test cases as described in
# http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationRequestCanonicalization
my $secret = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';
my $credentials = Amazon::S3::Thin::Credentials->new('', $secret);
{
diag "test Amazon example object GET";
my $verb = "GET";
my $date = "Tue, 27 Mar 2007 19:36:42 +0000";
my $path = "johnsmith/photos/puppy.jpg";
my $string_to_sign = "$verb\n\n\n$date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'bWq2s1WEIj+Ydj0vQ697zp+IXMU=', "puppy test (GET)";
t/02_signer_v2.t view on Meta::CPAN
{
diag "test Amazon example object PUT";
my $verb = "PUT";
my $date = "Tue, 27 Mar 2007 21:15:45 +0000";
my $path = "johnsmith/photos/puppy.jpg";
my $content_type = "image/jpeg";
my $content_length = 94328;
my $string_to_sign = "$verb\n\n$content_type\n$date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
$hdr->header("Content-Type", $content_type);
$hdr->header("Content-Length", $content_length);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
t/02_signer_v2.t view on Meta::CPAN
{
diag "test Amazon example list";
my $verb = "GET";
my $date = "Tue, 27 Mar 2007 19:42:41 +0000";
my $path = "johnsmith/?prefix=photos&max-keys=50&marker=puppy";
my $user_agent = "Mozilla/5.0";
my $string_to_sign = "$verb\n\n\n$date\n/johnsmith/";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
$hdr->header("User-Agent", $user_agent);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
t/02_signer_v2.t view on Meta::CPAN
}
{
diag "test Amazon example fetch";
my $verb = "GET";
my $date = "Tue, 27 Mar 2007 19:44:46 +0000";
my $path = "johnsmith/?acl";
my $string_to_sign = "$verb\n\n\n$date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'c2WLPFtWHVgbEmeEG93a4cG37dM=', "puppy fetch (GET)";
t/02_signer_v2.t view on Meta::CPAN
{
diag "test Amazon example delete";
my $verb = "DELETE";
my $date = "Tue, 27 Mar 2007 21:20:27 +0000";
my $path = "johnsmith/photos/puppy.jpg";
my $user_agent = "dotnet";
my $amz_date = "Tue, 27 Mar 2007 21:20:26 +0000";
my $string_to_sign = "$verb\n\n\n$amz_date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
$hdr->header("User-Agent", $user_agent);
$hdr->header("x-amz-date", $amz_date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
t/02_signer_v2.t view on Meta::CPAN
my $content_md5 = "4gJE4saaMU4BqNR0kLY+lw==";
my @x_amz_meta_reviewed_by = ('joe@johnsmith.net', 'jane@johnsmith.net');
my $x_amz_meta_filechecksum = '0x02661779';
my $x_amz_meta_checksum_algorithm = 'crc32';
my $content_disposition = "attachment; filename=database.dat";
my $content_encoding = "gzip";
my $content_length = 5913339;
my $string_to_sign = "PUT\n4gJE4saaMU4BqNR0kLY+lw==\napplication/x-download\nTue, 27 Mar 2007 21:06:08 +0000\nx-amz-acl:public-read\nx-amz-meta-checksumalgorithm:crc32\nx-amz-meta-filechecksum:0x02661779\nx-amz-meta-reviewedby:joe\@johnsmith.net,...
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
$hdr->header("User-Agent", $user_agent);
$hdr->header("x-amz-acl", $x_amz_acl);
$hdr->header("content-type", $content_type);
$hdr->header("Content-MD5", $content_md5);
$hdr->header("X-Amz-Meta-ReviewedBy", join(',' => @x_amz_meta_reviewed_by));
$hdr->header("X-Amz-Meta-FileChecksum", $x_amz_meta_filechecksum);
$hdr->header("X-Amz-Meta-ChecksumAlgorithm", $x_amz_meta_checksum_algorithm);
$hdr->header("Content-Disposition", $content_disposition);
t/02_signer_v2.t view on Meta::CPAN
}
{
diag "test Amazon example list buckets";
my $verb = "GET";
my $date = "Wed, 28 Mar 2007 01:29:59 +0000";
my $path = "";
my $string_to_sign = "$verb\n\n\n$date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'qGdzdERIC03wnaRNKh6OqZehG9s=', "puppy list buckets (GET)";
}
{
diag "test Amazon example unicode keys";
my $verb = "GET";
my $date = "Wed, 28 Mar 2007 01:49:49 +0000";
my $path = "dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re";
my $string_to_sign = "$verb\n\n\n$date\n/$path";
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials);
my $hdr = HTTP::Headers->new;
$hdr->header("Date", $date);
is(
$signer->string_to_sign($verb,$path,$hdr),
$string_to_sign,
'string to sign'
);
my $sig = $signer->calculate_signature($verb, $path, $hdr);
is $sig, 'DNEZGsoieTZ92F3bUfSPQcbGmlM=', "puppy unicode keys";
}
{
diag "test sign";
my $request = HTTP::Request->new(GET => 'https://mybucket.s3.amazonaws.com/myfile.txt');
$request->header('Date' => 'Wed, 28 Mar 2007 01:49:49 +0000');
my $credentials = Amazon::S3::Thin::Credentials->new('accesskey', 'secretkey');
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials, 's3.amazonaws.com');
$signer->sign($request);
is_deeply ($request->headers, {
authorization => 'AWS accesskey:Up4jVMLZzEbhnf+Thj0XJ68JREs=',
date => 'Wed, 28 Mar 2007 01:49:49 +0000',
}, 'Request headers');
}
{
diag "test sign (session token)";
my $request = HTTP::Request->new(GET => 'https://mybucket.s3.amazonaws.com/myfile.txt');
$request->header('Date' => 'Wed, 28 Mar 2007 01:49:49 +0000');
my $credentials = Amazon::S3::Thin::Credentials->new('accesskey', 'secretkey', 'sessiontoken');
my $signer = Amazon::S3::Thin::Signer::V2->new($credentials, 's3.amazonaws.com');
$signer->sign($request);
my $headers = $request->headers;
delete $headers->{'::std_case'};
is_deeply ($headers, {
authorization => 'AWS accesskey:jALzlsXtPsSS7qFbE7l2f7Dpx5Y=',
date => 'Wed, 28 Mar 2007 01:49:49 +0000',
'x-amz-security-token' => 'sessiontoken',
}, 'Request headers');
}
t/02_signer_v4.t view on Meta::CPAN
use strict;
use warnings;
use Amazon::S3::Thin::Signer::V4;
use Amazon::S3::Thin::Credentials;
use Test::More;
use HTTP::Request;
my $credentials = Amazon::S3::Thin::Credentials->new('accesskey', 'secretkey');
{
diag "test signer";
my $signer = Amazon::S3::Thin::Signer::V4->new($credentials);
my $signer_signer = $signer->signer;
isa_ok($signer_signer, 'AWS::Signature4', 'signer');
is_deeply($signer_signer, {
access_key => 'accesskey',
secret_key => 'secretkey',
}, 'signer keys');
}
{
diag "test sign";
my $request = HTTP::Request->new(GET => 'https://mybucket.s3.amazonaws.com/myfile.txt');
$request->header('Date' => 'Wed, 28 Mar 2007 01:49:49 +0000');
my $signer = Amazon::S3::Thin::Signer::V4->new($credentials);
$signer->sign($request);
my $headers = [ sort split /\n/, $request->headers->as_string ];
is_deeply ($headers, [
'Authorization: AWS4-HMAC-SHA256 Credential=accesskey/20070328/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=0dea3c9b65eede067ce9e38d48558a63924a6a08a8d21c27cfd7de50e5c78d4b',
'Date: Wed, 28 Mar 2007 01:49:49 +0000',
'Host: mybucket.s3.amazonaws.com',
'X-Amz-Content-SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
'X-Amz-Date: 20070328T014949Z',
], 'Request headers');
}
{
diag "test sign (session token)";
my $request = HTTP::Request->new(GET => 'https://mybucket.s3.amazonaws.com/myfile.txt');
$request->header('Date' => 'Wed, 28 Mar 2007 01:49:49 +0000');
my $credentials = Amazon::S3::Thin::Credentials->new('accesskey', 'secretkey', 'sessiontoken');
my $signer = Amazon::S3::Thin::Signer::V4->new($credentials);
$signer->sign($request);
my $headers = [ sort split /\n/, $request->headers->as_string ];
is_deeply ($headers, [
'Authorization: AWS4-HMAC-SHA256 Credential=accesskey/20070328/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=54a046a4241ef546a30aec8d9e9a1e91c2d095be24baaef1797350cd2cfef2fd',
'Date: Wed, 28 Mar 2007 01:49:49 +0000',
'Host: mybucket.s3.amazonaws.com',
'X-Amz-Content-SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
'X-Amz-Date: 20070328T014949Z',
'X-Amz-Security-Token: sessiontoken',
], 'Request headers');
xt/90_functional.t view on Meta::CPAN
use Amazon::S3::Thin;
if (!$ENV{EXTENDED_TESTING}) {
plan skip_all => 'Skip functional test because it would call S3 APIs and charge real money. $ENV{EXTENDED_TESTING} is not set.';
}
my $debug = 1;
my $use_https = 1;
my $config_file = $ENV{HOME} . '/.aws/credentials';
my $profile = 's3thin';
my $bucket = $ENV{TEST_S3THIN_BUCKET} || 'dqneo-private-test';
my $region = 'ap-northeast-1';
my $host = "s3.$region.amazonaws.com";
my $crd = Config::Tiny->read($config_file)->{$profile};
my $arg = {
%$crd,
region => $region,
xt/91_functional.t view on Meta::CPAN
use Amazon::S3::Thin;
if (!$ENV{EXTENDED_TESTING}) {
plan skip_all => 'Skip functional test because it would call S3 APIs and charge real money. $ENV{EXTENDED_TESTING} is not set.';
}
my $debug = 1;
my $use_https = 1;
my $config_file = $ENV{HOME} . '/.aws/credentials';
my $profile = 's3thin';
my $bucket = $ENV{TEST_S3THIN_BUCKET} || 'dqneo-private-test';
my $crd = Config::Tiny->read($config_file)->{$profile};
sub test_with_existing_bucket {
my $crd = shift;
my $arg = shift;
diag('Testing with existing resources.');
xt/92_presigned_post.t view on Meta::CPAN
use Amazon::S3::Thin;
if (!$ENV{EXTENDED_TESTING}) {
plan skip_all => 'Skip functional test because it would call S3 APIs and charge real money. $ENV{EXTENDED_TESTING} is not set.';
}
my $debug = 1;
my $use_https = 1;
my $config_file = $ENV{HOME} . '/.aws/credentials';
my $profile = 's3thin';
my $bucket = $ENV{TEST_S3THIN_BUCKET} || 'dqneo-private-test';
my $filename = 'xt/upload.txt';
my $content = do {
open my $fh, '<', $filename or die $!;
local $/; <$fh>;
};
my $crd = Config::Tiny->read($config_file)->{$profile};
xt/94_virtual_host.t view on Meta::CPAN
use Amazon::S3::Thin;
if (!$ENV{EXTENDED_TESTING}) {
plan skip_all => 'Skip functional test because it would call S3 APIs and charge real money. $ENV{EXTENDED_TESTING} is not set.';
}
my $debug = 1;
my $use_https = 1;
my $config_file = $ENV{HOME} . '/.aws/credentials';
my $profile = 's3thin';
my $bucket = $ENV{TEST_S3THIN_BUCKET} || 'dqneo-private-test';
my $region = 'ap-northeast-1';
my $host = "$bucket.s3.amazonaws.com";
my $crd = Config::Tiny->read($config_file)->{$profile};
my $arg = {
%$crd,
region => $region,
xt/95_delete_multiple_objects.t view on Meta::CPAN
use Amazon::S3::Thin;
if (!$ENV{EXTENDED_TESTING}) {
plan skip_all => 'Skip functional test because it would call S3 APIs and charge real money. $ENV{EXTENDED_TESTING} is not set.';
}
my $debug = 1;
my $use_https = 1;
my $config_file = $ENV{HOME} . '/.aws/credentials';
my $profile = 's3thin';
my $bucket = $ENV{TEST_S3THIN_BUCKET} || 'dqneo-private-test';
my $region = 'ap-northeast-1';
my $host = "s3.$region.amazonaws.com";
my $crd = Config::Tiny->read($config_file)->{$profile};
my $arg = {
%$crd,
region => $region,