Skip to content

SecretsManagerBuilder

Bases: AbstractAWSResourceBuilder['SecretsManagerBuilder', SecretConfig]

Builder for creating AWS Secrets Manager secrets with organizational standards.

Supports both database secrets with auto-generated passwords and standard secrets with custom key-value pairs. Automatically handles cross-account sharing and Parameter Store integration.

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class SecretsManagerBuilder(AbstractAWSResourceBuilder["SecretsManagerBuilder", SecretConfig]):
    """Builder for creating AWS Secrets Manager secrets with organizational standards.

    Supports both database secrets with auto-generated passwords and standard secrets
    with custom key-value pairs. Automatically handles cross-account sharing and
    Parameter Store integration.
    """

    _resource_type: AWSResourceType = AWSResourceType.SECRET

    def reset(self) -> None:
        """Reset the builder to its initial state.

        Clears all internal state including secret instance, configuration,
        and secret base name. Called internally to prepare for a new build.
        """
        super().reset()
        self._secret: secretsmanager.Secret = None
        self._secret_base_name: str = None

    def set_secret_base_name(self, name: str) -> 'SecretsManagerBuilder':
        """Set the base name for the secret.

        The base name will be used to construct the final secret name
        following the pattern: {base_name}-secret

        Args:
            name: Base name for the secret (e.g., 'my-app-db', 'api-keys')

        Returns:
            Self for method chaining
        """
        self._secret_base_name = name
        return self

    def build(self, scope: Construct) -> secretsmanager.Secret:
        """Build and return the configured Secrets Manager secret.

        Creates either a database secret with auto-generated passwords or
        a standard secret with custom values. Automatically stores secret
        references in Parameter Store and handles cross-account sharing.

        Args:
            scope: CDK construct scope where the secret will be created

        Returns:
            Configured Secrets Manager Secret instance

        Raises:
            ValidationError: If secret configuration validation fails
            ValueError: If required secret base name is not set
        """
        super().build()

        if self._config.is_db_secret:
            self._secret = self._create_rds_secret(scope)
        else: 
            self._secret = self._create_standard_secret(scope)

        # Store the secret name in the store parameter in order to be available to other stacks
        self._application_helper.store_parameter(scope, 
                                                f"SECRET_{self._config.secret_base_name.replace("-", "_")}", 
                                                self._secret.secret_name)
        param_tier = ssm.ParameterTier.ADVANCED if self._config.is_shareable else ssm.ParameterTier.STANDARD
        arn_param, _ = self._application_helper.store_parameter(scope, 
                                                f"SECRET_ARN_{self._config.secret_base_name.replace("-", "_")}", 
                                                self._secret.secret_arn,
                                                tier=param_tier)

        if param_tier == ssm.ParameterTier.ADVANCED:
            # Create a shared CfnResource so that the parameter can be accessed by devops (cross account)
            resource_name = self._get_name_for_resource(
                f"rds-ram-{self._application_helper.get_target_env()}", 
                max_length=AWSResourceNameLength.CFN_RESOURCE.value)
            consumer_account_id = self._application_helper.get_all_envs()["devops"]["account_id"]
            CrossAccountResourceSharer.share_ssm_parameter(scope, consumer_account_id, arn_param.parameter_arn, 
                                       resource_name, f"rds-ram-{self._application_helper.get_target_env()}")

        self._tag_resource(self._secret)

        return self._secret

    def _create_rds_secret(self, scope: Construct) -> secretsmanager.Secret:
        """Create a database secret with auto-generated password.

        Creates a secret with username and auto-generated password using
        SecretStringGenerator. Supports optional app user credentials.

        Args:
            scope: CDK construct scope

        Returns:
            Database secret with auto-generated password
        """
        devops_account_id = self._application_helper.get_all_envs().get("devops").get("account_id")
        logical_id = self._get_cfn_logical_id("rds-secret")
        secret_name = self._get_name_for_resource(f"{self._config.secret_base_name.lower()}-secret", 
                                                    max_length=AWSResourceNameLength.SECRET.value)
        secret_template = {
                "username": self._config.secret_data["db_username"],
        }

        if "schema" in self._config.secret_data:
            secret_template["schema"] = self._config.secret_data["schema"]

        if "db_app_username" in self._config.secret_data:
            secret_template["db_app_username"] = self._config.secret_data["db_app_username"]
            if not self._config.secret_data.get("db_app_user_is_iam_role", True):
                secret_template["db_app_user_password"] = PasswordGenerator().generate()

        snc_key: kms.IKey = None
        is_cross_account = self._application_helper.get_target_env().upper() != Environment.DEVOPS.name
        if self._config.use_snc_key:
            snc_key = self._get_snc_key_by_alias(scope, self._config.snc_key_from_foundation)
            if is_cross_account:
                snc_key.grant_decrypt(iam.AccountPrincipal(devops_account_id))

        secret = secretsmanager.Secret(
            scope, 
            id=logical_id,
            secret_name=secret_name,
            removal_policy=RemovalPolicy.DESTROY,
            encryption_key=snc_key,
            generate_secret_string=secretsmanager.SecretStringGenerator(
                secret_string_template=json.dumps(secret_template),
                generate_string_key="password",
                exclude_characters="\"@/[]{}<%>|\\`~^"
            )
        )

        if is_cross_account:
            if snc_key is not None:
                secret.grant_read(iam.AccountPrincipal(devops_account_id))
            else:
                secret.add_to_resource_policy(
                    iam.PolicyStatement(
                        principals=[iam.AccountPrincipal(devops_account_id)],
                        actions=[
                            "secretsmanager:GetSecretValue",
                            "secretsmanager:DescribeSecret"
                        ],
                        resources=["*"]
                    )
                )     

        return secret

    def _create_standard_secret(self, scope: Construct) -> secretsmanager.Secret:
        """Create a standard secret with custom key-value pairs.

        Creates a secret with provided values. Supports automatic password
        generation for values set to "GENERATE_PASSWORD".

        Args:
            scope: CDK construct scope

        Returns:
            Standard secret with custom values
        """
        devops_account_id = self._application_helper.get_all_envs().get("devops").get("account_id")
        logical_id = self._get_cfn_logical_id(self._config.secret_base_name)
        secret_name = self._get_name_for_resource(f"{self._config.secret_base_name.lower()}-secret", 
                                                  max_length=AWSResourceNameLength.SECRET.value)

        secret_data = {key: (PasswordGenerator(length=20).generate()
                            if value.upper() == "GENERATE_PASSWORD" else value) 
                            for key, value in self._config.secret_data.items()}

        snc_key: kms.IKey = None
        is_cross_account = self._application_helper.get_target_env().upper() != Environment.DEVOPS.name
        if self._config.use_snc_key:
            snc_key = self._get_snc_key_by_alias(scope, self._config.snc_key_from_foundation)
            if is_cross_account:
                snc_key.grant_decrypt(iam.AccountPrincipal(devops_account_id))

        secret = secretsmanager.Secret(
            scope, 
            id=logical_id,
            secret_name=secret_name,
            removal_policy=RemovalPolicy.DESTROY,
            encryption_key=snc_key,
            secret_string_value=SecretValue.unsafe_plain_text(json.dumps(secret_data))
        )

        if is_cross_account:
            if snc_key is not None:
                secret.grant_read(iam.AccountPrincipal(devops_account_id))
            else:
                secret.add_to_resource_policy(
                    iam.PolicyStatement(
                        principals=[iam.AccountPrincipal(devops_account_id)],
                        actions=[
                            "secretsmanager:GetSecretValue",
                            "secretsmanager:DescribeSecret"
                        ],
                        resources=["*"]
                    )
                )    

        return secret

    def _set_config(self) -> None:
        """Create and validate the AWS Secrets Manager configuration.

        Merges the base builder configuration with Secrets Manager-specific parameters
        to create a validated SecretConfig object. The configuration includes
        secret naming, encryption settings, rotation options, and other
        Secrets Manager-specific parameters required for secret creation.

        The method combines:
            - Base configuration from self._builder_config (tags, environment, etc.)
            - Secrets-specific parameter: secret_base_name from self._secret_base_name

        Raises:
            ValidationError: If the SecretConfig validation fails. Common causes:
            ValueError: If secret_base_name has not been set via set_secret_base_name()

        Side Effects:
            Sets self._config to a validated SecretConfig instance that can be
            accessed through the config property after this method completes.
        """
        try:
            self._config = SecretConfig(**{
                **self._builder_config,
                "secret_base_name": self._secret_base_name
            })
        except ValidationError as e:
            self._log_validation_error(e, SecretConfig)
            raise

    def _control_consistency(self) -> None:
        """Validate builder configuration and internal state consistency.

        Validates the SecretConfig using Pydantic models to ensure all
        required fields are present and database secrets have correct keys.

        Raises:
            ValueError: If basic builder validation fails
            ValidationError: If configuration validation fails. Error details
                           are printed to console for debugging.
        """
        super()._control_consistency()

        if not self._secret_base_name:
            raise ValueError("Secret base name must be set before building")

        self._set_config()

