File Coverage

blib/lib/Amazon/S3/Thin/Credentials.pm
Criterion Covered Total %
statement 58 58 100.0
branch 17 24 70.8
condition 6 12 50.0
subroutine 13 13 100.0
pod 6 7 85.7
total 100 114 87.7


line stmt bran cond sub pod time code
1             package Amazon::S3::Thin::Credentials;
2              
3             =head1 NAME
4              
5             Amazon::S3::Thin::Credentials - AWS credentials data container
6              
7             =head1 SYNOPSIS
8              
9             my $credentials = Amazon::S3::Thin::Credentials->new(
10             $aws_access_key_id, $aws_secret_access_key,
11             # optional:
12             $aws_session_token
13             );
14            
15             my $key = $credentials->access_key_id();
16             my $secret = $credentials->secret_access_key();
17             my $session_token = $credentials->session_token();
18              
19             1;
20              
21             =head1 DESCRIPTION
22              
23             This module contains AWS credentials and provide getters to the data.
24              
25             # Load from arguments
26             my $creds = Amazon::S3::Thin::Credentials->new($access_key, $secret_key, $session_token);
27              
28             # Load from environment
29             my $creds = Amazon::S3::Thin::Credentials->from_env;
30              
31             # Load from instance profile
32             my $creds = Amazon::S3::Thin::Credentials->from_metadata(role => 'foo', version => 2);
33              
34             # Load from ECS task role
35             my $creds = Amazon::S3::Thin::Credentials->from_ecs_container;
36              
37             =cut
38              
39 12     12   1732 use strict;
  12         38  
  12         293  
40 12     12   50 use warnings;
  12         18  
  12         258  
41              
42 12     12   61 use Carp;
  12         20  
  12         659  
43 12     12   6534 use JSON::PP ();
  12         143437  
  12         284  
44 12     12   2544 use LWP::UserAgent;
  12         157348  
  12         7677  
45              
46             my $JSON = JSON::PP->new->utf8->canonical;
47              
48             sub new {
49 28     28 0 12508 my ($class, $key, $secret, $session_token) = @_;
50 28         118 my $self = {
51             key => $key,
52             secret => $secret,
53             session_token => $session_token,
54             };
55 28         118 return bless $self, $class;
56             }
57              
58             =head2 from_env()
59              
60             Instantiate C and attempts to populate the credentials from
61             current environment.
62              
63             Croaks if either AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are not set but supports the
64             optional AWS_SESSION_TOKEN variable.
65              
66             my $creds = Amazon::S3::Thin::Credentials->from_env;
67              
68             =cut
69              
70             sub from_env {
71 1     1 1 3 my ($class) = @_;
72              
73             # Check the environment is configured
74 1 50       4 croak "AWS_ACCESS_KEY_ID is not set" unless $ENV{AWS_ACCESS_KEY_ID};
75 1 50       3 croak "AWS_SECRET_ACCESS_KEY is not set" unless $ENV{AWS_SECRET_ACCESS_KEY};
76              
77             my $self = {
78             key => $ENV{AWS_ACCESS_KEY_ID},
79             secret => $ENV{AWS_SECRET_ACCESS_KEY},
80             session_token => $ENV{AWS_SESSION_TOKEN}
81 1         5 };
82 1         4 return bless $self, $class;
83             }
84              
85             =head2 from_metadata()
86              
87             Instantiate C and attempts to populate the credentials from
88             the L. An instance can have multiple IAM
89             roles applied so you may optionally specify a role, otherwise the first one will be used.
90              
91             In November 2019 AWS released L of the instance metadata service which
92             is more secure against Server Side Request Forgery attacks. Using v2 is highly recommended thus
93             it is the default here.
94              
95             my $creds = Amazon::S3::Thin::Credentials->from_metadata(
96             role => 'foo', # The name of the IAM role on the instance
97             version => 2 # Metadata service version - either 1 or 2
98             );
99              
100             =cut
101              
102             sub from_metadata {
103 3     3 1 1796 my ($class, $args) = @_;
104              
105 3   33     11 my $ua = $args->{ua} // LWP::UserAgent->new;
106              
107             # Default to the more secure v2 metadata provider
108 3 100 66     15 if (!$args->{version} or $args->{version} != 1) {
109 1         4 my $res = $ua->put('http://169.254.169.254/latest/api/token', 'X-aws-ec2-metadata-token-ttl-seconds' => 90);
110 1 50       20 croak 'Error retreiving v2 token from metadata provider: ' . $res->decoded_content
111             unless $res->is_success;
112              
113 1         6 $ua->default_header('X-aws-ec2-metadata-token' => $res->decoded_content);
114             }
115              
116 3         25 return _instance_metadata($ua, $args->{role});
117             }
118              
119             sub _instance_metadata {
120 3     3   8 my ($ua, $role) = @_;
121              
122 3         6 my $res = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials');
123 3 50       55 croak 'Error querying metadata service for roles: ' . $res->decoded_content unless $res->is_success;
124              
125 3         14 my @roles = split /\n/, $res->decoded_content;
126 3 50       50 return unless @roles > 0;
127              
128 3 100 66     11 my $target_role = (defined $role and grep { $role eq $_ } @roles)
129             ? $role
130             : $roles[0];
131              
132 3         9 my $cred = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $target_role);
133 3 50       44 croak 'Error querying metadata service for credentials: ' . $cred->decoded_content unless $cred->is_success;
134              
135 3         11 my $obj = eval { $JSON->decode($cred->decoded_content) };
  3         6  
136 3 50       5066 croak "Invalid data returned from metadata service: $@" if $@;
137              
138 3         11 return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
139             }
140              
141             =head2 from_ecs_container()
142              
143             Instantiate C and attempts to populate the credentials from
144             the L.
145              
146             my $creds = Amazon::S3::Thin::Credentials->from_ecs_container;
147              
148             =cut
149              
150             sub from_ecs_container {
151 5     5 1 2903 my ($class, $args) = @_;
152              
153 5   33     23 my $ua = $args->{ua} // LWP::UserAgent->new;
154              
155 5         13 my $relative_uri = $ENV{AWS_CONTAINER_CREDENTIALS_RELATIVE_URI};
156 5 100       196 croak 'The environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is not set' unless defined $relative_uri;
157              
158 4         19 my $cred = $ua->get('http://169.254.170.2' . $relative_uri);
159 4 100       60 croak 'Error retrieving container credentials' unless $cred->is_success;
160              
161 3         31 my $obj = eval { $JSON->decode($cred->decoded_content) };
  3         14  
162 3 100       2315 croak "Invalid data returned: $@" if $@;
163              
164 2         11 return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
165             }
166              
167             =head2 access_key_id()
168              
169             Returns access_key_id
170              
171             =cut
172              
173             sub access_key_id {
174 32     32 1 3967 my $self = shift;
175 32         139 return $self->{key};
176             }
177              
178             =head2 secret_access_key()
179              
180             Returns secret_access_key
181              
182             =cut
183            
184             sub secret_access_key {
185 43     43 1 69 my $self = shift;
186 43         177 return $self->{secret};
187             }
188              
189             =head2 session_token()
190              
191             Returns session_token
192              
193             =cut
194              
195             sub session_token {
196 33     33 1 59 my $self = shift;
197 33         108 return $self->{session_token};
198             }
199              
200             1;