feat: database grantable + connectionUrl removal (#117)#124
Merged
Conversation
- add a {URL} value field (role's already-encoded connection URI) and compose
DSNs with it; hand-assembling {USER}:{PASSWORD}@... fails to URL-encode a
password with reserved chars (regression vs the passed-through connectionUrl)
- reject two grants on the same resource target in checkGrants (they would
collide on one per-service role at deploy)
- NeonRole.Delete: best-effort SQL cleanup, always proceed to the control-plane
role drop so a suspended endpoint at destroy time does not wedge teardown
- NeonRole.Diff: ignore drift in the transient owner connection URI / API key so
a non-byte-stable Neon URI never churns every consumer role
- redact url.Parse failures so a role/owner password never lands in deploy logs
- share the global/ redirect via types.ResolveScoped (ref: and grants resolve
a global resource identically) and interpolate only the fields a template uses
…diff transients) (#117)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #117. Second slice of ADR-0025 (the Grant capability) — makes the Database Grantable real and removes the credential-bearing
connectionUrlref surface. Builds on slice A (#123, merged: grant core + schema + validation).What this slice ships (slice B — Database Grantable)
Database.Grantis now real. A service that declaresgrants: [{resource: database/<name>, permission: ro|rw, outputs: …}]gets a scoped per-service Postgres role (never the database owner/admin credential), and its connection fields are materialized into the service's runtime secrets.neon:resources:NeonRoleplugin resource (providers/neon/cmd/pulumi-resource-neon/resources/neon_role.go): creates the role via the Neon API, connects as the database owner over pgx (pure-Go, CGO-free) to apply the GRANTs, and returns{USER,PASSWORD,HOST,PORT,DBNAME}. Delete reassigns/drops the role's owned objects + privileges before the API drop. Owner connection URI is a secret input, redacted from errors.ro=CONNECT+USAGE+SELECT(+ default privileges);rw=ro+INSERT/UPDATE/DELETE+ sequence privileges andCREATE ON SCHEMA publicso arwservice owns its own migrations (DDL). Identifiers quoted viapgx.Identifier.Sanitize().Breaking:
connectionUrlremovedDatabaseOutputs.ConnectionURLis gone — a database exposes no referenceable output.ref:database/*is now rejected by bothinforge validateand the deploy-timeresolveRefwith a "use a grants: entry for DB credentials" hint. DB credentials flow only through grants. Migrated all in-repo consumers:resolveRef/validaterejection paths,source.godocs, and the validate testdata fixtures (theokfixture already had a grant;encrypted-ok/provider-defaults-ok/pki-missing-intermediate/global-badconverted theirref:databaseenv vars togrants:). Downstream consumer repos migrate separately.Regional → global access is uniform
A regional service granting
database/global/<name>resolves through the sameglobal/redirect thatref:uses (the role-provisioning capability rides ontypes.DatabaseOutputs.RoleProvisionerthreaded throughAllOutputs, not adapter-instance memory — the global registry isn't shared with the regional loop). The per-service role is named for the consuming service instance (wardnet-<env>-<consumerSlug>-dbrole-<svc>-<db>), so two regions granting one global database never collide.Deploy wiring
program.resolveDatabaseGrantsmints eachdatabase/*grant's role and interpolates itsoutputs:templates over the value fields, theninfisical.ProvisionService(…, grantSecrets)merges them into the same/<svc>/infrabatch as the environment.yaml secrets. The provision skip now accounts for grant-only services. Bootstrapper untouched (the value/file field-kind split is the point).pki/*grants are not wired (slice C).Tests / gates
grantSQLtable test (exact ro/rw statements, DDL-for-rw, identifier quoting, URI parsing);Database.Grantfield mapping; adapterProvisionRoleregistersNeonRole;resolveDatabaseGrants(global redirect, consumer-scoped naming, pki-skip, missing target);ref:databaserejection in validate + resolveRef; migrated fixtures.go build ./...,go test -race ./...,golangci-lint run ./...clean;CGO_ENABLED=0 go build ./...andgoreleaser release --snapshotbuild all four static binaries with pgx linked.Docs: ADR-0025 Consequences + Status,
internal/CONTEXT.md(Database credential access + Source DSL dialogue),AGENTS.mdGrants section.Out of scope
Slice C (PKI resource Grantable: age-encrypted
pki.enc.yamlsidecar,inforge pki generate, file-field projection).PKIResource.Grantstays a stub.