mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-10-26 07:23:59 +08:00
## Summary Part of: #2097 Bundled PostgreSQL currently accepts any client certificate signed by the pod-identity CA without binding it to the requested database username. Require certificate authentication with a common name matching the login, using a dedicated PostgreSQL client CA. - Add a pod-certificate signer restricted to `ate-system` pods using the `ate-api-server` service account. It issues only the two permitted identities: `substrate_owner_user` and `substrate_readwrite_user`. - Project separate certificates for the owner and runtime connections. Configure passwordless connection strings with server certificate verification, and retain the existing owner/runtime role grants. - Have `ate-setup` provision the CA and configure bundled PostgreSQL automatically, including on an existing installation. Disable passwords on the bundled application users and keep administrator access local to the PostgreSQL pod. - Reuse existing rotation support: new database connections from ateapi reread projected credentials and trust roots, while the bundled PostgreSQL TLS-reloader detects server-certificate or client-trust-bundle changes every 60 seconds and requests a configuration reload. External PostgreSQL and Cloud SQL continue to use their configured connection strings and authentication settings. Both application certificates are available to the API-server process; separate credentials preserve database role separation but do not isolate the roles from a compromised API server. ## Validation - Passed `go test -race ./cmd/podcertcontroller/... ./cmd/ate-setup/... ./cmd/ateapi/internal/store/atepg ./pkg/postgressetup ./internal/installdefaults` after rebasing onto updated main. - Signer tests cover both allowed identities, unauthorized requests, pod identity checks, certificate lifetimes, and trust bundles. - Docker integration tests use the bundled PostgreSQL configuration and verify successful passwordless connections, bootstrap/migrations, and runtime operations. They reject mismatched usernames, untrusted certificates, missing certificates, remote administrator access, plaintext connections, and runtime access to owner privileges.
83 lines
4.6 KiB
SQL
83 lines
4.6 KiB
SQL
-- Copyright 2026 Google LLC
|
|
--
|
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
|
-- you may not use this file except in compliance with the License.
|
|
-- You may obtain a copy of the License at
|
|
--
|
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
--
|
|
-- Unless required by applicable law or agreed to in writing, software
|
|
-- distributed under the License is distributed on an "AS IS" BASIS,
|
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
-- See the License for the specific language governing permissions and
|
|
-- limitations under the License.
|
|
|
|
SELECT
|
|
set_config('agent_substrate.setup_schema', :'substrate_schema', true) AS schema,
|
|
set_config('agent_substrate.setup_owner_role', :'substrate_owner_role', true) AS owner_role,
|
|
set_config('agent_substrate.setup_owner_user', :'substrate_owner_user', true) AS owner_user,
|
|
set_config('agent_substrate.setup_owner_password', :'substrate_owner_password', true) AS owner_password,
|
|
set_config('agent_substrate.setup_readwrite_role', :'substrate_readwrite_role', true) AS readwrite_role,
|
|
set_config('agent_substrate.setup_readwrite_user', :'substrate_readwrite_user', true) AS readwrite_user,
|
|
set_config('agent_substrate.setup_readwrite_password', :'substrate_readwrite_password', true) AS readwrite_password
|
|
\gset
|
|
|
|
DO $setup$
|
|
DECLARE
|
|
schema_name text := current_setting('agent_substrate.setup_schema');
|
|
owner_role text := current_setting('agent_substrate.setup_owner_role');
|
|
owner_user text := current_setting('agent_substrate.setup_owner_user');
|
|
owner_password text := NULLIF(current_setting('agent_substrate.setup_owner_password'), '');
|
|
readwrite_role text := current_setting('agent_substrate.setup_readwrite_role');
|
|
readwrite_user text := current_setting('agent_substrate.setup_readwrite_user');
|
|
readwrite_password text := NULLIF(current_setting('agent_substrate.setup_readwrite_password'), '');
|
|
managed record;
|
|
role_attrs record;
|
|
schema_owner text;
|
|
BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('agent-substrate:setup:' || schema_name, 0));
|
|
|
|
FOR managed IN SELECT * FROM (VALUES
|
|
(owner_role, false, NULL::text),
|
|
(readwrite_role, false, NULL::text),
|
|
(owner_user, true, owner_password),
|
|
(readwrite_user, true, readwrite_password)
|
|
) AS roles(name, can_login, password) LOOP
|
|
SELECT rolcanlogin, rolsuper, rolcreatedb, rolcreaterole, rolreplication, rolinherit
|
|
INTO role_attrs FROM pg_roles WHERE rolname = managed.name;
|
|
IF NOT FOUND THEN
|
|
IF managed.can_login THEN
|
|
EXECUTE format('CREATE ROLE %I LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION', managed.name);
|
|
ELSE
|
|
EXECUTE format('CREATE ROLE %I NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION', managed.name);
|
|
END IF;
|
|
ELSIF role_attrs.rolcanlogin <> managed.can_login OR role_attrs.rolsuper
|
|
OR role_attrs.rolcreatedb OR role_attrs.rolcreaterole OR role_attrs.rolreplication
|
|
OR (managed.can_login AND role_attrs.rolinherit) THEN
|
|
RAISE EXCEPTION 'managed PostgreSQL role "%" conflicts with the required attributes', managed.name;
|
|
END IF;
|
|
IF managed.can_login THEN
|
|
EXECUTE format('ALTER ROLE %I PASSWORD %L', managed.name, managed.password);
|
|
END IF;
|
|
END LOOP;
|
|
|
|
EXECUTE format('GRANT %I TO %I', owner_role, owner_user);
|
|
EXECUTE format('GRANT %I TO %I', readwrite_role, readwrite_user);
|
|
|
|
SELECT pg_get_userbyid(nspowner) INTO schema_owner FROM pg_namespace WHERE nspname = schema_name;
|
|
IF NOT FOUND THEN
|
|
EXECUTE format('CREATE SCHEMA %I AUTHORIZATION %I', schema_name, owner_role);
|
|
ELSIF schema_owner <> owner_role THEN
|
|
RAISE EXCEPTION 'PostgreSQL schema "%" is owned by "%", not "%"', schema_name, schema_owner, owner_role;
|
|
END IF;
|
|
|
|
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
|
|
EXECUTE format('REVOKE ALL ON SCHEMA %I FROM PUBLIC', schema_name);
|
|
EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', schema_name, readwrite_role);
|
|
EXECUTE format('ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA %I GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO %I', owner_role, schema_name, readwrite_role);
|
|
EXECUTE format('ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA %I GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO %I', owner_role, schema_name, readwrite_role);
|
|
EXECUTE format('ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA %I GRANT EXECUTE ON ROUTINES TO %I', owner_role, schema_name, readwrite_role);
|
|
EXECUTE format('ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA %I GRANT USAGE ON TYPES TO %I', owner_role, schema_name, readwrite_role);
|
|
END
|
|
$setup$;
|