Skip to content

Commit

Permalink
Expand Credential Chain Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed Jan 26, 2021
1 parent 4932dc5 commit 52e5565
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 60 deletions.
182 changes: 141 additions & 41 deletions config/resolve_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -15,6 +16,7 @@ import (

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/internal/awstesting"
"github.com/aws/aws-sdk-go-v2/service/sso"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go/middleware"
)
Expand Down Expand Up @@ -66,13 +68,25 @@ func setupCredentialsEndpoints(t *testing.T) (aws.EndpointResolver, func()) {
Format("2006-01-02T15:04:05Z"))))
}))

ssoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf(
getRoleCredentialsResponse,
time.Now().
Add(15*time.Minute).
UnixNano()/int64(time.Millisecond))))
}))

resolver := aws.EndpointResolverFunc(
func(service, region string) (aws.Endpoint, error) {
switch service {
case sts.ServiceID:
return aws.Endpoint{
URL: stsServer.URL,
}, nil
case sso.ServiceID:
return aws.Endpoint{
URL: ssoServer.URL,
}, nil
default:
return aws.Endpoint{},
fmt.Errorf("unknown service endpoint, %s", service)
Expand All @@ -87,6 +101,44 @@ func setupCredentialsEndpoints(t *testing.T) (aws.EndpointResolver, func()) {
}
}

func ssoTestSetup() (func(), error) {
dir, err := ioutil.TempDir("", "sso-test")
if err != nil {
return nil, err
}

cacheDir := filepath.Join(dir, ".aws", "sso", "cache")
err = os.MkdirAll(cacheDir, 0750)
if err != nil {
os.RemoveAll(dir)
return nil, err
}

tokenFile, err := os.Create(filepath.Join(cacheDir, "eb5e43e71ce87dd92ec58903d76debd8ee42aefd.json"))
if err != nil {
os.RemoveAll(dir)
return nil, err
}
defer tokenFile.Close()

_, err = tokenFile.WriteString(fmt.Sprintf(ssoTokenCacheFile, time.Now().
Add(15*time.Minute).
Format(time.RFC3339)))
if err != nil {
os.RemoveAll(dir)
return nil, err
}

if runtime.GOOS == "windows" {
os.Setenv("USERPROFILE", dir)
} else {
os.Setenv("HOME", dir)
}

return func() {
}, nil
}

func TestSharedConfigCredentialSource(t *testing.T) {
var configFileForWindows = filepath.Join("testdata", "config_source_shared_for_windows")
var configFile = filepath.Join("testdata", "config_source_shared")
Expand All @@ -95,59 +147,67 @@ func TestSharedConfigCredentialSource(t *testing.T) {
var credFile = filepath.Join("testdata", "credentials_source_shared")

cases := map[string]struct {
name string
envProfile string
configProfile string
expectedError string
expectedAccessKey string
expectedSecretKey string
expectedChain []string
init func()
dependentOnOS bool
name string
envProfile string
configProfile string
expectedError string
expectedAccessKey string
expectedSecretKey string
expectedSessionToken string
expectedChain []string
init func() (func(), error)
dependentOnOS bool
}{
"credential source and source profile": {
envProfile: "invalid_source_and_credential_source",
expectedError: "only one credential type may be specified per profile",
init: func() {
init: func() (func(), error) {
os.Setenv("AWS_ACCESS_KEY", "access_key")
os.Setenv("AWS_SECRET_KEY", "secret_key")
return func() {}, nil
},
},
"env var credential source": {
configProfile: "env_var_credential_source",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
configProfile: "env_var_credential_source",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_role_arn_env",
},
init: func() {
init: func() (func(), error) {
os.Setenv("AWS_ACCESS_KEY", "access_key")
os.Setenv("AWS_SECRET_KEY", "secret_key")
return func() {}, nil
},
},
"ec2metadata credential source": {
envProfile: "ec2metadata",
expectedChain: []string{
"assume_role_w_creds_role_arn_ec2",
},
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
},
"ecs container credential source": {
envProfile: "ecscontainer",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "ecscontainer",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_role_arn_ecs",
},
init: func() {
init: func() (func(), error) {
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/ECS")
return func() {}, nil
},
},
"chained assume role with env creds": {
envProfile: "chained_assume_role",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "chained_assume_role",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_role_arn_chain",
"assume_role_w_creds_role_arn_ec2",
Expand All @@ -160,43 +220,70 @@ func TestSharedConfigCredentialSource(t *testing.T) {
expectedSecretKey: "cred_proc_secret",
},
"credential process with ARN set": {
envProfile: "cred_proc_arn_set",
dependentOnOS: true,
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "cred_proc_arn_set",
dependentOnOS: true,
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_proc_role_arn",
},
},
"chained assume role with credential process": {
envProfile: "chained_cred_proc",
dependentOnOS: true,
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "chained_cred_proc",
dependentOnOS: true,
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_proc_source_prof",
},
},
"credential source overrides config source": {
envProfile: "credentials_overide",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "credentials_overide",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_role_arn_ec2",
},
init: func() {
init: func() (func(), error) {
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/ECS")
return func() {}, nil
},
},
"only credential source": {
envProfile: "only_credentials_source",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
envProfile: "only_credentials_source",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_role_w_creds_role_arn_ecs",
},
init: func() {
init: func() (func(), error) {
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/ECS")
return func() {}, nil
},
},
"sso credentials": {
envProfile: "sso_creds",
expectedAccessKey: "SSO_AKID",
expectedSecretKey: "SSO_SECRET_KEY",
expectedSessionToken: "SSO_SESSION_TOKEN",
init: func() (func(), error) {
return ssoTestSetup()
},
},
"chained assume role with sso credentials": {
envProfile: "source_sso_creds",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"source_sso_creds_arn",
},
init: func() (func(), error) {
return ssoTestSetup()
},
},
}
Expand All @@ -222,8 +309,13 @@ func TestSharedConfigCredentialSource(t *testing.T) {
endpointResolver, cleanupFn := setupCredentialsEndpoints(t)
defer cleanupFn()

var cleanup func()
if c.init != nil {
c.init()
var err error
cleanup, err = c.init()
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
}

var credChain []string
Expand Down Expand Up @@ -278,7 +370,15 @@ func TestSharedConfigCredentialSource(t *testing.T) {
}

if e, a := c.expectedSecretKey, creds.SecretAccessKey; e != a {
t.Errorf("expected %v, but received %v", e, a)
t.Errorf("expect %v, but received %v", e, a)
}

if e, a := c.expectedSessionToken, creds.SessionToken; e != a {
t.Errorf("expect %v, got %v", e, a)
}

if cleanup != nil {
cleanup()
}
})
}
Expand Down
12 changes: 9 additions & 3 deletions config/shared_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,13 +759,19 @@ func (c *SharedConfig) setFromIniSections(profiles map[string]struct{}, profile
c.clearAssumeRoleOptions()
} else {
// First time a profile has been seen, It must either be a assume role
// or credentials. Assert if the credential type requires a role ARN,
// the ARN is also set.
// credentials, or SSO. Assert if the credential type requires a role ARN,
// the ARN is also set, or validate that the SSO configuration is complete.
if err := c.validateCredentialsConfig(profile); err != nil {
return err
}
}

if c.hasSSOConfiguration() {
// clear the other credential options since they are not valid
c.clearAssumeRoleOptions()
c.clearCredentialOptions()
}

// if not top level profile and has credentials, return with credentials.
if len(profiles) != 0 && (c.Credentials.HasKeys() || c.hasSSOConfiguration()) {
return nil
Expand Down Expand Up @@ -951,7 +957,7 @@ func (c *SharedConfig) validateSSOConfiguration(profile string) error {

if len(missing) > 0 {
return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
profile, strings.Join(missing, ","))
profile, strings.Join(missing, ", "))
}

return nil
Expand Down
37 changes: 35 additions & 2 deletions config/shared_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go-v2/internal/ini"
"github.com/aws/smithy-go/logging"
"github.com/aws/smithy-go/ptr"
"github.com/google/go-cmp/cmp"
)

