Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ jobs:
TSK_TEST_RECEIVER_PG_URL_B: postgresql://tsk_test:tsk-test-only-password@127.0.0.1:5433/tsk_test
TSK_TEST_CONTROL_PG_URL: postgresql://tsk_test:tsk-test-only-password@127.0.0.1:5434/tsk_test
TSK_TEST_REDIS_URL: redis://127.0.0.1:6379
# Enterprise credential authority: every TumblerMapStore mutation commits its
# authoritative map change and a secret-free signed TSK record in one
# SERIALIZABLE source-A transaction. Independent B verifies the head chain,
# revision continuity, fencing, gaps, replays and tampering before staging.
- run: npm run test:credential-authority
env:
TSK_TEST_POSTGRES_URL_A: postgresql://tsk_test:tsk-test-only-password@127.0.0.1:5432/tsk_test
TSK_TEST_POSTGRES_URL_B: postgresql://tsk_test:tsk-test-only-password@127.0.0.1:5433/tsk_test
# (PR2c acceptance) REAL Redis Sentinel/quorum: 1 master + 2 REPLICAS + 3 sentinels (quorum 2),
# AOF, min-replicas-to-write. A real master CRASH → automatic promotion; claim() ENFORCES a WAIT
# replica-quorum ACK so the fence SURVIVES (RPO=0), stays monotonic, RTO measured. run.sh brings up
Expand Down
60 changes: 60 additions & 0 deletions docs/POSTGRES_CREDENTIAL_AUTHORITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# PostgreSQL Credential Authority

`PgHaTumblerMapStore` is the production HA adapter for the server's complete
`TumblerMapStore` contract. Each lifecycle or HOTP-counter mutation updates the
authoritative secret-bearing map and appends a signed, hash-linked TSK record in
the same SERIALIZABLE PostgreSQL transaction. The existing source lease is
rechecked immediately before commit, so a revoked or stale writer cannot commit
either side alone.

The runtime role has no direct mutation rights on the credential tables or
outbox. It can invoke only fixed `SECURITY DEFINER` routines. Each atomic apply
requires a transaction-bound, five-second, single-use HMAC mutation ticket over
the exact signed record and credential effects. The ticket key is installed by
the provisioning identity, is unreadable by the runtime database role, and is
proved at capability mint with a fresh challenge. Missing, inactive, rotated,
or mismatched key material fails closed without disclosing the key. Nonces use
the database clock, indexed expiry pruning, and a serialized hard capacity of
10,000 rows; live tickets are never pruned early.

The replicated mutation is deliberately secret-free. It contains the public map,
its digest, a digest of the source secret, and the monotonic per-credential
revision. Node B verifies the operation digest, signed stream head, fence, global
sequence, head chain, and credential revision before staging it. Staged public
state cannot authenticate requests. Promotion must use the governed TSK cutover
and separately reprovision secret material through an approved custody channel.

## Deployment boundary

- Install `TSK_CREDENTIAL_AUTHORITY_SCHEMA` with a separate provisioning role.
- Run the application with no DDL privilege. Every owned operation pins the
schema, holds `ACCESS SHARE` locks, and compares the live full catalog to the
compiled PostgreSQL 16 manifest before reading or mutating authority state.
- The runtime identity must not inherit ownership, DDL, direct DML, secret-table
reads, or helper-routine execution through another role. Every mutation and
its pre-commit hook reassert this posture and re-prove the active ticket key,
so a grant or key change after startup invalidates the capability in use.
- Routine/table owners and routine definitions are catalog-attested. The
provisioning identity must serialize routine/ACL changes with deployments;
runtime execution is restricted to the four reviewed `SECURITY DEFINER`
entry points with a fixed `search_path`.
- Obtain `CredentialAuthorityReadyToken` only through
`assertCredentialAuthorityReady`; it is bound to the exact transactor and
schema and has no public mint helper.
- Provision the runtime mutation boundary with a dedicated key, retain that key
only in the application signer/custody layer, and erase temporary provisioning
copies. Re-run `assertCredentialRuntimeMutationBoundary` at startup.
- Construct `PgHaTumblerMapStore` with the attested outbox and credential tokens,
the runtime-boundary token and signer, and a verified source-fence capability.
- Keep source A, receiver B, and the control database in independent state
authorities. Transport and promotion use the existing authenticated TSK HA
path; this adapter does not create a second replication protocol.

This slice establishes the source credential authority and secret-free receiver
staging. It does not by itself activate a promoted receiver as an authentication
authority. Enterprise activation remains gated on the signed cutover receipt,
secret reprovisioning under approved custody, and the Enterprise #28 acceptance
drill.

The real-PG drill is `npm run test:credential-authority`. It fails rather than
skips when either independent PostgreSQL URL is absent.
7 changes: 7 additions & 0 deletions package-boundary-suite.mts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ for (const workspace of workspaces) {
for (const sym of ['NodePostgresTransactor', 'AmbiguousCommitError', 'PostCommitReleaseError', 'ConnectionDisposalError']) {
assert(typeof (exports as Record<string, unknown>)[sym] === 'function', `@tsk/server must export ${sym}`);
}
for (const sym of ['PgHaTumblerMapStore', 'PgTskCredentialReceiverCheckpoint',
'assertCredentialAuthorityReady', 'provisionCredentialRuntimeMutationBoundary',
'assertCredentialRuntimeMutationBoundary', 'HmacCredentialMutationTicketSigner']) {
assert(typeof (exports as Record<string, unknown>)[sym] === 'function', `@tsk/server must export ${sym}`);
}
assert(typeof (exports as Record<string, unknown>).TSK_CREDENTIAL_AUTHORITY_SCHEMA === 'string',
'@tsk/server must export the credential-authority provisioning DDL');
}
passed++;
console.log(` PASS ${manifest.name} entry points exist and import (no mint/unsafe export)`);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"test:redis": "npx tsx redis-fencing-integration.mts",
"test:postgres:ha": "npm run build -w packages/server && npx tsx tsk-hotp-outbox-integration.mts",
"test:pg-partition": "npm run build -w packages/server && npx tsx tsk-pg-partition-drill.mts",
"test:credential-authority": "npm run build -w packages/server && npx tsx tsk-credential-authority-drill.mts",
"test:ha-fencing": "npm run build -w packages/server && npx tsx tsk-ha-fencing-drill.mts",
"test:source-fence": "npm run build -w packages/server && npx tsx tsk-source-fence-drill.mts",
"test:source-fence-outbox": "npm run build -w packages/server && npx tsx tsk-source-fence-outbox-drill.mts",
Expand Down
Loading
Loading