File Coverage

blib/lib/Amazon/Credentials.pm
Criterion Covered Total %
statement 452 623 72.5
branch 140 266 52.6
condition 80 184 43.4
subroutine 70 84 83.3
pod 19 44 43.1
total 761 1201 63.3


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