AWS host phase 3: AWS connections and tiered permission check #80

Merged
jknapp merged 23 commits from feat/aws-connection into main 2026-11-15 04:15:00 +00:00
Owner

AWS host phase 3: connect Triple-C to an AWS account and check what it's allowed to do there. Spec: docs/superpowers/specs/2026-11-13-aws-ecs-host-design.md; plan: docs/superpowers/plans/2026-11-14-aws-connection-permission-check.md.

Nothing here starts AWS resources, and it ships no release by itself.

What users get

  • Settings → Hosts: "Local Docker" plus AWS connections. A connection is an AWS profile (SSO included) or access keys, plus a default region.
    • Keys live in the OS keychain, never settings.json.
    • Saving shows "Signed in as <arn>". An expired or revoked SSO session offers Sign in.
  • Check: reports Environment admin, Workspace user or Insufficient and names each missing action, with buttons to copy the policies for an admin.
    • It runs iam:SimulatePrincipalPolicy as the caller.
    • If simulation isn't permitted, it falls back to read-only probes, marks write actions unverified, and never reports more than "Workspace user (unverified)".
    • Throttled or failed checks show "Incomplete — check again", never a false Insufficient.
  • Settings export/import: carries connections and keys. The import preview discloses added, replaced (naming your local connection), removed and unverifiable entries.

Shipped IAM policies (aws/)

  • workspace-user.json, environment-admin.json and permissions-boundary.json, plus aws/README.md covering isolation guarantees, invariants, known gaps for phases 4–6, and the live-verification table.
  • Users can't reach each other's workspaces. Stop, exec, session, PassRole, secrets and access points are all scoped by the triple-c:owner tag. Users can never set triple-c:component, and environment secrets can't be claimed. User secrets must use the environment KMS key, and an explicit Deny blocks every other key.
  • Environment admins are bounded.
    • They can only create triple-c-* roles that carry the permissions boundary, and they can't remove, replace or edit that boundary.
    • Their destructive actions are tag-scoped.
    • Deploying stacks with a CloudFormation service role is denied.
  • A drift test keeps the Rust check table and the JSON policies identical. An in-repo evaluator checks the isolation rules, including both tiers attached together. Further tests enforce ARN-or-* resources and the managed-policy size limit.

Verified against a real account

cargo test aws::live_tests -- --ignored --test-threads=1 with TRIPLEC_AWS_E2E_PROFILE / TRIPLEC_AWS_E2E_REGION. It passes 10/10 using throwaway triple-c-e2e-* roles and as an SSO admin session.

Real IAM disagreed with the policies in five places, all fixed:

  • ${aws:PrincipalArn} is rejected as a Resource, so neither policy could even be attached.
  • ECS Exec also authorizes against the cluster.
  • Secrets Manager needs kms:Decrypt.
  • EFS CreateFileSystem needs *.
  • ssmmessages:OpenDataChannel is needed. This one was added from AWS documentation, not a live denial.

Items needing real ECS/EFS/EC2 resources are listed in the README for phases 4–6. All test resources were cleaned up; spend was under $0.05.

Notable changes outside the new modules

  • SettingsStore::update now saves before replacing the in-memory copy, so a failed save no longer leaves memory and disk diverged. A new modify does a read-modify-save under the lock.
  • update_settings keeps hosts as stored, the same as marketplace fields; import re-applies imported hosts.
  • run_sso_login passes --profile=<name> as a single argv element.
  • New crates: aws-sdk-sts, -iam, -cloudformation and -secretsmanager, plus aws-sdk-kms as a dev-dependency. They are default-features = false, and the legacy rustls 0.21 stack is still absent.

Design rulings made during implementation (recorded in the spec)

  • Permissions-boundary model for environment admins. The one-time creation of triple-c-permissions-boundary is a phase-4 design item.
  • Strict tenancy needs per-user execution roles too (phase 4).
  • The environment admin is a trusted operator of its environment.
  • Principal inspection is limited to SSO permission-set roles and triple-c-* roles. Users signing in through other roles fall back to probes; the README explains how an admin can add them.
  • China and GovCloud regions are rejected until the policies handle other partitions.

For phase 4

  • workspace-user.json is about 30 characters under the 6,144-character managed-policy limit, so it must be split.
  • Nothing sensitive may go in task-definition env vars or RunTask overrides, because they are readable via ecs:Describe*.

Testing

cargo test: 1049 passed. vitest: 1194 passed. tsc clean, and the build adds no new warnings. Each task was built by a subagent and reviewed for spec and quality, followed by a whole-branch review, and every finding was fixed.

🤖 Generated with Claude Code

