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

73 lines
4.0 KiB
Go

// Copyright 2026 certctl LLC. All rights reserved.
// SPDX-License-Identifier: BUSL-1.1
// Package deploy provides the shared atomic-write + validate + rollback
// primitive consumed by every target connector under
// internal/connector/target/*.
//
// The deploy package closes the three procurement-checklist items where
// commercial competitors (Venafi, DigiCert Certificate Manager, Sectigo)
// historically beat certctl on a head-to-head deployment-grade
// comparison:
//
// 1. Atomic deploy with rollback — every file write is "all or nothing".
// A connector can never leave a target in a half-deployed state where
// the cert is updated but the chain isn't (or vice versa). Ships via
// Plan + Apply: temp-write all files together, run validate, atomic
// rename them all, run reload; on reload failure restore previous
// bytes + reload again.
// 2. Post-deploy TLS verification — the Apply caller wires its own
// PostCommit to do a TLS handshake against the target endpoint and
// compare the leaf-cert SHA-256 against what was just written. The
// deploy package surfaces the rollback wire when PostCommit fails;
// the connector decides what failure means.
// 3. (Vendor-specific deployment recipes — out of scope for the deploy
// package; covered in Bundle II.)
//
// Design tenets — all load-bearing for 13 connectors:
//
// - All-or-nothing across files. A Plan with N File entries either
// succeeds for all N or rolls back all N. No "two of three written"
// intermediate states are possible from a successful or failed Apply.
// - Cross-filesystem safety. Temp files always live in the same
// directory as the final destination, so os.Rename is guaranteed
// atomic on POSIX (a rename within the same filesystem). Writing
// temp files in /tmp would silently fall back to copy-and-rename
// across filesystems, breaking atomicity.
// - Idempotency. If every File's destination already has identical
// bytes (SHA-256 match), Apply returns SkippedAsIdempotent=true and
// calls neither PreCommit nor PostCommit. Defends against agent
// restart retry storms that would otherwise hammer the target with
// no-op reloads.
// - Ownership + mode preservation. The single most common
// silent-failure mode in cert deploys is the agent running as root
// calling os.WriteFile(path, bytes, 0600), which clobbers the
// existing nginx:nginx 0640 ownership and locks NGINX out of the
// key file. Apply preserves the existing destination's
// owner+group+mode unless the per-target config overrides; for new
// files it falls back to per-target-type defaults (e.g. nginx:nginx
// 0640).
// - Per-file serialization. The package keeps a sync.Map of file-level
// mutexes so two concurrent Apply calls touching the same path
// serialize. (Per-target serialization is Phase 2's job in the
// agent dispatch; this is a finer-grained file-level guard.)
// - Backup retention. Each successful write copies the previous bytes
// to <path>.certctl-bak.<unix-nanos>. A janitor prunes to the last
// N backups (default 3, configurable via Plan.BackupRetention or
// the CERTCTL_DEPLOY_BACKUP_RETENTION env var the agent passes in).
// Setting retention to 0 disables backups entirely — rollback
// becomes impossible; documented as a foot-gun.
//
// Origin: this package was created in the deploy-hardening I master
// bundle (Phase 1) as the load-bearing replacement for the duplicated
// os.WriteFile flows in 13 connectors. The Apply API mirrors the F5
// transaction model already at internal/connector/target/f5/f5.go:267
// — F5 was the only connector with rollback semantics before this
// bundle. Apply lifts that pattern up so every other connector gets
// the same atomicity bar without re-implementing it.
//
// Concurrency: every exported function is safe for concurrent callers.
// File-level serialization is automatic via the package-internal
// sync.Map of mutexes; callers do not need their own per-file lock.
package deploy