Files
shankar0123 21aeed4f4e legal: addlicense headers + normalize legacy variants (Phase 0 RED-4)
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
2026-05-13 21:23:35 +00:00

149 lines
4.4 KiB
Go

// Copyright 2026 certctl LLC. All rights reserved.
// SPDX-License-Identifier: BUSL-1.1
package postgres
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/certctl-io/certctl/internal/domain"
"github.com/certctl-io/certctl/internal/repository"
)
// OCSPResponderRepository implements repository.OCSPResponderRepository.
//
// One row per issuer; rotation is an upsert (no historical rows kept —
// operators have the audit log + the previous CertSerial recorded in
// rotated_from for the most-recent rotation).
type OCSPResponderRepository struct {
db *sql.DB
}
// NewOCSPResponderRepository creates a new repository.
func NewOCSPResponderRepository(db *sql.DB) *OCSPResponderRepository {
return &OCSPResponderRepository{db: db}
}
// Compile-time interface check.
var _ repository.OCSPResponderRepository = (*OCSPResponderRepository)(nil)
// Get returns the current responder row, or (nil, nil) when missing.
func (r *OCSPResponderRepository) Get(ctx context.Context, issuerID string) (*domain.OCSPResponder, error) {
const query = `
SELECT issuer_id, cert_pem, cert_serial, key_path, key_alg,
not_before, not_after, COALESCE(rotated_from, ''),
created_at, updated_at
FROM ocsp_responders
WHERE issuer_id = $1
`
var resp domain.OCSPResponder
err := r.db.QueryRowContext(ctx, query, issuerID).Scan(
&resp.IssuerID,
&resp.CertPEM,
&resp.CertSerial,
&resp.KeyPath,
&resp.KeyAlg,
&resp.NotBefore,
&resp.NotAfter,
&resp.RotatedFrom,
&resp.CreatedAt,
&resp.UpdatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("ocsp_responders get %q: %w", issuerID, err)
}
return &resp, nil
}
// Put upserts the responder row. The DB sets created_at on first insert
// (default NOW()) and updated_at on every write (NOW() in the SET clause).
// Callers leave CreatedAt + UpdatedAt zero; the DB authoritative for both.
func (r *OCSPResponderRepository) Put(ctx context.Context, responder *domain.OCSPResponder) error {
if responder == nil {
return errors.New("ocsp_responders put: nil responder")
}
if responder.IssuerID == "" {
return errors.New("ocsp_responders put: empty issuer_id")
}
const query = `
INSERT INTO ocsp_responders (
issuer_id, cert_pem, cert_serial, key_path, key_alg,
not_before, not_after, rotated_from, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, NULLIF($8, ''), NOW())
ON CONFLICT (issuer_id) DO UPDATE SET
cert_pem = EXCLUDED.cert_pem,
cert_serial = EXCLUDED.cert_serial,
key_path = EXCLUDED.key_path,
key_alg = EXCLUDED.key_alg,
not_before = EXCLUDED.not_before,
not_after = EXCLUDED.not_after,
rotated_from = EXCLUDED.rotated_from,
updated_at = NOW()
`
_, err := r.db.ExecContext(ctx, query,
responder.IssuerID,
responder.CertPEM,
responder.CertSerial,
responder.KeyPath,
responder.KeyAlg,
responder.NotBefore,
responder.NotAfter,
responder.RotatedFrom,
)
if err != nil {
return fmt.Errorf("ocsp_responders put %q: %w", responder.IssuerID, err)
}
return nil
}
// ListExpiring returns responders whose not_after is at or before
// (now + grace). Used by the rotation scheduler to find responders due
// for rotation. Ordered by not_after ASC so earliest-expiring is first.
func (r *OCSPResponderRepository) ListExpiring(ctx context.Context, grace time.Duration, now time.Time) ([]*domain.OCSPResponder, error) {
threshold := now.Add(grace)
const query = `
SELECT issuer_id, cert_pem, cert_serial, key_path, key_alg,
not_before, not_after, COALESCE(rotated_from, ''),
created_at, updated_at
FROM ocsp_responders
WHERE not_after <= $1
ORDER BY not_after ASC
`
rows, err := r.db.QueryContext(ctx, query, threshold)
if err != nil {
return nil, fmt.Errorf("ocsp_responders list_expiring: %w", err)
}
defer rows.Close()
var out []*domain.OCSPResponder
for rows.Next() {
var resp domain.OCSPResponder
if err := rows.Scan(
&resp.IssuerID,
&resp.CertPEM,
&resp.CertSerial,
&resp.KeyPath,
&resp.KeyAlg,
&resp.NotBefore,
&resp.NotAfter,
&resp.RotatedFrom,
&resp.CreatedAt,
&resp.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("ocsp_responders list_expiring scan: %w", err)
}
out = append(out, &resp)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("ocsp_responders list_expiring iterate: %w", err)
}
return out, nil
}