AWS host **phase 3**: connect Triple-C to an AWS account and check what it's allowed to do there. Spec: `docs/superpowers/specs/2026-11-13-aws-ecs-host-design.md`; plan: `docs/superpowers/plans/2026-11-14-aws-connection-permission-check.md`. Nothing here starts AWS resources, and it ships no release by itself. ## What users get - **Settings → Hosts:** "Local Docker" plus AWS connections. A connection is an AWS profile (SSO included) or access keys, plus a default region. - Keys live in the OS keychain, never `settings.json`. - Saving shows "Signed in as `<arn>`". An expired or revoked SSO session offers **Sign in**. - **Check:** reports **Environment admin**, **Workspace user** or **Insufficient** and names each missing action, with buttons to copy the policies for an admin. - It runs `iam:SimulatePrincipalPolicy` as the caller. - If simulation isn't permitted, it falls back to read-only probes, marks write actions unverified, and never reports more than "Workspace user (unverified)". - Throttled or failed checks show "Incomplete — check again", never a false Insufficient. - **Settings export/import:** carries connections and keys. The import preview discloses added, replaced (naming your local connection), removed and unverifiable entries. ## Shipped IAM policies (`aws/`) - `workspace-user.json`, `environment-admin.json` and `permissions-boundary.json`, plus `aws/README.md` covering isolation guarantees, invariants, known gaps for phases 4–6, and the live-verification table. - **Users can't reach each other's workspaces.** Stop, exec, session, PassRole, secrets and access points are all scoped by the `triple-c:owner` tag. Users can never set `triple-c:component`, and environment secrets can't be claimed. User secrets must use the environment KMS key, and an explicit Deny blocks every other key. - **Environment admins are bounded.** - They can only create `triple-c-*` roles that carry the permissions boundary, and they can't remove, replace or edit that boundary. - Their destructive actions are tag-scoped. - Deploying stacks with a CloudFormation service role is denied. - **A drift test keeps the Rust check table and the JSON policies identical.** An in-repo evaluator checks the isolation rules, including both tiers attached together. Further tests enforce ARN-or-`*` resources and the managed-policy size limit. ## Verified against a real account `cargo test aws::live_tests -- --ignored --test-threads=1` with `TRIPLEC_AWS_E2E_PROFILE` / `TRIPLEC_AWS_E2E_REGION`. It passes 10/10 using throwaway `triple-c-e2e-*` roles and as an SSO admin session. Real IAM disagreed with the policies in five places, all fixed: - `${aws:PrincipalArn}` is rejected as a Resource, so neither policy could even be attached. - ECS Exec also authorizes against the cluster. - Secrets Manager needs `kms:Decrypt`. - EFS `CreateFileSystem` needs `*`. - `ssmmessages:OpenDataChannel` is needed. This one was added from AWS documentation, not a live denial. Items needing real ECS/EFS/EC2 resources are listed in the README for phases 4–6. All test resources were cleaned up; spend was under $0.05. ## Notable changes outside the new modules - `SettingsStore::update` now saves before replacing the in-memory copy, so a failed save no longer leaves memory and disk diverged. A new `modify` does a read-modify-save under the lock. - `update_settings` keeps `hosts` as stored, the same as marketplace fields; import re-applies imported hosts. - `run_sso_login` passes `--profile=<name>` as a single argv element. - New crates: `aws-sdk-sts`, `-iam`, `-cloudformation` and `-secretsmanager`, plus `aws-sdk-kms` as a dev-dependency. They are `default-features = false`, and the legacy rustls 0.21 stack is still absent. ## Design rulings made during implementation (recorded in the spec) - Permissions-boundary model for environment admins. The one-time creation of `triple-c-permissions-boundary` is a phase-4 design item. - Strict tenancy needs per-user execution roles too (phase 4). - The environment admin is a trusted operator of its environment. - Principal inspection is limited to SSO permission-set roles and `triple-c-*` roles. Users signing in through other roles fall back to probes; the README explains how an admin can add them. - China and GovCloud regions are rejected until the policies handle other partitions. ## For phase 4 - `workspace-user.json` is about 30 characters under the 6,144-character managed-policy limit, so it must be split. - Nothing sensitive may go in task-definition env vars or RunTask overrides, because they are readable via `ecs:Describe*`. ## Testing `cargo test`: 1049 passed. `vitest`: 1194 passed. `tsc` clean, and the build adds no new warnings. Each task was built by a subagent and reviewed for spec and quality, followed by a whole-branch review, and every finding was fixed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 23 commits 2026-11-15 02:23:22 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aws/policies/{workspace-user,environment-admin}.json are the IAM policies
for the two tiers; aws::policies embeds them and holds one representative
simulator check per action. Tests keep table and JSON in lock-step and run
each check through a small IAM-style evaluator, including the isolation
rule: owner-scoped actions are denied for someone else's resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ship aws/policies/permissions-boundary.json (triple-c-permissions-boundary):
  the most any Triple-C role may hold, nothing in IAM.
- environment-admin: role writes require iam:PermissionsBoundary = the
  boundary; explicit Denies keep the boundary on roles and the boundary
  policy unchanged. Security groups, EFS file systems and KMS keys need
  triple-c:component on create and to be modified or destroyed; tagging only
  at create or on already-tagged resources.
- workspace-user: RunTask needs an owner-tagged task definition; EFS access
  points create/delete by owner tag.
