mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:11:29 +00:00
21aeed4f4e
Phase 0 closure (Path B2, post-rewrite):
addlicense sweep — adds the canonical certctl LLC copyright + BUSL-1.1
SPDX header to every production Go file. Template:
// Copyright 2026 certctl LLC. All rights reserved.
// SPDX-License-Identifier: BUSL-1.1
Coverage: 338 / 338 production Go files (cmd/ + internal/, excluding
*_test.go and **/testdata/**). Pre-sweep coverage was 22 / 338 (6.5%);
post-sweep is 338 / 338 (100%).
Normalized 22 pre-existing legacy headers (`// Copyright (c) certctl`
+ `// SPDX-License-Identifier: BSL-1.1`) and 1 file using a
`Certctl Contributors` attribution. The legacy SPDX ID `BSL-1.1`
is non-standard; the official SPDX identifier for Business Source
License 1.1 is `BUSL-1.1` (capital U). All 338 files now share the
canonical form.
Generated via:
addlicense -c "certctl LLC" -y 2026 \
-f cowork/legal/copyright-header.tpl \
-ignore '**/testdata/**' -ignore '**/*_test.go' \
cmd/ internal/
Verification:
find cmd internal -name '*.go' -not -name '*_test.go' \
-not -path '*/testdata/*' \
-exec grep -L '^// Copyright 2026 certctl LLC' {} \; | wc -l
Returns: 0
gofmt clean. Header additions are comments only, no compile impact.
Closes: cowork/certctl-architecture-diligence-audit.html#fix-RED-4
250 lines
10 KiB
Go
250 lines
10 KiB
Go
// Copyright 2026 certctl LLC. All rights reserved.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package domain
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// ManagedCertificate represents a certificate managed by the control plane.
|
|
type ManagedCertificate struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
CommonName string `json:"common_name"`
|
|
SANs []string `json:"sans"`
|
|
Environment string `json:"environment"`
|
|
OwnerID string `json:"owner_id"`
|
|
TeamID string `json:"team_id"`
|
|
IssuerID string `json:"issuer_id"`
|
|
TargetIDs []string `json:"target_ids"`
|
|
RenewalPolicyID string `json:"renewal_policy_id"`
|
|
CertificateProfileID string `json:"certificate_profile_id,omitempty"`
|
|
Status CertificateStatus `json:"status"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
Tags map[string]string `json:"tags"`
|
|
LastRenewalAt *time.Time `json:"last_renewal_at,omitempty"`
|
|
LastDeploymentAt *time.Time `json:"last_deployment_at,omitempty"`
|
|
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
|
RevocationReason string `json:"revocation_reason,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// Source tags how this managed certificate was created. EST RFC 7030
|
|
// hardening master bundle Phase 11.1 — operators bulk-revoke
|
|
// EST-issued certs by filtering on Source=EST. Empty value preserves
|
|
// the v2.X.0 behavior (the bulk-revoke handler treats empty as
|
|
// equivalent to legacy/manual; new EST issuances stamp Source=EST,
|
|
// new SCEP issuances will eventually stamp Source=SCEP under a
|
|
// future bundle).
|
|
Source CertificateSource `json:"source,omitempty"`
|
|
}
|
|
|
|
// CertificateSource is the enum of provenance values stamped on each
|
|
// managed-certificate row when it's created. The empty string is the
|
|
// back-compat default — pre-Phase-11 rows have it set to "" by the
|
|
// migration's DEFAULT clause; the bulk-revoke filter treats empty as
|
|
// "any source" so existing call paths see no behavior change.
|
|
//
|
|
// EST RFC 7030 hardening master bundle Phase 11.1.
|
|
type CertificateSource string
|
|
|
|
const (
|
|
// CertificateSourceUnspecified preserves the v2.X.0 default ("").
|
|
CertificateSourceUnspecified CertificateSource = ""
|
|
// CertificateSourceEST stamps every cert issued through one of the
|
|
// EST endpoints (simpleenroll / simplereenroll / serverkeygen).
|
|
CertificateSourceEST CertificateSource = "EST"
|
|
// CertificateSourceSCEP / API / Agent reserve future provenance
|
|
// values — not stamped today; SCEP-issued certs continue to land
|
|
// with Source="" until a follow-up bundle wires the stamp at the
|
|
// SCEP service layer.
|
|
CertificateSourceSCEP CertificateSource = "SCEP"
|
|
CertificateSourceAPI CertificateSource = "API"
|
|
CertificateSourceAgent CertificateSource = "Agent"
|
|
// CertificateSourceACME stamps every cert issued through the
|
|
// built-in ACME server endpoint (RFC 8555 finalize → cert
|
|
// download). The ACME service (internal/service/acme.go)
|
|
// pins this on every managed_certificates row it inserts at
|
|
// finalize time. Operators bulk-revoke ACME-issued certs by
|
|
// filtering on Source=ACME.
|
|
CertificateSourceACME CertificateSource = "ACME"
|
|
)
|
|
|
|
// CertificateVersion represents a specific version of a certificate.
|
|
type CertificateVersion struct {
|
|
ID string `json:"id"`
|
|
CertificateID string `json:"certificate_id"`
|
|
SerialNumber string `json:"serial_number"`
|
|
NotBefore time.Time `json:"not_before"`
|
|
NotAfter time.Time `json:"not_after"`
|
|
FingerprintSHA256 string `json:"fingerprint_sha256"`
|
|
PEMChain string `json:"pem_chain"`
|
|
CSRPEM string `json:"csr_pem"`
|
|
KeyAlgorithm string `json:"key_algorithm,omitempty"`
|
|
KeySize int `json:"key_size,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// CertificateStatus represents the lifecycle status of a managed certificate.
|
|
type CertificateStatus string
|
|
|
|
const (
|
|
CertificateStatusPending CertificateStatus = "Pending"
|
|
CertificateStatusActive CertificateStatus = "Active"
|
|
CertificateStatusExpiring CertificateStatus = "Expiring"
|
|
CertificateStatusExpired CertificateStatus = "Expired"
|
|
CertificateStatusRenewalInProgress CertificateStatus = "RenewalInProgress"
|
|
CertificateStatusFailed CertificateStatus = "Failed"
|
|
CertificateStatusRevoked CertificateStatus = "Revoked"
|
|
CertificateStatusArchived CertificateStatus = "Archived"
|
|
)
|
|
|
|
// RenewalPolicy defines renewal parameters for a managed certificate.
|
|
type RenewalPolicy struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
RenewalWindowDays int `json:"renewal_window_days"`
|
|
AutoRenew bool `json:"auto_renew"`
|
|
MaxRetries int `json:"max_retries"`
|
|
RetryInterval int `json:"retry_interval_seconds"`
|
|
AlertThresholdsDays []int `json:"alert_thresholds_days"`
|
|
CertificateProfileID string `json:"certificate_profile_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// AlertChannels is the per-policy channel-matrix that maps each
|
|
// severity tier ("informational" / "warning" / "critical") to the
|
|
// set of NotificationChannel values that receive expiry alerts at
|
|
// that tier. Values are slices of channel-name strings matching
|
|
// the NotificationChannel constants ("Email", "Slack", "Teams",
|
|
// "PagerDuty", "OpsGenie", "Webhook"). nil or empty falls back to
|
|
// DefaultAlertChannels (Email-only across all tiers, the pre-2026-05-03
|
|
// behaviour preserved as the safe default for operators who have
|
|
// not yet opted into multi-channel routing).
|
|
//
|
|
// Off-enum severity keys or channel values are silently dropped at
|
|
// the dispatch site (closed-enum discipline; we do NOT dynamically
|
|
// grow Prometheus cardinality on a typo).
|
|
//
|
|
// Rank 4 of the 2026-05-03 Infisical deep-research deliverable
|
|
// (the project's deep-research deliverable, Part 5).
|
|
AlertChannels map[string][]string `json:"alert_channels,omitempty"`
|
|
|
|
// AlertSeverityMap maps each threshold-day value to its severity
|
|
// tier. Off-map thresholds default to "informational". Operators
|
|
// with non-default AlertThresholdsDays values supply their own
|
|
// severity mapping; operators on the canonical 30/14/7/0 thresholds
|
|
// can leave this empty to inherit DefaultAlertSeverityMap which
|
|
// maps:
|
|
//
|
|
// 30 → informational
|
|
// 14 → warning
|
|
// 7 → warning
|
|
// 0 → critical
|
|
AlertSeverityMap map[int]string `json:"alert_severity_map,omitempty"`
|
|
}
|
|
|
|
// DefaultAlertThresholds returns the standard alert thresholds when none are configured.
|
|
func DefaultAlertThresholds() []int {
|
|
return []int{30, 14, 7, 0}
|
|
}
|
|
|
|
// EffectiveAlertThresholds returns the configured thresholds or defaults if empty.
|
|
func (p *RenewalPolicy) EffectiveAlertThresholds() []int {
|
|
if len(p.AlertThresholdsDays) > 0 {
|
|
return p.AlertThresholdsDays
|
|
}
|
|
return DefaultAlertThresholds()
|
|
}
|
|
|
|
// Severity-tier names for the channel matrix. Closed-enum to keep
|
|
// Prometheus cardinality bounded and operator typos surfaceable in
|
|
// audit logs (off-enum tier values are dropped at dispatch).
|
|
const (
|
|
AlertSeverityInformational = "informational"
|
|
AlertSeverityWarning = "warning"
|
|
AlertSeverityCritical = "critical"
|
|
)
|
|
|
|
// DefaultAlertChannels returns the back-compat default channel matrix
|
|
// — Email only at every tier. This preserves the pre-2026-05-03
|
|
// behaviour for operators who have not yet opted into multi-channel
|
|
// routing. Nil or empty AlertChannels on a RenewalPolicy is read as
|
|
// "use this default."
|
|
func DefaultAlertChannels() map[string][]string {
|
|
return map[string][]string{
|
|
AlertSeverityInformational: {string(NotificationChannelEmail)},
|
|
AlertSeverityWarning: {string(NotificationChannelEmail)},
|
|
AlertSeverityCritical: {string(NotificationChannelEmail)},
|
|
}
|
|
}
|
|
|
|
// DefaultAlertSeverityMap returns the canonical threshold-to-tier
|
|
// mapping for the standard 30/14/7/0 thresholds. Operators with
|
|
// custom thresholds supply their own mapping.
|
|
func DefaultAlertSeverityMap() map[int]string {
|
|
return map[int]string{
|
|
30: AlertSeverityInformational,
|
|
14: AlertSeverityWarning,
|
|
7: AlertSeverityWarning,
|
|
0: AlertSeverityCritical,
|
|
}
|
|
}
|
|
|
|
// EffectiveAlertChannels returns the configured channel matrix on
|
|
// the policy, or the default if unset. Used by the dispatch site in
|
|
// RenewalService.sendThresholdAlerts to resolve the channel set for
|
|
// a given tier.
|
|
//
|
|
// A returned map is safe to mutate by the caller — the default-path
|
|
// branch returns a fresh map; the configured-path branch returns the
|
|
// caller-supplied map (which the caller already owns).
|
|
func (p *RenewalPolicy) EffectiveAlertChannels() map[string][]string {
|
|
if p == nil || len(p.AlertChannels) == 0 {
|
|
return DefaultAlertChannels()
|
|
}
|
|
return p.AlertChannels
|
|
}
|
|
|
|
// EffectiveAlertSeverity returns the severity tier for a given
|
|
// threshold. Off-map thresholds resolve to "informational" so a
|
|
// custom-thresholds policy without an explicit severity map still
|
|
// gets dispatch (just at the lowest tier).
|
|
func (p *RenewalPolicy) EffectiveAlertSeverity(threshold int) string {
|
|
if p != nil {
|
|
if tier, ok := p.AlertSeverityMap[threshold]; ok {
|
|
return tier
|
|
}
|
|
}
|
|
if tier, ok := DefaultAlertSeverityMap()[threshold]; ok {
|
|
return tier
|
|
}
|
|
return AlertSeverityInformational
|
|
}
|
|
|
|
// IsValidAlertSeverityTier reports whether tier is one of the closed-enum
|
|
// severity values. Used by the policy validation path in
|
|
// service.RenewalPolicyService to reject typos at write time.
|
|
func IsValidAlertSeverityTier(tier string) bool {
|
|
switch tier {
|
|
case AlertSeverityInformational, AlertSeverityWarning, AlertSeverityCritical:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsValidNotificationChannel reports whether channel is one of the
|
|
// closed-enum NotificationChannel values. Used by the policy
|
|
// validation path to reject typos at write time AND by the dispatch
|
|
// site to defensively drop off-enum values that survived a migration.
|
|
func IsValidNotificationChannel(channel string) bool {
|
|
switch NotificationChannel(channel) {
|
|
case NotificationChannelEmail, NotificationChannelWebhook,
|
|
NotificationChannelSlack, NotificationChannelTeams,
|
|
NotificationChannelPagerDuty, NotificationChannelOpsGenie:
|
|
return true
|
|
}
|
|
return false
|
|
}
|