35 types
require 'aws-cdk-lib'
To have SecretsManager generate a new secret value automatically, follow this example:
vpc = nil # AWSCDK::EC2::IVPC
instance1 = AWSCDK::RDS::DatabaseInstance.new(self, "PostgresInstance1", {
engine: AWSCDK::RDS::DatabaseInstanceEngine.POSTGRES,
# Generate the secret with admin username `postgres` and random password
credentials: AWSCDK::RDS::Credentials.from_generated_secret("postgres"),
vpc: vpc,
})
# Templated secret with username and password fields
templated_secret = AWSCDK::SecretsManager::Secret.new(self, "TemplatedSecret", {
generate_secret_string: {
secret_string_template: JSON[:stringify]({username: "postgres"}),
generate_string_key: "password",
exclude_characters: "/@\"",
},
})
# Using the templated secret as credentials
instance2 = AWSCDK::RDS::DatabaseInstance.new(self, "PostgresInstance2", {
engine: AWSCDK::RDS::DatabaseInstanceEngine.POSTGRES,
credentials: {
username: templated_secret.secret_value_from_json("username").to_string,
password: templated_secret.secret_value_from_json("password"),
},
vpc: vpc,
})
If you need to use a pre-existing secret, the recommended way is to manually
provision the secret in AWS SecretsManager and use the Secret.fromSecretArn
or Secret.fromSecretAttributes method to make it available in your CDK Application:
encryption_key = nil # AWSCDK::KMS::Key
secret = AWSCDK::SecretsManager::Secret.from_secret_attributes(self, "ImportedSecret", {
secret_arn: "arn:aws:secretsmanager:<region>:<account-id-number>:secret:<secret-name>-<random-6-characters>",
encryption_key: encryption_key,
})
SecretsManager secret values can only be used in select set of properties. For the list of properties, see the CloudFormation Dynamic References documentation.
A secret can set RemovalPolicy. If it set to RETAIN, removing that secret will fail.
You must grant permission to a resource for that resource to be allowed to
use a secret. This can be achieved with the Secret.grantRead and/or Secret.grantWrite
method, depending on your need:
role = AWSCDK::IAM::Role.new(self, "SomeRole", {assumed_by: AWSCDK::IAM::AccountRootPrincipal.new})
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret")
secret.grant_read(role)
secret.grant_write(role)
If, as in the following example, your secret was created with a KMS key:
role = nil # AWSCDK::IAM::Role
key = AWSCDK::KMS::Key.new(self, "KMS")
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret", {encryption_key: key})
secret.grant_read(role)
secret.grant_write(role)
then Secret.grantRead and Secret.grantWrite will also grant the role the
relevant encrypt and decrypt permissions to the KMS key through the
SecretsManager service principal.
The principal is automatically added to Secret resource policy and KMS Key policy for cross account access:
other_account = AWSCDK::IAM::AccountPrincipal.new("1234")
key = AWSCDK::KMS::Key.new(self, "KMS")
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret", {encryption_key: key})
secret.grant_read(other_account)
A rotation schedule can be added to a Secret using a custom Lambda function:
require 'aws-cdk-lib'
fn = nil # AWSCDK::Lambda::Function
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret")
secret.add_rotation_schedule("RotationSchedule", {
rotation_lambda: fn,
automatically_after: AWSCDK::Duration.days(15),
rotate_immediately_on_update: false,
})
Note: The required permissions for Lambda to call SecretsManager and the other way round are automatically granted based on AWS Documentation as long as the Lambda is not imported.
See Overview of the Lambda Rotation Function on how to implement a Lambda Rotation Function.
Use the hosted_rotation prop to rotate a secret with a hosted Lambda function:
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret")
secret.add_rotation_schedule("RotationSchedule", {
hosted_rotation: AWSCDK::SecretsManager::HostedRotation.mysql_single_user,
})
Hosted rotation is available for secrets representing credentials for MySQL, PostgreSQL, Oracle, MariaDB, SQLServer, Redshift and MongoDB (both for the single and multi user schemes).
When deployed in a VPC, the hosted rotation implements ec2.IConnectable:
my_vpc = nil # AWSCDK::EC2::IVPC
db_connections = nil # AWSCDK::EC2::Connections
secret = nil # AWSCDK::SecretsManager::Secret
my_hosted_rotation = AWSCDK::SecretsManager::HostedRotation.mysql_single_user({vpc: my_vpc})
secret.add_rotation_schedule("RotationSchedule", {hosted_rotation: my_hosted_rotation})
db_connections.allow_default_port_from(my_hosted_rotation)
Use the exclude_characters option to customize the characters excluded from
the generated password when it is rotated. By default, the rotation excludes
the same characters as the ones excluded for the secret. If none are defined
then the following set is used: % +~#$&*()|[]{}:;<>?!'/@"`.
See also Automating secret creation in AWS CloudFormation.
Define a SecretRotation to rotate database credentials:
my_secret = nil # AWSCDK::SecretsManager::Secret
my_database = nil # AWSCDK::EC2::IConnectable
my_vpc = nil # AWSCDK::EC2::VPC
AWSCDK::SecretsManager::SecretRotation.new(self, "SecretRotation", {
application: AWSCDK::SecretsManager::SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER,
# MySQL single user scheme
secret: my_secret,
target: my_database,
# a Connectable
vpc: my_vpc,
# The VPC where the secret rotation application will be deployed
exclude_characters: " %+:;{}",
})
The secret must be a JSON string with the following format:
{
"engine": "<required: database engine>",
"host": "<required: instance host name>",
"username": "<required: username>",
"password": "<required: password>",
"dbname": "<optional: database name>",
"port": "<optional: if not specified, default port will be used>",
"masterarn": "<required for multi user rotation: the arn of the master secret which will be used to create users/change passwords>"
}
For the multi user scheme, a master_secret must be specified:
my_user_secret = nil # AWSCDK::SecretsManager::Secret
my_master_secret = nil # AWSCDK::SecretsManager::Secret
my_database = nil # AWSCDK::EC2::IConnectable
my_vpc = nil # AWSCDK::EC2::VPC
AWSCDK::SecretsManager::SecretRotation.new(self, "SecretRotation", {
application: AWSCDK::SecretsManager::SecretRotationApplication.MYSQL_ROTATION_MULTI_USER,
secret: my_user_secret,
# The secret that will be rotated
master_secret: my_master_secret,
# The secret used for the rotation
target: my_database,
vpc: my_vpc,
})
By default, any stack updates will cause AWS Secrets Manager to rotate a secret immediately. To prevent this behavior and wait until the next scheduled rotation window specified via the automatically_after property, set the rotate_immediately_on_update property to false:
my_user_secret = nil # AWSCDK::SecretsManager::Secret
my_master_secret = nil # AWSCDK::SecretsManager::Secret
my_database = nil # AWSCDK::EC2::IConnectable
my_vpc = nil # AWSCDK::EC2::VPC
AWSCDK::SecretsManager::SecretRotation.new(self, "SecretRotation", {
application: AWSCDK::SecretsManager::SecretRotationApplication.MYSQL_ROTATION_MULTI_USER,
secret: my_user_secret,
# The secret that will be rotated
master_secret: my_master_secret,
# The secret used for the rotation
target: my_database,
vpc: my_vpc,
automatically_after: AWSCDK::Duration.days(7),
rotate_immediately_on_update: false,
})
See also aws-rds where credentials generation and rotation is integrated.
Existing secrets can be imported by ARN, name, and other attributes (including the KMS key used to encrypt the secret). Secrets imported by name should use the short-form of the name (without the SecretsManager-provided suffix); the secret name must exist in the same account and region as the stack. Importing by name makes it easier to reference secrets created in different regions, each with their own suffix and ARN.
secret_complete_arn = "arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9"
secret_partial_arn = "arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret" # No Secrets Manager suffix
encryption_key = AWSCDK::KMS::Key.from_key_arn(self, "MyEncKey", "arn:aws:kms:eu-west-1:111111111111:key/21c4b39b-fde2-4273-9ac0-d9bb5c0d0030")
my_secret_from_complete_arn = AWSCDK::SecretsManager::Secret.from_secret_complete_arn(self, "SecretFromCompleteArn", secret_complete_arn)
my_secret_from_partial_arn = AWSCDK::SecretsManager::Secret.from_secret_partial_arn(self, "SecretFromPartialArn", secret_partial_arn)
my_secret_from_name = AWSCDK::SecretsManager::Secret.from_secret_name_v2(self, "SecretFromName", "MySecret")
my_secret_from_attrs = AWSCDK::SecretsManager::Secret.from_secret_attributes(self, "SecretFromAttributes", {
secret_complete_arn: secret_complete_arn,
encryption_key: encryption_key,
})
Secrets can be replicated to multiple regions by specifying replica_regions:
my_key = nil # AWSCDK::KMS::Key
AWSCDK::SecretsManager::Secret.new(self, "Secret", {
replica_regions: [
{
region: "eu-west-1",
},
{
region: "eu-central-1",
encryption_key: my_key,
},
],
})
Alternatively, use add_replica_region():
secret = AWSCDK::SecretsManager::Secret.new(self, "Secret")
secret.add_replica_region("eu-west-1")
Sometimes it is necessary to create a secret in SecretsManager that contains a JSON object. For example:
{
"username": "myUsername",
"database": "foo",
"password": "mypassword"
}
In order to create this type of secret, use the secret_object_value input prop.
stack = nil # AWSCDK::Stack
user = AWSCDK::IAM::User.new(self, "User")
access_key = AWSCDK::IAM::AccessKey.new(self, "AccessKey", {user: user})
AWSCDK::SecretsManager::Secret.new(self, "Secret", {
secret_object_value: {
username: AWSCDK::SecretValue.unsafe_plain_text(user.user_name),
database: AWSCDK::SecretValue.unsafe_plain_text("foo"),
password: access_key.secret_access_key,
},
})
In this case both the username and database are not a Secret so SecretValue.unsafePlainText needs to be used.
This means that they will be rendered as plain text in the template, but in this case neither of those
are actual "secrets".