mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-11 10:49:05 +00:00
Implement M3: expiration threshold alerting with dedup and status transitions
- Add alert_thresholds_days JSONB column to renewal_policies (default [30,14,7,0]) - Add RenewalPolicy.AlertThresholdsDays field + EffectiveAlertThresholds() helper - Add RenewalPolicyRepository interface + postgres implementation - Rewrite CheckExpiringCertificates with per-policy threshold alerting - Add SendThresholdAlert + HasThresholdNotification for deduplication via [threshold:N] tags - Add Type and MessageLike filters to NotificationFilter + postgres query support - Auto-transition certs to Expiring (>0 days) or Expired (<=0 days) status - Record expiration_alert_sent audit events per threshold crossing - Fix .gitignore: allow SQL migration files, scope server/agent build artifact rules - Track previously untracked cmd/ and migrations/ directories - Update docs (README, architecture, demo-advanced) for threshold alerting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
-- Rollback initial schema - drop tables in reverse dependency order
|
||||
|
||||
DROP TABLE IF EXISTS notification_events;
|
||||
DROP TABLE IF EXISTS audit_events;
|
||||
DROP TABLE IF EXISTS policy_violations;
|
||||
DROP TABLE IF EXISTS policy_rules;
|
||||
DROP TABLE IF EXISTS jobs;
|
||||
DROP TABLE IF EXISTS certificate_versions;
|
||||
DROP TABLE IF EXISTS certificate_target_mappings;
|
||||
DROP TABLE IF EXISTS deployment_targets;
|
||||
DROP TABLE IF EXISTS managed_certificates;
|
||||
DROP TABLE IF EXISTS agents;
|
||||
DROP TABLE IF EXISTS issuers;
|
||||
DROP TABLE IF EXISTS renewal_policies;
|
||||
DROP TABLE IF EXISTS owners;
|
||||
DROP TABLE IF EXISTS teams;
|
||||
|
||||
DROP EXTENSION IF EXISTS "uuid-ossp";
|
||||
@@ -0,0 +1,223 @@
|
||||
-- Create initial schema for certificate control plane
|
||||
-- IDs are TEXT to support application-generated prefixed IDs (e.g., "team-123", "cert-456")
|
||||
|
||||
-- Table: teams
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_name ON teams(name);
|
||||
|
||||
-- Table: owners
|
||||
CREATE TABLE IF NOT EXISTS owners (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_owners_email ON owners(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_owners_team_id ON owners(team_id);
|
||||
|
||||
-- Table: renewal_policies
|
||||
CREATE TABLE IF NOT EXISTS renewal_policies (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
renewal_window_days INT NOT NULL,
|
||||
auto_renew BOOLEAN NOT NULL DEFAULT true,
|
||||
max_retries INT NOT NULL DEFAULT 3,
|
||||
retry_interval_minutes INT NOT NULL DEFAULT 60,
|
||||
alert_thresholds_days JSONB NOT NULL DEFAULT '[30, 14, 7, 0]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_renewal_policies_name ON renewal_policies(name);
|
||||
|
||||
-- Table: issuers
|
||||
CREATE TABLE IF NOT EXISTS issuers (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
config JSONB,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_issuers_name ON issuers(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_issuers_enabled ON issuers(enabled);
|
||||
|
||||
-- Table: agents
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
hostname VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'offline',
|
||||
last_heartbeat_at TIMESTAMPTZ,
|
||||
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
api_key_hash VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_agents_hostname ON agents(hostname);
|
||||
CREATE INDEX IF NOT EXISTS idx_agents_last_heartbeat_at ON agents(last_heartbeat_at);
|
||||
|
||||
-- Table: managed_certificates
|
||||
CREATE TABLE IF NOT EXISTS managed_certificates (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
common_name VARCHAR(255) NOT NULL,
|
||||
sans TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
environment VARCHAR(50),
|
||||
owner_id TEXT NOT NULL REFERENCES owners(id) ON DELETE RESTRICT,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
issuer_id TEXT NOT NULL REFERENCES issuers(id) ON DELETE RESTRICT,
|
||||
renewal_policy_id TEXT NOT NULL REFERENCES renewal_policies(id) ON DELETE RESTRICT,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
expires_at TIMESTAMPTZ,
|
||||
tags JSONB NOT NULL DEFAULT '{}',
|
||||
last_renewal_at TIMESTAMPTZ,
|
||||
last_deployment_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_status ON managed_certificates(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_expires_at ON managed_certificates(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_owner_id ON managed_certificates(owner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_team_id ON managed_certificates(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_issuer_id ON managed_certificates(issuer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_managed_certificates_name ON managed_certificates(name);
|
||||
|
||||
-- Table: deployment_targets
|
||||
CREATE TABLE IF NOT EXISTS deployment_targets (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
||||
config JSONB,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_deployment_targets_agent_id ON deployment_targets(agent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_deployment_targets_enabled ON deployment_targets(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_deployment_targets_name ON deployment_targets(name);
|
||||
|
||||
-- Table: certificate_target_mappings
|
||||
CREATE TABLE IF NOT EXISTS certificate_target_mappings (
|
||||
certificate_id TEXT NOT NULL REFERENCES managed_certificates(id) ON DELETE CASCADE,
|
||||
target_id TEXT NOT NULL REFERENCES deployment_targets(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (certificate_id, target_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_certificate_target_mappings_target_id ON certificate_target_mappings(target_id);
|
||||
|
||||
-- Table: certificate_versions
|
||||
CREATE TABLE IF NOT EXISTS certificate_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
certificate_id TEXT NOT NULL REFERENCES managed_certificates(id) ON DELETE CASCADE,
|
||||
serial_number VARCHAR(255) NOT NULL,
|
||||
not_before TIMESTAMPTZ NOT NULL,
|
||||
not_after TIMESTAMPTZ NOT NULL,
|
||||
fingerprint_sha256 VARCHAR(255) NOT NULL UNIQUE,
|
||||
pem_chain TEXT NOT NULL,
|
||||
csr_pem TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_certificate_versions_certificate_id ON certificate_versions(certificate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_certificate_versions_fingerprint ON certificate_versions(fingerprint_sha256);
|
||||
|
||||
-- Table: jobs
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
certificate_id TEXT REFERENCES managed_certificates(id) ON DELETE CASCADE,
|
||||
target_id TEXT REFERENCES deployment_targets(id) ON DELETE SET NULL,
|
||||
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
attempts INT NOT NULL DEFAULT 0,
|
||||
max_attempts INT NOT NULL DEFAULT 3,
|
||||
last_error TEXT,
|
||||
deployment_result JSONB,
|
||||
scheduled_at TIMESTAMPTZ,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_certificate_id ON jobs(certificate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_scheduled_at ON jobs(scheduled_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_agent_id ON jobs(agent_id);
|
||||
|
||||
-- Table: policy_rules
|
||||
CREATE TABLE IF NOT EXISTS policy_rules (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
config JSONB NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_rules_name ON policy_rules(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_rules_enabled ON policy_rules(enabled);
|
||||
|
||||
-- Table: policy_violations
|
||||
CREATE TABLE IF NOT EXISTS policy_violations (
|
||||
id TEXT PRIMARY KEY,
|
||||
certificate_id TEXT NOT NULL REFERENCES managed_certificates(id) ON DELETE CASCADE,
|
||||
rule_id TEXT NOT NULL REFERENCES policy_rules(id) ON DELETE CASCADE,
|
||||
message TEXT NOT NULL,
|
||||
severity VARCHAR(50) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_violations_certificate_id ON policy_violations(certificate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_violations_rule_id ON policy_violations(rule_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_violations_severity ON policy_violations(severity);
|
||||
|
||||
-- Table: audit_events
|
||||
CREATE TABLE IF NOT EXISTS audit_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor VARCHAR(255) NOT NULL,
|
||||
actor_type VARCHAR(50) NOT NULL,
|
||||
action VARCHAR(255) NOT NULL,
|
||||
resource_type VARCHAR(255) NOT NULL,
|
||||
resource_id VARCHAR(255) NOT NULL,
|
||||
details JSONB,
|
||||
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_events_resource_type_id ON audit_events(resource_type, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_events_actor ON audit_events(actor);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_events_timestamp ON audit_events(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_events_action ON audit_events(action);
|
||||
|
||||
-- Table: notification_events
|
||||
CREATE TABLE IF NOT EXISTS notification_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
certificate_id TEXT REFERENCES managed_certificates(id) ON DELETE CASCADE,
|
||||
channel VARCHAR(255) NOT NULL,
|
||||
recipient VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
sent_at TIMESTAMPTZ,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_events_certificate_id ON notification_events(certificate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_events_status ON notification_events(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_events_type ON notification_events(type);
|
||||
@@ -0,0 +1,53 @@
|
||||
-- Seed data for certificate control plane
|
||||
|
||||
-- Default renewal policy
|
||||
INSERT INTO renewal_policies (id, name, renewal_window_days, auto_renew, max_retries, retry_interval_minutes, alert_thresholds_days)
|
||||
VALUES (
|
||||
'rp-default',
|
||||
'default',
|
||||
30,
|
||||
true,
|
||||
3,
|
||||
60,
|
||||
'[30, 14, 7, 0]'::jsonb
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Policy rules: Require owner assignment
|
||||
INSERT INTO policy_rules (id, name, type, config, enabled)
|
||||
VALUES (
|
||||
'pr-require-owner',
|
||||
'require-owner',
|
||||
'ownership',
|
||||
'{"requirement": "owner_id must be set"}'::jsonb,
|
||||
true
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Policy rules: Allowed environments
|
||||
INSERT INTO policy_rules (id, name, type, config, enabled)
|
||||
VALUES (
|
||||
'pr-allowed-environments',
|
||||
'allowed-environments',
|
||||
'environment',
|
||||
'{"allowed": ["production", "staging", "development"]}'::jsonb,
|
||||
true
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Policy rules: Maximum certificate lifetime
|
||||
INSERT INTO policy_rules (id, name, type, config, enabled)
|
||||
VALUES (
|
||||
'pr-max-certificate-lifetime',
|
||||
'max-certificate-lifetime',
|
||||
'lifetime',
|
||||
'{"max_days": 90}'::jsonb,
|
||||
true
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Policy rules: Minimum renewal window
|
||||
INSERT INTO policy_rules (id, name, type, config, enabled)
|
||||
VALUES (
|
||||
'pr-min-renewal-window',
|
||||
'min-renewal-window',
|
||||
'renewal_window',
|
||||
'{"min_days": 14}'::jsonb,
|
||||
true
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
@@ -0,0 +1,156 @@
|
||||
-- =============================================================================
|
||||
-- Demo Seed Data for certctl
|
||||
-- Run after schema migration to populate a realistic demo environment
|
||||
-- =============================================================================
|
||||
|
||||
-- Teams
|
||||
INSERT INTO teams (id, name, description, created_at, updated_at) VALUES
|
||||
('t-platform', 'Platform Engineering', 'Core infrastructure and platform services', NOW(), NOW()),
|
||||
('t-security', 'Security Operations', 'Security tooling and compliance', NOW(), NOW()),
|
||||
('t-payments', 'Payments', 'Payment processing services', NOW(), NOW()),
|
||||
('t-frontend', 'Frontend', 'Web and mobile applications', NOW(), NOW()),
|
||||
('t-data', 'Data Engineering', 'Data pipelines and analytics', NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Owners
|
||||
INSERT INTO owners (id, name, email, team_id, created_at, updated_at) VALUES
|
||||
('o-alice', 'Alice Chen', 'alice@example.com', 't-platform', NOW(), NOW()),
|
||||
('o-bob', 'Bob Martinez', 'bob@example.com', 't-security', NOW(), NOW()),
|
||||
('o-carol', 'Carol Williams', 'carol@example.com', 't-payments', NOW(), NOW()),
|
||||
('o-dave', 'Dave Kim', 'dave@example.com', 't-frontend', NOW(), NOW()),
|
||||
('o-eve', 'Eve Johnson', 'eve@example.com', 't-data', NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Renewal Policies
|
||||
INSERT INTO renewal_policies (id, name, renewal_window_days, auto_renew, max_retries, retry_interval_minutes, alert_thresholds_days, created_at, updated_at) VALUES
|
||||
('rp-standard', 'Standard 30-day', 30, true, 3, 60, '[30, 14, 7, 0]'::jsonb, NOW(), NOW()),
|
||||
('rp-urgent', 'Urgent 14-day', 14, true, 5, 30, '[14, 7, 3, 0]'::jsonb, NOW(), NOW()),
|
||||
('rp-manual', 'Manual Only', 30, false, 0, 0, '[30, 14, 7, 0]'::jsonb, NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Issuers
|
||||
INSERT INTO issuers (id, name, type, config, enabled, created_at, updated_at) VALUES
|
||||
('iss-local', 'Local Dev CA', 'local', '{"ca_common_name": "CertCtl Demo CA", "validity_days": 90}', true, NOW(), NOW()),
|
||||
('iss-acme-le', 'Let''s Encrypt Staging', 'acme', '{"directory_url": "https://acme-staging-v02.api.letsencrypt.org/directory", "email": "admin@example.com"}', true, NOW(), NOW()),
|
||||
('iss-digicert', 'DigiCert (disabled)', 'generic_ca', '{"api_url": "https://api.digicert.com", "api_key": "REDACTED"}', false, NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Agents
|
||||
INSERT INTO agents (id, name, hostname, status, last_heartbeat_at, registered_at, api_key_hash) VALUES
|
||||
('ag-web-prod', 'web-prod-agent', 'web-prod-01.internal', 'online', NOW() - INTERVAL '30 seconds', NOW() - INTERVAL '90 days', 'demo_hash_1'),
|
||||
('ag-web-staging', 'web-staging-agent', 'web-stg-01.internal', 'online', NOW() - INTERVAL '45 seconds', NOW() - INTERVAL '60 days', 'demo_hash_2'),
|
||||
('ag-lb-prod', 'lb-prod-agent', 'f5-prod-01.internal', 'online', NOW() - INTERVAL '15 seconds', NOW() - INTERVAL '120 days', 'demo_hash_3'),
|
||||
('ag-iis-prod', 'iis-prod-agent', 'iis-prod-01.internal', 'offline', NOW() - INTERVAL '3 hours', NOW() - INTERVAL '30 days', 'demo_hash_4'),
|
||||
('ag-data-prod', 'data-prod-agent', 'data-prod-01.internal', 'online', NOW() - INTERVAL '20 seconds', NOW() - INTERVAL '45 days', 'demo_hash_5')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Deployment Targets
|
||||
INSERT INTO deployment_targets (id, name, type, agent_id, config, enabled, created_at, updated_at) VALUES
|
||||
('tgt-nginx-prod', 'NGINX Production', 'nginx', 'ag-web-prod', '{"cert_path": "/etc/nginx/ssl/cert.pem", "key_path": "/etc/nginx/ssl/key.pem", "reload_command": "nginx -s reload"}', true, NOW(), NOW()),
|
||||
('tgt-nginx-staging', 'NGINX Staging', 'nginx', 'ag-web-staging', '{"cert_path": "/etc/nginx/ssl/cert.pem", "key_path": "/etc/nginx/ssl/key.pem", "reload_command": "nginx -s reload"}', true, NOW(), NOW()),
|
||||
('tgt-f5-prod', 'F5 BIG-IP Production','f5', 'ag-lb-prod', '{"host": "f5-prod-01.internal", "partition": "Common", "ssl_profile": "clientssl"}', true, NOW(), NOW()),
|
||||
('tgt-iis-prod', 'IIS Production', 'iis', 'ag-iis-prod', '{"site_name": "Default Web Site", "binding_info": "*:443:"}', true, NOW(), NOW()),
|
||||
('tgt-nginx-data', 'NGINX Data Services', 'nginx', 'ag-data-prod', '{"cert_path": "/etc/nginx/ssl/cert.pem", "key_path": "/etc/nginx/ssl/key.pem", "reload_command": "nginx -s reload"}', true, NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Managed Certificates — varied statuses and expiry dates for realistic dashboard
|
||||
INSERT INTO managed_certificates (id, name, common_name, sans, environment, owner_id, team_id, issuer_id, renewal_policy_id, status, expires_at, tags, last_renewal_at, last_deployment_at, created_at, updated_at) VALUES
|
||||
-- Active, healthy certs
|
||||
('mc-api-prod', 'api-production', 'api.example.com', ARRAY['api.example.com', 'api-v2.example.com'], 'production', 'o-alice', 't-platform', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '75 days', '{"service": "api-gateway", "tier": "critical"}', NOW() - INTERVAL '15 days', NOW() - INTERVAL '15 days', NOW() - INTERVAL '180 days', NOW()),
|
||||
('mc-web-prod', 'web-production', 'www.example.com', ARRAY['www.example.com', 'example.com'], 'production', 'o-dave', 't-frontend', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '60 days', '{"service": "web-app", "tier": "critical"}', NOW() - INTERVAL '30 days', NOW() - INTERVAL '30 days', NOW() - INTERVAL '365 days', NOW()),
|
||||
('mc-pay-prod', 'payments-production', 'pay.example.com', ARRAY['pay.example.com', 'checkout.example.com'], 'production', 'o-carol', 't-payments', 'iss-local', 'rp-urgent', 'active', NOW() + INTERVAL '45 days', '{"service": "payments", "tier": "critical", "pci": "true"}', NOW() - INTERVAL '45 days', NOW() - INTERVAL '45 days', NOW() - INTERVAL '200 days', NOW()),
|
||||
('mc-dash-prod', 'dashboard-production', 'dashboard.example.com', ARRAY['dashboard.example.com'], 'production', 'o-dave', 't-frontend', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '82 days', '{"service": "dashboard", "tier": "high"}', NOW() - INTERVAL '8 days', NOW() - INTERVAL '8 days', NOW() - INTERVAL '100 days', NOW()),
|
||||
('mc-data-prod', 'data-api-production', 'data.example.com', ARRAY['data.example.com', 'analytics.example.com'], 'production', 'o-eve', 't-data', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '55 days', '{"service": "data-api", "tier": "high"}', NOW() - INTERVAL '35 days', NOW() - INTERVAL '35 days', NOW() - INTERVAL '150 days', NOW()),
|
||||
|
||||
-- Expiring soon (< 30 days)
|
||||
('mc-auth-prod', 'auth-production', 'auth.example.com', ARRAY['auth.example.com', 'login.example.com', 'sso.example.com'], 'production', 'o-bob', 't-security', 'iss-local', 'rp-urgent', 'expiring', NOW() + INTERVAL '12 days', '{"service": "auth", "tier": "critical"}', NOW() - INTERVAL '78 days', NOW() - INTERVAL '78 days', NOW() - INTERVAL '300 days', NOW()),
|
||||
('mc-cdn-prod', 'cdn-production', 'cdn.example.com', ARRAY['cdn.example.com', 'static.example.com'], 'production', 'o-alice', 't-platform', 'iss-local', 'rp-standard', 'expiring', NOW() + INTERVAL '8 days', '{"service": "cdn", "tier": "high"}', NOW() - INTERVAL '82 days', NOW() - INTERVAL '82 days', NOW() - INTERVAL '250 days', NOW()),
|
||||
('mc-mail-prod', 'mail-production', 'mail.example.com', ARRAY['mail.example.com', 'smtp.example.com'], 'production', 'o-bob', 't-security', 'iss-local', 'rp-standard', 'expiring', NOW() + INTERVAL '5 days', '{"service": "email", "tier": "medium"}', NOW() - INTERVAL '85 days', NOW() - INTERVAL '85 days', NOW() - INTERVAL '400 days', NOW()),
|
||||
|
||||
-- Expired
|
||||
('mc-legacy-prod', 'legacy-app', 'legacy.example.com', ARRAY['legacy.example.com'], 'production', 'o-alice', 't-platform', 'iss-local', 'rp-manual', 'expired', NOW() - INTERVAL '3 days', '{"service": "legacy", "tier": "low", "decom": "planned"}', NOW() - INTERVAL '93 days', NOW() - INTERVAL '93 days', NOW() - INTERVAL '500 days', NOW()),
|
||||
('mc-old-api', 'old-api-v1', 'api-v1.example.com', ARRAY['api-v1.example.com'], 'production', 'o-alice', 't-platform', 'iss-local', 'rp-manual', 'expired', NOW() - INTERVAL '15 days', '{"service": "api-v1", "tier": "low", "deprecated": "true"}', NULL, NULL, NOW() - INTERVAL '600 days', NOW()),
|
||||
|
||||
-- Staging certs
|
||||
('mc-api-stg', 'api-staging', 'api.staging.example.com', ARRAY['api.staging.example.com'], 'staging', 'o-alice', 't-platform', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '65 days', '{"service": "api-gateway", "tier": "low"}', NOW() - INTERVAL '25 days', NOW() - INTERVAL '25 days', NOW() - INTERVAL '120 days', NOW()),
|
||||
('mc-web-stg', 'web-staging', 'www.staging.example.com', ARRAY['www.staging.example.com', 'staging.example.com'], 'staging', 'o-dave', 't-frontend', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '70 days', '{"service": "web-app", "tier": "low"}', NOW() - INTERVAL '20 days', NOW() - INTERVAL '20 days', NOW() - INTERVAL '100 days', NOW()),
|
||||
|
||||
-- Renewal in progress
|
||||
('mc-grafana-prod', 'grafana-production', 'grafana.example.com', ARRAY['grafana.example.com', 'metrics.example.com'], 'production', 'o-eve', 't-data', 'iss-local', 'rp-standard', 'renewal_in_progress', NOW() + INTERVAL '3 days', '{"service": "monitoring", "tier": "high"}', NOW() - INTERVAL '87 days', NOW() - INTERVAL '87 days', NOW() - INTERVAL '180 days', NOW()),
|
||||
|
||||
-- Failed
|
||||
('mc-vpn-prod', 'vpn-production', 'vpn.example.com', ARRAY['vpn.example.com'], 'production', 'o-bob', 't-security', 'iss-acme-le', 'rp-urgent', 'failed', NOW() + INTERVAL '1 day', '{"service": "vpn", "tier": "critical"}', NULL, NULL, NOW() - INTERVAL '90 days', NOW()),
|
||||
|
||||
-- Wildcard
|
||||
('mc-wildcard-prod', 'wildcard-production', '*.example.com', ARRAY['*.example.com', 'example.com'], 'production', 'o-alice', 't-platform', 'iss-local', 'rp-standard', 'active', NOW() + INTERVAL '50 days', '{"service": "wildcard", "tier": "critical"}', NOW() - INTERVAL '40 days', NOW() - INTERVAL '40 days', NOW() - INTERVAL '365 days', NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Certificate-Target Mappings
|
||||
INSERT INTO certificate_target_mappings (certificate_id, target_id) VALUES
|
||||
('mc-api-prod', 'tgt-nginx-prod'),
|
||||
('mc-api-prod', 'tgt-f5-prod'),
|
||||
('mc-web-prod', 'tgt-nginx-prod'),
|
||||
('mc-web-prod', 'tgt-f5-prod'),
|
||||
('mc-pay-prod', 'tgt-nginx-prod'),
|
||||
('mc-pay-prod', 'tgt-f5-prod'),
|
||||
('mc-dash-prod', 'tgt-nginx-prod'),
|
||||
('mc-data-prod', 'tgt-nginx-data'),
|
||||
('mc-auth-prod', 'tgt-nginx-prod'),
|
||||
('mc-auth-prod', 'tgt-f5-prod'),
|
||||
('mc-cdn-prod', 'tgt-f5-prod'),
|
||||
('mc-mail-prod', 'tgt-nginx-prod'),
|
||||
('mc-legacy-prod', 'tgt-iis-prod'),
|
||||
('mc-api-stg', 'tgt-nginx-staging'),
|
||||
('mc-web-stg', 'tgt-nginx-staging'),
|
||||
('mc-grafana-prod', 'tgt-nginx-data'),
|
||||
('mc-vpn-prod', 'tgt-f5-prod'),
|
||||
('mc-wildcard-prod', 'tgt-nginx-prod'),
|
||||
('mc-wildcard-prod', 'tgt-f5-prod'),
|
||||
('mc-wildcard-prod', 'tgt-nginx-staging')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Certificate Versions (latest version for each active cert)
|
||||
INSERT INTO certificate_versions (id, certificate_id, serial_number, not_before, not_after, fingerprint_sha256, pem_chain, csr_pem, created_at) VALUES
|
||||
('cv-api-1', 'mc-api-prod', '0A:1B:2C:3D:4E:5F:00:01', NOW() - INTERVAL '15 days', NOW() + INTERVAL '75 days', 'sha256:ab12cd34ef56', '-----BEGIN CERTIFICATE-----\nMIIDemoAPI...\n-----END CERTIFICATE-----', NULL, NOW() - INTERVAL '15 days'),
|
||||
('cv-web-1', 'mc-web-prod', '0A:1B:2C:3D:4E:5F:00:02', NOW() - INTERVAL '30 days', NOW() + INTERVAL '60 days', 'sha256:cd34ef56ab12', '-----BEGIN CERTIFICATE-----\nMIIDemoWeb...\n-----END CERTIFICATE-----', NULL, NOW() - INTERVAL '30 days'),
|
||||
('cv-pay-1', 'mc-pay-prod', '0A:1B:2C:3D:4E:5F:00:03', NOW() - INTERVAL '45 days', NOW() + INTERVAL '45 days', 'sha256:ef56ab12cd34', '-----BEGIN CERTIFICATE-----\nMIIDemoPay...\n-----END CERTIFICATE-----', NULL, NOW() - INTERVAL '45 days'),
|
||||
('cv-auth-1', 'mc-auth-prod', '0A:1B:2C:3D:4E:5F:00:04', NOW() - INTERVAL '78 days', NOW() + INTERVAL '12 days', 'sha256:1234abcdef56', '-----BEGIN CERTIFICATE-----\nMIIDemoAuth...\n-----END CERTIFICATE-----', NULL, NOW() - INTERVAL '78 days'),
|
||||
('cv-wild-1', 'mc-wildcard-prod', '0A:1B:2C:3D:4E:5F:00:05', NOW() - INTERVAL '40 days', NOW() + INTERVAL '50 days', 'sha256:5678abcdef12', '-----BEGIN CERTIFICATE-----\nMIIDemoWild...\n-----END CERTIFICATE-----', NULL, NOW() - INTERVAL '40 days')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Recent Audit Events
|
||||
INSERT INTO audit_events (id, actor, actor_type, action, resource_type, resource_id, details, timestamp) VALUES
|
||||
('audit-demo-01', 'alice@example.com', 'user', 'certificate.renewed', 'certificate', 'mc-api-prod', '{"issuer": "local", "serial": "0A:1B:2C:3D:4E:5F:00:01"}', NOW() - INTERVAL '15 days'),
|
||||
('audit-demo-02', 'system', 'system', 'certificate.deployed', 'certificate', 'mc-api-prod', '{"target": "tgt-nginx-prod", "status": "success"}', NOW() - INTERVAL '15 days' + INTERVAL '5 minutes'),
|
||||
('audit-demo-03', 'system', 'system', 'certificate.deployed', 'certificate', 'mc-api-prod', '{"target": "tgt-f5-prod", "status": "success"}', NOW() - INTERVAL '15 days' + INTERVAL '8 minutes'),
|
||||
('audit-demo-04', 'dave@example.com', 'user', 'certificate.renewed', 'certificate', 'mc-web-prod', '{"issuer": "local", "serial": "0A:1B:2C:3D:4E:5F:00:02"}', NOW() - INTERVAL '30 days'),
|
||||
('audit-demo-05', 'carol@example.com', 'user', 'certificate.created', 'certificate', 'mc-pay-prod', '{"common_name": "pay.example.com"}', NOW() - INTERVAL '200 days'),
|
||||
('audit-demo-06', 'system', 'system', 'renewal.started', 'certificate', 'mc-grafana-prod', '{"reason": "expiring_in_3_days"}', NOW() - INTERVAL '2 hours'),
|
||||
('audit-demo-07', 'system', 'system', 'renewal.failed', 'certificate', 'mc-vpn-prod', '{"error": "ACME challenge failed: DNS timeout", "attempt": 3}', NOW() - INTERVAL '1 hour'),
|
||||
('audit-demo-08', 'system', 'system', 'expiration.warning', 'certificate', 'mc-auth-prod', '{"days_until_expiry": 12}', NOW() - INTERVAL '30 minutes'),
|
||||
('audit-demo-09', 'system', 'system', 'expiration.warning', 'certificate', 'mc-cdn-prod', '{"days_until_expiry": 8}', NOW() - INTERVAL '25 minutes'),
|
||||
('audit-demo-10', 'system', 'system', 'expiration.warning', 'certificate', 'mc-mail-prod', '{"days_until_expiry": 5}', NOW() - INTERVAL '20 minutes'),
|
||||
('audit-demo-11', 'bob@example.com', 'user', 'agent.registered', 'agent', 'ag-iis-prod', '{"hostname": "iis-prod-01.internal"}', NOW() - INTERVAL '30 days'),
|
||||
('audit-demo-12', 'system', 'system', 'agent.offline', 'agent', 'ag-iis-prod', '{"last_heartbeat": "3 hours ago"}', NOW() - INTERVAL '3 hours'),
|
||||
('audit-demo-13', 'alice@example.com', 'user', 'policy.violation', 'certificate', 'mc-legacy-prod', '{"rule": "max-certificate-lifetime", "message": "Certificate expired"}', NOW() - INTERVAL '3 days'),
|
||||
('audit-demo-14', 'bob@example.com', 'user', 'issuer.configured', 'issuer', 'iss-local', '{"type": "local", "ca_common_name": "CertCtl Demo CA"}', NOW() - INTERVAL '90 days'),
|
||||
('audit-demo-15', 'alice@example.com', 'user', 'target.configured', 'target', 'tgt-nginx-prod', '{"type": "nginx", "agent": "ag-web-prod"}', NOW() - INTERVAL '90 days')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Policy Violations (reference policy rules by their IDs from seed.sql)
|
||||
INSERT INTO policy_violations (id, certificate_id, rule_id, message, severity, created_at) VALUES
|
||||
('pv-demo-01', 'mc-legacy-prod', 'pr-max-certificate-lifetime', 'Certificate has expired and exceeds maximum lifetime policy', 'critical', NOW() - INTERVAL '3 days'),
|
||||
('pv-demo-02', 'mc-old-api', 'pr-max-certificate-lifetime', 'Certificate expired 15 days ago', 'critical', NOW() - INTERVAL '15 days'),
|
||||
('pv-demo-03', 'mc-vpn-prod', 'pr-min-renewal-window', 'Renewal failed within minimum renewal window', 'error', NOW() - INTERVAL '1 hour'),
|
||||
('pv-demo-04', 'mc-mail-prod', 'pr-min-renewal-window', 'Certificate expiring in 5 days, below 14-day minimum window','warning', NOW() - INTERVAL '20 minutes')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Notification Events
|
||||
INSERT INTO notification_events (id, type, certificate_id, channel, recipient, message, sent_at, status, error) VALUES
|
||||
('ne-demo-01', 'expiration_warning', 'mc-auth-prod', 'email', 'bob@example.com', 'Certificate auth-production expires in 12 days', NOW() - INTERVAL '30 minutes', 'sent', NULL),
|
||||
('ne-demo-02', 'expiration_warning', 'mc-cdn-prod', 'email', 'alice@example.com', 'Certificate cdn-production expires in 8 days', NOW() - INTERVAL '25 minutes', 'sent', NULL),
|
||||
('ne-demo-03', 'expiration_warning', 'mc-mail-prod', 'email', 'bob@example.com', 'Certificate mail-production expires in 5 days', NOW() - INTERVAL '20 minutes', 'sent', NULL),
|
||||
('ne-demo-04', 'renewal_failure', 'mc-vpn-prod', 'webhook', 'https://hooks.example.com/certctl', 'Renewal failed for vpn-production after 3 attempts', NOW() - INTERVAL '1 hour', 'sent', NULL),
|
||||
('ne-demo-05', 'renewal_success', 'mc-api-prod', 'email', 'alice@example.com', 'Certificate api-production renewed successfully', NOW() - INTERVAL '15 days', 'sent', NULL),
|
||||
('ne-demo-06', 'deployment_success', 'mc-api-prod', 'webhook', 'https://hooks.example.com/certctl', 'Certificate api-production deployed to NGINX Production', NOW() - INTERVAL '15 days', 'sent', NULL)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
Reference in New Issue
Block a user