Skip to content

feat: support Azure Managed Identity and Workload Identity authentication - #34

Merged
pewpewpotato merged 5 commits into
mainfrom
feat/azure-managed-identity-auth
Sep 14, 2026
Merged

pewpewpotato merged 5 commits into
mainfrom
feat/azure-managed-identity-auth

Conversation

@pewpewpotato

Copy link
Copy Markdown
Contributor

Description

Adds dual authentication support to Spool Rack, allowing it to authenticate to PostgreSQL databases using either standard password-based authentication (default, backwards-compatible) or Microsoft Entra ID OAuth2 access tokens via Azure Workload Identity in AKS, VM-based Managed Identity, or local Azure CLI logins.

Key Changes

  • Azure Token Provider: Implemented AzureTokenProvider using azidentity.NewDefaultAzureCredential to acquire tokens for the PostgreSQL resource scope https://ossrdbms-aad.database.windows.net/.default.
  • Dynamic Token Refreshing in pgxpool: Integrated BeforeConnect hook on pgxpool.Pool to dynamically fetch and assign fresh OAuth2 tokens on every new connection opened by the pool.
  • Connection Recycling: Added MaxConnLifetime configuration (defaulting to 45m with Azure auth) to cycle pooled connections before standard 60-90 minute token expiration.
  • Database User & Role Configuration: Enabled specifying the database user matching the PostgreSQL Entra role (e.g., id-ixs-rng-dev-em20-spl-02) via POSTGRES_USER or directly in the connection DSN.
  • Decoupled Schema Migrations: Supported running migrations via Azure Managed Identity, an independently authenticated password DSN (POSTGRES_MIGRATIONS_DSN), or skipping startup migrations (POSTGRES_RUN_MIGRATIONS=false) when executed in a separate CI/CD step or dedicated Kubernetes Job (POSTGRES_MIGRATE_ONLY=true).
  • Helm Chart Configuration: Updated chart templates, values, and schema to support Azure Workload Identity (serviceAccount.automount: true, Workload Identity annotations and pod labels), direct passwordless DSNs, and added values-azure-workload-identity.yaml example.
  • Testing: Added unit tests in postgres package for token acquisition, mock credentials, error handling, and BeforeConnect password assignment, plus helper tests in cmd/spool-rack.

Verification

  • make all: Passed (tidy-check, lint with 0 issues, unit tests, race detection, build).
  • helm lint charts/spool-rack and helm lint infra/charts/spool-rack: 0 failures.
  • helm template validated for both default values and values-azure-workload-identity.yaml.

- align appVersion to 0.3.0 in Helm charts
- add [Unreleased] section and compare links to CHANGELOG.md
- pass Helm login credentials via environment variables and upgrade checkout to v7
…tion

- add AzureTokenProvider in postgres package using azidentity.DefaultAzureCredential
- implement pgxpool BeforeConnect token refresh for dynamic Entra ID tokens
- support specifying database user via POSTGRES_USER or connection DSN
- configure connection recycling with POSTGRES_MAX_CONN_LIFETIME (default 45m for Azure auth)
- decouple schema migrations with POSTGRES_MIGRATIONS_AUTH_TYPE and POSTGRES_RUN_MIGRATIONS
- add POSTGRES_MIGRATE_ONLY flag to support dedicated migration jobs
- update Helm charts with Azure Workload Identity configuration and examples
- add unit tests covering Azure token provider, BeforeConnect hook, and options
Copilot AI lite review requested due to automatic review settings September 14, 2026 07:49

Copilot AI 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.

🟡 Changes recommended

Unresolved moderate findings affect migration-only execution, migration DSN user handling, and Azure client-ID selection.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds Azure Managed Identity and Workload Identity authentication for PostgreSQL, with token refresh, migration controls, and Helm support.

Changes:

  • Added Azure token acquisition and pgx connection-pool integration.
  • Added configurable migration DSNs, users, skip, and migrate-only modes.
  • Updated Helm charts, documentation, dependencies, release metadata, and publishing workflow.