Attributes

_resource_type = AWSResourceType.SECRET class-attribute instance-attribute

Functions

_control_consistency()

Validate builder configuration and internal state consistency.

Validates the SecretConfig using Pydantic models to ensure all required fields are present and database secrets have correct keys.

Raises:

Type Description
ValueError

If basic builder validation fails

ValidationError

If configuration validation fails. Error details are printed to console for debugging.

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def _control_consistency(self) -> None:
    """Validate builder configuration and internal state consistency.

    Validates the SecretConfig using Pydantic models to ensure all
    required fields are present and database secrets have correct keys.

    Raises:
        ValueError: If basic builder validation fails
        ValidationError: If configuration validation fails. Error details
                       are printed to console for debugging.
    """
    super()._control_consistency()

    if not self._secret_base_name:
        raise ValueError("Secret base name must be set before building")

    self._set_config()

_create_rds_secret(scope)

Create a database secret with auto-generated password.

Creates a secret with username and auto-generated password using SecretStringGenerator. Supports optional app user credentials.

Parameters:

Name Type Description Default
scope Construct

CDK construct scope

required

Returns:

Type Description
Secret

Database secret with auto-generated password

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def _create_rds_secret(self, scope: Construct) -> secretsmanager.Secret:
    """Create a database secret with auto-generated password.

    Creates a secret with username and auto-generated password using
    SecretStringGenerator. Supports optional app user credentials.

    Args:
        scope: CDK construct scope

    Returns:
        Database secret with auto-generated password
    """
    devops_account_id = self._application_helper.get_all_envs().get("devops").get("account_id")
    logical_id = self._get_cfn_logical_id("rds-secret")
    secret_name = self._get_name_for_resource(f"{self._config.secret_base_name.lower()}-secret", 
                                                max_length=AWSResourceNameLength.SECRET.value)
    secret_template = {
            "username": self._config.secret_data["db_username"],
    }

    if "schema" in self._config.secret_data:
        secret_template["schema"] = self._config.secret_data["schema"]

    if "db_app_username" in self._config.secret_data:
        secret_template["db_app_username"] = self._config.secret_data["db_app_username"]
        if not self._config.secret_data.get("db_app_user_is_iam_role", True):
            secret_template["db_app_user_password"] = PasswordGenerator().generate()

    snc_key: kms.IKey = None
    is_cross_account = self._application_helper.get_target_env().upper() != Environment.DEVOPS.name
    if self._config.use_snc_key:
        snc_key = self._get_snc_key_by_alias(scope, self._config.snc_key_from_foundation)
        if is_cross_account:
            snc_key.grant_decrypt(iam.AccountPrincipal(devops_account_id))

    secret = secretsmanager.Secret(
        scope, 
        id=logical_id,
        secret_name=secret_name,
        removal_policy=RemovalPolicy.DESTROY,
        encryption_key=snc_key,
        generate_secret_string=secretsmanager.SecretStringGenerator(
            secret_string_template=json.dumps(secret_template),
            generate_string_key="password",
            exclude_characters="\"@/[]{}<%>|\\`~^"
        )
    )

    if is_cross_account:
        if snc_key is not None:
            secret.grant_read(iam.AccountPrincipal(devops_account_id))
        else:
            secret.add_to_resource_policy(
                iam.PolicyStatement(
                    principals=[iam.AccountPrincipal(devops_account_id)],
                    actions=[
                        "secretsmanager:GetSecretValue",
                        "secretsmanager:DescribeSecret"
                    ],
                    resources=["*"]
                )
            )     

    return secret

