Skip to content

nhi: GitLab class-B — PAT/deploy-token/service-account syncers (Phase-1, S-scope)#90

Open
c1-squire-dev[bot] wants to merge 6 commits into
mainfrom
paul.querna/nhi-gitlab-class-b/s-scope
Open

nhi: GitLab class-B — PAT/deploy-token/service-account syncers (Phase-1, S-scope)#90
c1-squire-dev[bot] wants to merge 6 commits into
mainfrom
paul.querna/nhi-gitlab-class-b/s-scope

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Jun 10, 2026

Copy link
Copy Markdown

🔗 Preview: C1

nhi: GitLab class-B — PAT / deploy-token / service-account syncers (Phase-1, S-scope)

Part of the pqprime NHI program (RFC plans/nhi/rfc/rfc-v6.md). This is the S-scope half of the GitLab class-B work — the single highest-yield connector gap (5 new NHI resource types). The M-scope half (project/group access tokens) is stacked on top in a follow-up PR.

Build-location decision (gate)

The GitLab connector has historically existed in two places. I resolved where new syncer code must land today:

  • ConductorOne/baton-gitlab (this repo) is the live fleet / lambda deploy source: actively maintained (latest commit yesterday, via baton-admin), released (v0.0.31), vendors aws-lambda-go (deployed as a lambda executor), built on the Baton SDK. → build here.
  • The native ductone/c1 pkg/connector/gitlab is the legacy v1 implementation — c1 PR #12143 literally calls it "the v1 Gitlab connector". It is not the deploy source for new connector work.

Per the program's decision rule ("if an upstream baton-gitlab exists and is the deploy source → build there"), all new syncers land in this repo. This is connector source code, not a vendor bump of a connector inside c1.

What this PR adds (RFC §5.8.1, class B)

Resource type Kind / spine Axis-2 detail GitLab REST API (D-318)
personal_access_token K1 / STATIC_SECRET gitlab.token.pat GET /personal_access_tokens — admin lists all instance PATs, else self; metadata only, secret never returned on list
deploy_token K1 / STATIC_SECRET gitlab.token.deploy GET /deploy_tokens — admin/instance; metadata only, secret only on create
service_account K2 / SERVICE n/a (distinct user type) GET /service_accounts (self-managed) + GET /groups/:id/service_accounts (GitLab.com, 17.1+)
  • K1 emission: secret resources carry a SecretTrait stamped credential_type = STATIC_SECRET plus the axis-2 credential_detail. Identity back-ref: PATs expose user_id, so the owning user is set as SecretTrait.identity_id (K1↔K2 link). Deploy tokens are not tied to a user, so no back-ref is emitted (back-refs only where the API gives the owner).
  • K2 emission: service accounts are emitted as TRAIT_USER with account_type = SERVICE. On GitLab.com the instance endpoint is unavailable, so the builder fans out per top-level group via a ChildResourceType annotation (mirrors the existing user-sync dual path).
  • Runner authentication tokens are intentionally NOT synced — the runner auth token is write-only (returned only at registration / reset, never on list or get), so no K1 credential resource is possible (RFC §5.8.1 FLAGGED row). The runner resource could be synced later as a workload identity; the credential cannot.

RFC rule compliance

  • Emit positively or not at all; UNSPECIFIED on unknown (§2.6): credential_type is only ever set to STATIC_SECRET (the resource is the credential); nothing coerces a wrong concrete value.
  • No V1Identifier is set on any new resource (v1→v2 migration-only; precedent baton-temporalcloud#82 r3380796810).
  • No reserved slots; axis-2 strings follow the <platform>.<object>.<purpose> convention (§2.8).

baton-sdk bump (required dependency)

Emitting credential_type needs SecretTrait.credential_type + WithSecretType/WithSecretDetail, added upstream in baton-sdk#902 ("NHI Phase-1 spine traits" — PR-1 of this program). This repo vendored the stale v0.3.35, so the first commit bumps baton-sdk v0.3.35v0.13.0 and re-vendors. The only forced source change is crypto.GenerateRandomPassword, whose arg type moved to LocalCredentialOptions_RandomPassword. (Large vendor/ diff is the SDK bump; review the second commit for the feature.)

Testing

  • go build ./..., go test ./..., golangci-lint run, gofmt — all green.
  • Unit tests assert STATIC_SECRET + axis-2 detail + identity back-ref (PAT), no back-ref (deploy token), and account_type = SERVICE (service account).

🤖 Built with pqprime


Exposed endpoints

🏰 Squire environment: sleepy-dolphin-46579
Task: d94ceb36-4399-49c3-a3fd-977c87ce323b

pquerna and others added 2 commits June 10, 2026 17:22
Bumps baton-sdk v0.3.35 -> v0.13.0 to pick up the NHI Phase-1 spine
traits added in baton-sdk#902 (SecretTrait.credential_type +
WithSecretType/WithSecretDetail builders), required to emit K1
credential-type classification on the new token syncers.

