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   88075 use strict;
  11         24  
  11         327  
4 11     11   52 use warnings;
  11         20  
  11         271  
5              
6 11     11   245 use 5.010;
  11         32  
7              
8 11     11   595 use parent qw( Exporter Class::Accessor::Fast );
  11         335  
  11         93  
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   36604 use Carp;
  11         23  
  11         633  
48 11     11   5597 use Config::Tiny;
  11         12713  
  11         351  
49 11     11   71 use Cwd;
  11         21  
  11         762  
50 11     11   773 use Data::Dumper;
  11         7164  
  11         455  
51 11     11   542 use Date::Format;
  11         7773  
  11         542  
52 11     11   3103 use English qw(-no_match_vars);
  11         11771  
  11         65  
53 11     11   3912 use Exporter;
  11         30  
  11         378  
54 11     11   6165 use File::HomeDir;
  11         63367  
  11         570  
55 11     11   5410 use File::chdir;
  11         35507  
  11         954  
56 11     11   3531 use HTTP::Request;
  11         149444  
  11         411  
57 11     11   5884 use JSON::PP qw(decode_json encode_json);
  11         113581  
  11         729  
58 11     11   5409 use LWP::UserAgent;
  11         205833  
  11         431  
59 11     11   82 use List::Util qw(pairs any);
  11         22  
  11         1409  
60 11     11   5610 use MIME::Base64;
  11         7410  
  11         696  
61 11     11   5053 use POSIX::strptime qw(strptime);
  11         5983  
  11         658  
62 11     11   85 use Scalar::Util qw(reftype);
  11         21  
  11         431  
63 11     11   60 use Time::Local;
  11         20  
  11         1494  
64              
65             use constant { ## no critic (ProhibitConstantPragma, Capitalization)
66 11         3791 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   93 };
  11         34  
99              
100             our $VERSION = '1.1.20'; ## 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   81 no strict 'refs'; ## no critic (ProhibitNoStrict)
  11         24  
  11         83858  