_create_standard_secret(scope)

Create a standard secret with custom key-value pairs.

Creates a secret with provided values. Supports automatic password generation for values set to "GENERATE_PASSWORD".

Parameters:

Name Type Description Default
scope Construct

CDK construct scope

required

Returns:

Type Description
Secret

Standard secret with custom values

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
def _create_standard_secret(self, scope: Construct) -> secretsmanager.Secret:
    """Create a standard secret with custom key-value pairs.

    Creates a secret with provided values. Supports automatic password
    generation for values set to "GENERATE_PASSWORD".

    Args:
        scope: CDK construct scope

    Returns:
        Standard secret with custom values
    """
    devops_account_id = self._application_helper.get_all_envs().get("devops").get("account_id")
    logical_id = self._get_cfn_logical_id(self._config.secret_base_name)
    secret_name = self._get_name_for_resource(f"{self._config.secret_base_name.lower()}-secret", 
                                              max_length=AWSResourceNameLength.SECRET.value)

    secret_data = {key: (PasswordGenerator(length=20).generate()
                        if value.upper() == "GENERATE_PASSWORD" else value) 
                        for key, value in self._config.secret_data.items()}

    snc_key: kms.IKey = None
    is_cross_account = self._application_helper.get_target_env().upper() != Environment.DEVOPS.name
    if self._config.use_snc_key:
        snc_key = self._get_snc_key_by_alias(scope, self._config.snc_key_from_foundation)
        if is_cross_account:
            snc_key.grant_decrypt(iam.AccountPrincipal(devops_account_id))

    secret = secretsmanager.Secret(
        scope, 
        id=logical_id,
        secret_name=secret_name,
        removal_policy=RemovalPolicy.DESTROY,
        encryption_key=snc_key,
        secret_string_value=SecretValue.unsafe_plain_text(json.dumps(secret_data))
    )

    if is_cross_account:
        if snc_key is not None:
            secret.grant_read(iam.AccountPrincipal(devops_account_id))
        else:
            secret.add_to_resource_policy(
                iam.PolicyStatement(
                    principals=[iam.AccountPrincipal(devops_account_id)],
                    actions=[
                        "secretsmanager:GetSecretValue",
                        "secretsmanager:DescribeSecret"
                    ],
                    resources=["*"]
                )
            )    

    return secret

