Commit Graph

9 Commits

Author SHA1 Message Date
shankar0123 737c329824 iis,wincertstore: default-deadline ctx wrapper for PowerShell exec calls
Closes Top-10 fix #4 of the 2026-05-02 deployment-target audit
re-run (see cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md). Pre-fix, both IIS and WinCertStore's realExecutor
invoked PowerShell via exec.CommandContext(ctx, ...) and relied
entirely on the caller's ctx to provide a deadline. If the caller
forgot to attach one (context.Background() in a deeply-nested
path; an operator running an ad-hoc deploy via a CLI that doesn't
default-deadline its ctx), a hung WinRM session blocked the
deploy worker thread indefinitely.

S2 (failure isolation) bar from the audit: "does a hung WinRM
take down the deploy worker pool?" — today's answer was
"potentially yes" for these two connectors. Post-fix the answer
is "no, capped at the configured ExecDeadline (default 60s)".

This commit:

1. Adds Config.ExecDeadline (time.Duration, json: "exec_deadline")
   to both connectors, defaulted to 60 seconds. WinCertStore
   defaults via the existing applyDefaults helper; IIS defaults
   inline at New() and inside ValidateConfig (the IIS connector
   has no shared applyDefaults helper today; out-of-scope to
   refactor one in for this minor fix). Operators on slow
   Windows links can override via the JSON config field
   exec_deadline.

2. Wraps realExecutor.Execute with a fallback context.WithTimeout
   that fires ONLY when ctx has no deadline of its own. Caller-
   supplied deadlines always win — the wrapper is a safety net,
   not a hard cap. defer cancel() guards against goroutine leaks.