var _ regionProvider = (*SharedConfig)(nil)
Expand Down Expand Up @@ -245,6 +246,38 @@ func TestNewSharedConfig(t *testing.T) {
},
},
},
"AWS SSO Invalid Profile": {
Filenames: []string{testConfigFilename},
Profile: "invalid_sso_creds",
Err: fmt.Errorf("profile \"invalid_sso_creds\" is configured to use SSO but is missing required configuration: sso_region, sso_role_name, sso_start_url"),
},
"AWS SSO Profile and Static Credentials": {
Filenames: []string{testConfigFilename},
Profile: "sso_and_static",
Expected: SharedConfig{
Profile: "sso_and_static",
SSOAccountID: "012345678901",
SSORegion: "us-west-2",
SSORoleName: "TestRole",
SSOStartURL: "https://127.0.0.1/start",
},
},
"Assume Role with AWS SSO Profile configured with chain": {
Filenames: []string{testConfigFilename},
Profile: "source_sso_and_assume",
Expected: SharedConfig{
Profile: "source_sso_and_assume",
SourceProfileName: "sso_and_assume",
RoleARN: "source_sso_and_assume_arn",
Source: &SharedConfig{
Profile: "sso_and_assume",
SSOAccountID: "012345678901",
SSORegion: "us-west-2",
SSORoleName: "TestRole",
SSOStartURL: "https://127.0.0.1/start",
},
},
},
}

for name, c := range cases {
Expand All @@ -265,8 +298,8 @@ func TestNewSharedConfig(t *testing.T) {
if c.Err != nil {
t.Errorf("expect error: %v, got none", c.Err)
}
if e, a := c.Expected, cfg; !reflect.DeepEqual(e, a) {
t.Errorf(" expect %v, got %v", e, a)
if diff := cmp.Diff(c.Expected, cfg); len(diff) > 0 {
t.Error(diff)
}
})
}
Expand Down
Loading

0 comments on commit 52e5565

Please sign in to comment.