Skip to content

feat(auth): OAuth2 client_credentials + OIDC token exchange for non-human identities - #63

Merged
PenguinzTech merged 3 commits into
v2.1.xfrom
feature/nhi-machine-tokens
Jul 27, 2026
Merged

feat(auth): OAuth2 client_credentials + OIDC token exchange for non-human identities#63
PenguinzTech merged 3 commits into
v2.1.xfrom
feature/nhi-machine-tokens

Conversation

@PenguinzTech

Copy link
Copy Markdown
Contributor

First-class non-human identities. Part 1: machine_clients (bcrypt secrets shown once, rotation endpoint, per-client tenant+scopes) + OAuth2 client_credentials on POST /api/v1/auth/token — constant-time verification, scope-subset enforcement, 15-min tokens, no refresh tokens for machines. Part 2: oidc_trust_anchors + RFC 8693-style token exchange validating external OIDC tokens (JWKS or static PEM, RS256/ES256 only, glob subject patterns) — lets K8s service accounts, GitHub Actions, and AI agents authenticate with zero static secrets.

Tests: 170 green (28 new, JWKS stubbed — no network).
Merge note: contains alembic migrations 008/009 — collide with 008 in the MFA and audit branches; renumber whichever merges later.

Stack note: bases on chore/dedup-reusable-code (top of the #53#59 chain); auto-retargets toward v2.1.x as the stack merges bottom-up.

🤖 Generated with Claude Code

PenguinzTech and others added 2 commits July 25, 2026 15:22
Implement first-class machine client support for service-to-service
authentication via OAuth2 client_credentials grant:

- machine_clients table: OAuth2 client identity with bcrypt-hashed
  secrets, scope bundles, and last-used tracking
- Admin CRUD API: /api/v1/machine-clients with create, list, get,
  update, delete, and rotate-secret operations (admin-scoped)
- Machine client token endpoint: POST /api/v1/auth/token with
  grant_type=client_credentials supports HTTP Basic Auth or form body
  client auth; issues short-TTL (15 min) access tokens with machine
  marker claim and constant-time secret verification
- AuthService extensions: create_machine_client, verify_machine_client
  (timing-safe), create_machine_access_token, scope validation,
  last_used_at tracking
- Migration: 008_machine_clients.py creates machine_client table
- Tests: 19 tests covering CRUD, token grant, scope enforcement,
  secret rotation, and all auth flows

All 170 existing tests pass; Part 1 fully green and ready for use
(Part 2 OIDC token exchange committed separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement federated workload identity support (Part 2) extending the
/api/v1/auth/token endpoint to support OIDC token exchange:

- oidc_trust_anchors table: Trusted OIDC issuers with audience,
  JWKS URL or static PEM keys, allowed scopes, subject patterns
- Admin CRUD API: /api/v1/oidc-trust-anchors with create, list, get,
  update, delete operations (admin-scoped)
- Token exchange endpoint: POST /api/v1/auth/token with
  grant_type=urn:ietf:params:oauth:grant-type:token-exchange; validates
  external OIDC tokens, enforces issuer trust anchors, subject pattern
  matching, and scope subset constraints; issues short-TTL machine tokens
- AuthService extensions: validate_oidc_token (signature + claims),
  subject_matches_pattern (glob support)
- Migration: 009_oidc_trust_anchors.py creates oidc_trust_anchor table
- Tests: 9 new tests covering CRUD, token exchange validation,
  pattern matching, scope enforcement, issuer lookup, and error flows

Enables Kubernetes service account tokens and GitHub Actions OIDC
to authenticate with zero static secrets. Both Part 1 and Part 2
ship fully green with all 170 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PenguinzTech PenguinzTech self-assigned this Jul 25, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @PenguinzTech, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@PenguinzTech
PenguinzTech changed the base branch from chore/dedup-reusable-code to v2.1.x July 27, 2026 00:54
Combines both sides' additions rather than picking one:

- app/__init__.py: register both machine_clients_bp/oidc_trust_anchors_bp
  (this branch) AND audit_bp (already-merged #61) — both blueprint sets
  now registered, neither dropped.
- app/schema.py: both machine_client/oidc_trust_anchor tables (this
  branch) and audit_event table (#61) now defined side by side.
- tests/test_schema.py: expected-tables set includes all five new tables
  from both branches.
- alembic: 008_machine_clients and 008_audit_events both claimed
  down_revision=007_revoked_token (two branches minting migration "008"
  independently). Re-chained machine_clients after audit_events
  (008_audit_events -> 008_machine_clients -> 009_oidc_trust_anchors) so
  the migration graph has a single linear head instead of two.
- .pre-commit-config.yaml: also fixed the hadolint v2.13.0 pin (never a
  real tag -> v2.13.1), the unquoted flake8 --select YAML flow sequence
  (commas split it into bogus positional filename args), and switched
  gitleaks to `protect --staged` (scans only what's committed, matching
  PR #77's approach — the standard pre-commit pattern, no allowlist
  needed) instead of a full-tree scan. v2.1.x doesn't have these fixes
  yet since #77/#78 haven't merged; this converges toward the same fix
  ahead of that.

Full manager suite: 200/200 passing after the combine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PenguinzTech
PenguinzTech merged commit fac0501 into v2.1.x Jul 27, 2026
5 of 6 checks passed
@PenguinzTech
PenguinzTech deleted the feature/nhi-machine-tokens branch July 27, 2026 01:07
PenguinzTech added a commit that referenced this pull request Jul 27, 2026
008_add_mfa_fields (#62) and 008_machine_clients/008_audit_events
(already-merged #61/#63) both independently claimed
down_revision=007_revoked_token, creating two divergent heads
(009_oidc_trust_anchors and 008_add_mfa_fields). Git's merge saw no
textual conflict since they're different files, so this surfaced only
after the merge completed.

Re-chained 008_add_mfa_fields after 009_oidc_trust_anchors so the
migration graph has a single linear head again. Full manager suite:
229/229 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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