File Coverage

blib/lib/Amazon/S3/Thin/Credentials.pm
Criterion Covered Total %
statement 48 48 100.0
branch 11 18 61.1
condition 5 9 55.5
subroutine 12 12 100.0
pod 5 6 83.3
total 81 93 87.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_metadata(role => 'foo', version => 2);
33              
34             =cut
35              
36 11     11   1322 use strict;
  11         29  
  11         276  
37 11     11   55 use warnings;
  11         21  
  11         262  
38              
39 11     11   49 use Carp;
  11         20  
  11         677  
40 11     11   6142 use JSON::PP ();
  11         129414  
  11         262  
41 11     11   1816 use LWP::UserAgent;
  11         114526  
  11         5497  
42              
43             my $JSON = JSON::PP->new->utf8->canonical;
44              
45             sub new {
46 26     26 0 12582 my ($class, $key, $secret, $session_token) = @_;
47 26         103 my $self = {
48             key => $key,
49             secret => $secret,
50             session_token => $session_token,
51             };
52 26         94 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       4 croak "AWS_ACCESS_KEY_ID is not set" unless $ENV{AWS_ACCESS_KEY_ID};
72 1 50       3 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         17 };
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_metadata(
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 3     3 1 1838 my ($class, $args) = @_;
101              
102 3   33     11 my $ua = $args->{ua} // LWP::UserAgent->new;
103              
104             # Default to the more secure v2 metadata provider
105 3 100 66     16 if (!$args->{version} or $args->{version} != 1) {
106 1         4 my $res = $ua->put('http://169.254.169.254/latest/api/token', 'X-aws-ec2-metadata-token-ttl-seconds' => 90);
107 1 50       21 croak 'Error retreiving v2 token from metadata provider: ' . $res->decoded_content
108             unless $res->is_success;
109              
110 1         6 $ua->default_header('X-aws-ec2-metadata-token' => $res->decoded_content);
111             }
112              
113 3         26 return _instance_metadata($ua, $args->{role});
114             }
115              
116             sub _instance_metadata {
117 3     3   7 my ($ua, $role) = @_;
118              
119 3         8 my $res = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials');
120 3 50       56 croak 'Error querying metadata service for roles: ' . $res->decoded_content unless $res->is_success;
121              
122 3         12 my @roles = split /\n/, $res->decoded_content;
123 3 50       52 return unless @roles > 0;
124              
125 3 100 66     12 my $target_role = (defined $role and grep { $role eq $_ } @roles)
126             ? $role
127             : $roles[0];
128              
129 3         11 my $cred = $ua->get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $target_role);
130 3 50       45 croak 'Error querying metadata service for credentials: ' . $cred->decoded_content unless $cred->is_success;
131              
132 3         12 my $obj = eval { $JSON->decode($cred->decoded_content) };
  3         5  
133 3 50       5060 croak "Invalid data returned from metadata service: $@" if $@;
134              
135 3         11 return __PACKAGE__->new($obj->{AccessKeyId}, $obj->{SecretAccessKey}, $obj->{Token});
136             }
137              
138             =head2 access_key_id()
139              
140             Returns access_key_id
141              
142             =cut
143              
144             sub access_key_id {
145 31     31 1 3279 my $self = shift;
146 31         125 return $self->{key};
147             }
148              
149             =head2 secret_access_key()
150              
151             Returns secret_access_key
152              
153             =cut
154            
155             sub secret_access_key {
156 42     42 1 65 my $self = shift;
157 42         187 return $self->{secret};
158             }
159              
160             =head2 session_token()
161              
162             Returns session_token
163              
164             =cut
165              
166             sub session_token {
167 32     32 1 56 my $self = shift;
168 32         94 return $self->{session_token};
169             }
170              
171             1;