Skip to content

Commit

Permalink
plugins/rest: Add support to get temp creds via AssumeRole
Browse files Browse the repository at this point in the history
Adds support for signing AWS requests using temporary credentials
obtained from AWS STS via AssumeRole operation. One use-case of
this mechanism is for allowing existing IAM users to access AWS resources
that they don't already have access to. It is also useful as a means to
temporarily gain privileged access.

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
  • Loading branch information
ashutosh-narkar committed Mar 26, 2024
1 parent 6c08d3f commit 5f16f4a
Show file tree
Hide file tree
Showing 7 changed files with 684 additions and 34 deletions.
46 changes: 46 additions & 0 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,52 @@ containers have at most one associated IAM role.
| `services[_].credentials.s3_signing.metadata_credentials.aws_region` | `string` | No | The AWS region to use for the AWS signing service credential method. If unset, the `AWS_REGION` environment variable must be set |
| `services[_].credentials.s3_signing.metadata_credentials.iam_role` | `string` | No | The IAM role to use for the AWS signing service credential method |


##### Using AWS Security Token Service (AWS STS) via AssumeRole
If specifying `assume_role_credentials`, OPA will use [AWS STS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html)
to obtain temporary security credentials for accessing AWS resources. In order to retrieve temporary security credentials from STS
via [AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) valid AWS security credentials are required.

{{< info >}}
For using `services[_].credentials.s3_signing.assume_role_credentials`, a method for setting the AWS credentials has to be specified in the `services[_].credentials.s3_signing.assume_role_credentials.aws_signing`.
The value of `services[_].credentials.s3_signing.assume_role_credentials.aws_signing.service` is set to `STS`. Several methods of obtaining the necessary credentials are available; exactly one must be specified,
see description for `services[_].credentials.s3_signing`. Currently supported methods are `services[_].credentials.s3_signing.environment_credentials`, `services[_].credentials.s3_signing.profile_credentials` and
`services[_].credentials.s3_signing.metadata_credentials`. OPA will follow this *internally defined* order of precedence when multiple credential providers are specified.
{{< /info >}}


| Field | Type | Required | Description |
|-------------------------------------------------------------------| --- | -- | --- |
| `services[_].credentials.s3_signing.assume_role_credentials.aws_region` | `string` | Yes | The AWS region to use for the sts regional endpoint. Uses the global endpoint by default |
| `services[_].credentials.s3_signing.assume_role_credentials.iam_role_arn` | `string` | Yes | The IAM Role ARN to be assumed. Can also be set via the `AWS_ROLE_ARN` environment variable (config takes precedence) |
| `services[_].credentials.s3_signing.assume_role_credentials.aws_signing` | `{}` | Yes | AWS credentials for signing requests. |
| `services[_].credentials.s3_signing.assume_role_credentials.session_name` | `string` | No | The session name used to identify the assumed role session. Default: `open-policy-agent` |
| `services[_].credentials.s3_signing.assume_role_credentials.aws_domain` | `string` | No | The AWS domain name to use. Default: `amazonaws.com`. Can also be set via the `AWS_DOMAIN` environment variable (config takes precedence) |

##### Example

Using Assume Role Credentials type with EC2 Metadata Credentials signing plugin.

```yaml
services:
remote:
url: ${BUNDLE_SERVICE_URL}
credentials:
assume_role_credentials:
aws_region: us-east-1
iam_role_arn: arn:aws::iam::123456789012:role/demo
session_name: demo
aws_signing: # similar to s3_signing
metadata_credentials:
aws_region: us-east-1
iam_role: s3access
bundles:
authz:
service: remote
resource: bundles/http/example/authz.tar.gz
```

##### Using EKS IAM Roles for Service Account (Web Identity) Credentials
If specifying `web_identity_credentials`, OPA will expect to find environment variables for `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE`, in accordance with the convention used by the [AWS EKS IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).

Expand Down
33 changes: 30 additions & 3 deletions docs/content/management-bundles.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,32 @@ bundles:

**NOTE:** the S3 `url` is the bucket's regional endpoint.


##### Assume Role Credentials

```yaml
services:
s3:
url: https://my-example-opa-bucket.s3.us-east-1.amazonaws.com
credentials:
s3_signing:
assume_role_credentials:
aws_region: us-east-1
iam_role_arn: arn:aws::iam::123456789012:role/demo
session_name: my-open-policy-agent # Optional. Default: open-policy-agent
aws_signing: # similar to s3_signing
metadata_credentials:
aws_region: us-east-1
iam_role: s3access
bundles:
authz:
service: s3
resource: bundle.tar.gz
```

**NOTE:** the S3 `url` is the bucket's regional endpoint.

