Skip to content

Commit 5b0bf70

Browse files
authored
Merge branch 'main' into lula_ecosystem_addition
2 parents 483258d + 971a721 commit 5b0bf70

3 files changed

Lines changed: 71 additions & 6 deletions

File tree

docs/content/configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,9 @@ To use the EC2 metadata service, the IAM role to use and the AWS region for the
393393
be specified as `iam_role` and `aws_region` respectively.
394394

395395
To use the ECS metadata service, specify only the AWS region for the resource as `aws_region`. ECS
396-
containers have at most one associated IAM role.
396+
containers have at most one associated IAM role. As per the [AWS documentation](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html), credentials are
397+
sourced from the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` metadata environment variable or the
398+
`AWS_CONTAINER_CREDENTIALS_FULL_URI` metadata environment variable in order.
397399

398400
> Providing a value for `iam_role` will cause OPA to use the EC2 metadata service even
399401
> if running inside an ECS container. This may result in unexpected problems if, for example,

plugins/rest/aws.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ const (
3030
ec2DefaultTokenPath = "http://169.254.169.254/latest/api/token"
3131

3232
// ref. https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html
33-
ecsDefaultCredServicePath = "http://169.254.170.2"
34-
ecsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
33+
ecsDefaultCredServicePath = "http://169.254.170.2"
34+
ecsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
35+
ecsFullPathEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
36+
ecsAuthorizationTokenEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
3537

3638
// ref. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html
3739
stsDefaultDomain = "amazonaws.com"
@@ -211,7 +213,12 @@ func (cs *awsMetadataCredentialService) urlForMetadataService() (string, error)
211213
// otherwise, check environment to see if it looks like we're in an ECS
212214
// container (with implied role association)
213215
if isECS() {
214-
return ecsDefaultCredServicePath + os.Getenv(ecsRelativePathEnvVar), nil
216+
// first check if the relative env var exists; if so we use that otherwise we
217+
// use the "full" var
218+
if _, relativeExists := os.LookupEnv(ecsRelativePathEnvVar); relativeExists {
219+
return ecsDefaultCredServicePath + os.Getenv(ecsRelativePathEnvVar), nil
220+
}
221+
return os.Getenv(ecsFullPathEnvVar), nil
215222
}
216223
// if there's no role name and we don't appear to have a path to the
217224
// ECS container service, then the configuration is invalid
@@ -267,6 +274,16 @@ func (cs *awsMetadataCredentialService) refreshFromService(ctx context.Context)
267274
return errors.New("unable to construct metadata HTTP request: " + err.Error())
268275
}
269276

277+
// if using the AWS_CONTAINER_CREDENTIALS_FULL_URI variable, we need to associate the token
278+
// to the request
279+
if _, useFullPath := os.LookupEnv(ecsFullPathEnvVar); useFullPath {
280+
token, tokenExists := os.LookupEnv(ecsAuthorizationTokenEnvVar)
281+
if !tokenExists {
282+
return errors.New("unable to get ECS metadata authorization token")
283+
}
284+
req.Header.Set("Authorization", token)
285+
}
286+
270287
// if in the EC2 environment, we will use IMDSv2, which requires a session cookie from a
271288
// PUT request on the token endpoint before it will give the credentials, this provides
272289
// protection from SSRF attacks
@@ -604,8 +621,9 @@ func (cs *awsWebIdentityCredentialService) credentials(ctx context.Context) (aws
604621

605622
func isECS() bool {
606623
// the special relative path URI is set by the container agent in the ECS environment only
607-
_, isECS := os.LookupEnv(ecsRelativePathEnvVar)
608-
return isECS
624+
_, isECSRelative := os.LookupEnv(ecsRelativePathEnvVar)
625+
_, isECSFull := os.LookupEnv(ecsFullPathEnvVar)
626+
return isECSRelative || isECSFull
609627
}
610628

611629
// ecrAuthPlugin authorizes requests to AWS ECR.

plugins/rest/aws_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,13 @@ func TestMetadataCredentialService(t *testing.T) {
351351
_, err = cs.credentials(context.Background())
352352
assertErr("metadata endpoint cannot be determined from settings and environment", err, t)
353353

354+
// wrong path: missing token
355+
t.Setenv(ecsFullPathEnvVar, "fullPath")
356+
os.Unsetenv(ecsAuthorizationTokenEnvVar)
357+
_, err = cs.credentials(context.Background())
358+
assertErr("unable to get ECS metadata authorization token", err, t)
359+
os.Unsetenv(ecsFullPathEnvVar)
360+
354361
// wrong path: creds not found
355362
cs = awsMetadataCredentialService{
356363
RoleName: "not_my_iam_role", // not present
@@ -490,6 +497,34 @@ func TestMetadataCredentialService(t *testing.T) {
490497
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
491498
assertEq(creds.RegionName, cs.RegionName, t)
492499
assertEq(creds.SessionToken, ts.payload.Token, t)
500+
501+
// happy path: credentials fetched from full path var
502+
cs = awsMetadataCredentialService{
503+
RegionName: "us-east-1",
504+
credServicePath: "", // not set as we want to test env var resolution
505+
logger: logging.Get(),
506+
}
507+
ts.payload = metadataPayload{
508+
AccessKeyID: "MYAWSACCESSKEYGOESHERE",
509+
SecretAccessKey: "MYAWSSECRETACCESSKEYGOESHERE",
510+
Code: "Success",
511+
Token: "MYAWSSECURITYTOKENGOESHERE",
512+
Expiration: time.Now().UTC().Add(time.Minute * 2)} // short time
513+
t.Setenv(ecsFullPathEnvVar, ts.server.URL+"/fullPath")
514+
t.Setenv(ecsAuthorizationTokenEnvVar, "THIS_IS_A_GOOD_TOKEN")
515+
516+
creds, err = cs.credentials(context.Background())
517+
if err != nil {
518+
// Cannot proceed with test if unable to fetch credentials.
519+
t.Fatal(err)
520+
}
521+
522+
assertEq(creds.AccessKey, ts.payload.AccessKeyID, t)
523+
assertEq(creds.SecretKey, ts.payload.SecretAccessKey, t)
524+
assertEq(creds.RegionName, cs.RegionName, t)
525+
assertEq(creds.SessionToken, ts.payload.Token, t)
526+
os.Unsetenv(ecsFullPathEnvVar)
527+
os.Unsetenv(ecsAuthorizationTokenEnvVar)
493528
}
494529

495530
func TestMetadataServiceErrorHandled(t *testing.T) {
@@ -957,6 +992,7 @@ type ec2CredTestServer struct {
957992
func (t *ec2CredTestServer) handle(w http.ResponseWriter, r *http.Request) {
958993
goodPath := "/latest/meta-data/iam/security-credentials/my_iam_role"
959994
badPath := "/latest/meta-data/iam/security-credentials/my_bad_iam_role"
995+
goodPathFull := "/fullPath"
960996

961997
goodTokenPath := "/latest/api/token"
962998
badTokenPath := "/latest/api/bad_token"
@@ -987,6 +1023,15 @@ func (t *ec2CredTestServer) handle(w http.ResponseWriter, r *http.Request) {
9871023
// a metadata response that's not well-formed
9881024
w.WriteHeader(200)
9891025
_, _ = w.Write([]byte("This isn't a JSON payload"))
1026+
case goodPathFull:
1027+
// validate token...
1028+
if r.Header.Get("Authorization") == tokenValue {
1029+
w.WriteHeader(200)
1030+
_, _ = w.Write(jsonBytes)
1031+
} else {
1032+
// AWS returns a 404 if the token is wrong
1033+
w.WriteHeader(404)
1034+
}
9901035
default:
9911036
// something else that we won't be able to find
9921037
w.WriteHeader(404)

0 commit comments

Comments
 (0)