lol
1commit 4afa16864ac8ae23a450abf95db023b0c8bea698
2Author: Shea Levy <shea@shealevy.com>
3Date: Thu Aug 29 07:09:34 2013 -0400
4
5 Use CredentialsProviders à la the Java API
6
7 Signed-off-by: Shea Levy <shea@shealevy.com>
8
9diff --git a/lib/Net/Amazon/Auth/CredentialsProvider.pm b/lib/Net/Amazon/Auth/CredentialsProvider.pm
10new file mode 100755
11index 0000000..527acae
12--- /dev/null
13+++ b/lib/Net/Amazon/Auth/CredentialsProvider.pm
14@@ -0,0 +1,9 @@
15+package Net::Amazon::Auth::CredentialsProvider;
16+
17+use Moose::Role 0.85;
18+
19+requires 'get_credentials';
20+
21+sub refresh { }
22+
23+1;
24diff --git a/lib/Net/Amazon/Auth/CredentialsProviderChain.pm b/lib/Net/Amazon/Auth/CredentialsProviderChain.pm
25new file mode 100755
26index 0000000..85cd8e0
27--- /dev/null
28+++ b/lib/Net/Amazon/Auth/CredentialsProviderChain.pm
29@@ -0,0 +1,41 @@
30+package Net::Amazon::Auth::CredentialsProviderChain;
31+
32+use Moose 0.85;
33+use MooseX::StrictConstructor 0.16;
34+use Net::Amazon::Auth::EnvironmentVariableCredentialsProvider;
35+use Net::Amazon::Auth::InstanceProfileCredentialsProvider;
36+
37+with 'Net::Amazon::Auth::CredentialsProvider';
38+
39+has 'providers' => ( is => 'ro', isa => 'ArrayRef[Net::Amazon::Auth::CredentialsProvider]', required => 1 );
40+
41+sub refresh {
42+ my $self = shift;
43+
44+ map { $_->refresh } @{$self->providers};
45+}
46+
47+sub get_credentials {
48+ my $self = shift;
49+
50+ foreach my $provider (@{$self->providers}) {
51+ my $res = $provider->get_credentials;
52+ if (defined $res->{access_key_id}) {
53+ return $res;
54+ }
55+ }
56+
57+ return {};
58+}
59+
60+sub default_chain {
61+ my $class = shift;
62+ return $class->new(providers => [
63+ Net::Amazon::Auth::EnvironmentVariableCredentialsProvider->new,
64+ Net::Amazon::Auth::InstanceProfileCredentialsProvider->new
65+ ]);
66+}
67+
68+__PACKAGE__->meta->make_immutable;
69+
70+1;
71diff --git a/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm b/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm
72new file mode 100755
73index 0000000..ac38a84
74--- /dev/null
75+++ b/lib/Net/Amazon/Auth/EnvironmentVariableCredentialsProvider.pm
76@@ -0,0 +1,26 @@
77+package Net::Amazon::Auth::EnvironmentVariableCredentialsProvider;
78+
79+use Moose 0.85;
80+use MooseX::StrictConstructor 0.16;
81+
82+extends 'Net::Amazon::Auth::FixedCredentialsProvider';
83+
84+around BUILDARGS => sub {
85+ my $orig = shift;
86+ my $class = shift;
87+
88+ my %args = (
89+ access_key_id => $ENV{AWS_ACCESS_KEY_ID},
90+ secret_access_key => $ENV{AWS_SECRET_ACCESS_KEY}
91+ );
92+
93+ if (exists $ENV{AWS_SESSION_TOKEN}) {
94+ $args{session_token} = $ENV{AWS_SESSION_TOKEN};
95+ }
96+
97+ return $class->$orig(\%args);
98+};
99+
100+__PACKAGE__->meta->make_immutable;
101+
102+1;
103diff --git a/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm b/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm
104new file mode 100755
105index 0000000..21d56c7
106--- /dev/null
107+++ b/lib/Net/Amazon/Auth/FixedCredentialsProvider.pm
108@@ -0,0 +1,23 @@
109+package Net::Amazon::Auth::FixedCredentialsProvider;
110+
111+use Moose 0.85;
112+use MooseX::StrictConstructor 0.16;
113+
114+with 'Net::Amazon::Auth::CredentialsProvider';
115+
116+has 'access_key_id' => ( is => 'ro', isa => 'Maybe[Str]', required => 1 );
117+has 'secret_access_key' => ( is => 'ro', isa => 'Maybe[Str]', required => 1 );
118+has 'session_token' => ( is => 'ro', isa => 'Maybe[Str]', required => 0 );
119+
120+sub get_credentials {
121+ my $self = shift;
122+ return {
123+ access_key_id => $self->access_key_id,
124+ secret_access_key => $self->secret_access_key,
125+ session_token => $self->session_token
126+ };
127+}
128+
129+__PACKAGE__->meta->make_immutable;
130+
131+1;
132diff --git a/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm b/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm
133new file mode 100755
134index 0000000..b9f826a
135--- /dev/null
136+++ b/lib/Net/Amazon/Auth/InstanceProfileCredentialsProvider.pm
137@@ -0,0 +1,57 @@
138+package Net::Amazon::Auth::InstanceProfileCredentialsProvider;
139+
140+use Moose 0.85;
141+use MooseX::StrictConstructor 0.16;
142+use HTTP::Date;
143+use JSON;
144+
145+with 'Net::Amazon::Auth::CredentialsProvider';
146+
147+has '_ua' => ( is => 'rw', isa => 'LWP::UserAgent', required => 0 );
148+has '_access_key_id' => ( is => 'rw', isa => 'Str', required => 0 );
149+has '_secret_access_key' => ( is => 'rw', isa => 'Str', required => 0 );
150+has '_session_token' => ( is => 'rw', isa => 'Str', required => 0 );
151+has '_expiration_date' => ( is => 'rw', isa => 'Int', required => 0, default => 0 );
152+
153+sub BUILD {
154+ my $self = shift;
155+ my $ua = LWP::UserAgent->new;
156+ $ua->timeout(10);
157+ $self->_ua($ua);
158+}
159+
160+sub refresh {
161+ my $self = shift;
162+
163+ my $role_name_response =
164+ $self->_ua->get("http://169.254.169.254/latest/meta-data/iam/security-credentials/");
165+ if ($role_name_response->code == 200) {
166+ my $credentials_response = $self->_ua->get("http://169.254.169.254/latest/meta-data/iam/security-credentials/" . $role_name_response->content);
167+
168+ if ($credentials_response->code == 200) {
169+ my $credentials = decode_json($credentials_response->content);
170+ $self->_expiration_date(str2time($credentials->{Expiration}));
171+ $self->_access_key_id($credentials->{AccessKeyId});
172+ $self->_secret_access_key($credentials->{SecretAccessKey});
173+ $self->_session_token($credentials->{Token});
174+ }
175+ }
176+}
177+
178+sub get_credentials {
179+ my $self = shift;
180+
181+ if (time() - $self->_expiration_date > -5 * 60) { #Credentials available 5 minutes before expiry
182+ $self->refresh;
183+ }
184+
185+ return {
186+ access_key_id => $self->_access_key_id,
187+ secret_access_key => $self->_secret_access_key,
188+ session_token => $self->_session_token
189+ };
190+}
191+
192+__PACKAGE__->meta->make_immutable;
193+
194+1;
195diff --git a/lib/Net/Amazon/S3.pm b/lib/Net/Amazon/S3.pm
196index 907113e..a369e4b 100755
197--- a/lib/Net/Amazon/S3.pm
198+++ b/lib/Net/Amazon/S3.pm
199@@ -133,9 +133,10 @@ use LWP::UserAgent::Determined;
200 use URI::Escape qw(uri_escape_utf8);
201 use XML::LibXML;
202 use XML::LibXML::XPathContext;
203+use Net::Amazon::Auth::FixedCredentialsProvider;
204+use Net::Amazon::Auth::CredentialsProviderChain;
205
206-has 'aws_access_key_id' => ( is => 'ro', isa => 'Str', required => 1 );
207-has 'aws_secret_access_key' => ( is => 'ro', isa => 'Str', required => 1 );
208+has 'credentials_provider' => ( is => 'ro', isa => 'Net::Amazon::Auth::CredentialsProvider', required => 0, default => sub { return Net::Amazon::Auth::CredentialsProviderChain->default_chain; } );
209 has 'secure' => ( is => 'ro', isa => 'Bool', required => 0, default => 0 );
210 has 'timeout' => ( is => 'ro', isa => 'Num', required => 0, default => 30 );
211 has 'retry' => ( is => 'ro', isa => 'Bool', required => 0, default => 0 );
212@@ -144,7 +145,23 @@ has 'libxml' => ( is => 'rw', isa => 'XML::LibXML', required => 0 );
213 has 'ua' => ( is => 'rw', isa => 'LWP::UserAgent', required => 0 );
214 has 'err' => ( is => 'rw', isa => 'Maybe[Str]', required => 0 );
215 has 'errstr' => ( is => 'rw', isa => 'Maybe[Str]', required => 0 );
216-has 'aws_session_token' => ( is => 'ro', isa => 'Str', required => 0 );
217+
218+around BUILDARGS => sub {
219+ my $orig = shift;
220+ my $class = shift;
221+
222+ my $args = $class->$orig(@_);
223+
224+ if (exists $args->{aws_access_key_id}) {
225+ $args->{credentials_provider} = Net::Amazon::Auth::FixedCredentialsProvider->new({
226+ access_key_id => $args->{aws_access_key_id},
227+ secret_access_key => $args->{aws_secret_access_key},
228+ session_token => $args->{aws_session_token}
229+ });
230+ delete @{$args}{qw(aws_access_key_id aws_secret_access_key aws_session_token)};
231+ }
232+ return $args;
233+};
234
235 __PACKAGE__->meta->make_immutable;
236
237@@ -223,6 +240,24 @@ sub BUILD {
238
239 $self->ua($ua);
240 $self->libxml( XML::LibXML->new );
241+
242+ die "No AWS credentials found!" unless defined $self->credentials_provider->get_credentials->{access_key_id};
243+}
244+
245+# Backwards compatibility
246+sub aws_access_key_id {
247+ my $self = shift;
248+ return $self->credentials_provider->get_credentials->{access_key_id};
249+}
250+
251+sub aws_secret_access_key {
252+ my $self = shift;
253+ return $self->credentials_provider->get_credentials->{secret_access_key};
254+}
255+
256+sub aws_session_token {
257+ my $self = shift;
258+ return $self->credentials_provider->get_credentials->{session_token};
259 }
260
261 =head2 buckets
262diff --git a/lib/Net/Amazon/S3/HTTPRequest.pm b/lib/Net/Amazon/S3/HTTPRequest.pm
263index 69c6327..d49e95b 100755
264--- a/lib/Net/Amazon/S3/HTTPRequest.pm
265+++ b/lib/Net/Amazon/S3/HTTPRequest.pm
266@@ -63,8 +63,9 @@ sub query_string_authentication_uri {
267 my $path = $self->path;
268 my $headers = $self->headers;
269
270- my $aws_access_key_id = $self->s3->aws_access_key_id;
271- my $aws_secret_access_key = $self->s3->aws_secret_access_key;
272+ my $creds = $self->s3->credentials_provider->get_credentials;
273+ my $aws_access_key_id = $creds->{access_key_id};
274+ my $aws_secret_access_key = $creds->{secret_access_key};
275 my $canonical_string
276 = $self->_canonical_string( $method, $path, $headers, $expires );
277 my $encoded_canonical
278@@ -86,9 +87,10 @@ sub query_string_authentication_uri {
279
280 sub _add_auth_header {
281 my ( $self, $headers, $method, $path ) = @_;
282- my $aws_access_key_id = $self->s3->aws_access_key_id;
283- my $aws_secret_access_key = $self->s3->aws_secret_access_key;
284- my $aws_session_token = $self->s3->aws_session_token;
285+ my $creds = $self->s3->credentials_provider->get_credentials;
286+ my $aws_access_key_id = $creds->{access_key_id};
287+ my $aws_secret_access_key = $creds->{secret_access_key};
288+ my $aws_session_token = $creds->{session_token};
289
290 if ( not $headers->header('Date') ) {
291 $headers->header( Date => time2str(time) );