File summaries
File Summary and review findings
README.md Updated installation version.
internal/server/storage/postgres/store.go Added token-aware pool configuration.
internal/server/storage/postgres/options.go Added database and migration options.
internal/server/storage/postgres/migrate.go Added authenticated migration connections. Nit (1 vote): migration and confirm-retirement wrappers duplicate the existing postgres: error prefix.
internal/server/storage/postgres/auth.go Added Azure token provider. Moderate (3 votes): explicitly supplied ClientID can be ignored when AZURE_CLIENT_ID is set.
internal/server/storage/postgres/auth_test.go Added authentication tests. Nit (3 votes): the test bypasses Open and may not verify hook, token, or lifetime configuration.
infra/README.md Updated deployment version.
infra/charts/spool-rack/values.yaml Added Azure and migration settings.
infra/charts/spool-rack/values.schema.json Extended the Helm values schema.
infra/charts/spool-rack/templates/deployment.yaml Added authentication and migration environment wiring.
infra/charts/spool-rack/README.md Documented Azure configuration.
infra/charts/spool-rack/examples/values-azure-workload-identity.yaml Added Workload Identity example.
infra/charts/spool-rack/Chart.yaml Updated chart metadata. Nit (3 votes): default image tag 0.3.0 conflicts with the binary’s hardcoded 0.1.0-mvp version.
go.work.sum Updated workspace checksums.
go.sum Added Azure dependency checksums.
go.mod Added Azure Identity dependencies.
docker-compose.yml Updated default image version.
cmd/spool-rack/main.go Added authentication and migration modes. Moderate (3 votes): migration-only execution with only POSTGRES_MIGRATIONS_DSN skips migrations and starts the HTTP server. Moderate (3 votes): migration DSN fallback can override an independent DSN’s user with POSTGRES_USER (lines 93 and 208).
cmd/spool-rack/main_test.go Added authentication helper tests.
cmd/spool-rack/go.sum Updated command-module checksums.
cmd/spool-rack/go.mod Updated command-module dependencies.
charts/spool-rack/values.yaml Added Azure and migration settings.
charts/spool-rack/values.schema.json Mirrored Helm schema changes.
charts/spool-rack/templates/deployment.yaml Mirrored authentication and migration environment wiring.
charts/spool-rack/README.md Documented Azure configuration.
charts/spool-rack/examples/values-azure-workload-identity.yaml Added Workload Identity example.
charts/spool-rack/Chart.yaml Updated chart metadata. Nit (3 votes): default image tag 0.3.0 conflicts with the binary’s hardcoded 0.1.0-mvp version.
CHANGELOG.md Documented the authentication features.
.github/workflows/publish-chart.yml Updated chart publishing workflow.
Review details

Suppressed comments (3)

cmd/spool-rack/main.go:216

  • The dev-seeding path repeats the same override: with a separate POSTGRES_MIGRATIONS_DSN, it falls back to POSTGRES_USER/POSTGRES_AZURE_USER and replaces the migration DSN's own user. This makes an independently authenticated migration DSN ineffective for seeding unless another environment variable is supplied; preserve the embedded user whenever the DSNs differ.
			dbUser := os.Getenv("POSTGRES_MIGRATIONS_USER")
			if dbUser == "" {
				dbUser = os.Getenv("POSTGRES_USER")
			}
			if dbUser == "" {
				dbUser = os.Getenv("POSTGRES_AZURE_USER")
			}
			if dbUser != "" {
				seedOpts = append(seedOpts, postgres.WithMigrateUser(dbUser))

internal/server/storage/postgres/migrate.go:266

  • Connect already prefixes its errors with postgres:, so this wrapper now produces messages such as postgres: migrate: postgres: connect: ... instead of the previous postgres: migrate: connect: .... Avoid duplicating the package prefix while retaining the migration context.
		return nil, fmt.Errorf("postgres: migrate: %w", err)

internal/server/storage/postgres/migrate.go:379

  • Connect already prefixes its errors with postgres:, so this wrapper now produces messages such as postgres: confirm retirement: postgres: connect: .... Avoid duplicating the package prefix while retaining the confirm-retirement context.
		return fmt.Errorf("postgres: confirm retirement: %w", err)
  • Files reviewed: 27/29 changed files
  • Comments generated: 6
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cmd/spool-rack/main.go Outdated
Comment thread cmd/spool-rack/main.go Outdated
Comment thread internal/server/storage/postgres/auth.go Outdated
Comment thread charts/spool-rack/Chart.yaml
Comment thread infra/charts/spool-rack/Chart.yaml
Comment thread internal/server/storage/postgres/auth_test.go Outdated
@pewpewpotato

Copy link
Copy Markdown
Contributor Author

All Copilot review comments have been addressed in commit 8ee8274:

  1. Migration-only execution with POSTGRES_MIGRATIONS_DSN: Updated the database setup guard in cmd/spool-rack/main.go to postgresDSN != "" || migrationsDSN != "". When only POSTGRES_MIGRATIONS_DSN is provided (e.g. in a migration Job), migrations run properly and the HTTP server does not attempt to connect with an empty postgresDSN.
  2. Migration user fallback logic: Implemented resolveMigrationUser helper so POSTGRES_USER / POSTGRES_AZURE_USER is only inherited when migrationsDSN == postgresDSN (or when an explicit POSTGRES_MIGRATIONS_USER is supplied). Independent migration DSNs preserve their embedded credentials for both migrations and dev seeding.
  3. Explicit Azure Client ID handling: Refactored AzureTokenProvider to avoid mutating os.Setenv("AZURE_CLIENT_ID", ...). When opts.ClientID is explicitly provided, it now builds an explicit azidentity.ChainedTokenCredential prioritizing WorkloadIdentityCredential and ManagedIdentityCredential configured with that specific Client ID.
  4. Binary version mismatch: Updated hardcoded version in cmd/spool-rack/main.go to 0.3.0 to match Chart.yaml and release tags.
  5. Connection pool testing: Extracted newPoolConfig in internal/server/storage/postgres/store.go and updated auth_test.go to directly verify pool configuration, BeforeConnect token assignment, dynamic token refreshes, and default connection lifetime limits.
  6. Clean error prefixing: Replaced nested fmt.Errorf in internal/server/storage/postgres/migrate.go with a custom error wrapper that strips redundant postgres: prefixes from underlying errors.

@pewpewpotato
pewpewpotato merged commit c5ddde9 into main Sep 14, 2026
5 checks passed
@pewpewpotato
pewpewpotato deleted the feat/azure-managed-identity-auth branch September 14, 2026 08:11
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