3. Tests:
   - TestIIS_RealExecutor_AttachesDefaultDeadlineWhenCallerHasNone
     (passes context.Background; asserts the call returns within
     500ms with an error). On Linux/macOS runners powershell.exe
     is missing and exec.Cmd fails fast; on Windows the wrapper's
     ctx deadline cancels the running PowerShell process. Either
     path returns well under 500ms.
   - TestIIS_RealExecutor_RespectsCallerDeadlineWhenSet (10s
     fallback executor deadline, 50ms caller ctx; asserts caller
     deadline wins).
   - TestIIS_RealExecutor_NoDeadlineWiredWhenZero (deadline=0
     means no fallback wrapper; caller's tight ctx still bounds).
   - TestIIS_New_DefaultsExecDeadlineTo60s + TestIIS_New_RespectsExplicitExecDeadline
     pin the constructor's defaulting behavior (uses winrm mode
     so the test doesn't need powershell.exe in PATH).
   - Same five tests in wincertstore_test.go.

4. docs/connectors.md IIS + WinCertStore sections document the
   new exec_deadline field with: what it is (per-PowerShell-
   subprocess cap), default (60 seconds), override semantics
   (caller ctx deadline wins).

No change to behavior when the caller already attaches a deadline
(the common case in production code paths). Tests using the mock
executor (mockExecutor in iis_test.go / wincertstore_test.go)
are unaffected — they bypass realExecutor entirely.

S2 cross-cutting scorecard rating in
cowork/deployment-target-audit-2026-05-02-rerun/findings.json
flips from "gap" to "pass" for IIS and WinCertStore (in any
future re-audit).

Verified locally:
- gofmt / go vet / staticcheck clean across both packages.
- go test -race -count=1 ./internal/connector/target/iis/...
  ./internal/connector/target/wincertstore/...  green.

Audit reference: cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md Top-10 fix #4.
2026-05-02 22:38:35 +00:00
shankar0123 6d3d861acc iis,wincertstore,javakeystore: SHA-256 idempotency short-circuit
Closes Top-10 fix #3 of the 2026-05-02 deployment-target audit
re-run (see cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md). Pre-fix, the three PowerShell-driven connectors
(IIS / WinCertStore / JavaKeystore) bypass internal/deploy.Apply
because they write to the Windows cert store / Java keystore via
PowerShell + keytool rather than the local filesystem. They don't
get deploy.Apply's SHA-256 idempotency short-circuit for free, so
every renewal triggers a full Remove+Import cycle even on byte-
identical material. Operators with 60-day rotation see unnecessary
cert-store / keystore churn, briefly bumping CPU and possibly
disrupting connections in flight.

This commit adds a per-connector idempotency probe modeled on
Bundle 9's Caddy api-mode SHA-256 short-circuit (commit 8cda860).
Each probe runs at the top of DeployCertificate, BEFORE the
destructive step, with a unique # CERTCTL_IDEM_PROBE PowerShell
comment tag so test mocks match deterministically.

IIS: Get-ChildItem Cert:\... + Get-WebBinding; matches when both
the cert is in the store AND the active binding's certificateHash
equals the new thumbprint.

WinCertStore: Get-ChildItem Cert:\...\<thumbprint>; matches when
the cert exists in the configured store AND its NotAfter is
still in the future.

JavaKeystore: keytool -list -alias -v; matches when the parsed
SHA-256 fingerprint equals sha256(certPEM_DER).

On match: return Success=true with Metadata["idempotent"]="true",
no destructive operation. On any error during the probe (network,
parse, etc.): fall through to today's full deploy path.
False negatives are safe; false positives are dangerous.

Tests added (one positive + one negative per connector):
- TestIIS_Idempotent_SkipsDeployWhenBindingMatches
- TestIIS_Idempotent_DifferentBinding_FallsThroughToDeploy
- TestWinCertStore_Idempotent_SkipsImportWhenCertInStore
- TestWinCertStore_Idempotent_NotInStore_FallsThroughToDeploy
- TestJKS_Idempotent_SkipsDeployWhenAliasMatches
- TestJKS_Idempotent_DifferentAlias_FallsThroughToDeploy

Verified locally:
- gofmt clean across all three connectors.
- Syntax-validated via gofmt.

Audit reference: cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md Top-10 fix #3.
2026-05-02 22:09:30 +00:00
shankar0123 223f279d04 iis: pre-deploy binding snapshot + on-failure rollback
Closes Bundle 5 of the 2026-05-02 deployment-target coverage audit
(see cowork/deployment-target-audit-2026-05-02/RESULTS.md). Pre-fix,
DeployCertificate at iis.go:235-436 imported the cert via
Import-PfxCertificate (atomic at cert-store level) then ran a
separate PowerShell script for the SNI binding update. If the
binding script failed, the new cert was orphaned in the store AND
the old binding stayed pointed at the old thumbprint.
docs/deployment-atomicity.md L91 promised "explicit pre-deploy
backup + post-rollback re-import"; the code didn't deliver.

This commit:

1. Pre-deploy snapshot. snapshotOldBinding runs Get-WebBinding
   before the import; parses the bound SSL thumbprint into a local
   `oldThumbprint` variable. Empty = first-time binding (no
   rollback target).

2. On-failure rollback script. When the binding-update Execute
   returns error, rollbackBinding runs a single PowerShell script
   that:
   - Remove-Item Cert:\LocalMachine\<store>\<newThumbprint> (delete
     the cert we just imported but couldn't bind).
   - If oldThumbprint != "", AddSslCertificate('<oldThumbprint>',
     ...) to re-bind the old cert. Falls through to New-WebBinding
     + AddSslCertificate when the old binding entry is also gone.

3. Post-rollback verification. verifyRollback re-reads
   Get-WebBinding; asserts the bound thumbprint matches
   oldThumbprint. On mismatch, warn in the DeploymentResult
   message — the rollback ran but final state is suspect, operator
   inspection required. Skipped when oldThumbprint == "" (no
   binding to verify against).

4. Helper extraction. snapshotOldBinding / rollbackBinding /
   verifyRollback are private methods on Connector for clean test
   seams. Each emits a unique `# CERTCTL_*` PowerShell comment tag
   so test mocks can match scripts deterministically — multiple
   scripts call Get-WebBinding so substring matching otherwise
   collides under Go's randomized map iteration order.

DeploymentResult shape on failure:
- rollback OK   → Success=false, Message="binding update failed;
                  rolled back", clean error.
- rollback FAIL → Success=false, wrapped error containing both
                  binding error and rollback error; metadata
                  flags manual_action_required=true and surfaces
                  rollback_error / binding_error verbatim.

Tests added to iis_test.go:
- TestIIS_BindingUpdateFails_RemovesNewCert_RebindsOld — happy
  rollback path. Mock executor queued with snapshot →
  OLD_THUMBPRINT:abc123, import OK, binding fails, rollback →
  REBOUND_EXISTING. Asserts rollback script contains both
  Remove-Item for the new thumbprint AND
  AddSslCertificate('abc123', ...).
- TestIIS_BindingUpdateFails_NoOldBinding_RemovesNewCertOnly —
  first-time deploy variant. Snapshot returns NO_OLD_BINDING;
  rollback removes the new cert but does NOT call
  AddSslCertificate; verify script never runs.
- TestIIS_BindingUpdateFails_RollbackAlsoFails_OperatorActionable
  — wrapped-error escalation. Asserts the returned error mentions
  both `binding update failed` and `rollback also failed`, and
  metadata flags manual_action_required=true.

Two existing tests (TestIISConnector_DeployCertificate_Success and
…_SNIEnabled) updated to expect 3 commands (snapshot, import,
binding) and to look for the binding script at commands[2].

docs/deployment-atomicity.md L91 unchanged from today's text — the
"Already explicit pre-deploy backup + post-rollback re-import"
claim is now honest. (Bundle 1 doc-realignment hasn't shipped yet,
so there's no softened-pending claim to restore.)

Verified locally (sandbox lacks staticcheck install due to disk
pressure, ran via go vet + go test -race; CI runs the full lint
gate):
- gofmt -l ./internal/connector/target/iis/  clean
- go vet ./internal/connector/target/iis/...  clean
- go build ./internal/connector/target/iis/...  clean
- go test -race -count=1 ./internal/connector/target/iis/  green

Audit reference: cowork/deployment-target-audit-2026-05-02/RESULTS.md
Bundle 5.
2026-05-02 16:58:01 +00:00
Shankar e05424d188 feat(M46): Windows Certificate Store + Java Keystore target connectors, shared certutil package
Extract shared certutil helpers (CreatePFX, ParsePrivateKey, ComputeThumbprint,
GenerateRandomPassword, ParseCertificatePEM) from IIS connector for reuse.
Add WinCertStore connector (PowerShell Import-PfxCertificate, dual local/WinRM
mode, configurable store/location, expired cert cleanup) and JavaKeystore
connector (PEM→PKCS#12→keytool pipeline, JKS/PKCS12 support, shell injection
prevention, path traversal protection). 53 new tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 19:14:32 -04:00
Shankar e555af9288 feat(M39): IIS WinRM proxy agent mode + front-to-back wiring
Complete the IIS target connector with dual-mode deployment:
- WinRM proxy agent mode via masterzen/winrm for remote Windows servers
- Base64 PFX transfer with try/finally cleanup on remote host
- GUI wizard updated with 13 IIS config fields including WinRM settings
- TargetDetailPage sensitive field redaction (password/secret/token/key)
- OpenAPI TargetType enum updated (added Traefik, Caddy)
- connectors.md fully documented with WinRM proxy config example
- 38 total IIS tests (10 new WinRM tests), all passing with race detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 20:53:20 -04:00
Shankar 8f1dd10eac feat(M39): IIS target connector + README overhaul
Implement full IIS target connector with PEM-to-PFX conversion via
go-pkcs12, PowerShell-based deployment (Import-PfxCertificate, IIS
binding management), SHA-1 thumbprint computation, and SNI support.
Injectable PowerShellExecutor interface enables cross-platform testing.
Regex-validated config fields prevent PowerShell injection. 28 tests.

Restructure README from 563 to 313 lines: outcome-focused feature
descriptions, "Who Is This For" persona section, examples promoted
above the fold, configuration/API/security reference moved to docs.
All numbers verified against repo (25 GUI pages, 97 OpenAPI ops,
CI thresholds service 55%/handler 60%/domain 40%/middleware 30%).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 20:27:27 -04:00
Shankar 4aea791760 fix(ci): resolve 185 golangci-lint v2 issues — fix unused, tune config
Fix 6 unused function/variable errors (var _ assignment pattern, remove
IIS PowerShell stub). Reduce enabled linter set to govet + staticcheck +
unused with targeted staticcheck check exclusions for pre-existing style
issues (ST1005, QF1001, S1009, etc.). Noisy linters (errcheck, gocritic,
gosec, ineffassign, noctx, bodyclose) temporarily disabled — will be
re-enabled incrementally as pre-existing issues are fixed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 23:18:04 -04:00
Shankar f1eff55894 style: run gofmt -s across all Go files
Fixes Go Report Card gofmt score from 52% to 100%.
Pure formatting changes — no logic modifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 19:32:29 -04:00
shankar0123 d395776a95 Initial scaffold: certificate control plane v0.1.0 2026-03-14 08:22:17 -04:00