mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: Cea Stapleton Cordasco <261786559+cea-block@users.noreply.github.com>
95 lines
3.7 KiB
SQL
95 lines
3.7 KiB
SQL
-- Explicit corporate identity revocation and rotation semantics.
|
|
--
|
|
-- principal: disables every key for the issuer-qualified principal.
|
|
-- key: revokes only this key; a different key still requires an explicit
|
|
-- operator rotation because ordinary authentication never replaces an
|
|
-- active binding.
|
|
-- rotation: records the old key retired by an authorized atomic rotation.
|
|
|
|
ALTER TABLE identity_bindings
|
|
ADD COLUMN revocation_scope TEXT NOT NULL DEFAULT 'principal'
|
|
CHECK (revocation_scope IN ('principal', 'key', 'rotation')),
|
|
ADD COLUMN rotation_completed_at TIMESTAMPTZ,
|
|
ADD COLUMN rotated_to_pubkey BYTEA,
|
|
ADD COLUMN rotation_by BYTEA,
|
|
ADD COLUMN rotation_reason TEXT,
|
|
ADD CONSTRAINT chk_identity_bindings_rotation_state CHECK (
|
|
(rotation_completed_at IS NULL
|
|
AND rotated_to_pubkey IS NULL
|
|
AND rotation_by IS NULL
|
|
AND rotation_reason IS NULL)
|
|
OR
|
|
(rotation_completed_at IS NOT NULL
|
|
AND rotated_to_pubkey IS NOT NULL
|
|
AND length(rotated_to_pubkey) = 32
|
|
AND (rotation_by IS NULL OR length(rotation_by) = 32)
|
|
AND rotation_reason IS NOT NULL
|
|
AND length(rotation_reason) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_identity_bindings_revoked_principal
|
|
ON identity_bindings (community_id, issuer, uid)
|
|
WHERE revoked_at IS NOT NULL AND revocation_scope = 'principal';
|
|
|
|
-- Principal status is separate from key history so operators can disable a
|
|
-- principal before first enrollment and after a single-key revocation.
|
|
CREATE TABLE identity_principals (
|
|
community_id UUID NOT NULL REFERENCES communities(id),
|
|
issuer TEXT NOT NULL,
|
|
uid TEXT NOT NULL,
|
|
disabled_at TIMESTAMPTZ,
|
|
disabled_by BYTEA,
|
|
disabled_reason TEXT,
|
|
PRIMARY KEY (community_id, issuer, uid),
|
|
CHECK (length(issuer) > 0),
|
|
CHECK (length(uid) > 0),
|
|
CHECK (disabled_by IS NULL OR length(disabled_by) = 32),
|
|
CHECK ((disabled_at IS NULL) = (disabled_reason IS NULL))
|
|
);
|
|
|
|
-- Rows revoked before this lifecycle migration represented principal-level
|
|
-- disablement. Preserve that security state instead of allowing the same uid
|
|
-- to re-enroll with a fresh key after upgrade.
|
|
INSERT INTO identity_principals
|
|
(community_id, issuer, uid, disabled_at, disabled_by, disabled_reason)
|
|
SELECT DISTINCT ON (community_id, issuer, uid)
|
|
community_id,
|
|
issuer,
|
|
uid,
|
|
revoked_at,
|
|
revoked_by,
|
|
COALESCE(NULLIF(revoked_reason, ''), 'legacy principal revocation')
|
|
FROM identity_bindings
|
|
WHERE revoked_at IS NOT NULL
|
|
ORDER BY community_id, issuer, uid, revoked_at ASC;
|
|
|
|
-- A revoked credential cannot be rebound to a different principal in the
|
|
-- same community. Explicit rotation may consume an old revoked key, but may
|
|
-- never select a revoked key as the replacement.
|
|
CREATE TABLE identity_revoked_keys (
|
|
community_id UUID NOT NULL REFERENCES communities(id),
|
|
pubkey BYTEA NOT NULL,
|
|
revoked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
revoked_by BYTEA,
|
|
reason TEXT NOT NULL,
|
|
PRIMARY KEY (community_id, pubkey),
|
|
CHECK (length(pubkey) = 32),
|
|
CHECK (revoked_by IS NULL OR length(revoked_by) = 32),
|
|
CHECK (length(reason) > 0)
|
|
);
|
|
|
|
-- Preserve every pre-migration revoked credential as a community-wide key
|
|
-- tombstone. This prevents a legacy-revoked key from binding to a different
|
|
-- issuer-qualified principal after upgrade.
|
|
INSERT INTO identity_revoked_keys
|
|
(community_id, pubkey, revoked_at, revoked_by, reason)
|
|
SELECT DISTINCT ON (community_id, pubkey)
|
|
community_id,
|
|
pubkey,
|
|
revoked_at,
|
|
revoked_by,
|
|
COALESCE(NULLIF(revoked_reason, ''), 'legacy key revocation')
|
|
FROM identity_bindings
|
|
WHERE revoked_at IS NOT NULL
|
|
ORDER BY community_id, pubkey, revoked_at ASC;
|