Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

awsbase: Add unit testing for session code base #9

Merged
merged 13 commits into from
Oct 2, 2019
14 changes: 8 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
dist: xenial
language: go
go:
- "1.11.x"
- "1.12.x"

env:
GOFLAGS=-mod=vendor

matrix:
fast_finish: true
allow_failures:
- go: tip

install:
- make tools

script:
- make lint
- make test
- go test -timeout=30s -parallel=4 -v ./...

branches:
only:
- master
matrix:
fast_finish: true
allow_failures:
- go: tip
4 changes: 4 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
default: test lint

fmt:
@echo "==> Fixing source code with gofmt..."
gofmt -s -w ./

lint:
@echo "==> Checking source code against linters..."
@golangci-lint run ./...
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ An opinionated [AWS Go SDK](https://github.com/aws/aws-sdk-go) library for consi

## Requirements

- [Go](https://golang.org/doc/install) 1.11.4+
- [Go](https://golang.org/doc/install) 1.12

## Development

Expand Down
2 changes: 1 addition & 1 deletion awsauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
return awsCredentials.NewChainCredentials(providers), nil
}

// Otherwise we need to construct and STS client with the main credentials, and verify
// Otherwise we need to construct an STS client with the main credentials, and verify
// that we can assume the defined role.
log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q, Policy: %q)",
c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID, c.AssumeRolePolicy)
Expand Down
196 changes: 30 additions & 166 deletions awsauth_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package awsbase

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -19,7 +17,7 @@ func TestGetAccountIDAndPartition(t *testing.T) {
var testCases = []struct {
Description string
AuthProviderName string
EC2MetadataEndpoints []*endpoint
EC2MetadataEndpoints []*MetadataResponse
IAMEndpoints []*MockEndpoint
STSEndpoints []*MockEndpoint
ErrCount int
Expand Down Expand Up @@ -420,25 +418,27 @@ func TestAWSParseAccountIDAndPartitionFromARN(t *testing.T) {
}
}

func TestAWSGetCredentials_shouldError(t *testing.T) {
func TestAWSGetCredentials_shouldErrorWhenBlank(t *testing.T) {
resetEnv := unsetEnv(t)
defer resetEnv()
cfg := Config{}

cfg := Config{}
c, err := GetCredentials(&cfg)
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatal("Expected NoCredentialProviders error")
}

if err != nil {
t.Fatalf("Unexpected error: %s", err)
}

_, err = c.Get()
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatal("Expected NoCredentialProviders error")
}
} else {
t.Fatal("Expected AWS error")
}
if err == nil {
t.Fatal("Expected an error with empty env, keys, and IAM in AWS Config")
t.Fatal("Expected an error given empty env, keys, and IAM in AWS Config")
}
}

Expand Down Expand Up @@ -747,6 +747,17 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
}
}

// invalidAwsEnv establishes a httptest server to simulate behaviour
// when endpoint doesn't respond as expected
func invalidAwsEnv(t *testing.T) func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(400)
}))

os.Setenv("AWS_METADATA_URL", ts.URL+"/latest")
return ts.Close
}

// unsetEnv unsets environment variables for testing a "clean slate" with no
// credentials in the environment
func unsetEnv(t *testing.T) func() {
Expand All @@ -768,6 +779,10 @@ func unsetEnv(t *testing.T) func() {
if err := os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE"); err != nil {
t.Fatalf("Error unsetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
}
// The Shared Credentials Provider has a very reasonable fallback option of
// checking the user's home directory for credentials, which may create
// unexpected results for users running these tests
os.Setenv("HOME", "/dev/null")

return func() {
// re-set all the envs we unset above
Expand All @@ -786,6 +801,9 @@ func unsetEnv(t *testing.T) func() {
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", e.CredsFilename); err != nil {
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
}
if err := os.Setenv("HOME", e.Home); err != nil {
t.Fatalf("Error resetting env var HOME: %s", err)
}
}
}

Expand Down Expand Up @@ -828,40 +846,6 @@ func setEnv(s string, t *testing.T) func() {
}
}

// awsMetadataApiMock establishes a httptest server to mock out the internal AWS Metadata
// service. IAM Credentials are retrieved by the EC2RoleProvider, which makes
// API calls to this internal URL. By replacing the server with a test server,
// we can simulate an AWS environment
func awsMetadataApiMock(endpoints []*endpoint) func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Header().Add("Server", "MockEC2")
log.Printf("[DEBUG] Mocker server received request to %q", r.RequestURI)
for _, e := range endpoints {
if r.RequestURI == e.Uri {
fmt.Fprintln(w, e.Body)
w.WriteHeader(200)
return
}
}
w.WriteHeader(400)
}))

os.Setenv("AWS_METADATA_URL", ts.URL+"/latest")
return ts.Close
}

// invalidAwsEnv establishes a httptest server to simulate behaviour
// when endpoint doesn't respond as expected
func invalidAwsEnv(t *testing.T) func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(400)
}))

os.Setenv("AWS_METADATA_URL", ts.URL+"/latest")
return ts.Close
}

func getEnv() *currentEnv {
// Grab any existing AWS keys and preserve. In some tests we'll unset these, so
// we need to have them and restore them after
Expand All @@ -871,131 +855,11 @@ func getEnv() *currentEnv {
Token: os.Getenv("AWS_SESSION_TOKEN"),
Profile: os.Getenv("AWS_PROFILE"),
CredsFilename: os.Getenv("AWS_SHARED_CREDENTIALS_FILE"),
Home: os.Getenv("HOME"),
}
}

