File Coverage

blib/lib/Amazon/S3/Thin/Credentials.pm
Criterion Covered Total %
statement 29 48 60.4
branch 2 18 11.1
condition 0 6 0.0
subroutine 10 12 83.3
pod 5 6 83.3
total 46 90 51.1


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_instance(role => 'foo', version => 2);
33              
34             =cut
35              
36 10     10   1089 use strict;
  10         26  
  10         304  
37 10     10   48 use warnings;
  10         19  
  10         339  
38              
39 10     10   61 use Carp;
  10         17  
  10         636  
40 10     10   6672 use JSON::PP ();
  10         134417  
  10         301  
41 10     10   1473 use LWP::UserAgent;
  10         80814  
  10         5862  
42              
43             my $JSON = JSON::PP->new->utf8->canonical;
44              
45             sub new {
46 23     23 0 15890 my ($class, $key, $secret, $session_token) = @_;
47 23         113 my $self = {
48             key => $key,
49             secret => $secret,
50             session_token => $session_token,
51             };
52 23         87 return bless $self, $class;
53             }
54              
55             =head2 from_env()
56              
57             Instantiate C and attempts to populate the credentials from
58             current environment.
59              
60             Croaks if either AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are not set but supports the
61             optional AWS_SESSION_TOKEN variable.
62              
63             my $creds = Amazon::S3::Thin::Credentials->from_env;
64              
65             =cut
66              
67             sub from_env {
68 1     1 1 3 my ($class) = @_;
69              
70             # Check the environment is configured
71 1 50       6 croak "AWS_ACCESS_KEY_ID is not set" unless $ENV{AWS_ACCESS_KEY_ID};
72 1 50       5 croak "AWS_SECRET_ACCESS_KEY is not set" unless $ENV{AWS_SECRET_ACCESS_KEY};
73              
74             my $self = {
75             key => $ENV{AWS_ACCESS_KEY_ID},
76             secret => $ENV{AWS_SECRET_ACCESS_KEY},
77             session_token => $ENV{AWS_SESSION_TOKEN}
78 1         23 };
79 1         5 return bless $self, $class;
80             }
81              
82             =head2 from_metadata()
83              
84             Instantiate C and attempts to populate the credentials from
85             the L. An instance can have multiple IAM
86             roles applied so you may optionally specify a role, otherwise the first one will be used.
87              
88             In November 2019 AWS released L of the instance metadata service which
89             is more secure against Server Side Request Forgery attacks. Using v2 is highly recommended thus
90             it is the default here.
91              
92             my $creds = Amazon::S3::Thin::Credentials->from_instance(
93             role => 'foo', # The name of the IAM role on the instance
94             version => 2 # Metadata service version - either 1 or 2
95             );
96              
97             =cut
98              
99             sub from_metadata {
100 0     0 1 0 my ($class, $args) = @_;
101              
102 0         0 my $ua = LWP::UserAgent->new;
103              
104             # Default to the more secure v2 metadata provider
105 0 0 0     0 if (!$args->{version} or $args->{version} != 1) {
106 0         0 my $res = $ua->get('http://169.254.169.254/latest/api/token', {
107             'X-aws-ec2-metadata-token-ttl-seconds' => 90
108             });
109 0 0       0 croak 'Error retreiving v2 token from metadata provider: ' . $res->decoded_content
110             unless $res->is_success;
111              
112 0         0 $ua->default_header('X-aws-ec2-metadata-token' => $res->decoded_content);
113             }
114              
115 0         0 return _instance_metadata($ua, $args->{role});
116             }
117              
118             sub _instance_metadata {
119 0     0   0 my ($ua, $role) = @_;
120              
121 0         0 my $res = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials');
122 0 0       0 croak 'Error querying metadata service for roles: ' . $res->decoded_content unless $res->is_success;
123              
124 0         0 my @roles = split /\n/, $res->decoded_content;
125 0 0       0 return unless @roles > 0;
126              
127 0 0 0     0 my $target_role = (defined $role and grep { $role eq $_ } @roles)
128             ? $role
129             : $roles[0];
130              
131 0         0 my $cred = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $target_role);
132 0 0       0 croak 'Error querying metadata service for credentials: ' . $cred->decoded_content unless $cred->is_success;
133              
134 0         0 my $obj = eval { $JSON->decode($cred->decoded_content) };
  0         0  
135 0 0       0 croak "Invalid data returned from metadata service: $@" if $@;
136              
137 0         0 return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
138             }
139              
140             =head2 access_key_id()
141              
142             Returns access_key_id
143              
144             =cut
145              
146             sub access_key_id {
147 28     28 1 55 my $self = shift;
148 28         115 return $self->{key};
149             }
150              
151             =head2 secret_access_key()
152              
153             Returns secret_access_key
154              
155             =cut
156            
157             sub secret_access_key {
158 39     39 1 80 my $self = shift;
159 39         197 return $self->{secret};
160             }
161              
162             =head2 session_token()
163              
164             Returns session_token
165              
166             =cut
167              
168             sub session_token {
169 29     29 1 60 my $self = shift;
170 29         99 return $self->{session_token};
171             }
172              
173             1;