##### Web Identity Credentials

```yaml
Expand All @@ -852,9 +878,10 @@ bundles:
Multiple AWS credential providers can be configured. OPA will follow an *internally defined* order to try each of the credential provider given in the configuration till success. Following order of precedence is followed when multiple credential provider is given in the configuration

1. Environment Credential
2. Web Identity Credential
3. Profile Credential
4. Metadata Credential
1. Assume Role Credential
1. Web Identity Credential
1. Profile Credential
1. Metadata Credential

```yaml
services:
Expand Down
13 changes: 13 additions & 0 deletions plugins/rest/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ func (ap *clientTLSAuthPlugin) Prepare(req *http.Request) error {
type awsSigningAuthPlugin struct {
AWSEnvironmentCredentials *awsEnvironmentCredentialService `json:"environment_credentials,omitempty"`
AWSMetadataCredentials *awsMetadataCredentialService `json:"metadata_credentials,omitempty"`
AWSAssumeRoleCredentials *awsAssumeRoleCredentialService `json:"assume_role_credentials,omitempty"`
AWSWebIdentityCredentials *awsWebIdentityCredentialService `json:"web_identity_credentials,omitempty"`
AWSProfileCredentials *awsProfileCredentialService `json:"profile_credentials,omitempty"`

Expand Down Expand Up @@ -796,6 +797,11 @@ func (ap *awsSigningAuthPlugin) awsCredentialService() awsCredentialService {
chain.addService(ap.AWSEnvironmentCredentials)
}

if ap.AWSAssumeRoleCredentials != nil {
ap.AWSAssumeRoleCredentials.logger = ap.logger
chain.addService(ap.AWSAssumeRoleCredentials)
}

if ap.AWSWebIdentityCredentials != nil {
ap.AWSWebIdentityCredentials.logger = ap.logger
chain.addService(ap.AWSWebIdentityCredentials)
Expand Down Expand Up @@ -851,6 +857,7 @@ func (ap *awsSigningAuthPlugin) validateAndSetDefaults(serviceType string) error
cfgs := map[bool]int{}
cfgs[ap.AWSEnvironmentCredentials != nil]++
cfgs[ap.AWSMetadataCredentials != nil]++
cfgs[ap.AWSAssumeRoleCredentials != nil]++
cfgs[ap.AWSWebIdentityCredentials != nil]++
cfgs[ap.AWSProfileCredentials != nil]++

Expand All @@ -864,6 +871,12 @@ func (ap *awsSigningAuthPlugin) validateAndSetDefaults(serviceType string) error
}
}

if ap.AWSAssumeRoleCredentials != nil {
if err := ap.AWSAssumeRoleCredentials.populateFromEnv(); err != nil {
return err
}
}

if ap.AWSWebIdentityCredentials != nil {
if err := ap.AWSWebIdentityCredentials.populateFromEnv(); err != nil {
return err
Expand Down
56 changes: 56 additions & 0 deletions plugins/rest/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,59 @@ func TestOauth2WithAWSKMS(t *testing.T) {
t.Errorf("OAuth2.AWSSigningPlugin.kmsSignPlugin isn't setup")
}
}

func TestAssumeRoleWithNoSigningProvider(t *testing.T) {
conf := `{
"name": "foo",
"url": "https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com",
"credentials": {
"s3_signing": {
"service": "s3",
"assume_role_credentials": {}
}
}
}`

client, err := New([]byte(conf), map[string]*keys.Config{})
if err != nil {
t.Fatal(err)
}

_, err = client.config.Credentials.S3Signing.NewClient(client.config)
if err == nil {
t.Fatal("expected error but got nil")
}

expErrMsg := "a AWS signing plugin must be specified when AssumeRole credential provider is enabled"
if err.Error() != expErrMsg {
t.Fatalf("expected error: %v but got: %v", expErrMsg, err)
}
}

func TestAssumeRoleWithUnsupportedSigningProvider(t *testing.T) {
conf := `{
"name": "foo",
"url": "https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com",
"credentials": {
"s3_signing": {
"service": "s3",
"assume_role_credentials": {"aws_signing": {"web_identity_credentials": {}}}
}
}
}`

client, err := New([]byte(conf), map[string]*keys.Config{})
if err != nil {
t.Fatal(err)
}

_, err = client.config.Credentials.S3Signing.NewClient(client.config)
if err == nil {
t.Fatal("expected error but got nil")
}

expErrMsg := "unsupported AWS signing plugin with AssumeRole credential provider"
if err.Error() != expErrMsg {
t.Fatalf("expected error: %v but got: %v", expErrMsg, err)
}
}
Loading

0 comments on commit 5f16f4a

Please sign in to comment.