greghendershott/aws

Obtain credentials from IAM EC2 instance meta-data

greghendershott opened this issue · 0 comments

Code running on an EC2 instance with an appropriate IAM role can GET http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name} and obtain:

  • temporary access and secret keys
  • a value for an X-Amz-Security-Token header that must be supplied in requests signed with those keys
  • an expiration -- the app should GET new credentials about 15 minutes beforehand.

This can be a better way to manage keys. More information: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html.

This is easy enough to do yourself with code something like:

;; Set this to the name of the EC2 instance IAM role if any
(define/contract ec2-credentials-role (parameter/c (or/c #f string?)) (make-parameter #f))

;; Note: These aren't parameters because parameters are
;; thread-specific and we'll need to update this from a thread (in the
;; case where we get temporary credentials from EC2 instance
;; meta-data).
(struct aws-creds (access secret token) #:transparent)
(define the-creds (begin
                    (unless (current-ec2-credentials-role)
                      (with-handlers ([exn:fail? void])
                        (read-keys)))
                    (aws-creds (public-key)
                               (private-key)
                               #f)))

(when (current-ec2-credentials-role)
  (let loop ()
    (with-handlers ([exn:fail? void])
      (define url
        (string->url
         (string-append "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
                        (current-ec2-credentials-role))))
      (define ht (call/input-url url get-pure-port read-json))
      (define expires (gmt-8601-string->seconds (hash-ref ht 'Expiration)))
      (set! the-creds (aws-creds (hash-ref ht 'AccessKeyId)
                                 (hash-ref ht 'SecretAccessKey)
                                 (hash-ref ht 'Token)))
      ;; Refresh 15 minutes before expiration
      (void
       (thread
        (λ ()
          (sleep (- expires (current-seconds) (* 15 60)))
          (loop)))))))

Now your app config only needs to supply the instance role name, not the keys.

Although easy enough to write this, it would be a nice-to-have for this library to supply.

The must-have is that the Token must be supplied as an X-Amz-Security-Token request header. In other words, there should also be some function like:

(define (refresh-aws-creds h)
  (match-define (aws-creds access secret token) the-creds)
  (public-key access)
  (private-key secret)
  (if token
      (hash-set h 'X-Amz-Security-Token token)
      h))

And, various modules in this library would need to be modified to use it to (maybe) add the header prior to the AWSv4 authentication signature calculation.