feat(projects): add ServiceAccount and ServiceAccountAccessToken - #369
Conversation
…ged resources Adds project-scoped service account support, reaching parity with the group and instance service account resources. projects.gitlab.m.crossplane.io/v1alpha1 ServiceAccount manages a project service account (create/observe/update/delete) via the project service-account endpoints, keyed by projectId. Observe lists the project's service accounts and matches by id (there is no get-by-id endpoint). projects.gitlab.m.crossplane.io/v1alpha1 ServiceAccountAccessToken manages the personal access token of a project service account, keyed by projectId and serviceAccountId, mirroring the group resource. It has two modes, selected automatically. Owner mode (default): the ProviderConfig is a project owner/maintainer and the token is managed via the project service-account token endpoints: - Create -> Projects.CreateProjectServiceAccountPersonalAccessToken - Observe -> Projects.ListProjectServiceAccountPersonalAccessTokens (match by id) - Rotate -> Projects.RotateProjectServiceAccountPersonalAccessToken - Revoke -> Projects.RevokeProjectServiceAccountPersonalAccessToken Self-managed mode: when the referenced ProviderConfig authenticates with the very token this resource writes to its connection secret (detected when the PersonalAccessToken credential secretRef matches writeConnectionSecretToRef by namespace, name and key), the provider acts as the service account itself and uses the self endpoints (self-inform / self-rotate / self-revoke). A dead self-token surfaces as a clear terminal error. A SelfManaged status condition reports the mode. In owner mode the rotate-vs-create decision is made on external-name presence, and a failed rotation only falls through to a fresh create when the token no longer exists (404); any other rotation error is surfaced so a still-valid token is never orphaned. The external name is the token id and the token value is written to the connection secret on create/rotate. expiresAt/renewalPeriodDays and renewBeforeDays rotation semantics match the group resource. projectId, serviceAccountId, name and scopes are immutable (enforced via CEL); the rotation-timing fields stay mutable. The cluster-scoped variants, CRDs and generated code are produced by make generate. Closes #329 Signed-off-by: Markus Siebert <markus.siebert@deutschebahn.com>
Resolves 7 HIGH-severity CVEs reported by the Trivy fs scan on the tools module (CVE-2026-25680, -25681, -27136, -33814, -39821, -42502, -42506). golang.org/x/net v0.55.0 requires go >= 1.25, so the tools module go directive and the accompanying golang.org/x/* indirect deps were updated by go mod tidy. Signed-off-by: Markus Siebert <markus.siebert@deutschebahn.com>
Review + live e2e battle-testReviewed and ran a full end-to-end test on the same setup as #368: kind + Crossplane 2.3.3, provider out-of-cluster against a self-managed GitLab 18.11.5-ee instance, exercising both the namespaced ( This one is in good shape. Crucially, it mirrors the group resource (#326) using scoped project endpoints, so it does not carry the rotate-ownership issue I flagged on the instance PR (#368). Verified working end-to-end
Security: the #368 rotate-ownership issue does NOT apply hereOn #368 I showed that a drifted/attacker-set
I also confirmed owner-mode create with a In short, by keeping the group resource's scoped endpoints (rather than unscoped admin by-id), this PR avoids both #368 findings by construction. 👍 Minor (non-blocking)
Open question
|
…e-account-access-token # Conflicts: # README.md
- add serviceAccountId to self-mode SAAT example to enable mismatch guard - add shortName 'psaat' to project ServiceAccountAccessToken CRD - make ServiceAccount email mutable (rename-in-place) across group/instance/project - regenerate CRDs Signed-off-by: Markus Siebert <markus.siebert@deutschebahn.com>
26c9938 to
11b5c23
Compare
Description
Adds project-scoped
ServiceAccountandServiceAccountAccessTokenmanagedresources, completing the service-account family alongside the group resources
(#326) and the instance resources (#368).
ServiceAccount— manages a GitLab project service account.ServiceAccountAccessToken— manages the personal access token of aproject service account, keyed by
serviceAccountId. Two modes, selectedautomatically:
can manage the project's service-account tokens; create/observe/rotate/revoke
go through the project service-account token endpoints.
resource manages (self-sustaining rotation loop) and uses the
selfendpoints.
Both cluster-scoped and namespaced variants are generated.
Notes for reviewers
The
ServiceAccountAccessTokenimplementation mirrors the group resource from#326 (it is the same logic with
Group→Projectrenames). The review fixesraised on #326 are included here from the start:
UseLegacyProviderConfigpopulatesCredentialsSecretRef(self-mode detectiondepends on it) — inherited from master.
spec.forProvider.serviceAccountId(guards against a miswired credentialssecret rotating/revoking the wrong service account's token).
is genuinely gone (404, or 400 "token already revoked"); any other error
surfaces rather than silently minting a second token.
state=active.(
w.Namespace) instead ofcr.GetNamespace(), since cluster-scoped resourceshave no namespace.
How was it tested
go build ./...,go vet ./...— clean.go test ./...— full suite passes.rotation fall-through (404 and 400-revoked), in both namespaced and cluster
scopes.