// struct to preserve the current environment
type currentEnv struct {
Key, Secret, Token, Profile, CredsFilename string
}

type endpoint struct {
Uri string `json:"uri"`
Body string `json:"body"`
}

var ec2metadata_instanceIdEndpoint = &endpoint{
Uri: "/latest/meta-data/instance-id",
Body: "mock-instance-id",
}

var ec2metadata_securityCredentialsEndpoints = []*endpoint{
{
Uri: "/latest/meta-data/iam/security-credentials/",
Body: "test_role",
},
{
Uri: "/latest/meta-data/iam/security-credentials/test_role",
Body: "{\"Code\":\"Success\",\"LastUpdated\":\"2015-12-11T17:17:25Z\",\"Type\":\"AWS-HMAC\",\"AccessKeyId\":\"somekey\",\"SecretAccessKey\":\"somesecret\",\"Token\":\"sometoken\"}",
},
Key, Secret, Token, Profile, CredsFilename, Home string
}

var ec2metadata_iamInfoEndpoint = &endpoint{
Uri: "/latest/meta-data/iam/info",
Body: "{\"Code\": \"Success\",\"LastUpdated\": \"2016-03-17T12:27:32Z\",\"InstanceProfileArn\": \"arn:aws:iam::000000000000:instance-profile/my-instance-profile\",\"InstanceProfileId\": \"AIPAABCDEFGHIJKLMN123\"}",
}

const ec2metadata_iamInfoEndpoint_expectedAccountID = `000000000000`
const ec2metadata_iamInfoEndpoint_expectedPartition = `aws`

const iamResponse_GetUser_valid = `<GetUserResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<GetUserResult>
<User>
<UserId>AIDACKCEVSQ6C2EXAMPLE</UserId>
<Path>/division_abc/subdivision_xyz/</Path>
<UserName>Bob</UserName>
<Arn>arn:aws:iam::111111111111:user/division_abc/subdivision_xyz/Bob</Arn>
<CreateDate>2013-10-02T17:01:44Z</CreateDate>
<PasswordLastUsed>2014-10-10T14:37:51Z</PasswordLastUsed>
</User>
</GetUserResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</GetUserResponse>`

const iamResponse_GetUser_valid_expectedAccountID = `111111111111`
const iamResponse_GetUser_valid_expectedPartition = `aws`

const iamResponse_GetUser_unauthorized = `<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<Error>
<Type>Sender</Type>
<Code>AccessDenied</Code>
<Message>User: arn:aws:iam::123456789012:user/Bob is not authorized to perform: iam:GetUser on resource: arn:aws:iam::123456789012:user/Bob</Message>
</Error>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ErrorResponse>`

const stsResponse_GetCallerIdentity_valid = `<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<GetCallerIdentityResult>
<Arn>arn:aws:iam::222222222222:user/Alice</Arn>
<UserId>AKIAI44QH8DHBEXAMPLE</UserId>
<Account>222222222222</Account>
</GetCallerIdentityResult>
<ResponseMetadata>
<RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
</ResponseMetadata>
</GetCallerIdentityResponse>`

const stsResponse_GetCallerIdentity_valid_expectedAccountID = `222222222222`
const stsResponse_GetCallerIdentity_valid_expectedPartition = `aws`

const stsResponse_GetCallerIdentity_unauthorized = `<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<Error>
<Type>Sender</Type>
<Code>AccessDenied</Code>
<Message>User: arn:aws:iam::123456789012:user/Bob is not authorized to perform: sts:GetCallerIdentity</Message>
</Error>
<RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
</ErrorResponse>`

const iamResponse_GetUser_federatedFailure = `<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<Error>
<Type>Sender</Type>
<Code>ValidationError</Code>
<Message>Must specify userName when calling with non-User credentials</Message>
</Error>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ErrorResponse>`

const iamResponse_ListRoles_valid = `<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ListRolesResult>
<IsTruncated>true</IsTruncated>
<Marker>AWceSSsKsazQ4IEplT9o4hURCzBs00iavlEvEXAMPLE</Marker>
<Roles>
<member>
<Path>/</Path>
<AssumeRolePolicyDocument>%7B%22Version%22%3A%222008-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D</AssumeRolePolicyDocument>
<RoleId>AROACKCEVSQ6C2EXAMPLE</RoleId>
<RoleName>elasticbeanstalk-role</RoleName>
<Arn>arn:aws:iam::444444444444:role/elasticbeanstalk-role</Arn>
<CreateDate>2013-10-02T17:01:44Z</CreateDate>
</member>
</Roles>
</ListRolesResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</ListRolesResponse>`

const iamResponse_ListRoles_valid_expectedAccountID = `444444444444`
const iamResponse_ListRoles_valid_expectedPartition = `aws`

const iamResponse_ListRoles_unauthorized = `<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<Error>
<Type>Sender</Type>
<Code>AccessDenied</Code>
<Message>User: arn:aws:iam::123456789012:user/Bob is not authorized to perform: iam:ListRoles on resource: arn:aws:iam::123456789012:role/</Message>
</Error>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ErrorResponse>`
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/hashicorp/aws-sdk-go-base

require (
github.com/aws/aws-sdk-go v1.16.36
github.com/aws/aws-sdk-go v1.25.3
github.com/hashicorp/go-cleanhttp v0.5.0
github.com/hashicorp/go-multierror v1.0.0
github.com/stretchr/testify v1.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aws/aws-sdk-go v1.16.36 h1:POeH34ZME++pr7GBGh+ZO6Y5kOwSMQpqp5BGUgooJ6k=
github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ=
github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
Expand Down
Loading