Skip to content

Inline OAuth2 signing key in config#1083

Merged
SachaProbo merged 5 commits intomainfrom
gearnode/inline-oauth2-signing-key
Apr 21, 2026
Merged

Inline OAuth2 signing key in config#1083
SachaProbo merged 5 commits intomainfrom
gearnode/inline-oauth2-signing-key

Conversation

@gearnode
Copy link
Copy Markdown
Contributor

@gearnode gearnode commented Apr 21, 2026

Summary

  • Switch AuthConfig.OAuth2Server.SigningKeys[].KeyFile (path on disk) to PrivateKey (inline PEM string) so the OAuth2/OIDC server follows the same config convention as the SAML private key and the ACME account key. probod now decodes the PEM directly instead of calling os.ReadFile.
  • Teach pkg/bootstrap about the OAuth2 server: a new GenerateOAuth2SigningKey helper mints a 2048-bit RSA PEM on demand, builder.Build populates OAuth2Server with OAUTH2_SERVER_* env vars (falling back to auto-generation when no key is supplied), and durations have sensible prod defaults (1h / 30d / 10m / 10m).
  • Drop the static e2e/console/testdata/config.yaml and the test-only PEM it referenced; e2e/internal/testutil now renders the e2e config through bootstrap.NewBuilder at setup, writes it to a per-run temp file, and hands the path to probod. CI injects the Pebble rootCA.pem via ACME_ROOT_CA inline instead of mutating a YAML on disk.

Test plan

  • make lint (Go + JS) passes
  • go test ./pkg/bootstrap/... ./pkg/probod/... passes (new bootstrap tests cover auto-gen, env-driven, and preset paths)
  • go build ./... including cmd/probod-bootstrap
  • make test-e2e boots probod with a bootstrap-generated config (no PEM file on disk) and the OIDC/OAuth2 flows succeed
  • /.well-known/openid-configuration + JWKS serve a key whose kid matches the one used to sign issued id_tokens

Summary by cubic

Inline OAuth2/OIDC signing keys (PEM) in config and route OAuth2 server settings through pkg/bootstrap. Add make dev-config to generate cfg/dev.yaml from repo-root .env with a per-dev RSA key and inject ACME_ROOT_CA to trust the local Pebble ACME server.

  • New Features

    • pkg/probod reads probod.auth.oauth2-server.signing-keys[].private-key (inline PEM) to sign tokens.
    • pkg/bootstrap adds GenerateOAuth2SigningKey() (2048-bit RSA), maps OAUTH2_SERVER_* with defaults (KID "default"; access 1h, refresh 30d, auth/device codes 10m), and requires OAUTH2_SERVER_SIGNING_KEY.
    • Config generation: make dev-config writes cfg/dev.yaml, stashes a stable RSA key at cfg/.dev-oauth2-signing-key.pem, sources ./.env (ships .env.example), and injects ACME_ROOT_CA from compose/pebble/certs/rootCA.pem. E2E config is rendered via bootstrap.NewBuilder with a per-run key; the static e2e/console/testdata/config.yaml and PROBO_E2E_CONFIG are removed. CI passes ACME_ROOT_CA via env.
  • Migration

    • Replace signing-keys[].key-file with signing-keys[].private-key (inline PEM).
    • Set OAUTH2_SERVER_SIGNING_KEY (optional OAUTH2_SERVER_SIGNING_KEY_KID); bootstrap will not auto-generate a key.

Written for commit 565b715. Summary will update on new commits.

@gearnode gearnode requested a review from a team April 21, 2026 15:42
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 12 files

The OAuth2/OIDC server accepted its signing key via a file path
(key-file), while every other PEM key in the probod config (SAML
private key, ACME account key) is embedded inline. Switch the
field to a private-key string so the convention is uniform.

The signing key is operator-supplied material that must outlive
any process restart, so the bootstrap builder now treats
OAUTH2_SERVER_SIGNING_KEY as required and refuses to start
without one; silently minting a fresh key per boot would break
token validation across rollouts. The OAUTH2_SERVER_* env vars
otherwise flow through builder.Build like the existing SAML
block so the new OAuth2Server section is populated end-to-end.

Rework the e2e harness to render its config via bootstrap at
test setup, which removes the static
e2e/console/testdata/config.yaml and the previously generated
test-only PEM file. A per-run RSA key is minted via
bootstrap.GenerateOAuth2SigningKey (kept public for test
tooling) and injected through the builder env map. CI now
passes ACME_ROOT_CA inline instead of mutating a YAML on disk.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pkg/bootstrap/builder.go">

