docs(ssh): operator playbook for InsecureIgnoreHostKey design choice

Closes Top-10 fix #7 of the 2026-05-02 deployment-target audit
re-run (see cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md). Pre-fix, the SSH connector's
ssh.InsecureIgnoreHostKey() at internal/connector/target/ssh/
ssh.go (realSSHClient.Connect) had only an inline comment
justifying the design choice. An acquirer's diligence engineer
reading the connector cold pattern-matches "MITM hazard" without
seeing the comment.

This commit lands a doc-side operator playbook in
docs/connectors.md SSH section covering:

1. Why the connector accepts any host key (operator-configured
   target infrastructure; mirrors network scanner's
   InsecureSkipVerify and F5's Insecure flag).
2. Threat model the choice accepts (passive eavesdropper on
   operator-controlled network; layered SSH-key auth limits
   blast radius).
3. Threat model the choice does NOT accept (public-internet
   ephemeral hosts, multi-tenant networks, strict MITM-
   resistance regulatory requirements).
4. Mitigations operators can layer (custom SSHClient via
   NewWithClient + golang.org/x/crypto/ssh/knownhosts; SSH
   certificate authentication via @cert-authority pinning;
   network segmentation; per-target key rotation).
5. When to NOT use the SSH connector (regulatory environments,
   dynamic IPs, multi-tenant networks).
6. V3-Pro forward path (built-in known_hosts management,
   tracked in WORKSPACE-ROADMAP.md).

Inline comment in ssh.go realSSHClient.Connect updated to
forward-reference the new doc subsection (no logic change; same
HostKeyCallback: ssh.InsecureIgnoreHostKey() call).

Same shape Bundle 8 used for "Operator playbook: keytool argv
password exposure" in docs/connectors.md JavaKeystore section.

No code-behavior changes. No test changes.

Verified locally:
- gofmt / go vet clean.
- go test -short ./internal/connector/target/ssh/...  green.

Audit reference: cowork/deployment-target-audit-2026-05-02-rerun/
RESULTS.md Top-10 fix #7.
This commit is contained in:
shankar0123
2026-05-02 22:44:30 +00:00
parent 737c329824
commit f0d91fde39
2 changed files with 48 additions and 5 deletions
+37
View File
@@ -1108,6 +1108,43 @@ The SSH target connector enables agentless certificate deployment to any Linux/U
Location: `internal/connector/target/ssh/ssh.go` Location: `internal/connector/target/ssh/ssh.go`
#### Operator playbook: SSH host-key verification
certctl's SSH connector dials each target with `HostKeyCallback: ssh.InsecureIgnoreHostKey()`, meaning **the connector accepts any server host key without comparison against `known_hosts`**. This is a documented design choice (see `internal/connector/target/ssh/ssh.go` near `realSSHClient.Connect`) and not an oversight. The rationale + when it's safe + what to layer on top when it isn't:
**Why the connector accepts any host key:**
- certctl deploys to **operator-configured target infrastructure**. Each target is registered explicitly in the control plane with hostname + auth credentials + cert/key paths; the operator implicitly trusts the host they're deploying to (otherwise why give it a TLS cert).
- Mirrors the same posture certctl applies to the network scanner (`InsecureSkipVerify` for cert-monitoring TLS handshakes) and the F5 connector (`Insecure` flag for self-signed BIG-IP management interfaces).
- Avoids a heavyweight per-target `known_hosts` management layer that would shift complexity onto operators with no proportional security gain when the network model is "operator-configured infrastructure on operator-controlled network".
**Threat model the design choice accepts:**
- A **passive eavesdropper** on the agent-to-target link. SSH's transport encryption still applies — host-key acceptance affects MITM vulnerability, not on-the-wire confidentiality.
- A **MITM attacker** on the agent-to-target link who can intercept the SSH TCP handshake AND has positioned themselves on a hostname the operator has registered as a deploy target. Layered authentication (per-target SSH keys with strong passphrases stored at the agent) limits the blast radius — the MITM gets one target's cert+key payload, not the agent's broader credentials.
**Threat model the design choice does NOT accept:**
- Deploying across the **public internet** to a host whose IP rotates (e.g. ephemeral cloud instances behind a load balancer that doesn't pin SSH host keys). In that scenario, `InsecureIgnoreHostKey` opens an MITM window during IP rotation — register a `known_hosts` file path or use SSH certificates (below) instead.
- **Multi-tenant networks** where another tenant could plausibly impersonate the target host. certctl's design assumes operator-controlled network paths.
**Mitigations operators can layer on:**
- **`known_hosts` enforcement**: implement a custom `SSHClient` (the connector's `SSHClient` interface accepts injected clients via `NewWithClient`) whose `Connect` method builds an `ssh.ClientConfig` with `HostKeyCallback` set to `knownhosts.New("/path/to/known_hosts")` from `golang.org/x/crypto/ssh/knownhosts`. Configure the agent to use that client.
- **SSH certificate authentication**: use OpenSSH 5.4+ host certificates signed by an organizational CA. Configure the agent's `known_hosts` CA pinning via `@cert-authority` lines so any host presenting a certificate signed by the CA is trusted, regardless of IP rotation.
- **Network segmentation**: run the certctl agent on the same private network segment as its targets; require VPN tunnels for cross-network deploys; use bastion hosts with their own host-key validation.
- **Per-target SSH keys**: rotate the agent's SSH credentials per target so a successful MITM compromise is bounded to that one target's cert+key, not the agent's broader credential set.
**When you should NOT use the SSH connector:**
- Deploying to **unknown / dynamic / multi-tenant** hosts where the IP-to-hostname binding isn't operator-controlled.
- Environments with strict **regulatory MITM-resistance** requirements (PCI-DSS Level 1, FedRAMP High, etc.) — the inline-comment "out of scope" framing doesn't satisfy compliance auditors who want documented host-key verification at the connector level.
- For these cases, switch to a different connector (Kubernetes Secrets, WinCertStore, F5 with iControl REST under operator-managed cert pinning) **OR** layer a custom `SSHClient` with full `known_hosts` validation per the mitigations above.
**V3-Pro forward path:**
The operator-managed `known_hosts` integration (config field + `HostKeyCallback` plumbing + per-target root-of-trust enforcement) is documented as V3-Pro work. Tracking: `WORKSPACE-ROADMAP.md` (search for "SSH known_hosts").
### Windows Certificate Store ### Windows Certificate Store
The Windows Certificate Store connector imports certificates into the Windows cert store via PowerShell, without managing IIS site bindings. Use this for non-IIS Windows services that read certificates from the cert store (Exchange, RDP, SQL Server, ADFS, etc.). Same injectable `PowerShellExecutor` pattern as the IIS connector, with optional WinRM proxy mode. The Windows Certificate Store connector imports certificates into the Windows cert store via PowerShell, without managing IIS site bindings. Use this for non-IIS Windows services that read certificates from the cert store (Exchange, RDP, SQL Server, ADFS, etc.). Same injectable `PowerShellExecutor` pattern as the IIS connector, with optional WinRM proxy mode.
+11 -5
View File
@@ -668,11 +668,17 @@ func (c *realSSHClient) Connect(ctx context.Context) error {
User: c.config.User, User: c.config.User,
Auth: authMethods, Auth: authMethods,
Timeout: time.Duration(c.config.Timeout) * time.Second, Timeout: time.Duration(c.config.Timeout) * time.Second,
// InsecureIgnoreHostKey is used intentionally: certctl deploys to known // InsecureIgnoreHostKey is used intentionally — see "Operator playbook:
// infrastructure (the operator explicitly configures each target host). // SSH host-key verification" in docs/connectors.md (SSH section) for
// This is the same security rationale as network scanner's InsecureSkipVerify // the threat model accepted, the threat model rejected, available
// and F5 connector's insecure flag. Host key verification would require // mitigations (custom HostKeyCallback via NewWithClient + known_hosts;
// an additional known_hosts management layer that is out of scope. // SSH certificate authentication; network segmentation), and when not
// to use this connector. Same security rationale as the network
// scanner's InsecureSkipVerify and the F5 connector's insecure flag:
// certctl deploys to operator-configured target infrastructure on
// operator-controlled networks. Built-in known_hosts management is
// V3-Pro work (see WORKSPACE-ROADMAP.md). Top-10 fix #7 of the
// 2026-05-02 deployment-target audit re-run.
HostKeyCallback: ssh.InsecureIgnoreHostKey(), HostKeyCallback: ssh.InsecureIgnoreHostKey(),
} }