goamz: Using Temporary AWS STS Credentials to Access S3

Writing in go and using the goamz package for Amazon AWS, I had been following the standard directions for gaining access to objects stored on Amazon S3. I used an IAM-issued awsAccessKeyId and and awsSecretAccessKey corresponding to my user ID, something along the lines of:

    auth := aws.Auth{AccessKey: "myAwsAccessKeyId",
        SecretKey: "myAwsSecretAccessKey"}
    region := aws.USWest2
    connection := s3.New(auth, region)
    myBucket := connection.Bucket("myBucketName")
    err = mybucket.Put(path,
        []byte("Hello, World!"),
        "text/plain",
        s3.BucketOwnerFull,
        s3.Options{})

The problem occurred when I tried to repeat the same operation using an AccessKey and a SecretKey that I got from AWS STS via the GetFederationToken() call. GetFederationToken() yields a set of temporary credentials that can be tailored to the client via a policy document. I wanted to be able to hand these credentials to a client so that they could go directly to their data on S3 without me being in the middle. That the credentials would expire helped maintain sanity and made the job a little easier. But, when I followed the code, above, using the temporary STS credentials I received back the following error:

    <Code>InvalidAccessKeyId</Code>
    <Message>
        The AWS Access Key Id you provided does not exist
        in our records.
    </Message>

The error code and message are not particularly helpful. In fact, I would assert that they are wrong. After much debugging and searching, I discovered that the error is caused by the fact that with the temporary STS credentials I must also provide the awsSessionToken in my Auth structure. Let’s look at goamz’s Auth definition:

    type Auth struct {
        AccessKey, SecretKey string
        token                string
        expiration           time.Time
    }

By convention, struct elements whose names begin with an uppercase letter are exposed; those with names that begin with a lowercase letter are not. Therefore only AccessKey and SecretKey can be set via a New() as shown in the example, above. After a little digging, I found that goamz provides another method for creating an Auth struct:

    // To be used with other APIs that return auth
         // credentials such as STS
    func NewAuth(accessKey,
        secretKey,
        token string,
        expiration time.Time) *Auth {
            return &Auth{
            AccessKey:  accessKey,
            SecretKey:  secretKey,
            token:      token,
            expiration: expiration,
        }
    }

Thus the final solution for using temporary credentials from STS looks something like this:

    auth := aws.NewAuth(tempAwsAccessKeyId,
        tempAwsSecretAccessKey,
        tempAwsSessionToken,
        tempAwsExpiration)
    region := aws.USWest2
    connection := s3.New(auth, region)
    myBucket := connection.Bucket("myBucketName")
    err = mybucket.Put(path,
        []byte("Hello, World!"),
        "text/plain",
        s3.BucketOwnerFull,
        s3.Options{})

Note that all four parameters passed to aws.NewAuth() were returned to me from a call to GetFederationToken.

Advertisements