- Both tiers: iam:GetRole and iam:SimulatePrincipalPolicy on the caller's own
  ${aws:PrincipalArn} only, so SSO callers can be simulated.
- Checks carry the matching context; render() gains {principal}. The test
  evaluator resolves policy variables and applies explicit Denies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- workspace-user: TagOwnSecrets refuses component-tagged secrets, plus an
  explicit Deny on Tag/Untag/Put/Delete of component-tagged secrets;
  CreateAccessPoint only on the component-tagged file system.
- environment-admin: Deny PutRolePermissionsBoundary on triple-c-* roles to
  anything but the triple-c boundary; Deny Create/UpdateStack and
  CreateChangeSet on triple-c-* stacks with a service role.
- permissions boundary: kms:Decrypt and EFS client access only on
  component-tagged keys and file systems; ClientRootAccess dropped (access
  points fix the POSIX user).
- README: invariants for phase 4 templates, the actions that are on *, the
  phase-6 access-point root note, and the list to verify live.
- Tests: negated operators in the evaluator, both tiers evaluated together,
  secret claiming, service-role and boundary-reach cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CreateOwnSecrets requires secretsmanager:KmsKeyId to be a key ARN, and a
  workspace user's kms:Encrypt/GenerateDataKey (via Secrets Manager) only
  works on component-tagged keys, so secrets can't land on the default
  aws/secretsmanager key that the boundary can't decrypt.
- Every workspace statement that sets the owner tag limits aws:TagKeys to
  triple-c:owner/project/environment (ForAllValues:StringEquals), so a user
  can never set triple-c:component.
- Evaluator: multi-valued context keys and ForAllValues (vacuous when
  absent); Check documents repeated keys as one StringList entry.
- README: environment-key invariant and new verify-live entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
check_permissions simulates every check of both tiers against the
caller's resolved principal (bounded to 4 in flight), rendering resources
and literal context values, sending OwnerId and aws:userid as the
caller's user id, and grouping repeated context keys into one StringList
entry. An unsimulatable principal, a denied iam:GetRole or a denied first
simulation falls back to four read-only probes. Results are one row per
action (worst verdict wins).

The trait returns Send futures so the check is Send for Tauri commands.
The module-wide dead_code/unused_imports allow is replaced by allows on
the Task 6 entry points and the re-exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check_permissions now returns Result: credential errors from any call,
and non-denied failures resolving the principal, are errors rather than
a report. Other per-check failures (throttling, network) make the row
Unverified with a note, and a noted row blocks its tier. A probed run
never claims EnvironmentAdmin (R15), and the own-principal check whose
denial forced probing is reported Denied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real IAM contradicted the policies in five places, now fixed:
- IAM refuses ${aws:PrincipalArn} as a Resource, so neither tier policy
  could be attached. Self-inspection is user/${aws:username} for IAM users
  and role/* for role sessions (no role-name variable exists).
- ecs:ExecuteCommand is authorized against the cluster as well as the task;
  added an unconditioned cluster statement (the owner rule stays on the task).
- Secrets Manager needs kms:Decrypt with GenerateDataKey on a customer key.
- elasticfilesystem:CreateFileSystem has no resource type (Resource "*").
- ssmmessages:OpenDataChannel on the caller's own sessions (AWS docs).

The permission check now uses adaptive retries for IAM (SimulatePrincipalPolicy
throttled a full check), and the UI no longer says the simulator ignores
permission boundaries (it applies the principal's own; verified).

aws::live_tests (ignored, env-gated) repeats the verification with throwaway
triple-c-e2e-* roles, secrets and keys. New drift tests: every Resource is an
ARN or *, and every policy fits a 6,144-character managed policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InspectOwnPrincipal allowed iam:GetRole and iam:SimulatePrincipalPolicy on
role/*, letting any workspace user enumerate and simulate every role. IAM
refuses ${aws:PrincipalArn} as a Resource and has no role-name variable, so
the role part is now SSO permission-set roles
(role/aws-reserved/sso.amazonaws.com/*) and triple-c-* roles; IAM users stay
limited to themselves. Callers on other roles are probed and reported
Insufficient; the README says how an admin can add their sign-in roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(aws): final review wave (incomplete badge, removable bad connections, partition regions, format chars, store modify, docs)
Secret Scan / scan (push) Successful in 11s
Build App (Preview) / compute-version (pull_request) Successful in 14s
Build App (Preview) / create-release (pull_request) Successful in 10s
Build App (Preview) / build-linux (pull_request) Successful in 11m3s
Build App (Preview) / build-macos (pull_request) Successful in 5m33s
Build App (Preview) / build-windows (pull_request) Successful in 11m27s
Build App (Preview) / prune-previews (pull_request) Successful in 21s
Secret Scan / scan (pull_request) Successful in 11s
Build App (Preview) / test (pull_request) Successful in 7m56s
8ec2cfece6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit 3bc0e9fc3b into main 2026-11-15 04:15:00 +00:00
jknapp deleted branch feat/aws-connection 2026-11-15 04:15:02 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#80