File Coverage

blib/lib/Amazon/Credentials.pm
Criterion Covered Total %
statement 451 618 72.9
branch 139 264 52.6
condition 79 176 44.8
subroutine 70 84 83.3
pod 19 44 43.1
total 758 1186 63.9


line stmt bran cond sub pod time code
1             package Amazon::Credentials;
2             # - module for finding and providing AWS credentials
3 11     11   90146 use strict;
  11         23  
  11         320  
4 11     11   51 use warnings;
  11         21  
  11         264  
5              
6 11     11   211 use 5.010;
  11         34  
7              
8 11     11   557 use parent qw( Exporter Class::Accessor::Fast );
  11         354  
  11         74  
9              
10             __PACKAGE__->follow_best_practice;
11              
12             __PACKAGE__->mk_accessors(
13             qw(
14             _access_key_id
15             cache
16             cipher
17             container
18             debug
19             decrypt
20             encrypt
21             encryption
22             error
23             expiration
24             imdsv2
25             imdsv2_token
26             insecure
27             logger
28             order
29             no_passkey_warning
30             print_error
31             profile
32             raise_error
33             region
34             role
35             _secret_access_key
36             _session_token
37             session_token_required
38             source
39             sso_role_name
40             sso_account_id
41             sso_region
42             timeout
43             user_agent
44             )
45             );
46              
47 11     11   35603 use Carp;
  11         23  
  11         598  
48 11     11   5281 use Config::Tiny;
  11         12235  
  11         349  
49 11     11   72 use Cwd;
  11         18  
  11         676  
50 11     11   767 use Data::Dumper;
  11         7095  
  11         455  
51 11     11   577 use Date::Format;
  11         7849  
  11         571  
52 11     11   3016 use English qw(-no_match_vars);
  11         11264  
  11         72  
53 11     11   3934 use Exporter;
  11         25  
  11         370  
54 11     11   5777 use File::HomeDir;
  11         61120  
  11         575  
55 11     11   5024 use File::chdir;
  11         34156  
  11         985  
56 11     11   3262 use HTTP::Request;
  11         147224  
  11         445  
57 11     11   5597 use JSON::PP qw(decode_json encode_json);
  11         111869  
  11         769  
58 11     11   5340 use LWP::UserAgent;
  11         203004  
  11         435  
59 11     11   83 use List::Util qw(pairs any);
  11         21  
  11         1269  
60 11     11   5384 use MIME::Base64;
  11         7315  
  11         680  
61 11     11   4843 use POSIX::strptime qw(strptime);
  11         5739  
  11         1092  
62 11     11   77 use Scalar::Util qw(reftype);
  11         19  
  11         431  
63 11     11   57 use Time::Local;
  11         21  
  11         1480  
