Permalink
Browse files

Add user-iam-ec2-credentials!; closes #56

  • Loading branch information...
greghendershott committed Apr 9, 2018
1 parent fb38908 commit 84c28ba16f238a8c48c54a9e858ba4bb2ec53742
Showing with 190 additions and 115 deletions.
  1. +26 −1 aws/aws.scrbl
  2. +9 −11 aws/cw.rkt
  3. +6 −9 aws/dynamo.rkt
  4. +6 −6 aws/glacier.rkt
  5. +62 −21 aws/keys.rkt
  6. +6 −9 aws/r53.rkt
  7. +10 −13 aws/s3.rkt
  8. +6 −9 aws/ses.rkt
  9. +46 −18 aws/sigv4.rkt
  10. +6 −8 aws/sns.rkt
  11. +6 −9 aws/sqs.rkt
  12. +1 −1 info.rkt
@@ -127,6 +127,31 @@ calls @racket[read-keys]. If either is @italic{still} blank, calls
@racket[error] with a hopefully helpful reminder about how to set the
parameters. }
@defproc[(use-iam-ec2-credentials! [iam-role-name string?]) void?]{
@history[#:added "1.10"]
When your code is running on an EC2 instance, instead of you supplying
credentials in a configuration file (like @tt{~/.aws-keys}) or in
environment variables, it is possible to obtain credentials from EC2
instance meta-data. This simplifies configuration and is more secure.
For more information how to configure this, see
@link["https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html"
"IAM Roles for Amazon EC2"]. Step five of those instructions ---
``Have the application retrieve a set of temporary credentials and use
them'' --- is done by simply calling this function once when your
program starts.
Credentials are initially obtained -- and subsequently refreshed when
they expire -- from the EC2 instance meta-data. The
@racket[public-key] and @racket[private-key] parameters are
automatically set to these values. Those keys are used to sign
requests made by this library. The @tt{X-Amz-Security-Token} header is
supplied when making requests. }
@; ----------------------------------------------------------------------------
@subsection{Exception handling}
@@ -1541,7 +1566,7 @@ sorted in any particular order.
}
@defproc[(ses [params (listof (list/c symbol? string?))]) xexpr?]{
@defproc[(request [params (listof (list/c symbol? string?))]) xexpr?]{
The low-level procedure used by other procedures to make requests to SES.
@@ -31,20 +31,18 @@
. ->* . list?)
(ensure-have-keys)
(let* ([uri (endpoint->uri (cw-endpoint) "/")]
[heads (hasheq 'Host (endpoint-host (cw-endpoint))
'Date (seconds->gmt-8601-string 'basic (current-seconds))
'Content-Type "application/x-www-form-urlencoded; charset=utf-8")]
[params (sort (cons `(Version "2010-08-01") params)
param<?)]
[post-data (string->bytes/utf-8 (dict->form-urlencoded params))]
[heads (dict-set* heads
'Authorization
(aws-v4-authorization "POST"
uri
heads
(sha256-hex-string post-data)
(cw-region)
"monitoring"))]
[heads (hasheq 'Host (endpoint-host (cw-endpoint))
'Date (seconds->gmt-8601-string 'basic (current-seconds))
'Content-Type "application/x-www-form-urlencoded; charset=utf-8")]
[heads (add-v4-auth-heads #:heads heads
#:method "POST"
#:uri uri
#:sha256 (sha256-hex-string post-data)
#:region (cw-region)
#:service "monitoring")]
[x (post-with-retry uri params heads)])
(append (result-proc x)
(match (se-path* '(NextToken) x)
@@ -53,15 +53,12 @@
'Date (seconds->gmt-8601-string 'basic
(current-seconds))
'Content-Type "application/x-amz-json-1.0")])
(dict-set* heads
"Authorization"
(aws-v4-authorization
method
uri
heads
(sha256-hex-string body)
(dynamo-region)
service))))
(add-v4-auth-heads #:heads heads
#:method method
#:uri uri
#:sha256 (sha256-hex-string body)
#:region (dynamo-region)
#:service service)))
;; All of the Dynamo functions POST JSON to /. The only variation is
;; the JSON content and the `x-amz-target` header.
@@ -49,12 +49,12 @@
(call/input-request "1.1"
m
u
(dict-set h
'Authorization
(aws-v4-authorization m u h
(sha256-hex-string #"")
(region)
service))
(add-v4-auth-heads #:heads h
#:method m
#:uri u
#:sha256 (sha256-hex-string #"")
#:region (region)
#:service service)
(λ (p h)
(check-response p h)
(void (read-entity/bytes p h))
@@ -1,23 +1,30 @@
#lang racket/base
(require net/base64
racket/contract/base
racket/contract/region
(require (only-in http
gmt-8601-string->seconds)
json
net/base64
net/url
racket/contract
racket/dict
racket/file
racket/format
racket/match
sha
"util.rkt")
;; These parameters hold the AWS keys. Set them before using functions
;; in this module, for example by caling `read-aws-keys'.
(provide public-key
private-key
read-keys
ensure-have-keys
sha256-encode
use-iam-ec2-credentials!
ensure-ec2-instance-credentials-and-add-token-header)
(define public-key (make-parameter ""))
(define private-key (make-parameter ""))
(provide public-key
private-key)
(define/provide (read-keys [file
(build-path (find-system-path 'home-dir)
".aws-keys")])
(define (read-keys [file (build-path (find-system-path 'home-dir) ".aws-keys")])
(match (file->lines file #:mode 'text #:line-mode 'any)
;; same format that Amazon uses for their CL tools:
[(list (regexp #rx"^(?i:AWSAccessKeyId)=(.*)$" (list _ public))
@@ -37,7 +44,7 @@
"AWSAccessKeyId=<key>\n"
"AWSSecretKey=<key>\n"))]))
(define/provide (ensure-have-keys)
(define (ensure-have-keys)
(define (keys-blank?)
(or (string=? "" (public-key))
(string=? "" (private-key))))
@@ -51,18 +58,52 @@
"Tip: `(read-keys)' will read them "
"from a ~~/.aws-keys file."))))
(define/contract (shaX-encode str f)
(string? (bytes? bytes? . -> . bytes?) . -> . string?)
(define/contract (sha256-encode str)
(-> string? string?)
(match (bytes->string/utf-8
(base64-encode (f (string->bytes/utf-8 (private-key))
(string->bytes/utf-8 str))))
(base64-encode (sha256-encode (string->bytes/utf-8 (private-key))
(string->bytes/utf-8 str))))
[(regexp #rx"^(.*)\r\n$" (list _ x)) x] ;kill \r\n added by base64-encode
[s s]))
(define/contract/provide (sha1-encode str)
(string? . -> . string?)
(shaX-encode str hmac-sha1))
(define/contract/provide (sha256-encode str)
(string? . -> . string?)
(shaX-encode str hmac-sha256))
;;; Optional: Get credentials from EC2 instance meta-data
(struct creds (public private token expires))
;; Note: These aren't parameters because parameters are per-thread --
;; whereas we'll need to update values from one thread for all
;; threads.
(define/contract iam-role (or/c #f string?) #f)
(define/contract the-creds (or/c #f creds?) #f)
(define (use-iam-ec2-credentials! v)
(set! iam-role v))
(define (ensure-ec2-instance-credentials-and-add-token-header d)
(let ([sema (make-semaphore 1)])
(λ (d)
(cond [iam-role
(call-with-semaphore
sema
(λ ()
(unless (and the-creds
(< (+ (current-seconds) (* 5 60))
(creds-expires the-creds)))
(set! the-creds (get-creds)))
(public-key (creds-public the-creds))
(private-key (creds-private the-creds))
(dict-set d 'X-Amz-Security-Token (creds-token the-creds))))]
[else d]))))
(define (get-creds)
(define url
(string->url
(~a "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
iam-role)))
(match (call/input-url url get-pure-port read-json)
[(hash-table ['AccessKeyId public]
['SecretAccessKey private]
['Token token]
['Expiration expiration])
(creds public private token (gmt-8601-string->seconds expiration))]))
@@ -27,15 +27,12 @@
'Host (endpoint-host (r53-endpoint))
'Date (seconds->gmt-8601-string 'basic
(current-seconds)))])
(dict-set* heads
"Authorization"
(aws-v4-authorization
method
uri
heads
(sha256-hex-string body)
"us-east-1"
"route53"))))
(add-v4-auth-heads #:heads heads
#:method method
#:uri uri
#:sha256 (sha256-hex-string body)
#:region "us-east-1"
#:service "route53")))
;;; hosted zones
@@ -128,19 +128,16 @@
(define/contract (add-auth-heads* uri method heads body-hash)
(-> string? string? dict? string? dict?)
(ensure-have-keys)
(let* ([heads (maybe-dict-set* heads
'Host (uri->host+port uri)
'Date (seconds->gmt-8601-string 'basic)
'x-amz-content-sha256 body-hash)]
[heads (dict-set heads
'Authorization
(aws-v4-authorization method
uri
heads
body-hash
(s3-region)
"s3"))])
heads))
(let ([heads (maybe-dict-set* heads
'Host (uri->host+port uri)
'Date (seconds->gmt-8601-string 'basic)
'x-amz-content-sha256 body-hash)])
(add-v4-auth-heads #:heads heads
#:method method
#:uri uri
#:sha256 body-hash
#:region (s3-region)
#:service "s3")))
(define/contract (add-auth-heads uri method [heads '()] [body #""])
(->* (string? string?) (dict? bytes?) dict?)
@@ -33,15 +33,12 @@
[heads (hasheq 'Host (endpoint-host (ses-endpoint))
'Date date
'Content-Type "application/x-www-form-urlencoded; charset=utf-8")]
[heads (dict-set* heads
"Authorization"
(aws-v4-authorization
"POST"
uri
heads
(sha256-hex-string body)
(ses-region)
"ses"))])
[heads (add-v4-auth-heads #:heads heads
#:method "POST"
#:uri uri
#:sha256 (sha256-hex-string body)
#:region (ses-region)
#:service "ses")])
(call/output-request
"1.1" "POST" uri body (bytes-length body) heads
(λ (in h)
Oops, something went wrong.

0 comments on commit 84c28ba

Please sign in to comment.