Add AWS_SESSION_TOKEN and AWS_SECURITY_TOKEN to aws environemnt credentials

AWS_SESSION_TOKEN or AWS_SECURITY_TOKEN is required when signing AWS requests
using ENV credentials from IAM assumed role. Missing token
results with S3 403 error when trying to download bundle.

Signed-off-by: Kamil Piotrowski <kamil.piotrowski@nordcloud.com>
This commit is contained in:
Kamil Piotrowski
2020-04-25 15:58:58 +02:00
committed by Patrick East
parent 00a71ef465
commit a4412df0fa
3 changed files with 59 additions and 29 deletions
+2
View File
@@ -265,6 +265,8 @@ If specifying `environment_credentials`, OPA will expect to find environment var
for `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION`, in accordance with the
convention used by the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html).
Please note that if you are using temporary IAM credentials (e.g. assumed IAM role credentials) you have to provide additional `AWS_SESSION_TOKEN` or `AWS_SECURITY_TOKEN` environment variable.
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `services[_].credentials.s3_signing.environment_credentials` | `{}` | Yes | Enables AWS signing using environment variables to source the configuration and credentials |
+21 -11
View File
@@ -29,17 +29,19 @@ const (
ecsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
// ref. https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
accessKeyEnvVar = "AWS_ACCESS_KEY_ID"
secretKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
awsRegionEnvVar = "AWS_REGION"
accessKeyEnvVar = "AWS_ACCESS_KEY_ID"
secretKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
securityTokenEnvVar = "AWS_SECURITY_TOKEN"
sessionTokenEnvVar = "AWS_SESSION_TOKEN"
awsRegionEnvVar = "AWS_REGION"
)
// awsCredentials represents the credentials obtained from an AWS credential provider
type awsCredentials struct {
AccessKey string
SecretKey string
RegionName string
SecurityToken string
AccessKey string
SecretKey string
RegionName string
SessionToken string
}
// awsCredentialService represents the interface for AWS credential providers
@@ -64,7 +66,15 @@ func (cs *awsEnvironmentCredentialService) credentials() (awsCredentials, error)
if creds.RegionName == "" {
return creds, errors.New("no " + awsRegionEnvVar + " set in environment")
}
creds.SecurityToken = "" // not applicable to this credential provider
// SessionToken is required if using temporaty ENV credentials from assumed IAM role
// Missing SessionToken results with 403 s3 error.
creds.SessionToken = os.Getenv(sessionTokenEnvVar)
if creds.SessionToken == "" {
// In case of missing SessionToken try to get SecurityToken
// AWS switched to use SessionToken, but SecurityToken was left for backward compatibility
creds.SessionToken = os.Getenv(securityTokenEnvVar)
}
return creds, nil
}
@@ -162,7 +172,7 @@ func (cs *awsMetadataCredentialService) refreshFromService() error {
cs.expiration = payload.Expiration
cs.creds.AccessKey = payload.AccessKeyID
cs.creds.SecretKey = payload.SecretAccessKey
cs.creds.SecurityToken = payload.Token
cs.creds.SessionToken = payload.Token
cs.creds.RegionName = cs.RegionName
return nil
@@ -227,8 +237,8 @@ func signV4(req *http.Request, credService awsCredentialService, theTime time.Ti
// the security token header is necessary for ephemeral credentials, e.g. from
// the EC2 metadata service
if creds.SecurityToken != "" {
awsHeaders["x-amz-security-token"] = creds.SecurityToken
if creds.SessionToken != "" {
awsHeaders["x-amz-security-token"] = creds.SessionToken
}
// ref. https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
+36 -18
View File
@@ -42,6 +42,8 @@ func TestEnvironmentCredentialService(t *testing.T) {
os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
os.Setenv("AWS_REGION", "")
os.Setenv("AWS_SECURITY_TOKEN", "")
os.Setenv("AWS_SESSION_TOKEN", "")
cs := &awsEnvironmentCredentialService{}
@@ -59,20 +61,36 @@ func TestEnvironmentCredentialService(t *testing.T) {
os.Setenv("AWS_REGION", "us-east-1")
// happy path: all required environment is present
envCreds, err = cs.credentials()
if err != nil {
t.Error("unexpected error: " + err.Error())
expectedCreds := awsCredentials{
AccessKey: "MYAWSACCESSKEYGOESHERE",
SecretKey: "MYAWSSECRETACCESSKEYGOESHERE",
RegionName: "us-east-1",
SessionToken: ""}
testCases := []struct {
tokenEnv string
tokenValue string
}{
// happy path: all required environment is present
{"", ""},
// happy path: all required environment is present including security token
{"AWS_SECURITY_TOKEN", "MYSECURITYTOKENGOESHERE"},
// happy path: all required environment is present including session token that is preferred over security token
{"AWS_SESSION_TOKEN", "MYSESSIONTOKENGOESHERE"},
}
expectedCreds := awsCredentials{
AccessKey: "MYAWSACCESSKEYGOESHERE",
SecretKey: "MYAWSSECRETACCESSKEYGOESHERE",
RegionName: "us-east-1",
SecurityToken: ""}
for _, testCase := range testCases {
os.Setenv(testCase.tokenEnv, testCase.tokenValue)
expectedCreds.SessionToken = testCase.tokenValue
if envCreds != expectedCreds {
t.Error("expected: ", expectedCreds, " but got: ", envCreds)
envCreds, err = cs.credentials()
if err != nil {
t.Error("unexpected error: " + err.Error())
}
if envCreds != expectedCreds {
t.Error("expected: ", expectedCreds, " but got: ", envCreds)
}
}
}
@@ -143,7 +161,7 @@ func TestMetadataCredentialService(t *testing.T) {
assertEq(creds.AccessKey, ts.payload.AccessKeyID, t)
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
assertEq(creds.RegionName, cs.RegionName, t)
assertEq(creds.SecurityToken, ts.payload.Token, t)
assertEq(creds.SessionToken, ts.payload.Token, t)
// happy path: verify credentials are cached based on expiry
ts.payload.AccessKeyID = "ICHANGEDTHISBUTWEWONTSEEIT"
@@ -152,7 +170,7 @@ func TestMetadataCredentialService(t *testing.T) {
assertEq(creds.AccessKey, "MYAWSACCESSKEYGOESHERE", t) // the original value
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
assertEq(creds.RegionName, cs.RegionName, t)
assertEq(creds.SecurityToken, ts.payload.Token, t)
assertEq(creds.SessionToken, ts.payload.Token, t)
// happy path: with refresh
// first time through
@@ -172,7 +190,7 @@ func TestMetadataCredentialService(t *testing.T) {
assertEq(creds.AccessKey, ts.payload.AccessKeyID, t)
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
assertEq(creds.RegionName, cs.RegionName, t)
assertEq(creds.SecurityToken, ts.payload.Token, t)
assertEq(creds.SessionToken, ts.payload.Token, t)
// second time through, with changes
ts.payload.AccessKeyID = "ICHANGEDTHISANDWEWILLSEEIT"
@@ -181,16 +199,16 @@ func TestMetadataCredentialService(t *testing.T) {
assertEq(creds.AccessKey, ts.payload.AccessKeyID, t) // the new value
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
assertEq(creds.RegionName, cs.RegionName, t)
assertEq(creds.SecurityToken, ts.payload.Token, t)
assertEq(creds.SessionToken, ts.payload.Token, t)
}
type testCredentialService struct{}
func (cs *testCredentialService) credentials() (awsCredentials, error) {
return awsCredentials{AccessKey: "MYAWSACCESSKEYGOESHERE",
SecretKey: "MYAWSSECRETACCESSKEYGOESHERE",
RegionName: "us-east-1",
SecurityToken: "MYAWSSECURITYTOKENGOESHERE"}, nil
SecretKey: "MYAWSSECRETACCESSKEYGOESHERE",
RegionName: "us-east-1",
SessionToken: "MYAWSSECURITYTOKENGOESHERE"}, nil
}
func TestV4Signing(t *testing.T) {