Share PostgreSQL with Substrate - #2708
iplay88keys wants to merge 12 commits into
Conversation
Configure embedded Substrate to use Kagent's selected PostgreSQL database through a shared Secret and a separate schema. Support bundled, external, shared existing, and independently configured Substrate databases, with examples for each installation mode. Signed-off-by: Jeremy Alvis <[email protected]>
Signed-off-by: Jeremy Alvis <[email protected]>
…tgres-with-substrate Signed-off-by: Jeremy Alvis <[email protected]>
|
Three gaps after this PR. --postgres-schema defaults to public, and atepg.go assigns RuntimeParams["search_path"] unconditionally after parsing the DSN. In the shared-Secret setup this PR documents, a connection string carrying search_path=agents is therefore overridden to public on the ate-api-server side, silently, unless the flag is set to match. Migrations cannot use a separate identity. RunUp runs in-process on the same connection string before the pool opens, so the role serving traffic must also hold DDL rights on its own schema, and every migrated object ends up owned by it. A schema owner distinct from its runtime consumer is not expressible. Rotation has no input left. POSTGRES_DATABASE_URL is an env var, frozen for the life of the process. urlFile was the only input a rotation system could write to, and passfile is no substitute because ParseConfig resolves that once too. Is rotation meant to work another way? |
|
@smeegoan, thanks for taking a look! Just updating this PR and reviewing your comment. Just a heads up that Codex helped put together this response, though I reviewed it.
This PR defaults
Kagent already supports this operationally. An operator can create the schema/grants and run On the Substrate side, even if we added a way to pre-apply and skip startup migrations, that would not provide complete DDL/DML separation. Operators can configure Substrate with a separate connection Secret and role, even when it points to the same PostgreSQL server and database. This keeps Substrate’s DDL privileges off Kagent’s runtime role, but Substrate currently has no supported mechanism for separating its own migration/DDL and runtime/DML identities. Additional Substrate work would be required for that stricter separation.
So the PR does not remove an existing Kagent credential-separation or live-rotation capability. The real follow-up is adding equivalent migration-role separation to Substrate if that is required for shared production databases. |
…tgres-with-substrate Signed-off-by: Jeremy Alvis <[email protected]>
…t's role Signed-off-by: Jeremy Alvis <[email protected]>
Signed-off-by: Jeremy Alvis <[email protected]>
Signed-off-by: Jeremy Alvis <[email protected]>
…tgres-with-substrate Signed-off-by: Jeremy Alvis <[email protected]>
|
@smeegoan, I see now how As such, pgx's So the updated design preserves a live Kubernetes Secret input and supports credential rotation without a pod restart, while replacing the arbitrary file-path |
|
Thank you @iplay88keys, the @file: plus BeforeConnect design closes the rotation gap. One thing stops us adopting it as is. sameConnectionIdentity treats user as part of connection identity, so a rotation that changes the username fails every new acquire with database connection identity changed; restart required, while existing connections keep serving until maxConnLifetime expires. Our rotation changes the username by design: each cycle creates a new user with the new password and leaves the previous user able to log in until the following cycle, so that two credentials authenticate at once and no consumer fails while the secret store promotes the new version. That is a property of the database's rotation, not something a single consumer opts into. New dials should authenticate as the incoming user while older connections finish on the outgoing one, which is what the pool already does. Proposal: move User out of the identity comparison and into the set the refresh adopts, next to Password. Host, port, database and fallback host and port stay fenced, since those are the pool's endpoint and changing them mid-life does need a restart. That removes the user comparison rather than adding anything. |
Signed-off-by: Jeremy Alvis <[email protected]>
Signed-off-by: Jeremy Alvis <[email protected]>
|
@smeegoan, thanks for taking another look. I'm glad this approach overall will work for you. I have updated both sides of this (Substrate/Kagent) to allow for rotating the user to allow for the flow you laid out. |
Signed-off-by: Jeremy Alvis <[email protected]>
…tgres-with-substrate Signed-off-by: Jeremy Alvis <[email protected]>
Important
This PR depends on agent-substrate/substrate#1752 and kagent-dev/substrate#32. After both merge and release, this branch still needs its Substrate image/chart dependency bumped and locked.
Summary
substrateschema.database.postgres.urlFilewithdatabase.postgres.secretRef.{name,key}for Kagent 1.x.POSTGRES_DATABASE_URL=@file:...to the controller.database.postgres.role/POSTGRES_DATABASE_ROLEfor a stable Kagent PostgreSQL role.--db-roleandPOSTGRES_DATABASE_ROLEsupport tokagent db migrate.substrate.postgres.runtimeRoleandsubstrate.postgres.ddlRolefor embedded Substrate.database.postgres.pool.maxConnLifetime.Database layouts
Bundled database shared by default
Kagent and Substrate use Kagent's bundled PostgreSQL instance. Kagent uses its normal schema and Substrate uses the dedicated
substrateschema.The bundled database retains its existing static username behavior. Stable role settings are primarily for external systems that generate a new login during rotation.
Existing external Secret shared by Kagent and Substrate
Helm cannot dynamically copy one subchart value into another. Set the same Secret reference on both sides:
Because the same generated login is used by all three pools in this layout, it must be a member of
kagent_app,substrate_runtime, andsubstrate_ddl.Separate Kagent, Substrate runtime, and Substrate DDL credentials
All connections may point to the same PostgreSQL server and database. The dedicated
substrateschema prevents Substrate's runtime grants from covering Kagent tables.The stable DDL role owns and migrates the Substrate schema and performs outbox partition maintenance. The stable runtime role receives only the table and sequence permissions ateapi needs.
Omitting the Substrate DDL connection preserves single-connection operation. When both the DDL connection and
ddlRoleare omitted, Substrate uses the runtime source and role for both.ddlConnectionStringSecretRefrequires an explicitname. Kagent generates only the runtime connection Secret, so an unnamed DDL reference would resolve to a Secret without the DDL key.Credential rotation
Kubernetes updates mounted Secret volumes in place. Kagent and Substrate reread their selected files before opening each new physical pgx connection; an application reload or pod restart is not required.
Existing connections retain their current credentials until retired. Set both
database.postgres.pool.maxConnLifetimeandsubstrate.postgres.pool.maxConnLifetimeto bound the rotation window, and keep outgoing credentials valid during that overlap.Password and TLS rotation work without stable roles if the username remains unchanged.
Username-changing rotation requires stable roles:
database.postgres.role.substrate.postgres.runtimeRole.substrate.postgres.ddlRole.Each new connection authenticates as the generated login and then assumes its stable role. PostgreSQL grants and object ownership therefore remain attached to persistent roles rather than disposable users.
Without a configured stable role, Kagent or Substrate rejects a username change and requires a restart. If the incoming login lacks membership,
SET ROLEfails and the connection does not enter the pool.Host, port, fallback hosts, and database remain part of the pool identity and require a restart when changed. Inline URLs remain startup-static.
This supports credentials rotated into Kubernetes Secrets. It does not mint RDS IAM tokens in-process.
Migration and DDL credentials
database.postgres.roleis used by both Kagent's runtime pool and in-process startup migrations. When startup migrations are enabled, that stable role must have the required DDL privileges.Deployments that require separate Kagent runtime and migration privileges can continue to migrate out of band:
Then configure the controller with its restricted stable runtime role and set:
kagent db migratealso readsPOSTGRES_DATABASE_ROLEwhen--db-roleis omitted.Substrate's independent DDL/runtime split is provided by agent-substrate/substrate#1752. Both credentials remain in ateapi because partition maintenance is still performed in process.
Database prerequisites
Neither chart creates external PostgreSQL group roles or grants rotating login memberships. Provision the stable roles before installation and make the credential rotator grant every incoming login the corresponding membership.
For example:
The exact login names are generated by the external credential system. On the next rotation, it creates new login users and grants the same stable memberships before updating the Secrets.
ateapi creates the configured Substrate schema through the DDL role. It grants the stable runtime role access to migrated tables and sequences after each migration.
Breaking change
database.postgres.urlFileandPOSTGRES_DATABASE_URL_FILEare removed for Kagent 1.x. Usedatabase.postgres.secretRefin Kubernetes, or pass an@file:/absolute/pathvalue throughPOSTGRES_DATABASE_URLwhen running the binary directly.The chart fails with migration guidance if the removed Helm value is supplied.
Testing
current_userremains the stable role.The full Kagent Helm suite currently has one unrelated existing failure caused by the packaged Substrate dependency not containing the branch's template-resync argument. The focused controller suite and new role test pass.