<violation number="1" location="pkg/bootstrap/builder.go:340">
P1: `validateRequired` now makes OAuth2 signing key mandatory, but the new `getOAuth2SigningKey` no longer auto-generates one. This causes `Build()` to fail when no key is supplied instead of falling back to generated credentials.</violation>
</file>

<file name="pkg/bootstrap/builder_test.go">

<violation number="1" location="pkg/bootstrap/builder_test.go:51">
P2: Tests now enforce `OAUTH2_SERVER_SIGNING_KEY` as required, which contradicts the intended auto-generation fallback and can lock in incorrect bootstrap behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread pkg/bootstrap/builder.go
}
}

if b.oauth2SigningKey == "" && b.getEnv("OAUTH2_SERVER_SIGNING_KEY") == "" {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P1: validateRequired now makes OAuth2 signing key mandatory, but the new getOAuth2SigningKey no longer auto-generates one. This causes Build() to fail when no key is supplied instead of falling back to generated credentials.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/bootstrap/builder.go, line 340:

<comment>`validateRequired` now makes OAuth2 signing key mandatory, but the new `getOAuth2SigningKey` no longer auto-generates one. This causes `Build()` to fail when no key is supplied instead of falling back to generated credentials.</comment>

<file context>
@@ -340,6 +337,10 @@ func (b *Builder) validateRequired() error {
 		}
 	}
 
+	if b.oauth2SigningKey == "" && b.getEnv("OAUTH2_SERVER_SIGNING_KEY") == "" {
+		missing = append(missing, "OAUTH2_SERVER_SIGNING_KEY")
+	}
</file context>
Fix with Cubic

Comment thread pkg/bootstrap/builder_test.go
@gearnode gearnode force-pushed the gearnode/inline-oauth2-signing-key branch from c92cba5 to c4e81ed Compare April 21, 2026 16:05
Committing a fully-materialised cfg/dev.yaml hid the dev
configuration surface and blocked the OAuth2 signing-key inlining
change: the new config requires a per-dev private key that must
not be committed. Replace the checked-in file with a dev-config
Make target that shells out to probod-bootstrap with dev-safe
defaults and a stable RSA signing key stashed under
cfg/.dev-oauth2-signing-key.pem on first run.

The recipe sources cfg/dev.env when present so devs can override
any setting without editing the Makefile; cfg/dev.env.example
ships the full list of overridable knobs. cfg/dev.yaml,
cfg/dev.env, and the signing key are all gitignored.

Update README, CONTRIBUTING, and contrib/claude/config.md to
describe the new workflow.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Apply the review feedback on the dev-config target:

- Treat cfg/dev.env as a prerequisite via $(wildcard ...) so edits
  to it re-trigger cfg/dev.yaml without the dev having to delete
  the output first; update the help string accordingly.
- Drop the @ silence prefix on the recipe body so failures are
  debuggable; the values are all known dev placeholders, no leak.
- Call out in cfg/dev.env.example that the file is sourced as a
  POSIX shell snippet (not Docker-compose .env semantics), and
  list the previously-missing overrides: observability addrs,
  PG_DEBUG, SMTP auth/TLS, AUTH_COOKIE_DURATION, and the
  per-worker LLM knobs (probo-agent, evidence-describer).

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="GNUmakefile">

<violation number="1" location="GNUmakefile:164">
P2: `cfg/dev.yaml` generation uses `cfg/dev.env` but does not depend on it, so env changes won’t trigger regeneration.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread GNUmakefile Outdated
gearnode and others added 2 commits April 21, 2026 18:51
Use the conventional .env / .env.example location at the repo
root instead of cfg/dev.env / cfg/dev.env.example. .env is what
contributors expect, keeps cfg/ a pure generated-config directory,
and shares the same file if we ever add another dev target that
needs the same overrides.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
The dev-config Make target was missing the ACME_ROOT_CA env var,
causing probod to fail with an untrusted certificate error when
connecting to the local Pebble ACME server.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
@SachaProbo SachaProbo merged commit 565b715 into main Apr 21, 2026
18 checks passed
@SachaProbo SachaProbo deleted the gearnode/inline-oauth2-signing-key branch April 21, 2026 17:35
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.

2 participants