64              
65             use constant { ## no critic (ProhibitConstantPragma, Capitalization)
66 11         3837 AWS_AVAILABILITY_ZONE_URL =>
67             q{latest/meta-data/placement/availability-zone},
68             AWS_CONTAINER_CREDENTIALS_URL => q{http://169.254.170.2/},
69             AWS_IAM_SECURITY_CREDENTIALS_URL =>
70             q{latest/meta-data/iam/security-credentials/},
71             AWS_METADATA_BASE_URL => q{http://169.254.169.254/},
72             CACHE_DIR => '.aws/sso/cache',
73             COMMA => q{,},
74             DEFAULT_CIPHER => q{Cipher::AES},
75             DEFAULT_REGION => 'us-east-1',
76             DEFAULT_SEARCH_ORDER => q{env,container,role,file},
77             DEFAULT_TIMEOUT => 2,
78             DEFAULT_WINDOW_INTERVAL => 5,
79             EMPTY => q{},
80             FALSE => 0,
81             GET_ROLE_CREDENTIALS_HOSTNAME => 'portal.sso.%s.amazonaws.com',
82             GET_ROLE_CREDENTIALS_QUERY => '?account_id=%s&role_name=%s',
83             GET_ROLE_CREDENTIALS_URI => 'federation/credentials',
84             IMDSv2_DEFAULT_TTL => 21_600,
85             IMDSv2_HEADER => q{x-aws-ec2-metadata-token},
86             IMDSv2_TTL_HEADER => q{x-aws-ec2-metadata-token-ttl-seconds},
87             IMDSv2_URL => q{latest/api/token},
88             INSECURE_MODE => 2,
89             ISO8601_FORMAT => q{%Y-%m-%dT%H:%M:%S%z},
90             LOG_FORMAT => qq{ %s [%s] %s\n },
91             PASSKEY_FORMAT => q{%08X%08x},
92             RANDOM_VALUE => 0xffffffff,
93             SECONDS_IN_HOUR => 3600,
94             SECONDS_IN_MINUTE => 60,
95             SLASH => q{/},
96             TRUE => 1,
97             X_AMZ_SSO_BEARER_TOKEN => ':x-amz-sso_bearer_token',
98 11     11   87 };
  11         23  
99              
100             our $VERSION = '1.1.21'; ## no critic (RequireInterpolation)
101              
102             our @EXPORT_OK
103             = qw( create_passkey set_sso_credentials get_role_credentials );
104              
105             # we only log at debug level, create a default logger
106             {
107 11     11   126 no strict 'refs'; ## no critic (ProhibitNoStrict)
  11         22  
  11         82386  
108              
109             *{'Amazon::Credentials::Logger::debug'} = sub {
110 149     149   1813 my ( $self, @message ) = @_;
111              
112 149 100       446 return if !$self->{debug};
113              
114 26         629 my @tm = localtime time;
115              
116 26         121 my $timestamp = strftime '%c', @tm;
117              
118 26         2532 my $message = sprintf LOG_FORMAT, $timestamp, $PROCESS_ID, @message;
119              
120 26         32 print {*STDERR} $message;
  26         680  
121             };
122             }
123              
124             caller or __PACKAGE__->main();
125              
126             ########################################################################
127             sub new {
128             ########################################################################
129 18     18 1 17630 my ( $class, @args ) = @_;
130              
131 18 100       80 my $options = ref $args[0] ? $args[0] : {@args};
132              
133 18         38 my $passkey = delete $options->{passkey};
134              
135 18         206 my $self = $class->SUPER::new($options);
136              
137 18         229 $self->_set_defaults;
138              
139 16         61 $self->_init_logger;
140              
141             # note that multiple instances of the Amazon::Credentials will share
142             # the same passkey...this is probably a bug
143 16 50 33     49 if ( $self->get_passkey && $passkey ) {
    50          
144 0 0       0 if ( !$self->get_no_passkey_warning ) {
145 0         0 carp <<'WARNING';
146             WARNING: the encryption passkey has already been set. Resetting the
147             passkey will require that you restore the original passkey if you are
148             using more than one instance of Amazon::Credentials.
149              
150             To turn this warning off set 'no_passkey_warning' to a true value.
151             WARNING
152             }
153              
154 0         0 $self->_init_encryption($passkey);
155             }
156             elsif ( $self->get_passkey ) {
157 0 0       0 if ( !$self->get_no_passkey_warning ) {
158 0         0 carp <<'WARNING';
159             WARNING: the encryption passkey has already been set. Using the previous passkey.
160              
161             To turn this warning off set 'no_passkey_warning' to a true value.
162             WARNING
163             }
164              
165 0         0 $self->_init_encryption( $self->get_passkey );
166             }
167             else {
168 16         49 $self->_init_encryption($passkey);
169             }
170              
171 16 100       365 if ( $self->get_insecure ) {
172 2         53 $self->get_logger->debug( "!! CAUTION !!\n"
173             . "!! You are executing in 'insecure' mode !!\n"
174             . "!! Credentials may be exposed in debug messages !!\n" );
175             }
176              
177             # if the caller wants us to use his SSO credentials
178 16 50 33     404 if ( $self->get_sso_role_name && $self->get_sso_account_id ) {
179 0   0     0 my $region = $self->get_sso_region || $self->get_region;
180              
181             # this just sets the environmet
182 0         0 set_sso_credentials( $self->get_sso_role_name, $self->get_sso_account_id,
183             $region );
184              
185             # we'll set them from the environment below
186 0         0 $self->set_order( ['env'] );
187             }
188              
189 16 50 33     195 if ( !$options->{aws_secret_access_key}
190             || !$options->{aws_access_key_id} ) {
191 16         67 $self->set_credentials;
192              
193 13 100       317 if ( $self->get__session_token ) {
194 2         64 $self->set_session_token_required(TRUE);
195             }
196              
197 13 50       341 if ( !$self->get_cache ) {
198 0         0 $self->reset_credentials;
199             }
200             }
201             else {
202 0         0 $self->get_logger->debug( 'setting credentials from options ',
203             Dumper( [$options] ) );
204              
205 0         0 $self->set_credentials($options);
206             }
207              
208 13 100       360 if ( !$self->get_region ) {
209 5   33     63 $self->set_region( $self->get_region_from_env
210             || $self->get_default_region );
211             }
212              
213 13         190 return $self;
214             }
215              
216             ########################################################################
217             sub set_default_logger {
218             ########################################################################
219 16     16 0 38 my ( $self, $debug ) = @_;
220              
221 16   66     358 $debug = $debug // $self->get_debug;
222              
223 16         176 my $logger = bless { debug => $debug }, 'Amazon::Credentials::Logger';
224              
225 16         323 $self->set_logger($logger);
226              
227 16         143 return $self;
228             }
229              
230             ########################################################################
231             sub reset_credentials {
232             ########################################################################
233 0     0 1 0 my ( $self, $renew ) = @_;
234              
235 0 0       0 if ( !$renew ) {
236 0         0 $self->set__access_key_id(undef);
237 0         0 $self->set__secret_access_key(undef);
238 0         0 $self->set__session_token(undef);
239             }
240             else {
241 0 0       0 if ( $self->get_cache ) {
242 0         0 $self->set_credentials;
243             }
244             }
245              
246 0         0 return $self;
247             }
248              
249             ########################################################################
250             sub get_region_from_env {
251             ########################################################################
252 5     5 0 12 my ($self) = @_;
253              
254 5   33     33 my $region = $ENV{AWS_REGION} || $ENV{AWS_DEFAULT_REGION};
255              
256 5         44 return $region;
257             }
258              
259             ########################################################################
260             sub get_default_region {
261             ########################################################################
262 5     5 1 13 my ($self) = @_;
263              
264 5         16 my $region = $self->get_region_from_config;
265              
266             # only do this if we are sure we are on an EC2
267 5 100 100     129 if ( $self->get_source && $self->get_source eq 'IAM' ) {
268             # try to get credentials from instance role, but we may not be
269             # executing on an EC2 or container
270 1         39 my $url;
271              
272 1 50       23 if ( $self->get_container ) {
273 1         14 $url = "$ENV{ECS_CONTAINER_METADATA_URI_V4}/task";
274             }
275             else {
276 0         0 $url = _create_metadata_url(AWS_AVAILABILITY_ZONE_URL);
277             }
278              
279 1 50       20 my $ua = ref $self ? $self->get_user_agent : LWP::UserAgent->new();
280              
281 1         4 my @headers;
282              
283             # add imdsv2 token to metadata request
284 1 50 33     17 if ( !$self->get_container && $self->get_imdsv2_token ) {
285 0         0 @headers = ( IMDSv2_HEADER => $self->get_imdsv2_token );
286             }
287              
288 1         10 my $req = HTTP::Request->new( GET => $url, \@headers );
289              
290 1         5 $region = eval {
291 1         3 my $rsp = $ua->request($req);
292              
293             # if not 200, then get out of Dodge
294 1 50       7 croak "could not get availability zone\n"
295             if !$rsp->is_success;
296              
297 1         5 my $content = $rsp->content;
298 1         8 $content =~ s/(\d+)[[:lower:]]+$/$1/xsm;
299              
300 1         3 return $content;
301             };
302             }
303              
304 5         195 return $region;
305             }
306              
307             ########################################################################
308             sub set_credentials {
309             ########################################################################
310 19     19 1 231 my ( $self, $creds ) = @_;
311              
312 19         406 $self->set_error(EMPTY);
313              
314 19         169 $creds = eval {
315 19 100       70 if ( !$creds ) {
316 17         25 $creds = eval { return $self->find_credentials };
  17         64  
317             }
318              
319 19   66     588 $self->set_error( $creds->{error} || $EVAL_ERROR );
320              
321             croak 'no credentials available'
322 19 100 66     1274 if !$creds->{aws_secret_access_key} || !$creds->{aws_access_key_id};
323              
324 13         43 $self->set_aws_secret_access_key( $creds->{aws_secret_access_key} );
325              
326 13         159 $self->set_aws_access_key_id( $creds->{aws_access_key_id} );
327              
328 13   66     149 $self->set_token( $creds->{token} || $creds->{aws_session_token} );
329              
330 13         345 $self->set_expiration( $creds->{expiration} );
331              
332 13         154 return $creds;
333             };
334              
335 19 100 100     389 if ( $EVAL_ERROR && !$self->get_error ) {
336 5         120 $self->set_error($EVAL_ERROR);
337             }
338              
339 19         73 undef $EVAL_ERROR;
340              
341 19 100 100     356 croak $self->get_error
342             if $self->get_error && $self->get_raise_error;
343              
344 16 100 100     452 carp $self->get_error
345             if $self->get_error && $self->get_print_error;
346              
347 16         579 return $self;
348             }
349              
350             ########################################################################
351             sub get_ec2_credentials {
352             ########################################################################
353 0     0 1 0 goto &find_credentials;
354             }
355              
356             ########################################################################
357             sub find_credentials {
358             ########################################################################
359 17     17 1 39 my ( $self, @args ) = @_;
360              
361 17 50       68 my $options = ref $args[0] ? $args[0] : {@args};
362              
363 17 50       61 if ( $options->{profile} ) {
364 0         0 $self->set_profile( $options->{profile} );
365             }
366              
367 17 50       44 if ( $options->{order} ) {
368 0         0 $self->set_order( $options->{order} );
369             }
370              
371 17         50 my @search_order;
372              
373 17 100 33     368 if ( $self->get_profile ) {
    50          
    0          
374 14         109 @search_order = ('file');
375             }
376             elsif ( ref $self->get_order && reftype( $self->get_order ) eq 'ARRAY' ) {
377 3         143 @search_order = @{ $self->get_order };
  3         51  
378             }
379             elsif ( !ref $self->get_order ) {
380 0         0 @search_order = split /\s*,\s*/xsm, $self->get_order;
381             }
382              
383 17         325 $self->get_logger->debug( 'search order ' . join COMMA, @search_order );
384              
385             my %creds_getters = (
386             env => sub {
387 2     2   7 return $self->get_creds_from_env,;
388             },
389             role => sub {
390 0     0   0 return $self->get_creds_from_role;
391             },
392             container => sub {
393 1     1   13 return $self->get_creds_from_container;
394             },
395             file => sub {
396 14     14   56 return $self->get_creds_from_ini_file;
397             },
398 17         206 );
399              
400 17         35 my $creds;
401              
402 17         44 foreach my $location (@search_order) {
403              
404 17         346 $self->get_logger->debug( 'searching for credentials in: ' . $location );
405              
406 17 50       78 if ( $creds_getters{$location} ) {
407 17         43 $creds = $creds_getters{$location}->();
408             }
409              
410 16 100       74 last if $creds->{source};
411             }
412              
413 16         35 foreach my $k ( keys %{$creds} ) {
  16         66  
414              
415 68 100       981 if ( $k !~ /^aws|token/xsm ) {
    50          
416 24         135 $self->set( $k, $creds->{$k} );
417             }
418             elsif ( $self->can("set_$k") ) {
419 44         214 $self->can("set_$k")->( $self, $creds->{$k} );
420             }
421             }
422              
423 16         387 $self->get_logger->debug( $self->safe_dumper($creds) );
424              
425 16         190 return $creds;
426             }
427              
428             # +------------------+
429             # | get_creds_from_* |
430             # +------------------+
431              
432             ########################################################################
433             sub get_creds_from_env {
434             ########################################################################
435 2     2 0 3 my ($self) = @_;
436              
437             return {}
438 2 100 66     12 if !$ENV{AWS_ACCESS_KEY_ID} || !$ENV{AWS_SECRET_ACCESS_KEY};
439              
440 1         19 $self->get_logger->debug('fetching credentials from env');
441              
442 1         4 my @cred_keys = (
443             aws_access_key_id => 'AWS_ACCESS_KEY_ID',
444             aws_secret_access_key => 'AWS_SECRET_ACCESS_KEY',
445             aws_session_token => 'AWS_SESSION_TOKEN',
446             token => 'AWS_SESSION_TOKEN',
447             );
448              
449 1         4 return populate_creds( 'ENV', \@cred_keys, \%ENV );
450             }
451              
452             ########################################################################
453             sub populate_creds {
454             ########################################################################
455 31     31 0 1767 my ( $source, $cred_keys, $creds_source ) = @_;
456              
457 31         83 my $creds = { source => $source };
458              
459 31         53 foreach my $p ( pairs @{$cred_keys} ) {
  31         345  
460 154         195 my ( $k, $v ) = @{$p};
  154         321  
461 154         262 $creds->{$k} = $creds_source->{$v};
462 154 100       314 next if $source =~ /env/i;
463              
464 150         222 delete $creds_source->{$v};
465             }
466              
467 31         198 return $creds;
468             }
469              
470             ########################################################################
471             sub get_creds_from_process {
472             ########################################################################
473 2     2 1 4 my ( $self, $process ) = @_;
474              
475 2         39 $self->get_logger->debug("fetching credentials from $process");
476              
477 2         4 my $credentials = eval {
478 2 100       7807 open my $fh, q{-|}, $process
479             or croak "could not open pipe to $process\n$OS_ERROR";
480              
481 1         36 local $RS = undef;
482              
483 1         5021 my $credentials_str = <$fh>;
484              
485 1 50       58 close $fh
486             or croak "could not close filehandle on $process\n";
487              
488 1         50 return decode_json($credentials_str);
489             };
490              
491 2 100 66     10764 if ( $EVAL_ERROR || !$credentials ) {
492 1         286 croak "could not get credentials from process\n$EVAL_ERROR\n";
493             }
494              
495 1         45 $self->get_logger->debug( $self->safe_dumper($credentials) );
496              
497 1         20 my @cred_keys = (
498             aws_access_key_id => 'AccessKeyId',
499             aws_secret_access_key => 'SecretAccessKey',
500             token => 'SessionToken',
501             aws_session_token => 'SessionToken',
502             region => 'Region',
503             expiration => 'Expiration',
504             );
505              
506 1         12 return populate_creds( 'process', \@cred_keys, $credentials );
507             }
508              
509             ########################################################################
510             sub create_config_path {
511             ########################################################################
512 33     33 0 80 my ($config) = @_;
513              
514 33         113 my $fullpath = home . SLASH . $config;
515              
516 33 100       1951 return -e $fullpath ? $fullpath : EMPTY;
517             }
518              
519             ########################################################################
520             sub get_region_from_config {
521             ########################################################################
522 5     5 0 11 my ($self) = @_;
523              
524 5         13 my $config = create_config_path('.aws/config');
525              
526 5 100       47 return if !$config;
527              
528 4         102 $self->get_logger->debug("config: $config");
529              
530 4         29 my $ini = Config::Tiny->read($config);
531              
532 4         761 my $region;
533              
534 4 50       18 if ( $ini->{default} ) {
535 4         10 $region = $ini->{default}->{region};
536             }
537              
538 4   50     94 $self->get_logger->debug( 'default region: ' . $region // 'undef' );
539              
540 4         19 return $region;
541             }
542              
543             ########################################################################
544             sub get_creds_from_ini_file {
545             ########################################################################
546 14     14 0 34 my ( $self, $profile ) = @_;
547              
548 14   50     340 $profile = $profile || $self->get_profile || 'default';
549              
550 14         125 my $creds = {};
551 14         26 my $region;
552              
553 14         47 foreach my $config (qw( .aws/config .aws/credentials )) {
554 28 50       85 last if $creds->{source};
555              
556 28         34 my $profile_name = $profile;
557              
558 28         91 my $fullpath = create_config_path($config);
559              
560 28 50       100 if ( !$fullpath ) {
561 0         0 $self->get_logger->debug( 'skipping ' . $config . '...not found' );
562 0         0 next;
563             }
564              
565 28         704 $self->get_logger->debug( 'reading ' . $config );
566              
567 28         160 my $ini = Config::Tiny->read($fullpath);
568              
569 28         7586 $self->get_logger->debug( $self->safe_dumper($ini) );
570              
571 28 100       100 if ( $ini->{default} ) {
572 27         54 $region = $ini->{default}->{region};
573             }
574              
575 28         34 my $section;
576              
577 28 100       111 if ( $ini->{$profile_name} ) {
    100          
578 10         21 $section = $ini->{$profile_name};
579             }
580             elsif ( $ini->{"profile $profile_name"} ) { ## config file format
581 2         6 $section = $ini->{"profile $profile_name"};
582             }
583              
584             my $process
585 28   66     122 = $ini->{credential_process} || $section->{credential_process};
586              
587 28 100       69 if ($process) {
    50          
588 2         5 $creds = $self->get_creds_from_process($process);
589 1 50       42 $region = $section->{region} ? $section->{region} : $region;
590             }
591             elsif ($section) {
592              
593             # credentials in a config file shouldn't really have a session token,
594             # but if it does we'll capture it as aws_session_token. For
595             # historical reasons pertaining to how other CPAN classes
596             # referred to the token we store it internally as token
597 26         90 my @cred_keys = (
598             aws_access_key_id => 'aws_access_key_id',
599             aws_secret_access_key => 'aws_secret_access_key',
600             aws_session_token => 'aws_session_token',
601             token => 'aws_session_token',
602             region => 'region',
603             );
604              
605 26         70 $creds = populate_creds( $config, \@cred_keys, $section );
606              
607 26 100 66     135 if ( !$creds->{aws_access_key_id} || !$creds->{aws_secret_access_key} )
608             {
609 18         107 $creds = {};
610             }
611             }
612             }
613              
614 13   100     444 $self->set_region( $creds->{region} || $region );
615              
616 13         138 return $creds;
617             }
618              
619             ########################################################################
620             sub get_creds_from_role {
621             ########################################################################
622 1     1 1 5 my ($self) = @_;
623              
624 1 50       14 if ( $ENV{AWS_EC2_METADATA_DISABLED} eq 'true' ) {
625 0         0 $self->get_logger->debug(
626             'AWS_EC2_METADATA_DISABLED is true, skipping...');
627              
628 0         0 return {};
629             }
630              
631             # try to get credentials from instance role
632 1         5 my $url = _create_metadata_url(AWS_IAM_SECURITY_CREDENTIALS_URL);
633              
634 1         22 my $ua = $self->get_user_agent;
635              
636 1         8 my $role;
637              
638 1         3 my $creds = { error => undef };
639              
640 1 50       68 if ( $self->get_imdsv2 ) {
641 0         0 my $token_url = _create_metadata_url(IMDSv2_URL);
642              
643 0         0 my @headers = ( IMDSv2_TTL_HEADER, IMDSv2_DEFAULT_TTL );
644              
645 0         0 my $token_req = HTTP::Request->new( PUT => $token_url, \@headers );
646              
647 0         0 $self->get_logger->debug( Dumper $token_req);
648              
649 0         0 my $rsp = $ua->request($token_req);
650              
651 0         0 $self->get_logger->debug( Dumper $rsp);
652              
653 0 0       0 if ( $rsp->is_success ) {
654 0         0 $self->set_imdsv2_token( $rsp->content );
655             }
656             else {
657 0         0 croak "could not retrieve IMDSv2 token\n";
658             }
659             }
660              
661 1         14 my @cred_keys = (
662             aws_access_key_id => 'AccessKeyId',
663             aws_secret_access_key => 'SecretAccessKey',
664             token => 'Token',
665             aws_session_token => 'Token',
666             expiration => 'Expiration',
667             );
668              
669 1         2 $creds = eval {
670             # could be infinite, but I don't think so. Either we get an
671             # error ($@), or a non-200 response code
672 1         3 while ( !$creds->{token} ) {
673              
674 2 100       6 if ($role) {
675 1         2 $url .= $role;
676             }
677              
678 2         4 my @headers;
679              
680 2 50 33     43 if ( $self->get_imdsv2 && $self->get_imdsv2_token ) {
681 0         0 @headers = ( IMDSv2_HEADER, $self->get_imdsv2_token );
682             }
683              
684 2         27 my $req = HTTP::Request->new( GET => $url, \@headers );
685              
686 2         45 $self->get_logger->debug( Dumper [ "HTTP REQUEST:\n", $req ] );
687              
688 2         8 my $rsp = $ua->request($req);
689              
690 2         58 $self->get_logger->debug( $self->dump_response($rsp) );
691              
692             # if not 200, then get out of Dodge
693 2 50       7 last if !$rsp->is_success;
694              
695 2 100       10 if ($role) {
696 1         3 my $creds_source = decode_json( $rsp->content );
697              
698 1         2916 $creds = populate_creds( 'IAM', \@cred_keys, $creds_source );
699 1         5 $creds->{role} = $role;
700             }
701             else {
702 1         3 $role = $rsp->content;
703 1         28 $self->get_logger->debug( Dumper [ 'role', $role ] );
704              
705 1 50       15 last if !$role;
706             }
707             }
708              
709 1         3 return $creds;
710             };
711              
712 1 50 33     7 if ( !$creds || $EVAL_ERROR ) {
713 0         0 $creds->{error} = $EVAL_ERROR;
714             }
715              
716 1         3 return $creds;
717             }
718              
719             ########################################################################
720             sub get_creds_from_container {
721             ########################################################################
722 1     1 1 5 my ( $self, $uri ) = @_;
723              
724 1   33     8 $uri //= $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI};
725              
726 1         2 my $creds = {};
727              
728 1 50       12 if ( !$uri ) {
729 0         0 $self->get_logger->debug(
730             "not running in a container: no URI in environment\n");
731 0         0 return $creds;
732             }
733              
734 1         30 $self->get_logger->debug( Dumper( [ uri => $uri ] ) );
735              
736 1         6 $creds = eval {
737             # try to get credentials from instance role
738 1         3 my $url = AWS_CONTAINER_CREDENTIALS_URL . $uri;
739              
740 1         23 my $ua = $self->get_user_agent;
741              
742 1         27 my $req = HTTP::Request->new( GET => $url );
743 1         9 $req->header(qw( Accept */* ));
744              
745 1         19 $self->get_logger->debug(
746             Dumper [
747             request => $req,
748             as_string => $req->as_string,
749             ]
750             );
751              
752 1         4 my $rsp = $ua->request($req);
753              
754 1         27 $self->get_logger->debug( $self->dump_response($rsp) );
755              
756             # if not 200, then get out of Dodge
757 1 50       8 if ( $rsp->is_success ) {
758              
759 1         7 my $creds_source = decode_json( $rsp->content );
760              
761 1         1714 my @cred_keys = (
762             aws_access_key_id => 'AccessKeyId',
763             aws_secret_access_key => 'SecretAccessKey',
764             token => 'Token',
765             aws_session_token => 'Token',
766             expiration => 'Expiration',
767             );
768              
769 1         5 $creds = populate_creds( 'IAM', \@cred_keys, $creds_source );
770 1         6 $creds->{container} = 'ECS';
771             }
772             else {
773 0         0 $self->get_logger->debug( 'return code: ' . $rsp->status_line );
774             }
775              
776 1         18 return $creds;
777             };
778              
779 1         7 $creds->{error} = $EVAL_ERROR;
780 1         28 $self->get_logger->debug( "EVAL_ERROR: $EVAL_ERROR\n" . Dumper $creds);
781              
782 1         4 return $creds;
783             }
784              
785             # +---------+
786             # | DUMPERS |
787             # +---------+
788              
789             ########################################################################
790             sub safe_dumper {
791             ########################################################################
792 45     45 0 315 my ( $self, $obj ) = @_;
793              
794 45 50       142 return if !ref $obj;
795              
796 45         67 my $safe_rsp;
797              
798 45 100 100     805 if ( $self->get_insecure && $self->get_insecure =~ /^2$/xsm ) {
    100          
799 3         89 $safe_rsp = Dumper [$obj];
800             }
801             elsif ( $self->get_insecure ) {
802 3         144 $safe_rsp = Dumper [$obj];
803              
804 3         346 $safe_rsp
805             =~ s/(.*?)(access_?key[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
806              
807 3         33 $safe_rsp
808             =~ s/(.*?)(secret_?access[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
809              
810 3         13 $safe_rsp =~ s/(.*?)(token[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
811             }
812             else {
813 39         1035 $safe_rsp = '** configuration contents blocked by insecure setting **';
814             }
815              
816 45         467 return $safe_rsp;
817             }
818              
819             ########################################################################
820             sub dump_response {
821             ########################################################################
822 3     3 0 35 my ( $self, $rsp ) = @_;
823              
824 3         3 my $safe_rsp;
825              
826 3 50 33     52 if ( $self->get_insecure && $self->get_insecure =~ /^2$/xsm ) {
    50          
827 0         0 $safe_rsp = $rsp;
828             }
829             elsif ( $self->get_insecure ) {
830 0         0 $safe_rsp = {};
831              
832 0         0 foreach my $k ( keys %{$rsp} ) {
  0         0  
833 0 0       0 if ( $k =~ /content/xsm ) {
834 0         0 my $content = $rsp->{$k};
835 0         0 $content
836             =~ s/\"(AccessKeyId|Token|SecretAccessKey)\"\s+\:\s+\"[^\"]+\"/\"$1\" : \"...\"/gxsm;
837 0         0 $safe_rsp->{$k} = $content;
838             }
839             else {
840 0         0 $safe_rsp->{$k} = $rsp->{$k};
841             }
842             }
843             }
844             else {
845 3         133 $safe_rsp = '** HTTP RESPONSE blocked by insecure setting **';
846             }
847              
848 3         15 return Dumper [$safe_rsp];
849             }
850              
851             # +---------------+
852             # | TOKEN METHODS |
853             # +---------------+
854              
855             ########################################################################
856             sub is_token_expired {
857             ########################################################################
858 4     4 1 337 my ( $self, $window_interval ) = @_;
859 4   50     36 $window_interval = $window_interval // DEFAULT_WINDOW_INTERVAL;
860              
861 4         78 my $expiration_date = $self->get_expiration();
862              
863 4         23 my $expired = FALSE;
864              
865 4 50       8 if ( defined $expiration_date ) {
866             # AWS recommends getting credentials 5 minutes prior to expiration
867 4         9 my $g = _iso8601_to_time($expiration_date);
868              
869             # shave 5 minutes or window interval off of the expiration time
870 4         8 $g -= $window_interval * SECONDS_IN_MINUTE;
871              
872             # (expiration_time - window_interval) - current_time = # of seconds left before expiration
873 4         6 my $seconds_left = $g - time;
874              
875 4 50       99 if ( $self->get_debug ) {
876 0         0 $self->get_logger->debug("seconds left : $seconds_left");
877 0         0 my $hours = int( $seconds_left / SECONDS_IN_HOUR );
878              
879 0         0 my $minutes = int(
880             ( $seconds_left - $hours * SECONDS_IN_HOUR ) / SECONDS_IN_MINUTE );
881              
882 0         0 my $seconds = $seconds_left
883             - ( $hours * SECONDS_IN_HOUR + $minutes * SECONDS_IN_MINUTE );
884              
885 0         0 $self->get_logger->debug(
886             "$hours hours $minutes minutes $seconds seconds until expiry");
887             }
888              
889 4 100       28 $expired = ( $seconds_left < 0 ) ? TRUE : FALSE;
890              
891 4         70 $self->get_logger->debug(
892             Dumper [
893             expiration_date => $expiration_date,
894             expired => $expired
895             ]
896             );
897             }
898              
899 4         34 return $expired;
900             }
901              
902             ########################################################################
903             sub refresh_credentials {
904             ########################################################################
905 0     0 1 0 goto &refresh_token;
906             }
907              
908             ########################################################################
909             sub refresh_token {
910             ########################################################################
911 1     1 1 660 my ($self) = @_;
912              
913 1         2 my $creds;
914              
915 1 50 33     39 if ( $self->get_container && $self->get_container eq 'ECS' ) {
    50          
916 0         0 $creds = $self->get_creds_from_container;
917             }
918             elsif ( $self->get_role ) {
919 1         61 $creds = $self->get_creds_from_role;
920             }
921              
922             croak 'unable to refresh token!'
923 1 50 33     6 if !ref $creds || !keys %{$creds};
  1         5  
924              
925 1         3 return $self->set_credentials($creds);
926             }
927              
928             ########################################################################
929             sub credential_keys {
930             ########################################################################
931 3     3 1 3721 my ($self) = @_;
932              
933 3         5 my %credential_keys;
934              
935 3 50       73 if ( !$self->get_cache ) {
936 0         0 my $creds = $self->find_credentials;
937              
938             %credential_keys = (
939             AWS_ACCESS_KEY_ID => $creds->{aws_access_key_id},
940             AWS_SECRET_ACCESS_KEY => $creds->{aws_secret_access_key},
941             AWS_SESSION_TOKEN => $creds->{token},
942             AWS_SESSION_TOKEN_EXPIRATION => $creds->{expiration},
943 0         0 );
944              
945             }
946             else {
947 3         22 %credential_keys = (
948             AWS_ACCESS_KEY_ID => $self->get_aws_access_key_id,
949             AWS_SECRET_ACCESS_KEY => $self->get_aws_secret_access_key,
950             AWS_SESSION_TOKEN => $self->get_token,
951             AWS_SESSION_TOKEN_EXPIRATION => $self->get_expiration,
952             );
953             }
954              
955 3 50       29 if ( !defined $credential_keys{AWS_SESSION_TOKEN} ) {
956 3         5 delete $credential_keys{AWS_SESSION_TOKEN};
957 3         4 delete $credential_keys{AWS_SESSION_TOKEN_EXPIRATION};
958             }
959              
960 3         10 return \%credential_keys;
961             }
962              
963             ########################################################################
964             sub as_string {
965             ########################################################################
966 1     1 1 2992 my ($self) = @_;
967              
968 1         7 return JSON::PP->new->pretty->encode( $self->credential_keys );
969             }
970              
971             ########################################################################
972             sub format_credentials {
973             ########################################################################
974 1     1 1 1496 my ( $self, $format ) = @_;
975              
976 1   50     5 $format = $format || "%s %s\n";
977              
978 1         4 my $credential_keys = $self->credential_keys;
979              
980             return join EMPTY,
981 2   50     15 map { sprintf $format, $_, $credential_keys->{$_} // EMPTY }
982 1         2 keys %{$credential_keys};
  1         5  
983             }
984              
985             # +----------------------------+
986             # | CREDENTIALS getters/setter |
987             # +----------------------------+
988              
989             ########################################################################
990             sub get_aws_access_key_id {
991             ########################################################################
992 9     9 0 2998 my ($self) = @_;
993              
994 9 50       222 if ( !$self->get__access_key_id ) {
995 0         0 $self->set_credentials;
996             }
997              
998 9         222 my $access_key_id
999             = $self->decrypt( $self->get__access_key_id, $self->_fetch_passkey );
1000              
1001 9 50       172 if ( !$self->get_cache ) {
1002 0         0 $self->set__access_key_id(undef);
1003             }
1004              
1005 9         86 return $access_key_id;
1006             }
1007              
1008             ########################################################################
1009             sub get_aws_secret_access_key {
1010             ########################################################################
1011 7     7 0 943 my ($self) = @_;
1012              
1013 7 100       155 if ( !$self->get__secret_access_key ) {
1014 1         10 $self->set_credentials;
1015             }
1016              
1017 7         162 my $secret_access_key
1018             = $self->decrypt( $self->get__secret_access_key, $self->_fetch_passkey );
1019              
1020 7 50       126 if ( !$self->get_cache ) {
1021 0         0 $self->set__secret_access_key(undef);
1022             }
1023              
1024 7         66 return $secret_access_key;
1025             }
1026              
1027             ########################################################################
1028             sub get_token {
1029             ########################################################################
1030 5     5 0 17 my ($self) = @_;
1031              
1032 5 50 66     97 if ( !$self->get__session_token && $self->get_session_token_required ) {
1033 0         0 $self->set_credentials;
1034             }
1035              
1036 5         114 my $passkey = $self->_fetch_passkey;
1037 5         86 my $token = $self->decrypt( $self->get__session_token, $passkey );
1038              
1039 5 50       88 if ( !$self->get_cache ) {
1040 0         0 $self->set__session_token(undef);
1041             }
1042              
1043 5         111 return $token;
1044             }
1045              
1046             ########################################################################
1047             sub set_aws_access_key_id {
1048             ########################################################################
1049 24     24 0 58 my ( $self, $aws_access_key_id, $passkey ) = @_;
1050              
1051 24   50     69 my $key = $aws_access_key_id || undef;
1052              
1053 24 50       56 if ($aws_access_key_id) {
1054 24         84 $key = $self->encrypt( $aws_access_key_id, $passkey );
1055             }
1056              
1057 24         481 return $self->set__access_key_id($key);
1058             }
1059              
1060             ########################################################################
1061             sub set_aws_secret_access_key {
1062             ########################################################################
1063 24     24 0 57 my ( $self, $aws_secret_access_key, $passkey ) = @_;
1064              
1065 24   50     68 my $key = $aws_secret_access_key || undef;
1066              
1067 24 50       67 if ($aws_secret_access_key) {
1068 24         110 $key = $self->encrypt( $aws_secret_access_key, $passkey );
1069             }
1070              
1071 24         504 return $self->set__secret_access_key($key);
1072             }
1073              
1074             ########################################################################
1075 11     11 0 51 sub set_aws_session_token { goto &set_token; }
1076 0     0 0 0 sub get_aws_session_token { goto &get_token; }
1077             ########################################################################
1078              
1079             ########################################################################
1080             sub set_token {
1081             ########################################################################
1082 35     35 0 81 my ( $self, $session_token, $passkey ) = @_;
1083              
1084 35   100     184 my $token = $session_token || undef;
1085              
1086 35 100       93 if ($session_token) {
1087 6         13 $token = $self->encrypt( $session_token, $passkey );
1088             }
1089              
1090 35         720 return $self->set__session_token($token);
1091             }
1092              
1093             # +--------------------+
1094             # | ENCRYPTION METHODS |
1095             # +--------------------+
1096              
1097             ########################################################################
1098             sub decrypt {
1099             ########################################################################
1100 21     21 1 59 my ( $self, $str, $passkey ) = @_;
1101              
1102 21 50 33     368 if ( ref $self->get_decrypt && reftype( $self->get_decrypt ) eq 'CODE' ) {
1103 0   0     0 return $self->get_decrypt->( $str, $passkey || $self->_fetch_passkey );
1104             }
1105             else {
1106 21         148 return $self->_crypt( $str, 'decrypt', $passkey );
1107             }
1108             }
1109              
1110             ########################################################################
1111             sub encrypt {
1112             ########################################################################
1113 54     54 1 102 my ( $self, $str, $passkey ) = @_;
1114              
1115 54 50 33     1049 if ( ref $self->get_encrypt && reftype( $self->get_encrypt ) eq 'CODE' ) {
1116 0   0     0 return $self->get_encrypt->( $str, $passkey || $self->_fetch_passkey );
1117             }
1118             else {
1119 54         411 return $self->_crypt( $str, 'encrypt', $passkey );
1120             }
1121             }
1122              
1123             ########################################################################
1124             sub create_passkey {
1125             ########################################################################
1126 0     0 0 0 return sprintf PASSKEY_FORMAT, rand RANDOM_VALUE, rand RANDOM_VALUE;
1127             }
1128              
1129             ########################################################################
1130             sub rotate_passkey {
1131             ########################################################################
1132 0     0 0 0 my ( $self, $new_passkey ) = @_;
1133              
1134 0 0 0     0 if ( $new_passkey && !ref $new_passkey ) {
1135 0 0       0 if ( $self->get_cache ) {
1136 0         0 $self->set_aws_access_key_id( $self->get_aws_access_key_id,
1137             $new_passkey );
1138              
1139 0         0 $self->set_aws_secret_access_key( $self->get_aws_secret_access_key,
1140             $new_passkey );
1141              
1142 0         0 $self->set_token( $self->get_token, $new_passkey );
1143             }
1144              
1145             # if caller has his own passkey generator, don't reset
1146 0 0       0 if ( !ref $self->get_passkey ) {
1147 0         0 $self->set_passkey($new_passkey);
1148             }
1149             }
1150             else {
1151 0         0 $new_passkey = $self->create_passkey;
1152              
1153 0         0 $self->rotate_credentials($new_passkey);
1154             }
1155              
1156 0         0 return $new_passkey;
1157             }
1158              
1159             ########################################################################
1160             sub rotate_credentials {
1161             ########################################################################
1162 0     0 0 0 goto &rotate_passkey;
1163             }
1164              
1165             ########################################################################
1166             sub set_sso_credentials {
1167             ########################################################################
1168 0     0 1 0 my ( $role_name, $account_id, $region ) = @_;
1169              
1170 0 0       0 croak "usage: __PACKAGE__::set_sso_credentials(role-name, account-id)\n"
1171             if ref $role_name;
1172              
1173 0   0     0 my $credentials = get_role_credentials(
1174             role_name => $role_name,
1175             account_id => $account_id,
1176             region => $region // EMPTY,
1177             );
1178              
1179 0         0 my @cred_keys = (
1180             accessKeyId => 'AWS_ACCESS_KEY_ID',
1181             secretAccessKey => 'AWS_SECRET_ACCESS_KEY',
1182             sessionToken => 'AWS_SESSION_TOKEN',
1183             );
1184              
1185             # stuff credentials in environment, upstream caller will set order to 'env'
1186 0 0       0 if ($credentials) {
1187 0         0 foreach my $p ( pairs @cred_keys ) {
1188 0         0 my ( $k, $v ) = @{$p};
  0         0  
1189 0         0 $ENV{$v} = $credentials->{$k}; ## no critic (RequireLocalizedPunctuationVars)
1190             }
1191             }
1192              
1193 0         0 return $credentials;
1194             }
1195              
1196             ########################################################################
1197             sub get_role_credentials {
1198             ########################################################################
1199 0     0 1 0 my (%args) = @_;
1200              
1201             my ( $account_id, $role_name, $access_token, $region )
1202 0         0 = @args{qw(account_id role_name access_token region)};
1203              
1204 0 0       0 if ( !$access_token ) {
1205 0         0 $access_token = _get_access_token();
1206             }
1207              
1208 0 0       0 croak 'no access token'
1209             if !$access_token;
1210              
1211 0   0     0 $region ||= $ENV{AWS_REGION} || $ENV{AWS_DEFAULT_REGION} || DEFAULT_REGION;
      0        
1212              
1213 0         0 my $ua = LWP::UserAgent->new;
1214 0         0 $ua->default_header( X_AMZ_SSO_BEARER_TOKEN, $access_token );
1215              
1216 0         0 my $host = sprintf GET_ROLE_CREDENTIALS_HOSTNAME, $region;
1217              
1218 0         0 my $query = sprintf GET_ROLE_CREDENTIALS_QUERY, $account_id, $role_name;
1219              
1220 0         0 my $url = sprintf 'https://%s/%s%s', $host, GET_ROLE_CREDENTIALS_URI,
1221             $query;
1222              
1223 0         0 my $rsp = $ua->get($url);
1224              
1225 0 0       0 croak "no response from get($url)\n"
1226             if !$rsp;
1227              
1228 0         0 my $content = eval {
1229 0 0 0     0 return decode_json( $rsp->content )
1230             if $rsp && $rsp->content_type eq 'application/json';
1231             };
1232              
1233 0 0       0 croak sprintf "could not decode response from GetRoleCredentials\n%s",
1234             $EVAL_ERROR
1235             if !$content;
1236              
1237             return $content->{roleCredentials}
1238 0 0       0 if $rsp->is_success;
1239              
1240 0 0       0 croak sprintf "Status: %s\n%s", $rsp->status_line, Dumper( $rsp->content )
1241             if !ref $content;
1242              
1243 0         0 croak sprintf "Status: %s\n%s", $rsp->status_line, $content->{message};
1244             }
1245              
1246             # +-----------------+
1247             # | PRIVATE METHODS |
1248             # +-----------------+
1249              
1250             ########################################################################
1251             sub _init_logger {
1252             ########################################################################
1253 16     16   38 my ($self) = @_;
1254              
1255 16 50 33     320 if ( !$self->get_logger || !ref $self->get_logger ) {
1256 16         155 $self->set_default_logger;
1257             }
1258              
1259 16         316 $self->get_logger->debug( 'using ' . ref( $self->get_logger ) . ' logger' );
1260              
1261 16         55 return $self;
1262             }
1263              
1264             ########################################################################
1265             sub _set_defaults {
1266             ########################################################################
1267 18     18   38 my ($self) = @_;
1268              
1269 18   100     516 $self->set_debug( $self->get_debug // FALSE );
1270              
1271 18 50       969 $self->set_cache( defined $self->get_cache ? $self->get_cache : TRUE );
1272              
1273 18 50       838 if ( !$self->get_user_agent ) {
1274             # set a very low timeout
1275 18   50     544 $self->set_user_agent(
1276             LWP::UserAgent->new( timeout => $self->get_timeout || DEFAULT_TIMEOUT )
1277             );
1278             }
1279              
1280 18         17760 my $default_search_order = [ split /\s*,\s*/xsm, DEFAULT_SEARCH_ORDER ];
1281              
1282 18 100       395 if ( !$self->get_order ) {
1283 7         162 $self->set_order($default_search_order);
1284             }
1285              
1286 18 100 66     442 if ( !ref $self->get_order ) {
    100          
1287 3         62 $self->set_order( [ split /\s*,\s*/xsm, $self->get_order ] );
1288             }
1289             elsif ( ref $self->get_order && reftype( $self->get_order ) ne 'ARRAY' ) {
1290 1         198 croak 'order must be a comma delimited string or array ref';
1291             }
1292              
1293 17         801 foreach my $loc ( @{ $self->get_order } ) {
  17         305  
1294             croak "invalid credential location in search order: [$loc]"
1295 38 100   102   250 if !any {/^$loc$/xsm} @{$default_search_order};
  102         1019  
  38         120  
1296             }
1297              
1298 16 100 100 5   377 if ( !$self->get_profile && any {/file/xsm} @{ $self->get_order } ) {
  5         63  
  5         182  
1299 2         83 $self->set_profile( $ENV{AWS_PROFILE} );
1300             }
1301              
1302 16   100     426 $self->set_raise_error( $self->get_raise_error // TRUE );
1303 16   100     804 $self->set_print_error( $self->get_print_error // TRUE );
1304              
1305 16         593 return $self;
1306             }
1307              
1308             ########################################################################
1309             # note that $passkey is a class variable and as such, once initialized
1310             # in the fashion below, will persist for all instances of
1311             # Amazon::Credentials
1312             ########################################################################
1313             {
1314             my $passkey;
1315              
1316             ########################################################################
1317             sub get_passkey {
1318             ########################################################################
1319 216     216 0 482 return $passkey;
1320             }
1321              
1322             ########################################################################
1323             sub set_passkey {
1324             ########################################################################
1325 16     16 0 37 my ( $self, $key ) = @_;
1326              
1327 16         26 $passkey = $key;
1328              
1329 16         21 return $passkey;
1330             }
1331             }
1332              
1333             ########################################################################
1334             sub _fetch_passkey {
1335             ########################################################################
1336 92     92   183 my ($self) = @_;
1337              
1338 92         131 my $passkey = eval {
1339 92 50 33     164 if ( ref $self->get_passkey && reftype( $self->get_passkey ) eq 'CODE' ) {
1340 0         0 return $self->get_passkey->();
1341             }
1342             else {
1343 92         146 return $self->get_passkey;
1344             }
1345             };
1346              
1347 92         214 return $passkey;
1348             }
1349              
1350             ########################################################################
1351             sub _init_encryption {
1352             ########################################################################
1353 16     16   37 my ( $self, $passkey ) = @_;
1354              
1355 16         75 $self->set_passkey($passkey);
1356              
1357             # if one is set, both must be set
1358 16 50 33     351 if ( $self->get_encrypt || $self->get_decrypt ) {
1359 0 0       0 croak 'must be a code reference to encrypt()'
1360             if ref $self->get_encrypt ne 'CODE';
1361              
1362 0 0       0 croak 'must be a code reference to decrypt()'
1363             if ref $self->get_decrypt ne 'CODE';
1364              
1365 0         0 $self->set_encryption(TRUE);
1366             }
1367             else {
1368 16         501 my $has_crypt_cbc = eval {
1369 16         2304 require Crypt::CBC;
1370 0         0 require Crypt::Cipher::AES;
1371             };
1372              
1373 16 50       466 if ( !defined $self->get_encryption ) {
1374             # let's make the default to encrypt (if we can)
1375 16 50       566 $self->set_encryption( $has_crypt_cbc ? TRUE : FALSE );
1376             }
1377             else {
1378             # don't allow encryption if Crypt::CBC not present
1379 0   0     0 $self->set_encryption( $self->get_encryption && $has_crypt_cbc );
1380             }
1381              
1382 16 50 33     440 if ( $self->get_encryption && !$self->get_cipher ) {
1383 0         0 $self->set_cipher(DEFAULT_CIPHER);
1384             }
1385              
1386             }
1387              
1388 16 50 33     391 if ( $self->get_encryption && !$self->get_passkey ) {
1389 0         0 $self->set_passkey( $self->create_passkey );
1390             }
1391              
1392 16         384 return $self->get_encryption;
1393             }
1394              
1395             ########################################################################
1396             sub _crypt {
1397             ########################################################################
1398 75     75   160 my ( $self, $str, $op, $passkey ) = @_;
1399              
1400 75 100       141 return if !$str;
1401              
1402 71   33     198 $passkey = $passkey || $self->_fetch_passkey();
1403              
1404 71         96 my $cipher;
1405              
1406 71 50       1294 if ( $self->get_encryption ) {
1407 0         0 $cipher = Crypt::CBC->new(
1408             '-pass' => $passkey,
1409             '-key' => $passkey,
1410             '-cipher' => $self->get_cipher,
1411             '-nodeprecate' => TRUE,
1412             );
1413             }
1414              
1415             # at least obfuscate the credentials
1416 71 100       436 if ( $op eq 'decrypt' ) {
1417 17         63 $str = decode_base64($str);
1418 17 50       58 $str = ref $cipher ? $cipher->decrypt($str) : $str;
1419             }
1420             else {
1421 54 50       112 $str = ref $cipher ? $cipher->encrypt($str) : $str;
1422 54         185 $str = encode_base64($str);
1423             }
1424              
1425 71         178 return $str;
1426             }
1427              
1428             ########################################################################
1429             sub _iso8601_to_time {
1430             ########################################################################
1431 4     4   7 my $iso8601 = shift;
1432              
1433 4         25 $iso8601 =~ s/^(.*)Z$/$1\+00:00/xsm;
1434              
1435 4         9 my $gmtime = eval {
1436 4         24 local $ENV{TZ} = 'GMT';
1437              
1438 4         126 timegm( strptime( $iso8601, ISO8601_FORMAT ) );
1439             };
1440              
1441 4         185 return $gmtime;
1442             }
1443              
1444             ########################################################################
1445             sub _create_metadata_url {
1446             ########################################################################
1447 1     1   7 my ($url) = @_;
1448              
1449 1         5 return AWS_METADATA_BASE_URL . $url;
1450             }
1451              
1452             ########################################################################
1453             sub _get_access_token {
1454             ########################################################################
1455 0     0   0 my ($home) = @_;
1456              
1457 0         0 require File::Find;
1458              
1459 0         0 File::Find->import('find');
1460              
1461 0   0     0 $home //= $ENV{HOME};
1462 0         0 my $cache_dir = sprintf '%s/%s', $home, CACHE_DIR;
1463              
1464 0 0       0 croak "no $cache_dir found."
1465             if !-d $cache_dir;
1466              
1467 0         0 my $access_token;
1468              
1469 0         0 my $cwd = getcwd;
1470              
1471 0         0 eval {
1472              
1473             find(
1474             sub {
1475 0 0   0   0 return if !/[.]json$/xsm;
1476              
1477 0         0 local $RS = undef;
1478              
1479 0 0       0 open my $fh, '<', $File::Find::name
1480             or croak 'could not open ' . $File::Find::name;
1481              
1482 0         0 my $json = eval { decode_json(<$fh>) };
  0         0  
1483              
1484 0         0 close $fh;
1485              
1486 0 0 0     0 if ( ref $json && $json->{accessToken} ) {
1487 0         0 $access_token = $json->{accessToken};
1488 0         0 croak 'found';
1489             }
1490              
1491 0         0 return;
1492             },
1493 0         0 $cache_dir
1494             );
1495             };
1496              
1497 0         0 CORE::chdir $cwd;
1498              
1499 0         0 return $access_token;
1500             }
1501              
1502             #######################################################################
1503             sub help {
1504             ########################################################################
1505              
1506 0     0 0 0 print {*STDOUT} <<'END_OF_USAGE';
  0         0  
1507             amazon-credentials.sh options
1508              
1509             Formats credentials found in env, config, SSO, role
1510              
1511             Options
1512             -------
1513             --help, -h this
1514             --ec2, -E get credentials from server IAM profile
1515             --env -e get credentials from environment variables
1516             --profile, -p get credentials from profile in credentials configuration
1517             --role, -r get credentials from SSO role
1518             --account, -a use with --role, specify AWS account id
1519              
1520             $ amazon-credentials.sh --profile=test
1521             export AWS_ACCESS_KEY_ID=AKI*****************
1522             export AWS_SECRET_ACCESS_KEY=****************************************
1523              
1524             $ aws sso login
1525             $ amazon-credentials.sh --role my-sso-role --account 01234567890
1526             END_OF_USAGE
1527              
1528 0         0 exit 0;
1529             }
1530              
1531             ########################################################################
1532             sub export_credentials {
1533             ########################################################################
1534 1     1 0 4526 my ($credentials) = @_;
1535              
1536 1   50     4 $credentials //= \%ENV;
1537              
1538             my @cred_keys
1539 1         41 = qw(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN);
1540              
1541 1 50       4 @cred_keys = map { defined $credentials->{$_} ? $_ : () } @cred_keys;
  3         13  
1542              
1543             return join "\n",
1544 1         7 map { sprintf 'export %s=%s', $_, $credentials->{$_} } @cred_keys;
  3         15  
1545             }
1546              
1547             ########################################################################
1548             sub main {
1549             ########################################################################
1550 0     0 0   my ($self) = @_;
1551              
1552 11     11   8838 use Getopt::Long qw(:config no_ignore_case);
  11         116988  
  11         52  
1553              
1554 0           my %options;
1555              
1556 0 0         if (
1557             !GetOptions(
1558             \%options, 'help', 'env|e', 'ec2|E',
1559             'profile|p=s', 'role|r=s', 'account|a=s'
1560             )
1561             ) {
1562 0           help();
1563             }
1564              
1565 0 0         if ( $options{help} ) {
1566 0           help();
1567             }
1568              
1569 0 0 0       if ( $options{role} && $options{account} ) {
1570             my $credentials = Amazon::Credentials::set_sso_credentials(
1571 0           @options{qw(role account region)} );
1572              
1573 0           print {*STDOUT} export_credentials($credentials);
  0            
1574              
1575 0           exit 0;
1576             }
1577              
1578 0           my @order;
1579              
1580 0 0         if ( $options{env} ) {
    0          
    0          
1581 0           push @order, 'env';
1582             }
1583             elsif ( $options{ec2} ) {
1584 0           push @order, 'ec2';
1585             }
1586             elsif ( $options{profile} ) {
1587 0           push @order, 'file';
1588             }
1589              
1590             my $creds = $self->new(
1591 0 0 0       profile => $options{profile} // EMPTY,
1592             order => ( @order ? \@order : DEFAULT_SEARCH_ORDER )
1593             );
1594              
1595 0           print {*STDOUT} $creds->format_credentials("export %s=%s\n");
  0            
1596              
1597 0           exit 0;
1598             }
1599              
1600             1;
1601              
1602             __END__