docs: record EKS access entry lifecycle contract

This commit is contained in:
joshsiv-mulga
2026-10-08 11:53:52 +10:00
parent b36d70b222
commit 2dec7b0de7
2 changed files with 40 additions and 0 deletions
+1
View File
@@ -292,6 +292,7 @@ while the branch is in flight; they do not describe the umbrella repository's
| `17dc29251` | `spinifex/handlers/rds` (`DBSubnetGroupRecord`, `DBSubnetGroupSubnet`, `DBSubnetGroupKey`, `DBSubnetGroupsPrefix`, `getDBSubnetGroup`, `resolveGroupSubnets` and the create/modify/delete bodies) | `spinifex/domains/rds/subnetgroup` (`Owner`, `Record`, `Subnet`, `Key`, `Prefix`) | First ADR-0004 resource owner. The handler keeps AWS validation, projection and paging; delete reaches instances through the owner-declared `Dependants`, and instance placement reaches the group through the instance-declared `subnetGroupPlacement`. Stored shape unchanged; `docs/service-interfaces.yaml` lists the package with the RDS daemon. No conformance claimed; gaps in the lifecycle contract. |
| `1cb868e68` | — | — | ECS task definition characterization before extraction (ADR-0003 S1, ADR-0004): stored keys and field names, every reference form and its not-found fault, repeatable deregister, an inactive revision still launching, bare-family launch resolution, a service pinning its resolved revision. Present contract and gaps, including the bare-family `iam:PassRole` re-resolution security gap, in `docs/package-boundary/ecs-task-definition-lifecycle.md`; no conformance claimed. |
| `544b0c6a0` | `spinifex/handlers/ecs` (`TaskDefRecord`, `ContainerDef`, `TaskDefRevKey`, `TaskDefLatestRevKey`, `ParseTaskDefRef`, `TaskDefARN`, `TaskDefRefARN`, revision register/deregister/resolve/list) | `spinifex/domains/ecs/taskdefinition` (`Owner`, `Record`, `Container`, `PortMapping`, `SystemControl`, `ParseRef`, `ARN`, `RefARN`, keys) | ADR-0004 owner extraction (ADR-0003 S1 contract in `docs/package-boundary/ecs-task-definition-lifecycle.md`). Stored keys and JSON field names unchanged; the owner declares its own persisted `PortMapping`/`SystemControl` so it does not import `handlers/ecs/bus`. Launches use the launch-side `taskDefinitionResolver`; gateway authz and all `iam:PassRole` checks unchanged. `docs/service-interfaces.yaml` now lists `spinifex/handlers/ecs` and the new package under `spinifex_daemon`. Proves ownership and dependency direction only; no conformance claimed. |
| `b36d70b22` | — | — | EKS access entry characterization before extraction (ADR-0003 S1, ADR-0004): persisted and nested field names, pre-existing JSON decodes, actions ignore cluster status, associate without an entry is not found, disassociating an unbound policy is a no-op, an empty update keeps username and groups, cluster delete sweeps only that cluster's entries, token review needs no cluster record. Present contract and gaps in `docs/package-boundary/eks-access-entry-lifecycle.md`; no conformance claimed. |
The EC2 contract row is intentionally different from the directory moves: it creates
a compatibility boundary. `ec2.cmd.*` retains its deployed one-token NATS
@@ -0,0 +1,39 @@
# EKS access entry: lifecycle contract
Present contract under ADR-0003 S1, recorded before the resource moves.
It describes the code as it stands, so it claims no conformance; the gaps below are observed behaviour, not targets.
Paths are relative to `spinifex/` unless they start with `tests/`.
## Scope
An access entry has no realization of its own.
Create, update, delete and policy association complete synchronously within the request as writes to the account's EKS bucket; the token webhook reads the record when it authenticates a `get-token` bearer.
## Contract
| Item | Present behaviour | Evidence |
|---|---|---|
| Identity | Cluster name plus principal ARN. Key and ARN suffix use `sha256hex(principalARN)` (`handlers/eks/store.go` `PrincipalARNHash`). ARN `arn:aws:eks:{region}:{account}:access-entry/{cluster}/{hash}` (`foundation/aws/arn/eks.go`); the AWS ARN shape differs and is not verified. | `handlers/eks/service_impl_test.go` `TestAccessEntry_CreateDescribeListDelete`, `handlers/eks/store_test.go`, `gateway/eks/authz_test.go` `TestResourceARNsAccessEntryMatchesHandler` |
| Scope | Account and Region, within one cluster: keys live under `clusters/{cluster}/access-entries/` in the `eks-account-{account}` bucket. | `handlers/eks/access_lifecycle_test.go` `TestAccessEntryPersistedLayout_TopLevelAndNestedFieldNames`, `TestAccessEntryRecord_DecodesHandWrittenJSON`, `handlers/eks/create_cluster_test.go` `TestCreateCluster_SeedsCreatorAdminAccessEntry` |
| Durable owner and record | `handlers/eks/access_entry.go` writes `AccessEntryRecord` with nested `AssociatedAccessPolicy` and `AccessScope` (`handlers/eks/types.go`). The cluster launch also writes the creator-admin entry, and `DeleteClusterPrefix` (`handlers/eks/cluster_state.go`) erases entries with the cluster. The gateway process reads records directly through `ResolveTokenReview` (`handlers/eks/token_review.go`). | `handlers/eks/access_lifecycle_test.go` `TestAccessEntryPersistedLayout_TopLevelAndNestedFieldNames`, `TestAccessEntryRecord_DecodesHandWrittenJSON`, `handlers/eks/create_cluster_test.go` `TestCreateCluster_SeedsCreatorAdminAccessEntry` |
| Desired state | Principal, Kubernetes username and groups, type (`STANDARD` only), tags (set at create only) and cluster-scoped access policy associations. | `handlers/eks/service_impl_test.go` `TestAccessEntry_RejectsNodeType`, `TestAccessPolicy_AssociateRejectsUnsupportedPolicyAndScope`, `TestAccessPolicy_AssociateRejectsNamespaceScope`, `handlers/eks/access_lifecycle_test.go` `TestAccessEntry_UpdateWithNoFieldsKeepsUsernameAndGroups` |
| Generation | None. Update, associate and disassociate retry against the KV revision; create and delete do not. | |
| Observed state | Not tracked; the record is the only state. | |
| Readiness | Usable by token review as soon as the write returns. | `handlers/eks/token_review_test.go` `TestResolveTokenReview_AuthenticatesViaVerifyAndKV`, `TestResolveTokenReview_NoAccessEntryDenies`, `handlers/eks/service_impl_test.go` `TestAccessPolicy_DisassociateRemovesProjectedGroup` |
| Idempotency | `clientRequestToken` is ignored. Update always advances `modifiedAt`, even when nothing changes. A duplicate create returns `ResourceInUseException`; a repeated delete returns `ResourceNotFoundException`; disassociating an unbound policy succeeds without change. Internal NATS redelivery of create returns `ResourceInUseException` to the redelivered request. | `handlers/eks/service_impl_test.go` `TestAccessEntry_CreateDescribeListDelete`, `TestAccessEntry_DescribeDeleteMissingIsNotFound`, `handlers/eks/access_lifecycle_test.go` `TestAccessPolicy_DisassociateUnassociatedLeavesRecordUnchanged`, `TestAccessPolicy_AssociateWithNoEntryIsNotFound` |
| Dependencies | Every action requires only that the cluster's meta record exists (`acctKVForCluster`); cluster status is not read, so actions succeed on a `CREATING`, `FAILED` or `DELETING` cluster. Token review does not check the cluster at all. The principal is not checked against IAM. | `handlers/eks/service_impl_test.go` `TestAccessEntry_UnknownClusterIsNotFound`, `handlers/eks/access_lifecycle_test.go` `TestAccessEntryActionsIgnoreClusterLifecycleStatus`, `TestResolveTokenReview_AuthenticatesWithoutClusterMeta` |
| Authorization | Gateway authz derives the access-entry ARN with `PrincipalARNHash` (`gateway/eks/authz.go`). No `iam:PassRole`. The STS step of token review is the existing `eks.VerifyToken` NATS contract served by awsgw. | `gateway/eks/authz_test.go` `TestResourceARNsAccessEntryMatchesHandler` |
| Deletion ordering | Delete is get-then-delete, not conditional. Cluster delete erases entries in the prefix purge only after all infrastructure teardown succeeds; on failure they survive while the cluster stays `DELETING`. | `handlers/eks/access_lifecycle_test.go` `TestDeleteClusterPrefix_SweepsAccessEntriesScopedToCluster` |
| Restart and interruption | The prefix purge deletes keys in listing order; an interruption after the meta key is gone leaves entries that a new cluster of the same name inherits. Reclaiming a `FAILED` cluster and a failed launch purge with the meta kept, so the recreated cluster inherits the failed attempt's entries. | none |
| End-to-end | `tests/e2e/eks/eks_test.go` `AccessEntry` and `GetToken`; `tests/integration/cross_tenant_test.go`. No Terraform apply/destroy evidence for `aws_eks_access_entry` or `aws_eks_access_policy_association`. | |
## Gaps
1. Create is get-then-put with no exclusive create, so concurrent creates for one principal both succeed and the later write wins.
2. The creator-admin seed at cluster launch is an unconditional put after the cluster meta is final, so it replaces an entry for the same principal created while the cluster was `CREATING`, including its policy associations.
3. Access actions do not check cluster status (see dependencies row), and opening the bucket creates it for an account that has none.
4. Entries can outlive their cluster (interrupted prefix purge) or carry over to a recreated cluster (reclaim and failed launch keep the meta and do not purge entries separately).
5. Token review authenticates against an entry without checking the cluster exists or its status.
6. `ResolveTokenReview` opens the EKS bucket and reads the record in the gateway process, outside the owner.
7. List ignores `maxResults` and `nextToken`; tagging an access-entry ARN returns `NotImplemented`.
8. No ADR-0003 S5 failure or recovery evidence.