feat(aws): workspace users register their own task definitions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
01e7b2752a
commit
d1883310dd
6 files changed
+393
-14
No files matched your search
@@ -312,7 +312,7 @@ docker exec stdout → tokio task → emit("terminal-output-{sessionId}") → li
|
||||
`identity.rs` is `GetCallerIdentity` and the principal to simulate (an assumed role's ARN is
|
||||
recovered with `iam:GetRole`, because SSO roles have an `/aws-reserved/…` path the session ARN
|
||||
lacks); `error.rs` classifies SDK errors (SSO expired, access denied naming the action, …);
|
||||
`policies.rs` embeds `aws/policies/*.json` (the tier policies — the admin tier spans several
|
||||
`policies.rs` embeds `aws/policies/*.json` (the tier policies — both tiers span several
|
||||
files, `policy_files` lists them — and the permissions boundary) and the table of checks the
|
||||
simulator is asked; `permissions.rs` runs it
|
||||
(`SimulatePrincipalPolicy`, adaptive retries — it throttles — or read-only probes when the
|
||||
|
||||
@@ -561,6 +561,7 @@ async fn a_role_with_no_policy_checks_as_insufficient_and_lists_every_action() {
|
||||
#[ignore]
|
||||
async fn a_role_without_simulate_permission_falls_back_to_probes() {
|
||||
e2e(|e| async move {
|
||||
// `InspectOwnPrincipal` lives in the first file.
|
||||
let mut doc: serde_json::Value =
|
||||
serde_json::from_str(policy_files(Tier::WorkspaceUser)[0].1).unwrap();
|
||||
doc["Statement"]
|
||||
@@ -570,6 +571,11 @@ async fn a_role_without_simulate_permission_falls_back_to_probes() {
|
||||
let role = e.role("nosim", None, &[]).await;
|
||||
let arn = e.managed_policy("nosim", &doc.to_string()).await;
|
||||
e.attach(&role, &arn).await;
|
||||
// The tasks file is attached whole.
|
||||
let tasks = e
|
||||
.managed_policy("nosim-tasks", policy_files(Tier::WorkspaceUser)[1].1)
|
||||
.await;
|
||||
e.attach(&role, &tasks).await;
|
||||
let cfg = e.assume(&role).await;
|
||||
let me = caller_identity(&cfg).await.expect("identity");
|
||||
assert_eq!(me.user_id, role.session_user_id());
|
||||
@@ -790,6 +796,20 @@ async fn secrets_use_the_environment_key_and_only_their_owner_can_change_them()
|
||||
as_user.put_secret_value().secret_id(&own).secret_string("v2").send().await,
|
||||
);
|
||||
|
||||
// `workspace-user-tasks.json`: the owner reads their own secret, never
|
||||
// the environment's or someone else's.
|
||||
let read = ok(
|
||||
"get own",
|
||||
as_user.get_secret_value().secret_id(&own).send().await,
|
||||
);
|
||||
assert_eq!(read.secret_string(), Some("v2"));
|
||||
for (what, arn) in [("environment", &environment), ("someone else's", &someone_elses)] {
|
||||
denied(
|
||||
&format!("get {what}"),
|
||||
as_user.get_secret_value().secret_id(arn).send().await,
|
||||
);
|
||||
}
|
||||
|
||||
// Any other key is refused.
|
||||
for (i, (what, k)) in [("no KmsKeyId", None), ("aws/secretsmanager", Some(aws_managed.as_str()))]
|
||||
.into_iter()
|
||||
|
||||
@@ -35,10 +35,16 @@ pub fn policy_files(tier: Tier) -> &'static [(&'static str, &'static str)] {
|
||||
include_str!("../../../../aws/policies/environment-admin-network.json"),
|
||||
),
|
||||
],
|
||||
Tier::WorkspaceUser => &[(
|
||||
"workspace-user.json",
|
||||
include_str!("../../../../aws/policies/workspace-user.json"),
|
||||
)],
|
||||
Tier::WorkspaceUser => &[
|
||||
(
|
||||
"workspace-user.json",
|
||||
include_str!("../../../../aws/policies/workspace-user.json"),
|
||||
),
|
||||
(
|
||||
"workspace-user-tasks.json",
|
||||
include_str!("../../../../aws/policies/workspace-user-tasks.json"),
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +246,36 @@ const WORKSPACE_USER_CHECKS: &[Check] = &[
|
||||
("ecs:CreateAction", Literal("RunTask")),
|
||||
],
|
||||
),
|
||||
// `workspace-user-tasks.json`: a user registers, tags and retires their
|
||||
// own task definitions, and reads their own secrets.
|
||||
write(
|
||||
"ecs:RegisterTaskDefinition",
|
||||
PROBE_TASK_DEFINITION,
|
||||
&[(OWNER_REQUEST_TAG, OwnerId)],
|
||||
),
|
||||
write(
|
||||
"ecs:TagResource",
|
||||
PROBE_TASK_DEFINITION,
|
||||
&[
|
||||
(OWNER_REQUEST_TAG, OwnerId),
|
||||
("ecs:CreateAction", Literal("RegisterTaskDefinition")),
|
||||
],
|
||||
),
|
||||
write(
|
||||
"ecs:DeregisterTaskDefinition",
|
||||
PROBE_TASK_DEFINITION,
|
||||
&[(OWNER_RESOURCE_TAG, OwnerId)],
|
||||
),
|
||||
write(
|
||||
"ecs:DeleteTaskDefinitions",
|
||||
PROBE_TASK_DEFINITION,
|
||||
&[(OWNER_RESOURCE_TAG, OwnerId)],
|
||||
),
|
||||
read(
|
||||
"secretsmanager:GetSecretValue",
|
||||
PROBE_SECRET,
|
||||
&[(OWNER_RESOURCE_TAG, OwnerId)],
|
||||
),
|
||||
write(
|
||||
"iam:PassRole",
|
||||
PROBE_ROLE,
|
||||
@@ -904,6 +940,27 @@ mod tests {
|
||||
("ecs:RunTask", "task-definition/", OWNER_REQUEST_TAG),
|
||||
("ecs:RunTask", "task-definition/", OWNER_RESOURCE_TAG),
|
||||
("ecs:TagResource", "task/", OWNER_REQUEST_TAG),
|
||||
(
|
||||
"ecs:RegisterTaskDefinition",
|
||||
"task-definition/",
|
||||
OWNER_REQUEST_TAG,
|
||||
),
|
||||
("ecs:TagResource", "task-definition/", OWNER_REQUEST_TAG),
|
||||
(
|
||||
"ecs:DeregisterTaskDefinition",
|
||||
"task-definition/",
|
||||
OWNER_RESOURCE_TAG,
|
||||
),
|
||||
(
|
||||
"ecs:DeleteTaskDefinitions",
|
||||
"task-definition/",
|
||||
OWNER_RESOURCE_TAG,
|
||||
),
|
||||
(
|
||||
"secretsmanager:GetSecretValue",
|
||||
"secret:",
|
||||
OWNER_RESOURCE_TAG,
|
||||
),
|
||||
("ecs:StopTask", "task/", OWNER_RESOURCE_TAG),
|
||||
("ecs:ExecuteCommand", "task/", OWNER_RESOURCE_TAG),
|
||||
("ssm:StartSession", "task/", OWNER_RESOURCE_TAG),
|
||||
@@ -1066,6 +1123,170 @@ mod tests {
|
||||
assert!(!allowed(Tier::WorkspaceUser, &other_cluster, ME, ME));
|
||||
}
|
||||
|
||||
/// The three tag keys a workspace user may put on anything they create.
|
||||
const OWN_TAG_KEYS: Context = &[
|
||||
("aws:TagKeys", Literal("triple-c:owner")),
|
||||
("aws:TagKeys", Literal("triple-c:project")),
|
||||
("aws:TagKeys", Literal("triple-c:environment")),
|
||||
];
|
||||
|
||||
fn ws_check(action: &str, resource_part: &str) -> &'static Check {
|
||||
checks(Tier::WorkspaceUser)
|
||||
.iter()
|
||||
.find(|c| c.action == action && c.resource.contains(resource_part))
|
||||
.unwrap_or_else(|| panic!("no workspace-user check for {action} on {resource_part}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registering_a_task_definition_needs_the_owner_tag_and_only_the_three_keys() {
|
||||
let reg = ws_check("ecs:RegisterTaskDefinition", "task-definition/");
|
||||
let with_keys = |extra: Option<&'static str>| -> Check {
|
||||
let ctx: Context = match extra {
|
||||
None => &[
|
||||
(OWNER_REQUEST_TAG, OwnerId),
|
||||
("aws:TagKeys", Literal("triple-c:owner")),
|
||||
("aws:TagKeys", Literal("triple-c:project")),
|
||||
("aws:TagKeys", Literal("triple-c:environment")),
|
||||
],
|
||||
Some(_) => &[
|
||||
(OWNER_REQUEST_TAG, OwnerId),
|
||||
("aws:TagKeys", Literal("triple-c:owner")),
|
||||
("aws:TagKeys", Literal("triple-c:project")),
|
||||
("aws:TagKeys", Literal("triple-c:environment")),
|
||||
("aws:TagKeys", Literal("extra")),
|
||||
],
|
||||
};
|
||||
Check { context: ctx, ..*reg }
|
||||
};
|
||||
assert!(allowed(Tier::WorkspaceUser, &with_keys(None), ME, ME));
|
||||
assert!(
|
||||
!allowed(Tier::WorkspaceUser, &with_keys(Some("extra")), ME, ME),
|
||||
"an extra tag key must be refused"
|
||||
);
|
||||
let no_owner = Check {
|
||||
context: OWN_TAG_KEYS,
|
||||
..*reg
|
||||
};
|
||||
assert!(
|
||||
!allowed(Tier::WorkspaceUser, &no_owner, ME, ME),
|
||||
"no owner request tag"
|
||||
);
|
||||
let someone_elses = Check {
|
||||
context: &[(OWNER_REQUEST_TAG, Literal(SOMEONE_ELSE))],
|
||||
..*reg
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &someone_elses, ME, ME));
|
||||
// Only Triple-C's families.
|
||||
let other_family = Check {
|
||||
resource: "arn:aws:ecs:{region}:{account}:task-definition/production:1",
|
||||
..*reg
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &other_family, ME, ME));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn task_definitions_are_tagged_only_at_register_and_only_with_the_three_keys() {
|
||||
let tag = ws_check("ecs:TagResource", "task-definition/");
|
||||
assert!(allowed(Tier::WorkspaceUser, tag, ME, ME));
|
||||
let retag = Check {
|
||||
context: &[(OWNER_REQUEST_TAG, OwnerId)],
|
||||
..*tag
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &retag, ME, ME));
|
||||
let other_create = Check {
|
||||
context: &[
|
||||
(OWNER_REQUEST_TAG, OwnerId),
|
||||
("ecs:CreateAction", Literal("RunTask")),
|
||||
],
|
||||
..*tag
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &other_create, ME, ME));
|
||||
let extra_key = Check {
|
||||
context: &[
|
||||
(OWNER_REQUEST_TAG, OwnerId),
|
||||
("ecs:CreateAction", Literal("RegisterTaskDefinition")),
|
||||
("aws:TagKeys", Literal("triple-c:owner")),
|
||||
("aws:TagKeys", Literal("triple-c:component")),
|
||||
],
|
||||
..*tag
|
||||
};
|
||||
assert!(
|
||||
!allowed(Tier::WorkspaceUser, &extra_key, ME, ME),
|
||||
"a task definition must not be tagged triple-c:component"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_owner_retires_a_task_definition() {
|
||||
for action in ["ecs:DeregisterTaskDefinition", "ecs:DeleteTaskDefinitions"] {
|
||||
let c = ws_check(action, "task-definition/");
|
||||
assert!(allowed(Tier::WorkspaceUser, c, ME, ME), "{action}");
|
||||
let theirs = Check {
|
||||
context: &[(OWNER_RESOURCE_TAG, Literal(SOMEONE_ELSE))],
|
||||
..*c
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &theirs, ME, ME), "{action}");
|
||||
let other_family = Check {
|
||||
resource: "arn:aws:ecs:{region}:{account}:task-definition/production:1",
|
||||
..*c
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &other_family, ME, ME), "{action}");
|
||||
}
|
||||
}
|
||||
|
||||
/// RegisterTaskDefinition with a task or execution role also authorizes
|
||||
/// `iam:PassRole` on each; `PassOwnTaskRoles` (owner-tagged, to ECS
|
||||
/// tasks, `triple-c-*` roles) must cover both and nothing else.
|
||||
#[test]
|
||||
fn register_task_definition_can_pass_the_users_task_and_execution_roles() {
|
||||
let pass = ws_check("iam:PassRole", "role/");
|
||||
for role in [
|
||||
"arn:aws:iam::{account}:role/triple-c-prod-0123456789ab-task",
|
||||
"arn:aws:iam::{account}:role/triple-c-prod-0123456789ab-exec",
|
||||
] {
|
||||
let c = Check { resource: role, ..*pass };
|
||||
assert!(allowed(Tier::WorkspaceUser, &c, ME, ME), "{role}");
|
||||
assert!(!allowed(Tier::WorkspaceUser, &c, ME, SOMEONE_ELSE), "{role}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secrets_are_readable_only_by_their_owner_and_never_the_environments() {
|
||||
let get = ws_check("secretsmanager:GetSecretValue", "secret:");
|
||||
assert!(allowed(Tier::WorkspaceUser, get, ME, ME));
|
||||
assert!(!allowed(Tier::WorkspaceUser, get, ME, SOMEONE_ELSE));
|
||||
// Even tagged with the caller's owner ID, a component-tagged secret
|
||||
// (the environment's) is off limits: the Deny holds on its own.
|
||||
let environments = Check {
|
||||
context: &[
|
||||
(OWNER_RESOURCE_TAG, OwnerId),
|
||||
(COMPONENT_RESOURCE_TAG, Literal("environment")),
|
||||
],
|
||||
..*get
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &environments, ME, ME));
|
||||
let component_only = Check {
|
||||
context: &[(COMPONENT_RESOURCE_TAG, Literal("environment"))],
|
||||
..*get
|
||||
};
|
||||
let req = Request {
|
||||
check: &component_only,
|
||||
caller: ME,
|
||||
owner: ME,
|
||||
};
|
||||
let stmts = denies(Tier::WorkspaceUser);
|
||||
assert!(
|
||||
matching(&stmts, &req).iter().any(|s| conditions_hold(s, &req)),
|
||||
"missing a Deny of GetSecretValue on component-tagged secrets"
|
||||
);
|
||||
// Not outside Triple-C's secrets.
|
||||
let other = Check {
|
||||
resource: "arn:aws:secretsmanager:{region}:{account}:secret:prod/db-AbCdEf",
|
||||
..*get
|
||||
};
|
||||
assert!(!allowed(Tier::WorkspaceUser, &other, ME, ME));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ecs_tag_resource_only_tags_at_run_task() {
|
||||
let tag = checks(Tier::WorkspaceUser)
|
||||
@@ -1617,7 +1838,10 @@ mod tests {
|
||||
names(Tier::EnvironmentAdmin),
|
||||
["environment-admin.json", "environment-admin-network.json"]
|
||||
);
|
||||
assert_eq!(names(Tier::WorkspaceUser), ["workspace-user.json"]);
|
||||
assert_eq!(
|
||||
names(Tier::WorkspaceUser),
|
||||
["workspace-user.json", "workspace-user-tasks.json"]
|
||||
);
|
||||
}
|
||||
|
||||
/// D8: CloudFormation leaves the policies, the checks and the probes.
|
||||
|
||||
@@ -547,8 +547,11 @@ mod tests {
|
||||
serde_json::from_str::<serde_json::Value>(&f.json).expect("policy JSON");
|
||||
}
|
||||
let ws = aws_policy_document(Tier::WorkspaceUser);
|
||||
assert_eq!(ws.len(), 1);
|
||||
assert_eq!(ws[0].name, "workspace-user.json");
|
||||
let names: Vec<&str> = ws.iter().map(|f| f.name.as_str()).collect();
|
||||
assert_eq!(names, ["workspace-user.json", "workspace-user-tasks.json"]);
|
||||
for f in &ws {
|
||||
serde_json::from_str::<serde_json::Value>(&f.json).expect("policy JSON");
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ws[0]).unwrap()["name"],
|
||||
"workspace-user.json"
|
||||
|
||||
+42
-6
@@ -6,15 +6,16 @@ tier the signed-in identity has under **Settings → Hosts → Check**.
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `policies/workspace-user.json` | The **workspace user** tier: create, start, stop, connect to and delete **their own** workspaces in an existing environment |
|
||||
| `policies/workspace-user.json` | The **workspace user** tier, part 1: create, start, stop, connect to and delete **their own** workspaces in an existing environment |
|
||||
| `policies/workspace-user-tasks.json` | The **workspace user** tier, part 2: register, tag and retire **their own** ECS task definitions, and read **their own** secrets (`workspace-user.json` has no room left) |
|
||||
| `policies/environment-admin.json` | The **environment admin** tier, part 1: apply and destroy the environment and onboarding modules (`terraform/`) with OpenTofu, and keep their state |
|
||||
| `policies/environment-admin-network.json` | The **environment admin** tier, part 2: the network those modules create (VPC, subnets, gateways, routes, security groups) |
|
||||
| `policies/permissions-boundary.json` | The **permissions boundary** every Triple-C role must carry. It is never attached to a person |
|
||||
|
||||
The admin tier is **two** files, because one managed policy holds only 6,144 characters; both are
|
||||
required, and the app's **Copy** lists every file of a tier. An environment admin attaches all three
|
||||
tier files (the workspace user's and both admin files): the admin files add environment management
|
||||
on top of everything a workspace user can do. Explicit Deny statements in any file apply to anyone
|
||||
The workspace tier and the admin tier are each **two** files, because one managed policy holds only
|
||||
6,144 characters; both files of a tier are required, and the app's **Copy** lists every file of a
|
||||
tier. An environment admin attaches all four tier files (both workspace user files and both admin
|
||||
files): the admin files add environment management on top of everything a workspace user can do. Explicit Deny statements in any file apply to anyone
|
||||
who has it attached.
|
||||
|
||||
## Workspace user
|
||||
@@ -26,6 +27,23 @@ following:
|
||||
- **Run tasks.** The task definition must be a `triple-c-*` one tagged with the user's own owner
|
||||
ID, and the cluster must be a `triple-c-*` one. The new task is tagged with the user's owner ID.
|
||||
The user can pass only `triple-c-*` roles tagged with their owner ID, and only to ECS tasks.
|
||||
- **Register their own task definitions** (`workspace-user-tasks.json`). `ecs:RegisterTaskDefinition`
|
||||
on `triple-c-*` families needs `triple-c:owner = ${aws:userid}` in the request and only the three
|
||||
ordinary tag keys. `ecs:TagResource` on a task definition is allowed only at registration
|
||||
(`ecs:CreateAction = RegisterTaskDefinition`, same owner and tag-key conditions), so a
|
||||
definition can never be retagged. `ecs:DeregisterTaskDefinition` and `ecs:DeleteTaskDefinitions`
|
||||
need the definition to carry the user's owner ID. A task definition that names a task or
|
||||
execution role is also authorized with `iam:PassRole` on it, which `PassOwnTaskRoles` already
|
||||
covers (owner-tagged `triple-c-*` roles, to ECS tasks only; a unit test checks both roles).
|
||||
`RunOwnTasks` still requires the definition's owner tag, so a user can run only definitions they
|
||||
registered. The statements use the revision-free and revision forms alike: the `*` in
|
||||
`task-definition/triple-c-*` matches `family:revision`.
|
||||
- **Read their own secrets.** `secretsmanager:GetSecretValue` on `triple-c/*` secrets tagged with
|
||||
their owner ID, so a second machine can read the agent token and attach to a running task
|
||||
(`ReadOwnSecrets`). An explicit Deny (`NeverReadEnvironmentSecrets`) refuses it on any secret
|
||||
tagged `triple-c:component`, even one that also carries their owner tag. (`NeverTouchEnvironmentSecrets`
|
||||
in `workspace-user.json` covers writes; it has no room for another action.) Reading also
|
||||
needs `kms:Decrypt` through Secrets Manager on the environment key, which the first file grants.
|
||||
- **Work with their own tasks.** They can stop them, `ecs execute-command` into them, and open an
|
||||
SSM session to them. They can end or resume only sessions they started, and open data channels
|
||||
(`ssmmessages:OpenDataChannel`) only on those.
|
||||
@@ -341,7 +359,8 @@ Create each file as a **customer managed policy** and attach it. In the console,
|
||||
or role. Inline policies do not fit: a role's inline policies share 10,240 characters and an
|
||||
environment admin needs every tier file (about 14,500), and a user's share 2,048. A managed policy
|
||||
may hold 6,144 characters, not counting whitespace. Without whitespace, `workspace-user.json` is
|
||||
about 6,040 (roughly 100 characters of headroom), `environment-admin.json` about 5,800 (340)
|
||||
about 6,040 (roughly 100 characters of headroom), `workspace-user-tasks.json` about 1,460,
|
||||
`environment-admin.json` about 5,800 (340)
|
||||
and `environment-admin-network.json` about 3,340; a unit test (`every_policy_fits_a_managed_policy`)
|
||||
fails before any file outgrows the limit. A tier that outgrows its files gains another file, which
|
||||
the app then lists with the others. For IAM Identity Center, add the files as inline policies on the permission set
|
||||
@@ -352,8 +371,12 @@ With the CLI:
|
||||
```sh
|
||||
aws iam create-policy --policy-name triple-c-workspace-user \
|
||||
--policy-document file://policies/workspace-user.json
|
||||
aws iam create-policy --policy-name triple-c-workspace-user-tasks \
|
||||
--policy-document file://policies/workspace-user-tasks.json
|
||||
aws iam attach-user-policy --user-name <user> \
|
||||
--policy-arn arn:aws:iam::<account>:policy/triple-c-workspace-user
|
||||
aws iam attach-user-policy --user-name <user> \
|
||||
--policy-arn arn:aws:iam::<account>:policy/triple-c-workspace-user-tasks
|
||||
```
|
||||
|
||||
## Known gaps
|
||||
@@ -492,6 +515,19 @@ asserts that every role the admin creates carries the boundary.
|
||||
|
||||
Still open:
|
||||
|
||||
- **`ecs:RegisterTaskDefinition` and resource-level permissions (phase 5, unverified).** The
|
||||
statements in `workspace-user-tasks.json` scope the action to `task-definition/triple-c-*`, but
|
||||
whether ECS authorizes a register call against that ARN (the family is known, the revision is
|
||||
not yet) or only against `*` has not been checked on real IAM; the unit tests' evaluator only
|
||||
proves the JSON means what it says. The `aws:RequestTag` and `aws:TagKeys` conditions are
|
||||
documented for the action either way. Probe, as a role holding the workspace files: register a
|
||||
`triple-c-e2e-*` definition tagged with the owner and the two other keys. If it is denied
|
||||
while the policy simulator allows it, change `Resource` of `RegisterOwnTaskDefinitions` to
|
||||
`"*"` (the request-tag conditions still confine it; the family name is then unenforced) and
|
||||
record that here. Also confirm `ecs:TagResource` with `ecs:CreateAction = RegisterTaskDefinition`
|
||||
tags at registration, that `DeregisterTaskDefinition`/`DeleteTaskDefinitions` honour
|
||||
`aws:ResourceTag`, and that a user can read their own secret only (`live_tests.rs` checks the
|
||||
secret read);
|
||||
- the `existing` network mode against a user's own VPC, and an onboarding by a workspace user
|
||||
from an admin bundle (the same module, applied with admin credentials);
|
||||
- `DisassociateGoneAddresses` cannot touch a **live** association. Probe: allocate an untagged
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "RegisterOwnTaskDefinitions",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ecs:RegisterTaskDefinition"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:ecs:*:*:task-definition/triple-c-*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:RequestTag/triple-c:owner": "${aws:userid}"
|
||||
},
|
||||
"ForAllValues:StringEquals": {
|
||||
"aws:TagKeys": [
|
||||
"triple-c:owner",
|
||||
"triple-c:project",
|
||||
"triple-c:environment"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "TagOwnTaskDefinitionsAtRegister",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ecs:TagResource"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:ecs:*:*:task-definition/triple-c-*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"ecs:CreateAction": "RegisterTaskDefinition",
|
||||
"aws:RequestTag/triple-c:owner": "${aws:userid}"
|
||||
},
|
||||
"ForAllValues:StringEquals": {
|
||||
"aws:TagKeys": [
|
||||
"triple-c:owner",
|
||||
"triple-c:project",
|
||||
"triple-c:environment"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "RetireOwnTaskDefinitions",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ecs:DeregisterTaskDefinition",
|
||||
"ecs:DeleteTaskDefinitions"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:ecs:*:*:task-definition/triple-c-*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/triple-c:owner": "${aws:userid}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "ReadOwnSecrets",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"secretsmanager:GetSecretValue"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:secretsmanager:*:*:secret:triple-c/*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/triple-c:owner": "${aws:userid}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sid": "NeverReadEnvironmentSecrets",
|
||||
"Effect": "Deny",
|
||||
"Action": [
|
||||
"secretsmanager:GetSecretValue"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:secretsmanager:*:*:secret:triple-c/*"
|
||||
],
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:ResourceTag/triple-c:component": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user