_set_config()

Create and validate the AWS Secrets Manager configuration.

Merges the base builder configuration with Secrets Manager-specific parameters to create a validated SecretConfig object. The configuration includes secret naming, encryption settings, rotation options, and other Secrets Manager-specific parameters required for secret creation.

The method combines
  • Base configuration from self._builder_config (tags, environment, etc.)
  • Secrets-specific parameter: secret_base_name from self._secret_base_name

Raises:

Type Description
ValidationError

If the SecretConfig validation fails. Common causes:

ValueError

If secret_base_name has not been set via set_secret_base_name()

Side Effects

Sets self._config to a validated SecretConfig instance that can be accessed through the config property after this method completes.

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
def _set_config(self) -> None:
    """Create and validate the AWS Secrets Manager configuration.

    Merges the base builder configuration with Secrets Manager-specific parameters
    to create a validated SecretConfig object. The configuration includes
    secret naming, encryption settings, rotation options, and other
    Secrets Manager-specific parameters required for secret creation.

    The method combines:
        - Base configuration from self._builder_config (tags, environment, etc.)
        - Secrets-specific parameter: secret_base_name from self._secret_base_name

    Raises:
        ValidationError: If the SecretConfig validation fails. Common causes:
        ValueError: If secret_base_name has not been set via set_secret_base_name()

    Side Effects:
        Sets self._config to a validated SecretConfig instance that can be
        accessed through the config property after this method completes.
    """
    try:
        self._config = SecretConfig(**{
            **self._builder_config,
            "secret_base_name": self._secret_base_name
        })
    except ValidationError as e:
        self._log_validation_error(e, SecretConfig)
        raise

