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 68b819c
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 66 deletions.
8 changes: 4 additions & 4 deletions config/resolve_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ func resolveCredentialChain(ctx context.Context, cfg *aws.Config, configs config
func resolveCredsFromProfile(ctx context.Context, cfg *aws.Config, envConfig *EnvConfig, sharedConfig *SharedConfig, configs configs) (err error) {

switch {
case sharedConfig.hasSSOConfiguration():
err = resolveSSOCredentials(ctx, cfg, sharedConfig, configs)

case sharedConfig.Source != nil:
// Assume IAM role with credentials source from a different profile.
err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig.Source, configs)
Expand All @@ -123,6 +120,9 @@ func resolveCredsFromProfile(ctx context.Context, cfg *aws.Config, envConfig *En
Value: sharedConfig.Credentials,
}

case sharedConfig.hasSSOConfiguration():
err = resolveSSOCredentials(ctx, cfg, sharedConfig, configs)

case len(sharedConfig.CredentialProcess) != 0:
// Get credentials from CredentialProcess
err = processCredentials(ctx, cfg, sharedConfig, configs)
Expand Down Expand Up @@ -166,7 +166,7 @@ func resolveSSOCredentials(ctx context.Context, cfg *aws.Config, sharedConfig *S
options = append(options, v)
}

cfgCopy := *cfg
cfgCopy := cfg.Copy()
cfgCopy.Region = sharedConfig.SSORegion

cfg.Credentials = ssocreds.New(sso.NewFromConfig(cfgCopy), sharedConfig.SSOAccountID, sharedConfig.SSORoleName, sharedConfig.SSOStartURL, options...)
Expand Down
188 changes: 147 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,79 @@ 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()
},
},
"chained assume role with sso and static credentials": {
envProfile: "assume_sso_and_static",
expectedAccessKey: "AKID",
expectedSecretKey: "SECRET",
expectedSessionToken: "SESSION_TOKEN",
expectedChain: []string{
"assume_sso_and_static_arn",
},
},
}
Expand All @@ -222,8 +318,14 @@ 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)
}
defer cleanup()
}

var credChain []string
Expand Down Expand Up @@ -278,7 +380,11 @@ 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)
}
})
}
Expand Down
11 changes: 6 additions & 5 deletions config/shared_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,15 +759,15 @@ 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 not top level profile and has credentials, return with credentials.
if len(profiles) != 0 && (c.Credentials.HasKeys() || c.hasSSOConfiguration()) {
if len(profiles) != 0 && c.Credentials.HasKeys() {
return nil
}

Expand Down Expand Up @@ -798,7 +798,7 @@ func (c *SharedConfig) setFromIniSections(profiles map[string]struct{}, profile
return err
}

if !srcCfg.hasCredentials() && !srcCfg.hasSSOConfiguration() {
if !srcCfg.hasCredentials() {
return SharedConfigAssumeRoleError{
RoleARN: c.RoleARN,
Profile: c.SourceProfileName,
Expand Down Expand Up @@ -951,7 +951,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 All @@ -963,6 +963,7 @@ func (c *SharedConfig) hasCredentials() bool {
case len(c.CredentialSource) != 0:
case len(c.CredentialProcess) != 0:
case len(c.WebIdentityTokenFile) != 0:
case c.hasSSOConfiguration():
case c.Credentials.HasKeys():
default:
return false
Expand Down
Loading

0 comments on commit 68b819c

Please sign in to comment.