fix(aws): explicitly deny Secrets Manager encryption with non-environment keys

The AWS-managed aws/secretsmanager key (or a customer key whose key policy
grants account principals) needs no identity-policy Allow, so the tagged-key
Allow alone did not keep user secrets on environment keys. An explicit Deny
on kms:Encrypt / kms:GenerateDataKey* via secretsmanager when the key lacks
triple-c:component does. README claim corrected; verify-live entry added.
Check.context doc now states the StringList rule as a requirement on the
simulation builder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-11-14 16:34:35 -08:00
1 parent 9d1630eb97
commit 7fdd5dc277
3 files changed
+73 -4

No files matched your search

+44 -1
View File
@@ -56,7 +56,9 @@ pub struct Check {
/// An ARN template ([`render`]).
pub resource: &'static str,
/// Context entries. A key that appears more than once is a multi-valued
/// key (e.g. `aws:TagKeys`) and is sent as one `StringList` entry.
/// key (e.g. `aws:TagKeys`): whoever builds the simulation request must
/// send all its values as one `StringList` context entry (not enforced
/// here; the permission check's simulation builder owns it).
pub context: &'static [(&'static str, ContextValue)],
pub write: bool,
}
@@ -1693,4 +1695,45 @@ mod tests {
("aws:TagKeys", Literal("c"))
])));
}
/// A key whose *key policy* grants account principals through Secrets
/// Manager (the AWS-managed `aws/secretsmanager` key, or any such customer
/// key) needs no identity-policy Allow, so only an explicit Deny keeps a
/// workspace user's secrets on environment keys.
#[test]
fn untagged_keys_are_explicitly_denied_through_secrets_manager() {
let tier = Tier::WorkspaceUser;
const VIA_SECRETS_MANAGER: Context = &[(
"kms:ViaService",
Literal("secretsmanager.{region}.amazonaws.com"),
)];
const VIA_SECRETS_MANAGER_ENVIRONMENT_KEY: Context = &[
(
"kms:ViaService",
Literal("secretsmanager.{region}.amazonaws.com"),
),
(COMPONENT_RESOURCE_TAG, Literal("environment")),
];
for action in [
"kms:Encrypt",
"kms:GenerateDataKey",
"kms:GenerateDataKeyWithoutPlaintext",
] {
assert!(
explicitly_denied(tier, &write(action, PROBE_KEY, VIA_SECRETS_MANAGER)),
"{action} on an untagged key via Secrets Manager is not explicitly denied"
);
assert!(
!explicitly_denied(
tier,
&write(action, PROBE_KEY, VIA_SECRETS_MANAGER_ENVIRONMENT_KEY)
),
"{action} on an environment key must not be denied"
);
}
// The checks themselves (environment key) stay allowed.
for c in checks(tier).iter().filter(|c| c.action.starts_with("kms:")) {
assert!(allowed(tier, c, ME, ME), "{}", c.action);
}
}
}
+10 -3
View File
@@ -31,8 +31,11 @@ following:
`triple-c:component`. `CreateSecret` must name the key by its full ARN in `KmsKeyId`, and the
user can use KMS (`Encrypt`/`GenerateDataKey`) only through Secrets Manager and only with
environment keys.
- A secret on the default `aws/secretsmanager` key is refused. That key can't carry the tag, so
the workspace's own task role, which is limited by the boundary, couldn't decrypt it.
- A secret on any other key is refused by an **explicit Deny**: `kms:Encrypt` and
`kms:GenerateDataKey*` through Secrets Manager on a key without `triple-c:component`. The Deny
is needed because the AWS-managed `aws/secretsmanager` key, or any customer key whose key
policy grants account principals directly, needs no identity-policy Allow. A secret on such a
key would be unreadable by the workspace's own task role, which is limited by the boundary.
- A secret tagged `triple-c:component` belongs to the environment. A workspace user can never
tag, untag, overwrite or delete one; an explicit Deny enforces this.
- **Manage their own access points.** They can create and delete EFS access points tagged with
@@ -121,7 +124,8 @@ detection should flag any environment that breaks them.
4. **Every environment KMS key and EFS file system carries `triple-c:component`.** The boundary
and the admin tier both rely on this tag.
5. **User secrets use the environment key.** The app must pass the environment KMS key's ARN as
`KmsKeyId` when it creates a secret. The policy refuses any other choice.
`KmsKeyId` when it creates a secret. The workspace policy explicitly denies Secrets Manager
encryption with any key that lacks `triple-c:component`, including `aws/secretsmanager`.
## One-time setup: the boundary policy
@@ -209,6 +213,9 @@ a real account:
- Check that Secrets Manager calls `GenerateDataKey`/`Encrypt` as the caller, so the KMS
statement's `aws:ResourceTag/triple-c:component` rule is what enforces the environment key.
- Check that `PutSecretValue` on such a secret passes.
- Check that a `CreateSecret` naming the AWS-managed `aws/secretsmanager` key's ARN is denied.
KMS should leave `aws:ResourceTag/triple-c:component` absent for that key, so the
`NeverEncryptSecretsWithOtherKeys` Deny fires even though the key policy allows the call.
- **Tag keys.** Check that `aws:TagKeys` is populated for `RunTask`, for `CreateSecret` and secret
`TagResource`, for `CreateAccessPoint`, and for the ECS and EFS tag-on-create `TagResource`
calls. Check also whether ECS-managed (`aws:ecs:*`) or propagated task-definition tags appear in
+19
View File
@@ -292,6 +292,25 @@
}
}
},
{
"Sid": "NeverEncryptSecretsWithOtherKeys",
"Effect": "Deny",
"Action": [
"kms:Encrypt",
"kms:GenerateDataKey*"
],
"Resource": [
"arn:aws:kms:*:*:key/*"
],
"Condition": {
"StringLike": {
"kms:ViaService": "secretsmanager.*.amazonaws.com"
},
"Null": {
"aws:ResourceTag/triple-c:component": "true"
}
}
},
{
"Sid": "ReadEcs",
"Effect": "Allow",