Skip to content

feat(aws): optional permissions boundary for workload IAM roles - #376

Merged
Cre-eD merged 5 commits into
mainfrom
feat/workload-permissions-boundary
Aug 11, 2026
Merged

feat(aws): optional permissions boundary for workload IAM roles#376
Cre-eD merged 5 commits into
mainfrom
feat/workload-permissions-boundary

Conversation

@Cre-eD

@Cre-eD Cre-eD commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What

Adds an optional permissionsBoundary field to the AWS AccountConfig. When set to an IAM policy ARN, SC applies it as the PermissionsBoundary on every IAM role it creates for workloads:

  • ECS task/execution roles (ecs_fargate, exec_ecs_task)
  • Lambda execution roles (aws_lambda)
  • Alert Lambda execution roles (alerts / cloudtrail security alerts)
  • ECS init-task execution roles (pg-init / mysql-init)

Why

A permissions boundary caps what a deployed workload role can ever do, independent of the role's own policy — useful for operators who provision SC workloads under a least-privilege deploy identity and want a hard ceiling on the roles it can mint.

Backward compatibility

Empty by default. permissionsBoundaryPtr returns nil for an empty/whitespace ARN, so no boundary is set and existing stacks are byte-for-byte unaffected. The field is omitempty, so it is not added to any schema's required list.

Notes for reviewers

  • ConvertAuth only carries credential fields when it rebuilds an AccountConfig from a resolved ${auth:...} blob, which would drop this non-credential field. It is therefore explicitly preserved at each converter that rebuilds the config (lambda, cloudtrail alerts, pg-init/mysql-init). The ecs-fargate path reads the template's AccountConfig directly (embedded via TemplateConfig), so it needs no preserve.
  • Setting/adding permissionsBoundary on aws.iam.Role is an in-place update (PutRolePermissionsBoundary), not a replacement — no role churn for existing deployments when a boundary is later added.
  • Regenerated docs/schemas (only the AWS configs that embed AccountConfig).

Tests

  • pkg/clouds/aws: YAML round-trip of permissionsBoundary (+ empty-when-absent).
  • pkg/clouds/pulumi/aws: permissionsBoundaryPtr returns nil for empty/whitespace, non-nil for an ARN (the backward-compat contract).

go vet + package tests pass (the cmd/sc binary link is unrelated).

Add an optional permissionsBoundary field to the AWS AccountConfig. When set to
an IAM policy ARN, it is applied as the PermissionsBoundary on every IAM role SC
creates for workloads: ECS task/execution roles, Lambda execution roles, the
alert Lambda roles, and the ECS init-task (pg-init / mysql-init) execution roles.

Empty by default, so existing stacks are unaffected: permissionsBoundaryPtr
returns nil for an empty or whitespace ARN, i.e. no boundary is set. This lets an
operator cap what a deployed workload role can ever do, independent of the role's
own policy.

ConvertAuth carries only credential fields when it rebuilds an AccountConfig, so
the non-credential boundary is explicitly preserved at the converters that
rebuild it (lambda, cloudtrail alerts, pg-init and mysql-init exec tasks); the
ecs-fargate path already reads the template's AccountConfig directly.

Setting or adding a permissions boundary on an aws.iam.Role is an in-place
update (PutRolePermissionsBoundary), not a replacement, so running tasks are not
disrupted.

Regenerated JSON schemas (field is optional, so it is not marked required).
Added unit tests for the YAML round-trip and the nil-when-empty contract.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Semgrep Scan Results

Repository: api | Commit: 4af66ea

Check Status Details
⚠️ Semgrep Warning 1 warning(s), 5 total

Scanned at 2026-08-11 08:52 UTC

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Security Scan Results

Repository: api | Commit: 4af66ea

Check Status Details
✅ Secret Scan Pass No secrets detected
✅ Dependencies (Trivy) Pass 1 total (no critical/high)
✅ Dependencies (Grype) Pass 1 total (no critical/high)
📦 SBOM Generated 523 components (CycloneDX)

Scanned at 2026-08-11 08:53 UTC

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

📊 Statement coverage

Measured on the documented included set (see docs/TESTING.md → Coverage scope). Observe-only — no regression gate is enforced yet.

Scope This PR main baseline Δ
Included set (Gold-tier denominator) 90.4% 90.3% +0.1 pp
Full set (whole repo, transparency) 28.4% 28.4% +0.0 pp

Baseline: main @ bd30332

Cre-eD added 4 commits August 10, 2026 12:48
The preserve-after-ConvertAuth lines were unconditional, which clobbered an
auth-level permissionsBoundary (declared inside the resolved credentials) with
the empty template-level sibling, silently no-opping it for the lambda,
cloudtrail alert, and pg-init/mysql-init exec roles. Guard each assignment so a
template-level value still wins but an auth-level value ConvertAuth already
loaded is kept. Consistent behavior regardless of where the field is declared.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…vival tests

Multi-model review of the change surfaced a consistency bug and a coverage gap
(no correctness P0). Address them:

- Precedence: the two in-place ConvertAuth sites (ecs_fargate, aws_lambda pulumi
  layer) had no guard, so a boundary declared at BOTH template and auth level let
  the auth value overwrite the template one, contradicting the "template wins"
  the other two constructors enforce. Introduce AccountConfig.KeepBoundary and
  route all six ConvertAuth-into-workload-role sites through it, so precedence is
  defined once and ECS is explicit instead of correct-by-accident.
- permissionsBoundaryPtr now trims the returned value, not just the emptiness
  check, so a padded ARN can't reach the API malformed.
- Broaden the field doc to list all covered role kinds and warn that one boundary
  caps every workload role in the stack at once (too-tight = whole-stack outage).
- Tests: add boundary-survival coverage (KeepBoundary precedence; in-place
  ConvertAuth; ToAwsLambdaConfig template-level/auth-level/unset) using a
  boundary-free credentials blob so the preserve logic is actually load-bearing
  (verified: deleting a KeepBoundary call fails the test). Assert the exact
  value in TestPermissionsBoundaryPtr, incl. a trimmed padded-ARN case.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Adds a section to the ECS Fargate parent guide covering the optional
permissionsBoundary field: which workload roles it caps, the
ceiling/intersection semantics, empty-by-default compatibility, the
in-place PutRolePermissionsBoundary update, template-vs-auth precedence,
and a too-tight-boundary warning. Adds a commented example line to the
multi-region parent server.yaml.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
@Cre-eD
Cre-eD merged commit 0d7b941 into main Aug 11, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants