Skip to content
This repository has been archived by the owner on Dec 19, 2017. It is now read-only.

Commit

Permalink
Add instance based IAM role authentication
Browse files Browse the repository at this point in the history
This change consolidates authentication decisions into a single new function.
First it will try using the passed in key/secret, then environment variables
and finally instance based IAM role authentication. Since instance based
IAM role authentication requires an additional token for signing the request,
the existing API has changed slightly since most use cases construct auth via
struct initialization. This will also allow for future changes such as
supporting credential files or STS AssumeRole.
  • Loading branch information
markpeek committed Jul 11, 2013
1 parent 5188645 commit 1ff9b0e
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 16 deletions.
95 changes: 92 additions & 3 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
package aws

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
)

Expand Down Expand Up @@ -146,7 +150,7 @@ var Regions = map[string]Region{
}

type Auth struct {
AccessKey, SecretKey string
AccessKey, SecretKey, Token string
}

var unreserved = make([]bool, 128)
Expand All @@ -160,17 +164,102 @@ func init() {
}
}

type credentials struct {
Code string
LastUpdated string
Type string
AccessKeyId string
SecretAccessKey string
Token string
Expiration string
}

func getMetaData(path string) (contents []byte, err error) {
url := "http://169.254.169.254/latest/meta-data/" + path

resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
return
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return []byte(body), err
}

func getInstanceCredentials() (cred credentials, err error) {
credentialPath := "iam/security-credentials/"

// Get the instance role
role, err := getMetaData(credentialPath)
if err != nil {
return
}

// Get the instance role credentials
credentialJSON, err := getMetaData(credentialPath + string(role))
if err != nil {
return
}

err = json.Unmarshal([]byte(credentialJSON), &cred)
return
}

// GetAuth creates an Auth based on either passed in credentials,
// environment information or instance based role credentials.
func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
// First try passed in credentials
if accessKey != "" && secretKey != "" {
return Auth{accessKey, secretKey, ""}, nil
}

// Next try to get auth from the environment
auth, err = EnvAuth()
if err == nil {
// Found auth, return
return
}

// Next try getting auth from the instance role
cred, err := getInstanceCredentials()
if err == nil {
// Found auth, return
auth.AccessKey = cred.AccessKeyId
auth.SecretKey = cred.SecretAccessKey
auth.Token = cred.Token
return
}
err = errors.New("No valid AWS authentication found")
return
}