The only forced source change is crypto.GenerateRandomPassword, whose
argument type moved from CredentialOptions_RandomPassword to
LocalCredentialOptions_RandomPassword.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
…se-1, S-scope)

Adds three new NHI resource-type syncers to the GitLab connector
(pqprime NHI Phase-1, RFC §5.8.1 — class B):

- personal_access_token (K1 / STATIC_SECRET, axis-2 gitlab.token.pat)
  GET /personal_access_tokens; owner user_id -> SecretTrait identity
  back-reference.
- deploy_token (K1 / STATIC_SECRET, axis-2 gitlab.token.deploy)
  GET /deploy_tokens; not tied to a user, so no identity back-reference.
- service_account (K2 / SERVICE) GET /service_accounts (self-managed)
  and per-top-level-group GET /groups/:id/service_accounts (GitLab.com),
  emitted as TRAIT_USER with account_type SERVICE.

Credential type is emitted positively (STATIC_SECRET) only; unknown
classification is left UNSPECIFIED per RFC §2.6. No V1Identifier is set.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
baton-sdk v0.11.0+ (the NHI spine traits) declares go 1.25.2, which
propagates to this module's go.mod. The CI workflows were pinned to
1.23.x (matching the previously-vendored v0.3.35) and must move to
1.25.x or go-test/go-lint fail on the go directive.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: nhi: GitLab class-B — PAT/deploy-token/service-account syncers (Phase-1, S-scope)

Blocking Issues: 0 | Suggestions: 0 | Threads Resolved: 0
Review mode: incremental since 6e4b710
View review run

Review Summary

The new commit adds handling for HTTP 401 Unauthorized responses alongside the existing 403 Forbidden handling. A new ErrUnauthorized sentinel error is added in pkg/connector/client/errors.go, and handlePermissionError in pkg/connector/helpers.go now treats both 401 (gRPC Unauthenticated / ErrUnauthorized) and 403 as "skip this resource" during sync. This is a correct and well-documented graceful degradation for GitLab endpoints that return 401 when the token lacks the required role on a specific resource. No issues found.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

None.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Two changes forced by the baton-sdk v0.13.0 bump:

- The account-manager interface now takes *v2.LocalCredentialOptions
  instead of *v2.CredentialOptions; userBuilder's CreateAccount/helpers
  are migrated so the connector no longer trips the SDK's "old account
  manager interface" startup guard (caught by test-cloud-version).
- golangci-lint (CI) flags repeated string literals (goconst); extract
  the profile/account-schema field keys (name/email/username) and the
  keyset sort direction ("asc") into constants.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

The instance PAT, deploy-token and service-account endpoints require an
admin (or group-owner) token. A non-admin token returns 403, which
previously failed the entire sync. Handle PermissionDenied/403 the same
way member listing does (handlePermissionError): log and skip the
resource type, leaving the rest of the sync intact.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

GitLab returns 401 (not 403) for group/project access-token and
service-account endpoints when the token lacks the required role on a
specific resource. The token is valid (Validate exercises it), so treat
401 like 403 in handlePermissionError: log and skip the resource instead
of aborting the sync. Adds a client.ErrUnauthorized sentinel + 401 case
in CheckResponse.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

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.

1 participant