build(scope)

Build and return the configured Secrets Manager secret.

Creates either a database secret with auto-generated passwords or a standard secret with custom values. Automatically stores secret references in Parameter Store and handles cross-account sharing.

Parameters:

Name Type Description Default
scope Construct

CDK construct scope where the secret will be created

required

Returns:

Type Description
Secret

Configured Secrets Manager Secret instance

Raises:

Type Description
ValidationError

If secret configuration validation fails

ValueError

If required secret base name is not set

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def build(self, scope: Construct) -> secretsmanager.Secret:
    """Build and return the configured Secrets Manager secret.

    Creates either a database secret with auto-generated passwords or
    a standard secret with custom values. Automatically stores secret
    references in Parameter Store and handles cross-account sharing.

    Args:
        scope: CDK construct scope where the secret will be created

    Returns:
        Configured Secrets Manager Secret instance

    Raises:
        ValidationError: If secret configuration validation fails
        ValueError: If required secret base name is not set
    """
    super().build()

    if self._config.is_db_secret:
        self._secret = self._create_rds_secret(scope)
    else: 
        self._secret = self._create_standard_secret(scope)

    # Store the secret name in the store parameter in order to be available to other stacks
    self._application_helper.store_parameter(scope, 
                                            f"SECRET_{self._config.secret_base_name.replace("-", "_")}", 
                                            self._secret.secret_name)
    param_tier = ssm.ParameterTier.ADVANCED if self._config.is_shareable else ssm.ParameterTier.STANDARD
    arn_param, _ = self._application_helper.store_parameter(scope, 
                                            f"SECRET_ARN_{self._config.secret_base_name.replace("-", "_")}", 
                                            self._secret.secret_arn,
                                            tier=param_tier)

    if param_tier == ssm.ParameterTier.ADVANCED:
        # Create a shared CfnResource so that the parameter can be accessed by devops (cross account)
        resource_name = self._get_name_for_resource(
            f"rds-ram-{self._application_helper.get_target_env()}", 
            max_length=AWSResourceNameLength.CFN_RESOURCE.value)
        consumer_account_id = self._application_helper.get_all_envs()["devops"]["account_id"]
        CrossAccountResourceSharer.share_ssm_parameter(scope, consumer_account_id, arn_param.parameter_arn, 
                                   resource_name, f"rds-ram-{self._application_helper.get_target_env()}")

    self._tag_resource(self._secret)

    return self._secret

reset()

Reset the builder to its initial state.

Clears all internal state including secret instance, configuration, and secret base name. Called internally to prepare for a new build.

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
27
28
29
30
31
32
33
34
35
def reset(self) -> None:
    """Reset the builder to its initial state.

    Clears all internal state including secret instance, configuration,
    and secret base name. Called internally to prepare for a new build.
    """
    super().reset()
    self._secret: secretsmanager.Secret = None
    self._secret_base_name: str = None

set_secret_base_name(name)

Set the base name for the secret.

The base name will be used to construct the final secret name following the pattern: {base_name}-secret

Parameters:

Name Type Description Default
name str

Base name for the secret (e.g., 'my-app-db', 'api-keys')

required

Returns:

Type Description
SecretsManagerBuilder

Self for method chaining

Source code in mare_aws_common_lib/builders/secrets_manager_builder.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def set_secret_base_name(self, name: str) -> 'SecretsManagerBuilder':
    """Set the base name for the secret.

    The base name will be used to construct the final secret name
    following the pattern: {base_name}-secret

    Args:
        name: Base name for the secret (e.g., 'my-app-db', 'api-keys')

    Returns:
        Self for method chaining
    """
    self._secret_base_name = name
    return self