Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ terraform/state/
| `scripts/check-aws-access.sh` | CI (app tf-plan): skips AWS steps with a notice when the run has no OIDC token (fork/Dependabot PRs) (tests: `scripts/tests/`) |
| `scripts/auth-doctor.py` | CI (after app apply) / local: checks an app's Cognito sign-in end to end from auth-status + HTTP probes (tests: `scripts/tests/`) |
| `scripts/reconcile-app-auth.sh` | CI (after platform apply): queue an app-auth-sync reconcile |
| `scripts/sync-team-access.sh` | CI (after platform apply): queue team-provisioner `sync_team_access` (new teams' permission set + account assignment once their guardrails policy exists) |
| `scripts/sync-team-access.sh` | CI (after platform apply): queue team-provisioner `sync_team_access` (switches teams from read-only to full access once their guardrails policy exists; rolls inline policy changes out) |

## Naming

Expand All @@ -223,7 +223,7 @@ terraform/state/
| SSM params (app) | `/javabin/apps/{team}/{service}/{name}` |
| ECS cluster | `javabin-platform` |
| ECS execution roles | `javabin-ecs-exec-{team}` (platform-owned, reads only `/javabin/apps/{team}/*`) |
| SSO team guardrails | `javabin-team-{team}-guardrails` (developer boundary with the team written in, for `javabin-team-{team}` permission sets) |
| SSO team guardrails | `javabin-team-{team}-guardrails` (developer boundary with the team written in; the permissions boundary of `javabin-team-{team}` permission sets) |
| ECR repos | `{team}-{service}` |
| SNS topics | `javabin-{alerts,security,budget-enforcement}` |
| Log groups | `/ecs/{team}/{service}` |
Expand Down Expand Up @@ -333,7 +333,7 @@ Team group OWNERs are admin-managed and never removed by the sync; non-@java.no
- **ECS deploy stabilization**: platform-test-app task registers but service fails health check
- **Cognito**: pools deployed; internal pool's `GoogleSAML` IdP was created outside Terraform. It is replaced by the Terraform-managed Google OIDC IdP at cutover (docs/cognito-google-setup.md), then deleted
- **Google SCIM provisions only direct members** of `drift@`, `styret@` and `javabin-developer@java.no` to Identity Center; it does not expand nested groups. Nobody gets an Identity Center user (and so no AWS access, team role included) unless they are a direct member of one of those. The team provisioner adds team members and (via `flatten: true` in the registry's `groups/access.yaml`) kodesmia members to `javabin-developer@` one by one; SCIM creates the user later, and the 3-hourly `sync_access_members` run adds them to their Identity Center groups. Don't create Identity Center users through the API: they'd have no link to Google (see `docs/lambda-functions.md`).
- **SSO sessions carry no `team` tag**: the Identity Center ABAC attribute (`${path:enterprise.team}`) is never set on users, so `${aws:PrincipalTag/team}` is empty for humans. Per-team permission sets (`javabin-team-{team}`) therefore use `javabin-team-{team}-guardrails` (the developer boundary rendered with the team name) instead. The ABAC-based `javabin-developer` permission set in `terraform/org` cannot reach team resources
- **SSO sessions carry no `team` tag**: the Identity Center ABAC attribute (`${path:enterprise.team}`) is never set on users, so `${aws:PrincipalTag/team}` is empty for humans. Per-team permission sets (`javabin-team-{team}`) therefore use `javabin-team-{team}-guardrails` (the developer boundary rendered with the team name) as their permissions boundary instead. It must stay a boundary: its baseline `Allow *` attached as a policy grants everything its Denies miss (every untagged resource). A team with no guardrails policy (`platform`, or a new team before platform CI) gets read-only access only. The ABAC-based `javabin-developer` permission set in `terraform/org` cannot reach team resources
- **App databases share one network path** (fixed by `app-network`): service-rds now allows ingress only from the app's own `{team}-{app}-tasks` SG; each app moves off the shared ECS tasks SG on its next pipeline run
- **Team provisioner Lambda**: All sync functions working (Google/GitHub/Budget/Cognito/Identity Center). Password-set flow deployed.
- **`registered_app_repos` manually managed**: Being replaced with team-scoped IAM roles (repo→team resolved via GitHub API at runtime)
Expand Down
36 changes: 26 additions & 10 deletions docs/lambda-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,37 @@ makes someone a plain member. Registering a team (`javabin register-team`) write
member's existing role is changed in place, and logins not in the registry are removed. The Google
group keeps **MEMBER** as its default (only `role: maintainer` makes a Google MANAGER).

### Team permission set (`javabin-team-{team}`)

`_ensure_team_permission_set` creates the Identity Center permission set for each registry team and assigns it to the team's group. It is the only AWS access team members get:

| Part | What it is | Effect |
|------|-----------|--------|
| Permissions boundary | `javabin-team-{team}-guardrails` (platform Terraform, `iam/boundary.tf`) | The developer boundary's Denies with the team written in. A boundary only limits; its baseline `Allow *` grants nothing |
| AWS managed policy | `ViewOnlyAccess` | Console browsing (List/Describe, no object, parameter or secret reads), as `javabin-readonly` |
| Inline policy | `_team_inline_policy` | Everything on resources tagged `team={team}` (`StringEquals`, so untagged resources, S3 objects, Route53 records and creates are not included); by name: `/javabin/apps/{team}/*` parameters (read, overwrite), `/ecs/{team}/*` log groups, `{team}-*-{account}` buckets, the cluster half of ECS Exec; a few untagged reads for `javabin status`. Platform-managed parameters and new parameters stay denied |

So a team member can read and overwrite their secrets (`javabin secret`), read their logs, push and pull their ECR repos, redeploy, stop or exec into their services, and browse the console. They cannot change shared or untagged resources (state bucket, legacy buckets and instances, Route53 zones, untagged roles and Lambdas) or create resources by hand: infrastructure goes through `app.yaml` and CI.

Order on each run: set the boundary, then detach any `javabin-team-{team}-guardrails` or `javabin-developer-boundary` attached as a policy by older versions, attach `ViewOnlyAccess`, put the inline policy, provision. If the boundary cannot be set the permission set is left as it is and the result has an `error`; a `ViewOnlyAccess` failure is reported as `view_only` and the rest still applies. A team without a Terraform-managed guardrails policy (`pending`, e.g. a brand-new team before platform CI or `platform` itself, or `refused`) gets read-only access instead: no boundary, the legacy policies detached, `ViewOnlyAccess`, and `_read_only_inline_policy` (only the untagged reads). Without a boundary, the team inline policy's tag allow would also cover platform-tagged resources. The result reports the state as `guardrails`, and the next run after platform CI creates the policy switches the team to full access.

### Team AWS access (`sync_identity_center_group`, `sync_team_access`)

The team sync puts members in the Identity Center group `team-{name}` and gives that group the
permission set `javabin-team-{name}` on the account, with the customer-managed policy
`javabin-team-{name}-guardrails`. That policy is made by platform Terraform, which only learns of a
new team when platform CI runs on the registry's `teams-changed` dispatch, after the team sync.
Until it exists the team sync stops before the inline policy and the account assignment and reports
`guardrails: pending` (`:hourglass_flowing_sand:` in Slack): without the guardrails the inline
policy's `Action: *` would be unbounded.
The team sync puts members in the Identity Center group `team-{name}` and runs
`_ensure_team_permission_set` (above) for the permission set `javabin-team-{name}`. Its boundary,
`javabin-team-{name}-guardrails`, is made by platform Terraform, which only learns of a new team when
platform CI runs on the registry's `teams-changed` dispatch, after the team sync. Until it exists
the team sync gives the team read-only access (no boundary, `ViewOnlyAccess`,
`_read_only_inline_policy`, account assignment) and reports `guardrails: pending`
(`:hourglass_flowing_sand:` in Slack): without the boundary the team inline policy's tag allow would
also cover platform-tagged resources.

Platform CI finishes the job: after every apply it queues `{"action": "sync_team_access"}`
(`scripts/sync-team-access.sh`, async). For every `team-*` Identity Center group whose guardrails
policy exists and is tagged `managed-by=terraform`, `repo=javaBin/platform`, it attaches the
guardrails, writes the inline policy, makes the account assignment and re-provisions the permission
set; teams without one (e.g. `platform`) are left alone. `{"teams": [...]}` limits the run. It posts
policy exists and is tagged `managed-by=terraform`, `repo=javaBin/platform`, it sets the guardrails
as the boundary, writes the team inline policy, makes the account assignment and re-provisions the
permission set; teams without one (e.g. `platform`) are left alone (they keep the read-only access
the team sync gave them). `{"teams": [...]}` limits the run. It posts
to #javabin-infra-alerts only when a team got access for the first time or something failed
(`CreateAccountAssignment` accepts an assignment that already exists without a conflict, so the
handler checks `ListAccountAssignments` first; if that lookup fails it stays quiet). Since it
Expand Down
2 changes: 1 addition & 1 deletion docs/platform-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ SNS topics, EventBridge rules, Config, GuardDuty, Security Hub, account guardrai

GuardDuty filters only affect new findings; archive existing matches once in the console.

Team SSO roles' `CredentialAccess:IAMUser/AnomalousBehavior` on `ssm:GetParameter` is deliberately not filtered. The `javabin-team-{team}` permission set is `Action "*"` scoped by `StringEqualsIfExists aws:ResourceTag/team`, so untagged parameters are readable too, and this is the finding a stolen SSO session produces. Check the source country and confirm with the user before archiving one.
Team SSO roles' `CredentialAccess:IAMUser/AnomalousBehavior` on `ssm:GetParameter` is deliberately not filtered. The `javabin-team-{team}` permission set can read its own team's parameters (`/javabin/apps/{team}/*`, see [team-provisioner](lambda-functions.md#team-permission-set-javabin-teamteam)), and this is the finding a stolen SSO session produces. Check the source country and confirm with the user before archiving one.

## lambdas

Expand Down
Loading
Loading