108              
109             *{'Amazon::Credentials::Logger::debug'} = sub {
110 149     149   1873 my ( $self, @message ) = @_;
111              
112 149 100       435 return if !$self->{debug};
113              
114 26         612 my @tm = localtime time;
115              
116 26         115 my $timestamp = strftime '%c', @tm;
117              
118 26         2576 my $message = sprintf LOG_FORMAT, $timestamp, $PROCESS_ID, @message;
119              
120 26         71 print {*STDERR} $message;
  26         588  
121             };
122             }
123              
124             caller or __PACKAGE__->main();
125              
126             ########################################################################
127             sub new {
128             ########################################################################
129 18     18 1 18200 my ( $class, @args ) = @_;
130              
131 18 100       81 my $options = ref $args[0] ? $args[0] : {@args};
132              
133 18         42 my $passkey = delete $options->{passkey};
134              
135 18         181 my $self = $class->SUPER::new($options);
136              
137 18         224 $self->_set_defaults;
138              
139 16         55 $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     57 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         59 $self->_init_encryption($passkey);
169             }
170              
171 16 100       430 if ( $self->get_insecure ) {
172 2         46 $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     439 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     193 if ( !$options->{aws_secret_access_key}
190             || !$options->{aws_access_key_id} ) {
191 16         71 $self->set_credentials;
192              
193 13 100       329 if ( $self->get__session_token ) {
194 2         81 $self->set_session_token_required(TRUE);
195             }
196              
197 13 50       380 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       348 if ( !$self->get_region ) {
209 5   33     85 $self->set_region( $self->get_region_from_env
210             || $self->get_default_region );
211             }
212              
213 13         274 return $self;
214             }
215              
216             ########################################################################
217             sub set_default_logger {
218             ########################################################################
219 16     16 0 40 my ( $self, $debug ) = @_;
220              
221 16   66     330 $debug = $debug // $self->get_debug;
222              
223 16         230 my $logger = bless { debug => $debug }, 'Amazon::Credentials::Logger';
224              
225 16         330 $self->set_logger($logger);
226              
227 16         190 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 13 my ($self) = @_;
253              
254 5   33     47 my $region = $ENV{AWS_REGION} || $ENV{AWS_DEFAULT_REGION};
255              
256 5         63 return $region;
257             }
258              
259             ########################################################################
260             sub get_default_region {
261             ########################################################################
262 5     5 1 26 my ($self) = @_;
263              
264 5         18 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     134 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         31 my $url;
271              
272 1 50       26 if ( $self->get_container ) {
273 1         17 $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         10 my @headers;
282              
283             # add imdsv2 token to metadata request
284 1 50 33     18 if ( !$self->get_container && $self->get_imdsv2_token ) {
285 0         0 @headers = ( IMDSv2_HEADER => $self->get_imdsv2_token );
286             }
287              
288 1         18 my $req = HTTP::Request->new( GET => $url, \@headers );
289              
290 1         6 $region = eval {
291 1         3 my $rsp = $ua->request($req);
292              
293             # if not 200, then get out of Dodge
294 1 50       19 croak "could not get availability zone\n"
295             if !$rsp->is_success;
296              
297 1         7 my $content = $rsp->content;
298 1         10 $content =~ s/(\d+)[[:lower:]]+$/$1/xsm;
299              
300 1         3 return $content;
301             };
302             }
303              
304 5         252 return $region;
305             }
306              
307             ########################################################################
308             sub set_credentials {
309             ########################################################################
310 19     19 1 230 my ( $self, $creds ) = @_;
311              
312 19         456 $self->set_error(EMPTY);
313              
314 19         193 $creds = eval {
315 19 100       66 if ( !$creds ) {
316 17         26 $creds = eval { return $self->find_credentials };
  17         53  
317             }
318              
319 19   66     615 $self->set_error( $creds->{error} || $EVAL_ERROR );
320              
321             croak 'no credentials available'
322 19 100 66     1349 if !$creds->{aws_secret_access_key} || !$creds->{aws_access_key_id};
323              
324 13         55 $self->set_aws_secret_access_key( $creds->{aws_secret_access_key} );
325              
326 13         125 $self->set_aws_access_key_id( $creds->{aws_access_key_id} );
327              
328 13   66     172 $self->set_token( $creds->{token} || $creds->{aws_session_token} );
329              
330 13         377 $self->set_expiration( $creds->{expiration} );
331              
332 13         173 return $creds;
333             };
334              
335 19 100 100     377 if ( $EVAL_ERROR && !$self->get_error ) {
336 5         129 $self->set_error($EVAL_ERROR);
337             }
338              
339 19         98 undef $EVAL_ERROR;
340              
341 19 100 100     387 croak $self->get_error
342             if $self->get_error && $self->get_raise_error;
343              
344 16 100 100     501 carp $self->get_error
345             if $self->get_error && $self->get_print_error;
346              
347 16         700 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 65 my ( $self, @args ) = @_;
360              
361 17 50       69 my $options = ref $args[0] ? $args[0] : {@args};
362              
363 17 50       71 if ( $options->{profile} ) {
364 0         0 $self->set_profile( $options->{profile} );
365             }
366              
367 17 50       54 if ( $options->{order} ) {
368 0         0 $self->set_order( $options->{order} );
369             }
370              
371 17         32 my @search_order;
372              
373 17 100 33     366 if ( $self->get_profile ) {
    50          
    0          
374 14         114 @search_order = ('file');
375             }
376             elsif ( ref $self->get_order && reftype( $self->get_order ) eq 'ARRAY' ) {
377 3         170 @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         346 $self->get_logger->debug( 'search order ' . join COMMA, @search_order );
384              
385             my %creds_getters = (
386             env => sub {
387 2     2   6 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   19 return $self->get_creds_from_container;
394             },
395             file => sub {
396 14     14   62 return $self->get_creds_from_ini_file;
397             },
398 17         208 );
399              
400 17         30 my $creds;
401              
402 17         52 foreach my $location (@search_order) {
403              
404 17         344 $self->get_logger->debug( 'searching for credentials in: ' . $location );
405              
406 17 50       94 if ( $creds_getters{$location} ) {
407 17         44 $creds = $creds_getters{$location}->();
408             }
409              
410 16 100       78 last if $creds->{source};
411             }
412              
413 16         32 foreach my $k ( keys %{$creds} ) {
  16         68  
414              
415 68 100       1052 if ( $k !~ /^aws|token/xsm ) {
    50          
416 24         154 $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         391 $self->get_logger->debug( $self->safe_dumper($creds) );
424              
425 16         216 return $creds;
426             }
427              
428             # +------------------+
429             # | get_creds_from_* |
430             # +------------------+
431              
432             ########################################################################
433             sub get_creds_from_env {
434             ########################################################################
435 2     2 0 4 my ($self) = @_;
436              
437             return {}
438 2 100 66     10 if !$ENV{AWS_ACCESS_KEY_ID} || !$ENV{AWS_SECRET_ACCESS_KEY};
439              
440 1         21 $self->get_logger->debug('fetching credentials from env');
441              
442 1         19 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         5 return populate_creds( 'ENV', \@cred_keys, \%ENV );
450             }
451              
452             ########################################################################
453             sub populate_creds {
454             ########################################################################
455 31     31 0 1674 my ( $source, $cred_keys, $creds_source ) = @_;
456              
457 31         87 my $creds = { source => $source };
458              
459 31         90 foreach my $p ( pairs @{$cred_keys} ) {
  31         356  
460 154         200 my ( $k, $v ) = @{$p};
  154         299  
461 154         284 $creds->{$k} = $creds_source->{$v};
462 154 100       325 next if $source =~ /env/i;
463              
464 150         233 delete $creds_source->{$v};
465             }
466              
467 31         213 return $creds;
468             }
469              
470             ########################################################################
471             sub get_creds_from_process {
472             ########################################################################
473 2     2 1 13 my ( $self, $process ) = @_;
474              
475 2         37 $self->get_logger->debug("fetching credentials from $process");
476              
477 2         6 my $credentials = eval {
478 2 100       10875 open my $fh, q{-|}, $process
479             or croak "could not open pipe to $process\n$OS_ERROR";
480              
481 1         32 local $RS = undef;
482              
483 1         9098 my $credentials_str = <$fh>;
484              
485 1 50       62 close $fh
486             or croak "could not close filehandle on $process\n";
487              
488 1         48 return decode_json($credentials_str);
489             };
490              
491 2 100 66     12173 if ( $EVAL_ERROR || !$credentials ) {
492 1         346 croak "could not get credentials from process\n$EVAL_ERROR\n";
493             }
494              
495 1         59 $self->get_logger->debug( $self->safe_dumper($credentials) );
496              
497 1         36 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         14 return populate_creds( 'process', \@cred_keys, $credentials );
507             }
508              
509             ########################################################################
510             sub create_config_path {
511             ########################################################################
512 33     33 0 74 my ($config) = @_;
513              
514 33         131 my $fullpath = home . SLASH . $config;
515              
516 33 100       1878 return -e $fullpath ? $fullpath : EMPTY;
517             }
518              
519             ########################################################################
520             sub get_region_from_config {
521             ########################################################################
522 5     5 0 22 my ($self) = @_;
523              
524 5         31 my $config = create_config_path('.aws/config');
525              
526 5 100       33 return if !$config;
527              
528 4         107 $self->get_logger->debug("config: $config");
529              
530 4         39 my $ini = Config::Tiny->read($config);
531              
532 4         795 my $region;
533              
534 4 50       23 if ( $ini->{default} ) {
535 4         16 $region = $ini->{default}->{region};
536             }
537              
538 4   50     101 $self->get_logger->debug( 'default region: ' . $region // 'undef' );
539              
540 4         18 return $region;
541             }
542              
543             ########################################################################
544             sub get_creds_from_ini_file {
545             ########################################################################
546 14     14 0 32 my ( $self, $profile ) = @_;
547              
548 14   50     343 $profile = $profile || $self->get_profile || 'default';
549              
550 14         151 my $creds = {};
551 14         34 my $region;
552              
553 14         42 foreach my $config (qw( .aws/config .aws/credentials )) {
554 28 50       82 last if $creds->{source};
555              
556 28         46 my $profile_name = $profile;
557              
558 28         71 my $fullpath = create_config_path($config);
559              
560 28 50       101 if ( !$fullpath ) {
561 0         0 $self->get_logger->debug( 'skipping ' . $config . '...not found' );
562 0         0 next;
563             }
564              
565 28         689 $self->get_logger->debug( 'reading ' . $config );
566              
567 28         149 my $ini = Config::Tiny->read($fullpath);
568              
569 28         7678 $self->get_logger->debug( $self->safe_dumper($ini) );
570              
571 28 100       100 if ( $ini->{default} ) {
572 27         56 $region = $ini->{default}->{region};
573             }
574              
575 28         34 my $section;
576              
577 28 100       116 if ( $ini->{$profile_name} ) {
    100          
578 10         32 $section = $ini->{$profile_name};
579             }
580             elsif ( $ini->{"profile $profile_name"} ) { ## config file format
581 2         8 $section = $ini->{"profile $profile_name"};
582             }
583              
584             my $process
585 28   66     121 = $ini->{credential_process} || $section->{credential_process};
586              
587 28 100       71 if ($process) {
    50          
588 2         11 $creds = $self->get_creds_from_process($process);
589 1 50       44 $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         86 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         69 $creds = populate_creds( $config, \@cred_keys, $section );
606              
607 26 100 66     125 if ( !$creds->{aws_access_key_id} || !$creds->{aws_secret_access_key} )
608             {
609 18         94 $creds = {};
610             }
611             }
612             }
613              
614 13   100     503 $self->set_region( $creds->{region} || $region );
615              
616 13         182 return $creds;
617             }
618              
619             ########################################################################
620             sub get_creds_from_role {
621             ########################################################################
622 1     1 1 2 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         4 my $url = _create_metadata_url(AWS_IAM_SECURITY_CREDENTIALS_URL);
633              
634 1         24 my $ua = $self->get_user_agent;
635              
636 1         7 my $role;
637              
638 1         4 my $creds = { error => undef };
639              
640 1 50       20 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         5 my @headers;
679              
680 2 50 33     51 if ( $self->get_imdsv2 && $self->get_imdsv2_token ) {
681 0         0 @headers = ( IMDSv2_HEADER, $self->get_imdsv2_token );
682             }
683              
684 2         26 my $req = HTTP::Request->new( GET => $url, \@headers );
685              
686 2         46 $self->get_logger->debug( Dumper [ "HTTP REQUEST:\n", $req ] );
687              
688 2         11 my $rsp = $ua->request($req);
689              
690 2         56 $self->get_logger->debug( $self->dump_response($rsp) );
691              
692             # if not 200, then get out of Dodge
693 2 50       8 last if !$rsp->is_success;
694              
695 2 100       12 if ($role) {
696 1         7 my $creds_source = decode_json( $rsp->content );
697              
698 1         2976 $creds = populate_creds( 'IAM', \@cred_keys, $creds_source );
699 1         6 $creds->{role} = $role;
700             }
701             else {
702 1         3 $role = $rsp->content;
703 1         31 $self->get_logger->debug( Dumper [ 'role', $role ] );
704              
705 1 50       13 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         4 return $creds;
717             }
718              
719             ########################################################################
720             sub get_creds_from_container {
721             ########################################################################
722 1     1 1 4 my ( $self, $uri ) = @_;
723              
724 1   33     7 $uri //= $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI};
725              
726 1         2 my $creds = {};
727              
728 1 50       3 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         22 $self->get_logger->debug( Dumper( [ uri => $uri ] ) );
735              
736 1         3 $creds = eval {
737             # try to get credentials from instance role
738 1         3 my $url = AWS_CONTAINER_CREDENTIALS_URL . $uri;
739              
740 1         21 my $ua = $self->get_user_agent;
741              
742 1         9 my $req = HTTP::Request->new( GET => $url );
743 1         8 $req->header(qw( Accept */* ));
744              
745 1         18 $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         32 $self->get_logger->debug( $self->dump_response($rsp) );
755              
756             # if not 200, then get out of Dodge
757 1 50       4 if ( $rsp->is_success ) {
758              
759 1         5 my $creds_source = decode_json( $rsp->content );
760              
761 1         1717 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         7 $creds = populate_creds( 'IAM', \@cred_keys, $creds_source );
770 1         4 $creds->{container} = 'ECS';
771             }
772             else {
773 0         0 $self->get_logger->debug( 'return code: ' . $rsp->status_line );
774             }
775              
776 1         7 return $creds;
777             };
778              
779 1         14 $creds->{error} = $EVAL_ERROR;
780 1         27 $self->get_logger->debug( "EVAL_ERROR: $EVAL_ERROR\n" . Dumper $creds);
781              
782 1         6 return $creds;
783             }
784              
785             # +---------+
786             # | DUMPERS |
787             # +---------+
788              
789             ########################################################################
790             sub safe_dumper {
791             ########################################################################
792 45     45 0 371 my ( $self, $obj ) = @_;
793              
794 45 50       135 return if !ref $obj;
795              
796 45         70 my $safe_rsp;
797              
798 45 100 100     861 if ( $self->get_insecure && $self->get_insecure =~ /^2$/xsm ) {
    100          
799 3         80 $safe_rsp = Dumper [$obj];
800             }
801             elsif ( $self->get_insecure ) {
802 3         168 $safe_rsp = Dumper [$obj];
803              
804 3         404 $safe_rsp
805             =~ s/(.*?)(access_?key[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
806              
807 3         42 $safe_rsp
808             =~ s/(.*?)(secret_?access[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
809              
810 3         14 $safe_rsp =~ s/(.*?)(token[^']+)'([^']+)'([^']+)'/$1$2'$3'...'/ixsmg;
811             }
812             else {
813 39         1040 $safe_rsp = '** configuration contents blocked by insecure setting **';
814             }
815              
816 45         461 return $safe_rsp;
817             }
818              
819             ########################################################################
820             sub dump_response {
821             ########################################################################
822 3     3 0 23 my ( $self, $rsp ) = @_;
823              
824 3         3 my $safe_rsp;
825              
826 3 50 33     53 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         120 $safe_rsp = '** HTTP RESPONSE blocked by insecure setting **';
846             }
847              
848 3         13 return Dumper [$safe_rsp];
849             }
850              
851             # +---------------+
852             # | TOKEN METHODS |
853             # +---------------+
854              
855             ########################################################################
856             sub is_token_expired {
857             ########################################################################
858 4     4 1 319 my ( $self, $window_interval ) = @_;
859 4   50     35 $window_interval = $window_interval // DEFAULT_WINDOW_INTERVAL;
860              
861 4         95 my $expiration_date = $self->get_expiration();
862              
863 4         22 my $expired = FALSE;
864              
865 4 50       10 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         7 $g -= $window_interval * SECONDS_IN_MINUTE;
871              
872             # (expiration_time - window_interval) - current_time = # of seconds left before expiration
873 4         7 my $seconds_left = $g - time;
874              
875 4 50       101 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         111 $self->get_logger->debug(
892             Dumper [
893             expiration_date => $expiration_date,
894             expired => $expired
895             ]
896             );
897             }
898              
899 4         24 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 645 my ($self) = @_;
912              
913 1         2 my $creds;
914              
915 1 50 33     22 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         36 $creds = $self->get_creds_from_role;
920             }
921              
922             croak 'unable to refresh token!'
923 1 50 33     4 if !ref $creds || !keys %{$creds};
  1         5  
924              
925 1         4 return $self->set_credentials($creds);
926             }
927              
928             ########################################################################
929             sub credential_keys {
930             ########################################################################
931 3     3 1 4048 my ($self) = @_;
932              
933 3         6 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         23 %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       28 if ( !defined $credential_keys{AWS_SESSION_TOKEN} ) {
956 3         6 delete $credential_keys{AWS_SESSION_TOKEN};
957 3         4 delete $credential_keys{AWS_SESSION_TOKEN_EXPIRATION};
958             }
959              
960 3         29 return \%credential_keys;
961             }
962              
963             ########################################################################
964             sub as_string {
965             ########################################################################
966 1     1 1 3017 my ($self) = @_;
967              
968 1         13 return JSON::PP->new->pretty->encode( $self->credential_keys );
969             }
970              
971             ########################################################################
972             sub format_credentials {
973             ########################################################################
974 1     1 1 1576 my ( $self, $format ) = @_;
975              
976 1   50     4 $format = $format || "%s %s\n";
977              
978 1         4 my $credential_keys = $self->credential_keys;
979              
980             return join EMPTY,
981 2   50     16 map { sprintf $format, $_, $credential_keys->{$_} // EMPTY }
982 1         2 keys %{$credential_keys};
  1         4  
983             }
984              
985             # +----------------------------+
986             # | CREDENTIALS getters/setter |
987             # +----------------------------+
988              
989             ########################################################################
990             sub get_aws_access_key_id {
991             ########################################################################
992 9     9 0 3502 my ($self) = @_;
993              
994 9 50       216 if ( !$self->get__access_key_id ) {
995 0         0 $self->set_credentials;
996             }
997              
998 9         250 my $access_key_id
999             = $self->decrypt( $self->get__access_key_id, $self->_fetch_passkey );
1000              
1001 9 50       182 if ( !$self->get_cache ) {
1002 0         0 $self->set__access_key_id(undef);
1003             }
1004              
1005 9         122 return $access_key_id;
1006             }
1007              
1008             ########################################################################
1009             sub get_aws_secret_access_key {
1010             ########################################################################
1011 7     7 0 1247 my ($self) = @_;
1012              
1013 7 100       159 if ( !$self->get__secret_access_key ) {
1014 1         16 $self->set_credentials;
1015             }
1016              
1017 7         164 my $secret_access_key
1018             = $self->decrypt( $self->get__secret_access_key, $self->_fetch_passkey );
1019              
1020 7 50       124 if ( !$self->get_cache ) {
1021 0         0 $self->set__secret_access_key(undef);
1022             }
1023              
1024 7         57 return $secret_access_key;
1025             }
1026              
1027             ########################################################################
1028             sub get_token {
1029             ########################################################################
1030 5     5 0 15 my ($self) = @_;
1031              
1032 5 50 66     91 if ( !$self->get__session_token && $self->get_session_token_required ) {
1033 0         0 $self->set_credentials;
1034             }
1035              
1036 5         143 my $passkey = $self->_fetch_passkey;
1037 5         88 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         116 return $token;
1044             }
1045              
1046             ########################################################################
1047             sub set_aws_access_key_id {
1048             ########################################################################
1049 24     24 0 69 my ( $self, $aws_access_key_id, $passkey ) = @_;
1050              
1051 24   50     56 my $key = $aws_access_key_id || undef;
1052              
1053 24 50       65 if ($aws_access_key_id) {
1054 24         75 $key = $self->encrypt( $aws_access_key_id, $passkey );
1055             }
1056              
1057 24         507 return $self->set__access_key_id($key);
1058             }
1059              
1060             ########################################################################
1061             sub set_aws_secret_access_key {
1062             ########################################################################
1063 24     24 0 78 my ( $self, $aws_secret_access_key, $passkey ) = @_;
1064              
1065 24   50     74 my $key = $aws_secret_access_key || undef;
1066              
1067 24 50       66 if ($aws_secret_access_key) {
1068 24         77 $key = $self->encrypt( $aws_secret_access_key, $passkey );
1069             }
1070              
1071 24         490 return $self->set__secret_access_key($key);
1072             }
1073              
1074             ########################################################################
1075 11     11 0 86 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 76 my ( $self, $session_token, $passkey ) = @_;
1083              
1084 35   100     128 my $token = $session_token || undef;
1085              
1086 35 100       72 if ($session_token) {
1087 6         21 $token = $self->encrypt( $session_token, $passkey );
1088             }
1089              
1090 35         704 return $self->set__session_token($token);
1091             }
1092              
1093             # +--------------------+
1094             # | ENCRYPTION METHODS |
1095             # +--------------------+
1096              
1097             ########################################################################
1098             sub decrypt {
1099             ########################################################################
1100 21     21 1 69 my ( $self, $str, $passkey ) = @_;
1101              
1102 21 50 33     376 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         180 return $self->_crypt( $str, 'decrypt', $passkey );
1107             }
1108             }
1109              
1110             ########################################################################
1111             sub encrypt {
1112             ########################################################################
1113 54     54 1 105 my ( $self, $str, $passkey ) = @_;
1114              
1115 54 50 33     1111 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         482 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   43 my ($self) = @_;
1254              
1255 16 50 33     334 if ( !$self->get_logger || !ref $self->get_logger ) {
1256 16         180 $self->set_default_logger;
1257             }
1258              
1259 16         349 $self->get_logger->debug( 'using ' . ref( $self->get_logger ) . ' logger' );
1260              
1261 16         41 return $self;
1262             }
1263              
1264             ########################################################################
1265             sub _set_defaults {
1266             ########################################################################
1267 18     18   40 my ($self) = @_;
1268              
1269 18   100     521 $self->set_debug( $self->get_debug // FALSE );
1270              
1271 18 50       1011 $self->set_cache( defined $self->get_cache ? $self->get_cache : TRUE );
1272              
1273 18 50       837 if ( !$self->get_user_agent ) {
1274             # set a very low timeout
1275 18   50     542 $self->set_user_agent(
1276             LWP::UserAgent->new( timeout => $self->get_timeout || DEFAULT_TIMEOUT )
1277             );
1278             }
1279              
1280 18         18542 my $default_search_order = [ split /\s*,\s*/xsm, DEFAULT_SEARCH_ORDER ];
1281              
1282 18 100       398 if ( !$self->get_order ) {
1283 7         156 $self->set_order($default_search_order);
1284             }
1285              
1286 18 100 66     487 if ( !ref $self->get_order ) {
    100          
1287 3         162 $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         208 croak 'order must be a comma delimited string or array ref';
1291             }
1292              
1293 17         954 foreach my $loc ( @{ $self->get_order } ) {
  17         286  
1294             croak "invalid credential location in search order: [$loc]"
1295 38 100   102   223 if !any {/^$loc$/xsm} @{$default_search_order};
  102         1062  
  38         119  
1296             }
1297              
1298 16 100 100 5   387 if ( !$self->get_profile && any {/file/xsm} @{ $self->get_order } ) {
  5         67  
  5         199  
1299 2         44 $self->set_profile( $ENV{AWS_PROFILE} );
1300             }
1301              
1302 16   100     473 $self->set_raise_error( $self->get_raise_error // TRUE );
1303 16   100     881 $self->set_print_error( $self->get_print_error // TRUE );
1304              
1305 16         586 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 489 return $passkey;
1320             }
1321              
1322             ########################################################################
1323             sub set_passkey {
1324             ########################################################################
1325 16     16 0 31 my ( $self, $key ) = @_;
1326              
1327 16         25 $passkey = $key;
1328              
1329 16         21 return $passkey;
1330             }
1331             }
1332              
1333             ########################################################################
1334             sub _fetch_passkey {
1335             ########################################################################
1336 92     92   221 my ($self) = @_;
1337              
1338 92         116 my $passkey = eval {
1339 92 50 33     178 if ( ref $self->get_passkey && reftype( $self->get_passkey ) eq 'CODE' ) {
1340 0         0 return $self->get_passkey->();
1341             }
1342             else {
1343 92         161 return $self->get_passkey;
1344             }
1345             };
1346              
1347 92         231 return $passkey;
1348             }
1349              
1350             ########################################################################
1351             sub _init_encryption {
1352             ########################################################################
1353 16     16   40 my ( $self, $passkey ) = @_;
1354              
1355 16         56 $self->set_passkey($passkey);
1356              
1357             # if one is set, both must be set
1358 16 50 33     348 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         578 my $has_crypt_cbc = eval {
1369 16         2334 require Crypt::CBC;
1370 0         0 require Crypt::Cipher::AES;
1371             };
1372              
1373 16 50       445 if ( !defined $self->get_encryption ) {
1374             # let's make the default to encrypt (if we can)
1375 16 50       506 $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     438 if ( $self->get_encryption && !$self->get_cipher ) {
1383 0         0 $self->set_cipher(DEFAULT_CIPHER);
1384             }
1385              
1386             }
1387              
1388 16 50 33     369 if ( $self->get_encryption && !$self->get_passkey ) {
1389 0         0 $self->set_passkey( $self->create_passkey );
1390             }
1391              
1392 16         394 return $self->get_encryption;
1393             }
1394              
1395             ########################################################################
1396             sub _crypt {
1397             ########################################################################
1398 75     75   174 my ( $self, $str, $op, $passkey ) = @_;
1399              
1400 75 100       141 return if !$str;
1401              
1402 71   33     252 $passkey = $passkey || $self->_fetch_passkey();
1403              
1404 71         82 my $cipher;
1405              
1406 71 50       1317 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       452 if ( $op eq 'decrypt' ) {
1417 17         74 $str = decode_base64($str);
1418 17 50       34 $str = ref $cipher ? $cipher->decrypt($str) : $str;
1419             }
1420             else {
1421 54 50       131 $str = ref $cipher ? $cipher->encrypt($str) : $str;
1422 54         203 $str = encode_base64($str);
1423             }
1424              
1425 71         173 return $str;
1426             }
1427              
1428             ########################################################################
1429             sub _iso8601_to_time {
1430             ########################################################################
1431 4     4   7 my $iso8601 = shift;
1432              
1433 4         28 $iso8601 =~ s/^(.*)Z$/$1\+00:00/xsm;
1434              
1435 4         9 my $gmtime = eval {
1436 4         23 local $ENV{TZ} = 'GMT';
1437              
1438 4         115 timegm( strptime( $iso8601, ISO8601_FORMAT ) );
1439             };
1440              
1441 4         182 return $gmtime;
1442             }
1443              
1444             ########################################################################
1445             sub _create_metadata_url {
1446             ########################################################################
1447 1     1   4 my ($url) = @_;
1448              
1449 1         4 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 4638 my ($credentials) = @_;
1535              
1536 1   50     14 $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         10  
1542              
1543             return join "\n",
1544 1         3 map { sprintf 'export %s=%s', $_, $credentials->{$_} } @cred_keys;
  3         14  
1545             }
1546              
1547             ########################################################################
1548             sub main {
1549             ########################################################################
1550 0     0 0   my ($self) = @_;
1551              
1552 11     11   9640 use Getopt::Long qw(:config no_ignore_case);
  11         119012  
  11         62  
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__