Harden compliance portal auth and TLS
Align console references and OAuth branding with the compliance-page model, and fix certificate cache eviction, portal OAuth handlers, and magic-link edge cases left after the trust-center rename. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -170,22 +170,25 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUnreferenced removes cache entries whose certificate is no longer
|
||||
// referenced by any custom domain, so deleted domains cannot keep a usable
|
||||
// TLS cache entry.
|
||||
func (cc *CachedCertificates) DeleteUnreferenced(ctx context.Context, conn pg.Querier) error {
|
||||
// DeleteWhereCertificateIDNotIn removes cache rows whose certificate is not
|
||||
// among the provided IDs. An empty keep set deletes every cache row.
|
||||
func (cc *CachedCertificates) DeleteWhereCertificateIDNotIn(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
keepCertificateIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM
|
||||
cached_certificates
|
||||
WHERE
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM custom_domains
|
||||
WHERE custom_domains.certificate_id = cached_certificates.certificate_id
|
||||
)
|
||||
NOT (certificate_id = ANY(@keep_certificate_ids::text[]))
|
||||
`
|
||||
|
||||
_, err := conn.Exec(ctx, q, pgx.NamedArgs{})
|
||||
_, err := conn.Exec(
|
||||
ctx,
|
||||
q,
|
||||
pgx.NamedArgs{"keep_certificate_ids": keepCertificateIDs},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete unreferenced certificate cache: %w", err)
|
||||
}
|
||||
|
||||
@@ -377,3 +377,32 @@ WHERE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadReferencedCertificateIDs returns certificate IDs currently linked from
|
||||
// any custom domain. Used by the certificate cache warmer to drop orphaned
|
||||
// cache rows without joining across entity tables.
|
||||
func (domains *CustomDomains) LoadReferencedCertificateIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
) ([]gid.GID, error) {
|
||||
q := `
|
||||
SELECT DISTINCT
|
||||
certificate_id
|
||||
FROM
|
||||
custom_domains
|
||||
WHERE
|
||||
certificate_id IS NOT NULL
|
||||
`
|
||||
|
||||
rows, err := conn.Query(ctx, q, pgx.NamedArgs{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query referenced certificate ids: %w", err)
|
||||
}
|
||||
|
||||
certificateIDs, err := pgx.CollectRows(rows, pgx.RowTo[gid.GID])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot collect referenced certificate ids: %w", err)
|
||||
}
|
||||
|
||||
return certificateIDs, nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ INSERT INTO trust_centers (
|
||||
tenant_id,
|
||||
active,
|
||||
slug,
|
||||
search_engine_indexing,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -41,6 +42,7 @@ SELECT
|
||||
'\s+', '-', 'g'
|
||||
)
|
||||
),
|
||||
'NOT_INDEXABLE',
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM organizations o
|
||||
|
||||
@@ -25,7 +25,7 @@ CREATE TABLE certificates (
|
||||
ssl_certificate_chain TEXT,
|
||||
status custom_domain_ssl_status NOT NULL,
|
||||
ssl_expires_at TIMESTAMP WITH TIME ZONE,
|
||||
ssl_retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
ssl_retry_count INTEGER NOT NULL,
|
||||
ssl_last_attempt_at TIMESTAMP WITH TIME ZONE,
|
||||
http_challenge_token TEXT,
|
||||
http_challenge_key_auth TEXT,
|
||||
@@ -100,6 +100,12 @@ SET certificate_id = cd.certificate_id
|
||||
FROM custom_domains cd
|
||||
WHERE cd.id = cc.custom_domain_id;
|
||||
|
||||
-- Entries that could not be repointed (stale custom_domain_id, or a domain
|
||||
-- whose certificate was never migrated) are unusable cache rows; drop them
|
||||
-- rather than leaving certificate_id NULL for callers that always expect it.
|
||||
DELETE FROM cached_certificates WHERE certificate_id IS NULL;
|
||||
|
||||
ALTER TABLE cached_certificates ALTER COLUMN certificate_id SET NOT NULL;
|
||||
ALTER TABLE cached_certificates DROP COLUMN custom_domain_id;
|
||||
|
||||
-- Drop the certificate lifecycle columns now living on certificates.
|
||||
|
||||
@@ -29,6 +29,19 @@ WITH pending_pages AS (
|
||||
FROM trust_centers tc
|
||||
WHERE tc.default_domain_id IS NULL
|
||||
AND NULLIF(current_setting('probo.trust_center_base_domain', true), '') IS NOT NULL
|
||||
-- Skip hostnames that already exist: minting a certificate or custom
|
||||
-- domain for them would violate their unique constraints and abort
|
||||
-- the whole migration.
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM certificates c
|
||||
WHERE c.hostname = (tc.slug || '.' || current_setting('probo.trust_center_base_domain', true))::citext
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM custom_domains cd
|
||||
WHERE cd.domain = (tc.slug || '.' || current_setting('probo.trust_center_base_domain', true))::citext
|
||||
)
|
||||
),
|
||||
minted_certificates AS (
|
||||
INSERT INTO certificates (
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
|
||||
UPDATE trust_centers
|
||||
SET
|
||||
slug = slug || '-' || encode(gen_random_bytes(4), 'hex'),
|
||||
slug = slug || '-' || encode(gen_random_bytes(16), 'hex'),
|
||||
updated_at = clock_timestamp();
|
||||
|
||||
Reference in New Issue
Block a user