File Coverage

blib/lib/Signer/AWSv4.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Signer::AWSv4;
2 4     4   2509 use Moo;
  4         10  
  4         27  
3 4     4   1449 use Types::Standard qw/Str Int HashRef Bool InstanceOf ArrayRef/;
  4         8  
  4         32  
4 4     4   7160 use Time::Piece;
  4         44797  
  4         24  
5 4     4   2855 use Digest::SHA qw//;
  4         11284  
  4         134  
6 4     4   2206 use URI::Escape qw//;
  4         6384  
  4         5234  
7              
8             our $VERSION = '0.06';
9              
10             has access_key => (is => 'ro', isa => Str, required => 1);
11             has secret_key => (is => 'ro', isa => Str, required => 1);
12             has session_token => (is => 'ro', isa => Str);
13             has method => (is => 'ro', isa => Str, required => 1);
14             has uri => (is => 'ro', isa => Str, required => 1);
15             has region => (is => 'ro', isa => Str, required => 1);
16             has service => (is => 'ro', isa => Str, required => 1);
17              
18             has expires => (is => 'ro', isa => Int, required => 1);
19              
20             # build_params and build_headers have to be implemented in subclasses to include
21             # the query string parameters (params) and the headers for the request
22             has params => (is => 'ro', isa => HashRef, lazy => 1, builder => 'build_params');
23             has headers => (is => 'ro', isa => HashRef, lazy => 1, builder => 'build_headers');
24             has content => (is => 'ro', isa => Str, default => '');
25             has unsigned_payload => (is => 'ro', isa => Bool, default => 0);
26              
27             has time => (is => 'ro', isa => InstanceOf['Time::Piece'], default => sub {
28             gmtime;
29             });
30              
31             has date => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
32             my $self = shift;
33             $self->time->ymd('');
34             });
35              
36             has date_timestamp => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
37             my $self = shift;
38             $self->time->ymd('') . 'T' . $self->time->hms('') . 'Z';
39             });
40              
41             has canonical_qstring => (is => 'ro', isa => Str, lazy => 1, default => sub {
42             my $self = shift;
43             join '&', map { $_ . '=' . URI::Escape::uri_escape($self->params->{ $_ }) } sort keys %{ $self->params };
44             });
45              
46             has header_list => (is => 'ro', isa => ArrayRef, init_arg => undef, lazy => 1, default => sub {
47             my $self = shift;
48             [ sort keys %{ $self->headers } ];
49             });
50              
51             has canonical_headers => (is => 'ro', isa => Str, lazy => 1, default => sub {
52             my $self = shift;
53             join '', map { lc( $_ ) . ":" . $self->headers->{ $_ } . "\n" } @{ $self->header_list };
54             });
55              
56             has hashed_payload => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
57             my $self = shift;
58             return ($self->unsigned_payload) ? 'UNSIGNED-PAYLOAD' : Digest::SHA::sha256_hex($self->content);
59             });
60              
61             has signed_header_list => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
62             my $self = shift;
63             join ';', map { lc($_) } @{ $self->header_list };
64             });
65              
66             has canonical_request => (is => 'ro', isa => Str, lazy => 1, default => sub {
67             my $self = shift;
68             join "\n", $self->method,
69             $self->uri,
70             $self->canonical_qstring,
71             $self->canonical_headers,
72             $self->signed_header_list,
73             $self->hashed_payload;
74             });
75              
76             has credential_scope => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
77             my $self = shift;
78             join '/', $self->date, $self->region, $self->service, 'aws4_request';
79             });
80              
81             has aws_algorithm => (is => 'ro', isa => Str, init_arg => undef, default => 'AWS4-HMAC-SHA256');
82              
83             has string_to_sign => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
84             my $self = shift;
85             join "\n", $self->aws_algorithm,
86             $self->date_timestamp,
87             $self->credential_scope,
88             Digest::SHA::sha256_hex($self->canonical_request);
89             });
90              
91             has signing_key => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
92             my $self = shift;
93             my $kSecret = "AWS4" . $self->secret_key;
94             my $kDate = Digest::SHA::hmac_sha256($self->date, $kSecret);
95             my $kRegion = Digest::SHA::hmac_sha256($self->region, $kDate);
96             my $kService = Digest::SHA::hmac_sha256($self->service, $kRegion);
97             return Digest::SHA::hmac_sha256("aws4_request", $kService);
98             });
99              
100             has signature => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
101             my $self = shift;
102             Digest::SHA::hmac_sha256_hex($self->string_to_sign, $self->signing_key);
103             });
104              
105             has signed_qstring => (is => 'ro', isa => Str, init_arg => undef, lazy => 1, default => sub {
106             my $self = shift;
107             $self->canonical_qstring . '&X-Amz-Signature=' . $self->signature;
108             });
109              
110             1;
111             ### main pod documentation begin ###
112              
113             =encoding UTF-8
114              
115             =head1 NAME
116              
117             Signer::AWSv4 - Implements the AWS v4 signature algorithm
118              
119             =head1 DESCRIPTION
120              
121             Yet Another module to sign requests to Amazon Web Services APIs
122             with the AWSv4 signing algorithm. This module has a different twist. The
123             rest of modules out there are tied to signing HTTP::Request objects, but
124             AWS uses v4 signatures in other places: IAM user login to MySQL RDSs, EKS,
125             S3 Presigned URLs, etc. When building authentication modules for these services,
126             I've had to create artificial HTTP::Request objects, just for a signing module
127             to sign them, and then retrieve the signature. This module solves that problem,
128             not being tied to any specific object to sign.
129              
130             Signer::AWSv4 is a base class that implements the main v4 Algorithm. You're supposed
131             L
132             to subclass and override attributes to adjust how you want the signature to
133             be built.
134              
135             It's attributes let you inspect the entire signing process (making the string to
136             sign, the signature, etc available for inspection)
137              
138             =head1 Specialized Signers
139              
140             L - Build presigned S3 URLs
141              
142             L - Login to EKS clusters
143              
144             L - Login to MySQL RDS servers with IAM credentials
145              
146             =head1 Request Attributes
147              
148             =head2 access_key
149              
150             Holds the AWS Access Key to sign with. Please don't hardcode your credentials. Get them
151             from some AWS authentication readers like L, L,
152             L, One of L subclasses.
153              
154             =head2 secret_key String
155              
156             Holds the AWS Secret Key
157              
158             =head2 session_token String
159              
160             Optional. The session token when using STS temporary credentials. Some services
161             may not support authenticating with temporary credentials.
162              
163             =head2 method String
164              
165             The method to sign with. This can be overwritten by subclasses to provide an
166             appropiate default for a specific service.
167              
168             =head2 uri String
169              
170             The uri to sign with. This can be overwritten by subclasses to provide an
171             appropiate default for a specific service
172              
173             =head2 region String
174              
175             The uri to sign with. This can be overwritten by subclasses to provide an
176             appropiate default for a specific service
177              
178             =head2 service String
179              
180             The service to sign with. This can be overwritten by subclasses to provide an
181             appropiate default for a specific service
182              
183             =head2 expires Integer
184              
185             The time for which the signature will be valid. This may be defaulted in
186             subclasses so the user doesn't have to specify it.
187              
188             =head2 params HashRef of Strings
189              
190             The query parameters to sign. Subclasses must implement a build_params method
191             that sets the query parameters to sign appropiately.
192              
193             =head2 headers HashRef of Strings
194              
195             The headers to sign. Subclasses must implement a build_headers method that sets
196             the headers to sign appropiately.
197              
198             =head2 content String
199              
200             The content of the request to be signed.
201              
202             =head2 unsigned_payload Bool
203              
204             Indicates wheather the payload (content) should be signed or not.
205              
206             =head1 Signature Attributes
207              
208             Attributes for obtaining the final signature
209              
210             =head1 signature
211              
212             The final signature. Just a hexadecimal string with the result of signing the request
213              
214             =head1 signed_qstring
215              
216             The query string that should be added to a URL to obtain a signed URL (some subclasses
217             use this signed query string internally)
218              
219             =head1 Internal Attributes
220              
221             The computation of the signature is heald in a series of attributes that are
222             built for dumping, diagnosing and controlling the signature process
223              
224             =head2 time
225              
226             A L object that holds the time for the signature. Defaulted to "now"
227              
228             =head2 date, date_timestamp
229              
230             Values used in intermediate parts of the signature process. Derived from time.
231            
232             =head2 canonical_qstring
233              
234             The Canonical Query String to be used in the signature process.
235              
236             =head2 header_list
237              
238             The list of headers to sign. Defaults to all headers in the headers attribute
239              
240             =head2 canonical_headers
241              
242             The cannonical list of headers to use in the signature process. Depends on header_list
243              
244             =head2 hashed_payload
245              
246             The hashed payload of the request
247              
248             =head2 signed_header_list
249              
250             The list of signed headers, ready for inclusion in the canonical request
251              
252             =head2 canonical_request
253              
254             The canonical request that will be signed. Brings together the method, uri,
255             canonical_qstring, canonical_headers, signed_header_list and hashed_payload
256              
257             =head2 credential_scope
258              
259             The credential scope to be used to sign the request
260              
261             =head2 aws_algorithm
262              
263             The string that identifies the signing algorithm version. Defaults to C
264              
265             =head2 string_to_sign
266              
267             The string to sign
268              
269             =head2 signing_key
270              
271             The signing key
272              
273             These internal concepts can be found in L, that describes the signature process.
274              
275             =head1 TODO
276              
277             Implement a signer for the AWS ElasticSearch service
278              
279             Implement a generic "sign an HTTP::Request" signer
280              
281             Pass the same test suite that L has
282              
283             =head1 AUTHOR
284              
285             Jose Luis Martinez
286             CPAN ID: JLMARTIN
287             CAPSiDE
288             jlmartinez@capside.com
289              
290             =head1 SEE ALSO
291              
292             L
293              
294             L
295              
296             L
297              
298             L
299              
300             =head1 CONTRIBUTIONS
301              
302             manwar: specify missing prereqs
303              
304             mschout: add version support to S3
305              
306             =head1 BUGS and SOURCE
307              
308             The source code is located here: L
309              
310             Please report bugs to: L
311              
312             =head1 AUTHOR
313              
314             Jose Luis Martinez
315             CAPSiDE
316             jlmartinez@capside.com
317              
318             =head1 COPYRIGHT and LICENSE
319              
320             Copyright (c) 2018 by CAPSiDE
321              
322             This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module.
323              
324             =cut