AWS Dynamic Secrets
AWS Dynamic Secrets generate a temporary access key, secret key, and session token. AWS Security Token Service (STS) provides for either federate
or assumeRole
. federate
and is ideal for assigning dynamic secrets from a single AWS account. assumeRole
allows cross account access in AWS, so a single set of credentials in DSV can grant access to multiple AWS accounts.
These are the links to AWS documentation for each STS type.
AWS Federate
Setup the AWS IAM User
For the federate
example, create a new IAM User and note the access key and secret key.
Assign a policy to the IAM user with sts:GetFederationToken
permission as well as any other permissions the IAM user should have. In this example, we assign the user full CodeDeploy rights.
When you get temporary tokens from AWS via GetFederationToken
the resulting token's permissions will be the intersection of the IAM User and the policy ARN specified on the dynamic decret. In other words, the dynamic secret is only allowed the permissions that are in both the IAM policies and the dynamic secret attached policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:GetFederationToken",
"codedeploy:*"
],
"Resource": "*"
}
]
}
Create the Base Secret
Next create a secret in DSV with the AWS IAM user access key, secret key, and region.
Create a file named secret_root.json
substituting your values.
{
"accessKey": "youraccesskey",
"region": "us-east-1",
"secretKey": "yoursecretkey"
}
Create the secret via the CLI at a path of your choosing.
dsv secret create --path aws/base/api-account --data @secret_root.json --attributes '{"type": "aws"}'
Create the Dynamic Secret
Attribute | Description |
---|---|
policyArn | AWS ARN of the policy to assign the federated user token. Can be customer or AWS managed. |
providerType | federate
|
ttl | optional time to live in seconds of the generated token. If none is specified it will default to the minimum of 900. |
If the TTL is set to less than 900 seconds, AWS will fail to create the token.
Now, you need to create a dynamic secret, which points to the base secret via its attributes. The dynamic secret doesn't have any data stored in it because data is only populated when you read the secret.
Create an attributes JSON file named secret_attributes.json, substituting your values.
{
"linkConfig": {
"linkType": "dynamic",
"linkedSecret": "aws:base:api-account"
},
"policyArn": "arn:aws:iam::aws:policy/AWSCodeDeployReadOnlyAccess",
"providerType": "federate",
"ttl": 1200
}
Create a new Dynamic Secret
dsv secret create --path dynamic/aws/federate-api --attributes @secret_attributes.json
Now, anytime you read the dynamic secret, the data is populated with the temporary AWS access credentials.
dsv secret read --path dynamic/aws/federate-api
This returns a result like:
{
"attributes": {
"linkConfig": {
"linkType": "dynamic",
"linkedSecret": "aws:base:api-account"
},
"policyArn": "arn:aws:iam::aws:policy/AWSCodeDeployReadOnlyAccess",
"providerType": "federate",
"ttl": 1200
},
"data": {
"accessKey": "youraccesskey",
"expiration": "2020-02-06T18:49:17Z",
"secretKey": "yoursecretkey",
"sessionToken": "yoursessiontoken",
"ttl": 1200
},
"description": "",
"id": "yourId",
"version": "0"
}
You can validate the credentials only grant read access to Code Deploy by putting the credentials in a python script and attempting to create a Code Deploy application:
import boto3
import json
from botocore.exceptions import ClientError
sess = boto3.Session(
aws_access_key_id="accesskeyid",
aws_secret_access_key="secretaccesskey",
aws_session_token="yoursessiontoken"
)
client = sess.client("codedeploy")
resp = client.list_applications()
print("----list code deploy apps----")
print(json.dumps(resp["applications"], indent=4))
print("----create code deploy app----")
try:
resp = client.create_application(
applicationName="TestApp",
computePlatform="Server"
)
except ClientError as e:
print(e.response["Error"]["Code"])
The result should look something like this (depending on how many CodeDeploy apps exist):
----list code deploy apps----
[
"ExampleApp"
]
----create code deploy app----
AccessDeniedException
AWS Assume Role
In this example, we assume the IAM user and the role that the user will assume are in separate AWS accounts. This is not required, but then it might make more sense to use the sts:Federated
approach.
Setup the AWS IAM user
In the AWS account for the IAM user, create or modify an IAM user policy to include the sts:AssumeRole
permissions as well as any other permissions the user should have. In this example, we assign the user full CodeDeploy
rights.
For setting up, if you don't know the role account ID or name at this point, Resources could be set to all *
, but best practices would be to come back and restrict the Resources to only the role once the name is known as shown here.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codedeploy:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": "arn:aws:iam::{account id of role}:role/{role-name}"
}
]
}
Setup the AWS IAM role
In the AWS account with the role that is to be used, create a new Role or identify an existing one with the proper policies (not shown here).
The sts:AssumeRole
token will have permissions that intersect between the IAM user policy(ies) and the role ploicy(ies) they assume. In other words, the token can't have permissions enabled by both the user and role policies.
Additionally, this role must have a trust relationship setup between the IAM user in the first account and this role. It might look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{account id of user}:{iam-user}"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
Create the Base Secret
Next, create a secret in DSV with the AWS IAM user access key, secret key, and region.
Create a file named secret_root.json
substituting your values.
{
"accessKey": "youraccesskey",
"region": "us-east-1",
"secretKey": "secretkey"
}
Create the Secret via the CLI at a path of your choosing.
dsv secret create --path aws/base/api-account --data @secret_root.json --attributes '{"type": "aws"}'
Create the Dynamic Secret
Attribute | Description |
---|---|
roleArn | AWS ARN of the role to assign the AssumeRole user token. Can be customer or AWS managed. |
providerType | assumeRole
|
ttl | optional time to live in seconds of the generated token. If none is specified will default to 900. |
Create the Dynamic Secret
Now you need to create a dynamic secret which points to the base secret via its attributes. The dynamic secret doesn't have any data stored in it. Data is only populated when you read the secret.
Create or update the attributes json file named `secret_attributes.json substituting the ARN of the role you created.
{
"linkConfig": {
"linkType": "dynamic",
"linkedSecret": "aws:base:api-account"
},
"roleArn": "arn:aws:iam::{account id of role}:role/{role-name}",
"providerType": "assumeRole",
"ttl": 1200
}
Now, create the dynamic secret in the CLI using the JSON above.
dsv secret create --path dynamic/aws/assume-api --attributes @secret_attributes.json
Now, anytime you read the dynamic secret, the data is populated with the temporary AWS access credentials.
dsv secret read --path dynamic/aws/assume-api
This returns a result like:
{
"attributes": {
"linkConfig": {
"linkType": "dynamic",
"linkedSecret": "aws:base:api-account"
},
"roleArn": "arn:aws:iam::{account id of role}:role/{role-name}",
"providerType": "assumeRole",
"ttl": 1200
},
"data": {
"accessKey": "youraccesskey",
"expiration": "2020-02-06T18:49:17Z",
"secretKey": "yoursecretkey",
"sessionToken": "yoursessiontoken",
"ttl": 1200
},
"description": "",
"id": "yourid",
"version": "0"
}