commit 4afa16864ac8ae23a450abf95db023b0c8bea698 Author: Shea Levy Date: Thu Aug 29 07:09:34 2013 -0400 Use CredentialsProviders à la the Java API Signed-off-by: Shea Levy diff --git a/lib/Net/Amazon/Auth/CredentialsProvider.pm b/lib/Net/Amazon/Auth/CredentialsProvider.pm new file mode 100755 index 0000000..527acae --- /dev/null +++ b/lib/Net/Amazon/Auth/CredentialsProvider.pm @@ -0,0 +1,9 @@ +package Net::Amazon::Auth::CredentialsProvider; + +use Moose::Role 0.85; + +requires 'get_credentials'; + +sub refresh { } + +1; diff --git a/lib/Net/Amazon/Auth/CredentialsProviderChain.pm b/lib/Net/Amazon/Auth/CredentialsProviderChain.pm new file mode 100755 index 0000000..85cd8e0 --- /dev/null +++ b/lib/Net/Amazon/Auth/CredentialsProviderChain.pm @@ -0,0 +1,41 @@ +package Net::Amazon::Auth::CredentialsProviderChain; + +use Moose 0.85; +use MooseX::StrictConstructor 0.16; +use Net::Amazon::Auth::EnvironmentVariableCredentialsProvider; +use Net::Amazon::Auth::InstanceProfileCredentialsProvider; + +with 'Net::Amazon::Auth::CredentialsProvider'; + +has 'providers' => ( is => 'ro', isa => 'ArrayRef[Net::Amazon::Auth::CredentialsProvider]', required => 1 ); + +sub refresh { + my $self = shift; + + map { $_->refresh } @{$self->providers}; +} + +sub get_credentials { + my $self = shift; + + foreach my $provider (@{$self->providers}) { + my $res = $provider->get_credentials; + if (defined $res->{access_key_id}) { + return $res; + } + } + + return {}; +} + +sub default_chain { + my $class = shift; + return $class->new(providers => [ + Net::Amazon::Auth::EnvironmentVariableCredentialsProvider->new, + Net::Amazon::Auth::InstanceProfileCredentialsProvider->new + ]); +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm b/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm new file mode 100755 index 0000000..ac38a84 --- /dev/null +++ b/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm @@ -0,0 +1,26 @@ +package Net::Amazon::Auth::EnvironmentVariableCredentialsProvider; + +use Moose 0.85; +use MooseX::StrictConstructor 0.16; + +extends 'Net::Amazon::Auth::FixedCredentialsProvider'; + +around BUILDARGS => sub { + my $orig = shift; + my $class = shift; + + my %args = ( + access_key_id => $ENV{AWS_ACCESS_KEY_ID}, + secret_access_key => $ENV{AWS_SECRET_ACCESS_KEY} + ); + + if (exists $ENV{AWS_SESSION_TOKEN}) { + $args{session_token} = $ENV{AWS_SESSION_TOKEN}; + } + + return $class->$orig(\%args); +}; + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm b/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm new file mode 100755 index 0000000..21d56c7 --- /dev/null +++ b/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm @@ -0,0 +1,23 @@ +package Net::Amazon::Auth::FixedCredentialsProvider; + +use Moose 0.85; +use MooseX::StrictConstructor 0.16; + +with 'Net::Amazon::Auth::CredentialsProvider'; + +has 'access_key_id' => ( is => 'ro', isa => 'Maybe[Str]', required => 1 ); +has 'secret_access_key' => ( is => 'ro', isa => 'Maybe[Str]', required => 1 ); +has 'session_token' => ( is => 'ro', isa => 'Maybe[Str]', required => 0 ); + +sub get_credentials { + my $self = shift; + return { + access_key_id => $self->access_key_id, + secret_access_key => $self->secret_access_key, + session_token => $self->session_token + }; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm b/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm new file mode 100755 index 0000000..b9f826a --- /dev/null +++ b/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm @@ -0,0 +1,57 @@ +package Net::Amazon::Auth::InstanceProfileCredentialsProvider; + +use Moose 0.85; +use MooseX::StrictConstructor 0.16; +use HTTP::Date; +use JSON; + +with 'Net::Amazon::Auth::CredentialsProvider'; + +has '_ua' => ( is => 'rw', isa => 'LWP::UserAgent', required => 0 ); +has '_access_key_id' => ( is => 'rw', isa => 'Str', required => 0 ); +has '_secret_access_key' => ( is => 'rw', isa => 'Str', required => 0 ); +has '_session_token' => ( is => 'rw', isa => 'Str', required => 0 ); +has '_expiration_date' => ( is => 'rw', isa => 'Int', required => 0, default => 0 ); + +sub BUILD { + my $self = shift; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + $self->_ua($ua); +} + +sub refresh { + my $self = shift; + + my $role_name_response = + $self->_ua->get("http://169.254.169.254/latest/meta-data/iam/security-credentials/"); + if ($role_name_response->code == 200) { + my $credentials_response = $self->_ua->get("http://169.254.169.254/latest/meta-data/iam/security-credentials/" . $role_name_response->content); + + if ($credentials_response->code == 200) { + my $credentials = decode_json($credentials_response->content); + $self->_expiration_date(str2time($credentials->{Expiration})); + $self->_access_key_id($credentials->{AccessKeyId}); + $self->_secret_access_key($credentials->{SecretAccessKey}); + $self->_session_token($credentials->{Token}); + } + } +} + +sub get_credentials { + my $self = shift; + + if (time() - $self->_expiration_date > -5 * 60) { #Credentials available 5 minutes before expiry + $self->refresh; + } + + return { + access_key_id => $self->_access_key_id, + secret_access_key => $self->_secret_access_key, + session_token => $self->_session_token + }; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/Net/Amazon/S3.pm b/lib/Net/Amazon/S3.pm index 907113e..a369e4b 100755 --- a/lib/Net/Amazon/S3.pm +++ b/lib/Net/Amazon/S3.pm @@ -133,9 +133,10 @@ use LWP::UserAgent::Determined; use URI::Escape qw(uri_escape_utf8); use XML::LibXML; use XML::LibXML::XPathContext; +use Net::Amazon::Auth::FixedCredentialsProvider; +use Net::Amazon::Auth::CredentialsProviderChain; -has 'aws_access_key_id' => ( is => 'ro', isa => 'Str', required => 1 ); -has 'aws_secret_access_key' => ( is => 'ro', isa => 'Str', required => 1 ); +has 'credentials_provider' => ( is => 'ro', isa => 'Net::Amazon::Auth::CredentialsProvider', required => 0, default => sub { return Net::Amazon::Auth::CredentialsProviderChain->default_chain; } ); has 'secure' => ( is => 'ro', isa => 'Bool', required => 0, default => 0 ); has 'timeout' => ( is => 'ro', isa => 'Num', required => 0, default => 30 ); has 'retry' => ( is => 'ro', isa => 'Bool', required => 0, default => 0 ); @@ -144,7 +145,23 @@ has 'libxml' => ( is => 'rw', isa => 'XML::LibXML', required => 0 ); has 'ua' => ( is => 'rw', isa => 'LWP::UserAgent', required => 0 ); has 'err' => ( is => 'rw', isa => 'Maybe[Str]', required => 0 ); has 'errstr' => ( is => 'rw', isa => 'Maybe[Str]', required => 0 ); -has 'aws_session_token' => ( is => 'ro', isa => 'Str', required => 0 ); + +around BUILDARGS => sub { + my $orig = shift; + my $class = shift; + + my $args = $class->$orig(@_); + + if (exists $args->{aws_access_key_id}) { + $args->{credentials_provider} = Net::Amazon::Auth::FixedCredentialsProvider->new({ + access_key_id => $args->{aws_access_key_id}, + secret_access_key => $args->{aws_secret_access_key}, + session_token => $args->{aws_session_token} + }); + delete @{$args}{qw(aws_access_key_id aws_secret_access_key aws_session_token)}; + } + return $args; +}; __PACKAGE__->meta->make_immutable; @@ -223,6 +240,24 @@ sub BUILD { $self->ua($ua); $self->libxml( XML::LibXML->new ); + + die "No AWS credentials found!" unless defined $self->credentials_provider->get_credentials->{access_key_id}; +} + +# Backwards compatibility +sub aws_access_key_id { + my $self = shift; + return $self->credentials_provider->get_credentials->{access_key_id}; +} + +sub aws_secret_access_key { + my $self = shift; + return $self->credentials_provider->get_credentials->{secret_access_key}; +} + +sub aws_session_token { + my $self = shift; + return $self->credentials_provider->get_credentials->{session_token}; } =head2 buckets diff --git a/lib/Net/Amazon/S3/HTTPRequest.pm b/lib/Net/Amazon/S3/HTTPRequest.pm index 69c6327..d49e95b 100755 --- a/lib/Net/Amazon/S3/HTTPRequest.pm +++ b/lib/Net/Amazon/S3/HTTPRequest.pm @@ -63,8 +63,9 @@ sub query_string_authentication_uri { my $path = $self->path; my $headers = $self->headers; - my $aws_access_key_id = $self->s3->aws_access_key_id; - my $aws_secret_access_key = $self->s3->aws_secret_access_key; + my $creds = $self->s3->credentials_provider->get_credentials; + my $aws_access_key_id = $creds->{access_key_id}; + my $aws_secret_access_key = $creds->{secret_access_key}; my $canonical_string = $self->_canonical_string( $method, $path, $headers, $expires ); my $encoded_canonical @@ -86,9 +87,10 @@ sub query_string_authentication_uri { sub _add_auth_header { my ( $self, $headers, $method, $path ) = @_; - my $aws_access_key_id = $self->s3->aws_access_key_id; - my $aws_secret_access_key = $self->s3->aws_secret_access_key; - my $aws_session_token = $self->s3->aws_session_token; + my $creds = $self->s3->credentials_provider->get_credentials; + my $aws_access_key_id = $creds->{access_key_id}; + my $aws_secret_access_key = $creds->{secret_access_key}; + my $aws_session_token = $creds->{session_token}; if ( not $headers->header('Date') ) { $headers->header( Date => time2str(time) );