// EnvAuth creates an Auth based on environment information.
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
// variables are used.
func EnvAuth() (auth Auth, err error) {
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
if auth.AccessKey == "" {
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
}

auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
if auth.SecretKey == "" {
auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
}
if auth.AccessKey == "" {
err = errors.New("AWS_ACCESS_KEY_ID not found in environment")
err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
}
if auth.SecretKey == "" {
err = errors.New("AWS_SECRET_ACCESS_KEY not found in environment")
err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
}
return
}
Expand Down
28 changes: 26 additions & 2 deletions aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ func (s *S) TearDownTest(c *C) {
func (s *S) TestEnvAuthNoSecret(c *C) {
os.Clearenv()
_, err := aws.EnvAuth()
c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in environment")
c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
}

func (s *S) TestEnvAuthNoAccess(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
_, err := aws.EnvAuth()
c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID not found in environment")
c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
}

func (s *S) TestEnvAuth(c *C) {
Expand All @@ -52,6 +52,30 @@ func (s *S) TestEnvAuth(c *C) {
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}

func (s *S) TestEnvAuthAlt(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_KEY", "secret")
os.Setenv("AWS_ACCESS_KEY", "access")
auth, err := aws.EnvAuth()
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}

func (s *S) TestGetAuthStatic(c *C) {
auth, err := aws.GetAuth("access", "secret")
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}

func (s *S) TestGetAuthEnv(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
os.Setenv("AWS_ACCESS_KEY_ID", "access")
auth, err := aws.GetAuth("", "")
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}

func (s *S) TestEncode(c *C) {
c.Assert(aws.Encode("foo"), Equals, "foo")
c.Assert(aws.Encode("/"), Equals, "%2F")
Expand Down
2 changes: 1 addition & 1 deletion ec2/ec2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
s.ec2 = ec2.New(auth, aws.Region{EC2Endpoint: testServer.URL})
}

Expand Down
3 changes: 3 additions & 0 deletions ec2/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func sign(auth aws.Auth, method, path string, params map[string]string, host str
params["AWSAccessKeyId"] = auth.AccessKey
params["SignatureVersion"] = "2"
params["SignatureMethod"] = "HmacSHA256"
if auth.Token != "" {
params["SecurityToken"] = auth.Token
}

// AWS specifies that the parameters in a signed request must
// be provided in the natural order of the keys. This is distinct
Expand Down
4 changes: 2 additions & 2 deletions ec2/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// EC2 ReST authentication docs: http://goo.gl/fQmAN

var testAuth = aws.Auth{"user", "secret"}
var testAuth = aws.Auth{"user", "secret", ""}

func (s *S) TestBasicSignature(c *C) {
params := map[string]string{}
Expand Down Expand Up @@ -62,7 +62,7 @@ func (s *S) TestSignatureExample1(c *C) {
"Version": "2007-11-07",
"Action": "ListDomains",
}
ec2.Sign(aws.Auth{"access", "secret"}, "GET", "/", params, "sdb.amazonaws.com")
ec2.Sign(aws.Auth{"access", "secret", ""}, "GET", "/", params, "sdb.amazonaws.com")
expected := "okj96/5ucWBSc1uR2zXVfm6mDHtgfNv657rRtt/aunQ="
c.Assert(params["Signature"], Equals, expected)
}
2 changes: 1 addition & 1 deletion exp/mturk/mturk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
u, err := url.Parse(testServer.URL)
if err != nil {
panic(err.Error())
Expand Down
2 changes: 1 addition & 1 deletion exp/mturk/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// Mechanical Turk REST authentication docs: http://goo.gl/wrzfn

var testAuth = aws.Auth{"user", "secret"}
var testAuth = aws.Auth{"user", "secret", ""}

// == fIJy9wCApBNL2R4J2WjJGtIBFX4=
func (s *S) TestBasicSignature(c *C) {
Expand Down
2 changes: 1 addition & 1 deletion exp/sdb/sdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
s.sdb = sdb.New(auth, aws.Region{SDBEndpoint: testServer.URL})
}

Expand Down
3 changes: 3 additions & 0 deletions exp/sdb/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func sign(auth aws.Auth, method, path string, params url.Values, headers http.He
params["AWSAccessKeyId"] = []string{auth.AccessKey}
params["SignatureVersion"] = []string{"2"}
params["SignatureMethod"] = []string{"HmacSHA256"}
if auth.Token != "" {
params["SecurityToken"] = auth.Token
}

// join up all the incoming params
var sarray []string
Expand Down
2 changes: 1 addition & 1 deletion exp/sdb/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// SimpleDB ReST authentication docs: http://goo.gl/CaY81

var testAuth = aws.Auth{"access-key-id-s8eBOWuU", "secret-access-key-UkQjTLd9"}
var testAuth = aws.Auth{"access-key-id-s8eBOWuU", "secret-access-key-UkQjTLd9", ""}

func (s *S) TestSignExampleDomainCreate(c *C) {
method := "GET"
Expand Down
3 changes: 3 additions & 0 deletions exp/sns/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ func sign(auth aws.Auth, method, path string, params url.Values, headers http.He
params["AWSAccessKeyId"] = []string{auth.AccessKey}
params["SignatureVersion"] = []string{"2"}
params["SignatureMethod"] = []string{"HmacSHA256"}
if auth.Token != "" {
params["SecurityToken"] = auth.Token
}
var sarry []string
for k, v := range params {
Expand Down
2 changes: 1 addition & 1 deletion exp/sns/sns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
s.sns = sns.New(auth, aws.Region{SNSEndpoint: testServer.URL})
}

Expand Down
2 changes: 1 addition & 1 deletion iam/iam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
s.iam = iam.New(auth, aws.Region{IAMEndpoint: testServer.URL})
}

Expand Down
3 changes: 3 additions & 0 deletions iam/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func sign(auth aws.Auth, method, path string, params map[string]string, host str
params["AWSAccessKeyId"] = auth.AccessKey
params["SignatureVersion"] = "2"
params["SignatureMethod"] = "HmacSHA256"
if auth.Token != "" {
params["SecurityToken"] = auth.Token
}

var sarray []string
for k, v := range params {
Expand Down
2 changes: 1 addition & 1 deletion s3/s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var testServer = testutil.NewHTTPServer()

func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123"}
auth := aws.Auth{"abc", "123", ""}
s.s3 = s3.New(auth, aws.Region{Name: "faux-region-1", S3Endpoint: testServer.URL})
}

Expand Down
2 changes: 1 addition & 1 deletion s3/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// S3 ReST authentication docs: http://goo.gl/G1LrK

var testAuth = aws.Auth{"0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"}
var testAuth = aws.Auth{"0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o", ""}

func (s *S) TestSignExampleObjectGet(c *C) {
method := "GET"
Expand Down

0 comments on commit 1ff9b0e

Please sign in to comment.