From 8aa291d948260014005e070253b6666223f2d7c6 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Tue, 5 May 2026 04:07:21 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=204=20follow-on=20batch=204=20?= =?UTF-8?q?=E2=80=94=205=20final=20target=20per-pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the remaining target connectors: - ssh.md (194 lines) — agentless SSH/SFTP deploy with full host-key-acceptance threat model (what's accepted, what's not, mitigations including known_hosts enforcement and SSH cert auth); V3-Pro forward path - wincertstore.md (118 lines) — non-IIS Windows services via local PowerShell or WinRM proxy mode; store selection (My / Root / WebHosting); private-key permissions guidance - jks.md (189 lines) — JKS / PKCS#12 via keytool with full atomic snapshot+rollback contract (Bundle 8 'snapshot → delete → import → reload'), keytool argv password exposure threat model + mitigations - aws-acm.md (208 lines) — ACM target with full IAM policy, IRSA / instance-profile / SSO auth recipes, atomic-rollback contract, ALB attachment Terraform recipe, procurement-checklist crib - azure-kv.md (195 lines) — Key Vault target with managed-identity / workload-identity / service-principal auth recipes, version- semantics rollback caveat (no in-place restore without soft-delete), App Gateway / Front Door attachment recipe Index forward-list expanded to enumerate all 15 target connectors (5 from Phase 4 structural + 5 from batch 3 + 5 from this batch) in alphabetical order. This is part 4 of 4 for the Phase 4 follow-on (per-connector page extraction) tracked in cowork/docs-overhaul-phase-2-restructure-2026-05-04/log.md. Net add: 5 files, 904 lines. No content removed from index.md. End-state of Phase 4 follow-on: - 13 issuer per-pages (5 batch 1 + 8 batch 2) - 15 target per-pages (5 Phase 4 structural + 5 batch 3 + 5 batch 4) - index.md keeps its inline reference content; per-pages add operator depth on top, matching the pattern set by apache/f5/iis/k8s/nginx in Phase 4 structural --- docs/reference/connectors/aws-acm.md | 208 ++++++++++++++++++++++ docs/reference/connectors/azure-kv.md | 195 ++++++++++++++++++++ docs/reference/connectors/index.md | 5 + docs/reference/connectors/jks.md | 189 ++++++++++++++++++++ docs/reference/connectors/ssh.md | 194 ++++++++++++++++++++ docs/reference/connectors/wincertstore.md | 118 ++++++++++++ 6 files changed, 909 insertions(+) create mode 100644 docs/reference/connectors/aws-acm.md create mode 100644 docs/reference/connectors/azure-kv.md create mode 100644 docs/reference/connectors/jks.md create mode 100644 docs/reference/connectors/ssh.md create mode 100644 docs/reference/connectors/wincertstore.md diff --git a/docs/reference/connectors/aws-acm.md b/docs/reference/connectors/aws-acm.md new file mode 100644 index 0000000..ef332ba --- /dev/null +++ b/docs/reference/connectors/aws-acm.md @@ -0,0 +1,208 @@ +# AWS Certificate Manager (ACM) Target Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the AWS Certificate Manager +> (ACM) target connector. For the connector-development context +> (interface contract, registry, atomic deploy primitive shared +> across all targets), see the [connector index](index.md). +> +> **Note:** this is the **target** connector that deploys +> certificates *into* ACM for ALB / CloudFront / API Gateway / App +> Runner consumption. The **issuer** connector that pulls certs +> *from* AWS ACM Private CA is documented separately at +> [aws-acm-pca.md](aws-acm-pca.md). + +## Overview + +The AWS ACM target connector deploys certificates into AWS +Certificate Manager — the public AWS service that ALB / +CloudFront / API Gateway / App Runner consume by ARN. Closes the +"we terminate TLS at AWS, how do we get certctl-issued certs to +ALB?" question for cloud-first deployments. Rank 5 of the +2026-05-03 Infisical deep-research deliverable. + +Implementation lives at `internal/connector/target/awsacm/`. + +## When to use this connector + +Use the AWS ACM target connector when: + +- TLS terminates at AWS-managed edges (ALB, CloudFront, API + Gateway, App Runner) and those services consume certs by ACM + ARN. +- You want certctl to drive the rotation while Terraform / + CloudFormation handles the ARN-to-resource attachment. +- You need short-lived IAM credentials (IRSA, instance profiles) + rather than long-lived access keys. + +Look elsewhere when: + +- The target is an EC2 instance running NGINX / HAProxy / Apache + directly — those connectors are simpler than the ACM round-trip. +- You're using ACM Private CA for internal trust — that's the + [aws-acm-pca.md](aws-acm-pca.md) issuer, a different connector. + +## Configuration + +```json +{ + "region": "us-east-1", + "certificate_arn": "arn:aws:acm:us-east-1:123456789012:certificate/abcdef01-2345-6789-abcd-ef0123456789", + "tags": {"env": "production", "app": "api-gateway"} +} +``` + +| Field | Default | Description | +|---|---|---| +| `region` | (required) | AWS region for the ACM endpoint (e.g. `us-east-1`). CloudFront-attached certs MUST live in `us-east-1`; ALB / API Gateway use the same region as the load balancer. | +| `certificate_arn` | — | ARN of an existing ACM certificate to rotate in place. Empty on first deploy — the adapter creates a new ACM cert via `ImportCertificate` and the deployment record's Metadata captures the resulting ARN. Operators can also pre-create the ARN out-of-band (Terraform, CloudFormation) and pin it here. | +| `tags` | — | Tags applied to the ACM cert at first import + re-applied via `AddTagsToCertificate` on every subsequent import (ACM strips tags on re-import). The reserved keys `certctl-managed-by` and `certctl-certificate-id` are set automatically and cannot be overridden. | + +## IAM policy (minimum permissions) + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "acm:ImportCertificate", + "acm:GetCertificate", + "acm:DescribeCertificate", + "acm:ListCertificates", + "acm:AddTagsToCertificate" + ], + "Resource": "arn:aws:acm:*:*:certificate/*" + }] +} +``` + +## Auth recipes + +- **IRSA (IAM Roles for Service Accounts) — recommended for K8s + deploys.** Annotate the agent's ServiceAccount with + `eks.amazonaws.com/role-arn=arn:aws:iam:::role/certctl-acm-deployer`. + The role's trust policy allows the cluster's OIDC provider; + permission policy is the JSON above. Short-lived STS + credentials are auto-rotated by EKS — no long-lived access + keys. +- **EC2 instance profile — recommended for VM-based agents.** + Attach an instance profile referencing the same role. SDK's + `LoadDefaultConfig` picks credentials up via the IMDS metadata + service. +- **AWS SSO / `aws configure sso` — recommended for operator + workstations.** SDK reads `~/.aws/config` for the SSO profile + and refreshes tokens via the existing CLI session. +- **Long-lived access keys are NOT supported in connector + Config** — the credential chain is configured at the SDK + level, not the connector level. This is a procurement- + readability decision: a security reviewer reading the + `deployment_targets` table should never find an access key. + +## Atomic-rollback contract + +Every `DeployCertificate` snapshots the existing cert via +`DescribeCertificate` + `GetCertificate` BEFORE calling +`ImportCertificate` with the new bytes. After import, the +connector re-fetches the cert metadata and compares serial +numbers. + +On serial-mismatch (post-verify failure), the connector calls +`ImportCertificate` again with the snapshotted bytes to restore +the previous cert. The rollback path emits a `WARN`-level slog +entry; the rollback's own success or failure is exposed via +`certctl_deploy_rollback_total{target_type="AWSACM",outcome="restored"|"also_failed"}` +per the deploy-hardening I Phase 10 metric exposer. + +Mirrors the Bundle 5+ pre-deploy-snapshot pattern shipped for +IIS / WinCertStore / JavaKeystore. + +## ALB attachment recipe + +certctl creates / rotates the ACM cert; the operator (or +Terraform / CloudFormation) attaches it to the ALB listener +separately. For Terraform-driven deployments, look up the ARN by +tag: + +```hcl +data "aws_acm_certificate" "certctl_managed" { + domain = "api.example.com" + most_recent = true + + # Filter by certctl provenance tags so an unrelated ACM cert with + # the same SAN doesn't get picked up. + tags = { + "certctl-managed-by" = "certctl" + "certctl-certificate-id" = "mc-api-prod" + } +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.api.arn + port = 443 + protocol = "HTTPS" + certificate_arn = data.aws_acm_certificate.certctl_managed.arn + # ... +} +``` + +The ARN updates in place across renewals (ACM `ImportCertificate` +is upsert-style when given an ARN), so the ALB listener's +`certificate_arn` reference doesn't change. CloudFront / API +Gateway distributions can reference the same ARN via their +respective Terraform resources. + +## Threat model carve-outs + +- **Cert key bytes never written to disk on the agent.** + `DeployCertificate` reads `request.KeyPEM` from memory and + passes it to the SDK's `ImportCertificate` call. No temp file. + No swap-out window. +- **Provenance tags are mandatory.** The reserved + `certctl-managed-by=certctl` + `certctl-certificate-id=` + pair is set automatically on every import. Operators + identifying a stray ACM cert in their account can match + against `certctl-managed-by` to confirm it was certctl-issued + (or NOT — the absence of the tag means a manual import). +- **No long-lived AWS credentials in `Config`.** `Config` + carries region + ARN + operator tags only. AWS auth is the + SDK credential chain (IRSA / instance profile / SSO). +- **`ListCertificates` IAM permission is required for the V2 + ARN-discovery dance to work.** Operators who pin + `Config.CertificateArn` after the first deploy can drop this + permission; the V2 fallback emits a warning and reverts to + "always create new ARN" if the operator forgets to update + `certificate_arn` post-first-deploy. + +## Procurement checklist crib + +Paste into security review: + +- certctl uses short-lived IAM-role credentials via IRSA / + instance profile, not long-lived access keys. +- The cert key is held only in agent memory during the import + call; never written to disk. +- Every imported ACM cert is tagged with + `certctl-managed-by=certctl` + + `certctl-certificate-id=` for forensic traceability. +- Failed imports trigger automatic rollback to the snapshotted + previous cert; both outcomes are surfaced via Prometheus. +- The minimum IAM policy is 5 actions on + `arn:aws:acm:*:*:certificate/*`; CloudTrail captures every + API call for compliance audits. + +## ValidateOnly contract + +ACM has no dry-run API for `ImportCertificate`; `ValidateOnly` +returns `target.ErrValidateOnlyNotSupported` per the deploy- +hardening I Phase 3 sentinel contract. Operators preview deploys +via `ValidateConfig` + `aws acm describe-certificate +--certificate-arn ` against the current ARN. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [Azure Key Vault](azure-kv.md) — Azure equivalent target +- [AWS ACM Private CA issuer](aws-acm-pca.md) — the *issuer* counterpart (same vendor, opposite direction) +- [Cloud targets runbook](../../operator/runbooks/cloud-targets.md) — operator playbook covering both AWS ACM and Azure KV diff --git a/docs/reference/connectors/azure-kv.md b/docs/reference/connectors/azure-kv.md new file mode 100644 index 0000000..7647ea5 --- /dev/null +++ b/docs/reference/connectors/azure-kv.md @@ -0,0 +1,195 @@ +# Azure Key Vault Target Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Azure Key Vault target +> connector. For the connector-development context (interface +> contract, registry, atomic deploy primitive shared across all +> targets), see the [connector index](index.md). + +## Overview + +The Azure Key Vault target connector deploys certificates into +Azure Key Vault — the Azure-managed cert/secret store that +Application Gateway / Front Door / App Service / Container Apps +consume by KID URI. Rank 5 (Azure half) of the 2026-05-03 +Infisical deep-research deliverable. + +Implementation lives at `internal/connector/target/azurekv/`. + +## When to use this connector + +Use the Azure Key Vault target connector when: + +- TLS terminates at Azure-managed edges (Application Gateway, + Front Door, App Service, Container Apps) and those services + consume certs by Key Vault KID URI. +- You need short-lived Azure credentials (managed identity, + workload identity) rather than long-lived service-principal + secrets. +- You need cross-region or cross-cloud-environment Key Vault + endpoints (US-Gov `.vault.usgovcloudapi.net`, China + `.vault.azure.cn`). + +Look elsewhere when: + +- The target is an Azure VM running NGINX / IIS / HAProxy + directly — those connectors are simpler. +- The cert is for an internal Azure service that doesn't read + from Key Vault (e.g. a custom .NET app reading PEM from disk). + +## Configuration + +```json +{ + "vault_url": "https://my-vault.vault.azure.net", + "certificate_name": "api-prod", + "tags": {"env": "production", "app": "api-gateway"}, + "credential_mode": "managed_identity" +} +``` + +| Field | Default | Description | +|---|---|---| +| `vault_url` | (required) | Key Vault DNS endpoint (`https://.vault.azure.net`). For US-Gov: `.vault.usgovcloudapi.net`; for China: `.vault.azure.cn`. | +| `certificate_name` | (required) | Cert object name in the vault (1-127 chars, alphanumeric + hyphens). Versions are auto-generated per import. | +| `tags` | — | Tags applied at every import (Key Vault carries tags forward across versions, unlike ACM). Reserved keys `certctl-managed-by` + `certctl-certificate-id` are set automatically. | +| `credential_mode` | `default` | One of `default` / `managed_identity` / `client_secret` / `workload_identity`. See "Auth recipes" below. | + +## RBAC role (minimum permissions) + +The off-the-shelf builtin role **Key Vault Certificates Officer** +covers everything. For minimum-permission deploys, use a custom +role with these data-plane operations on the vault scope +(`/subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults/`): + +``` +Microsoft.KeyVault/vaults/certificates/import/action +Microsoft.KeyVault/vaults/certificates/read +Microsoft.KeyVault/vaults/certificates/listversions/read +``` + +## Auth recipes + +- **AKS workload identity (`credential_mode: workload_identity`) + — recommended for AKS deploys.** Annotate the agent's + ServiceAccount with + `azure.workload.identity/client-id=`. The AKS + cluster's OIDC issuer + the federated credential on the app + registration handle token exchange; no long-lived secrets. +- **Managed identity (`credential_mode: managed_identity`) — + recommended for VM / App Service deploys.** Assign a + system-assigned or user-assigned managed identity to the + host; certctl-server / agent picks it up via IMDS. Pin + `credential_mode` rather than letting `default` fall through + to env vars (defends against accidental local-dev creds + leaking into production). +- **Service principal (`credential_mode: client_secret`).** + Configure `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` + + `AZURE_CLIENT_SECRET` env vars on the agent. NOT recommended + for production — long-lived client secret risk; rotate via + Key Vault soft-delete recovery if leaked. +- **Default (`credential_mode: default` or unset).** SDK's + `DefaultAzureCredential` walks env vars → managed identity → + Azure CLI fallback. Useful for local-dev where the operator + already has `az login` active. +- **Long-lived secrets in connector Config NOT supported** — + same procurement-readability rule as AWS ACM. + +## Atomic-rollback contract + Azure-version semantics + +Every `DeployCertificate` snapshots the existing latest version +via `GetCertificate(name, "" /* latest */)` BEFORE calling +`ImportCertificate`. After import, the connector re-fetches the +latest version and compares serial numbers. + +On serial-mismatch, the connector calls `ImportCertificate` +again with the snapshotted CER bytes (re-PFX'd with the +operator's key) — **as a NEW VERSION**. Key Vault doesn't +support "version-restore" without soft-delete recovery (which we +keep off the minimum-RBAC surface). The version history will +show e.g. v1=initial, v2=failed-renewal, v3=rollback-of-v2; +operators reading audit dashboards filter by tag. + +### Soft-delete caveat + +V2 doesn't manage Key Vault soft-delete recovery. If a previous +version was soft-deleted out-of-band (e.g. operator ran +`az keyvault certificate delete`), the rollback re-imports the +snapshot bytes as a new version rather than restoring the +soft-deleted version. Operators alerting on rollback frequency +should also watch for soft-delete events. + +## App Gateway / Front Door attachment recipe + +```hcl +data "azurerm_key_vault_certificate" "certctl_managed" { + name = "api-prod" + key_vault_id = azurerm_key_vault.main.id +} + +resource "azurerm_application_gateway" "main" { + # ... + ssl_certificate { + name = "certctl-managed" + key_vault_secret_id = data.azurerm_key_vault_certificate.certctl_managed.secret_id + } +} +``` + +Application Gateway / Front Door reference the cert by KID URI; +certctl rotates the version under the same name, and the AGW / +Front Door reference auto-resolves to the latest version (the +SDK's behaviour when the KID points to +`/certificates//` vs `/certificates/` +differs — the latter auto-tracks "latest"; the former pins). +**Pin the version-less KID for auto-tracking renewals.** + +## Threat model carve-outs + +- **Cert key bytes never written to disk on the agent.** PFX + wrapping happens in memory (PKCS#12 via + `software.sslmate.com/src/go-pkcs12`); the base64-encoded PFX + is passed straight to the SDK's `ImportCertificate` call. +- **Provenance tags are mandatory.** Same + `certctl-managed-by=certctl` + + `certctl-certificate-id=` shape as AWS ACM. Operators + identifying a stray Key Vault cert match against + `certctl-managed-by`. +- **No long-lived Azure credentials in `Config`.** `Config` + carries vault URL + cert name + operator tags + credential + mode only. Auth is the Azure SDK credential chain. +- **`credential_mode: managed_identity` is the recommended + production posture.** Defends against accidental env-var + creds leaking into deployments where the host already has a + managed identity assigned. + +## Procurement checklist crib + +Paste into security review: + +- certctl uses Azure managed identity (or workload identity for + AKS), not long-lived service-principal secrets. +- The cert key is held only in agent memory during the PFX wrap + + import call; never written to disk. +- Every imported Key Vault cert is tagged with + `certctl-managed-by=certctl` + + `certctl-certificate-id=` for forensic traceability. +- Failed imports trigger automatic rollback by re-importing the + snapshotted previous version's bytes; both outcomes are + surfaced via Prometheus. +- The minimum RBAC role is 3 data-plane actions; Activity Log + captures every API call for compliance audits. + +## ValidateOnly contract + +Key Vault has no dry-run API; `ValidateOnly` returns +`target.ErrValidateOnlyNotSupported`. Operators preview deploys +via `ValidateConfig` + `az keyvault certificate show +--vault-name --name `. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [AWS ACM target](aws-acm.md) — AWS equivalent target +- [Cloud targets runbook](../../operator/runbooks/cloud-targets.md) — operator playbook covering both AWS ACM and Azure KV diff --git a/docs/reference/connectors/index.md b/docs/reference/connectors/index.md index 5fa0a8f..5a69b12 100644 --- a/docs/reference/connectors/index.md +++ b/docs/reference/connectors/index.md @@ -33,15 +33,20 @@ Issuer connectors: Target connectors: - [Apache](apache.md) — Apache httpd, separate-file deploy + `apachectl configtest` +- [AWS Certificate Manager](aws-acm.md) — deploy into ACM for ALB / CloudFront / API Gateway +- [Azure Key Vault](azure-kv.md) — deploy into Key Vault for App Gateway / Front Door / App Service - [Caddy](caddy.md) — admin-API hot reload or file-watcher fallback - [Envoy](envoy.md) — file SDS hot reload, optional `sds.json` - [F5 BIG-IP](f5.md) — proxy-agent pattern + transactional iControl REST - [HAProxy](haproxy.md) — combined-PEM deploy + `haproxy -c` validate - [IIS](iis.md) — Microsoft IIS, local PowerShell + WinRM modes +- [Java Keystore](jks.md) — JKS / PKCS#12 via `keytool` with atomic snapshot rollback - [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets atomic update - [NGINX](nginx.md) — separate-file deploy + `nginx -t` validate - [Postfix / Dovecot](postfix.md) — dual-mode mail-server TLS connector +- [SSH (agentless)](ssh.md) — agentless deploy over SSH/SFTP for Linux/Unix targets - [Traefik](traefik.md) — file-provider zero-reload deploy +- [Windows Certificate Store](wincertstore.md) — non-IIS Windows services (Exchange, RDP, SQL, ADFS) ## Contents diff --git a/docs/reference/connectors/jks.md b/docs/reference/connectors/jks.md new file mode 100644 index 0000000..104450e --- /dev/null +++ b/docs/reference/connectors/jks.md @@ -0,0 +1,189 @@ +# Java Keystore (JKS / PKCS#12) Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Java Keystore target +> connector. For the connector-development context (interface +> contract, registry, atomic deploy primitive shared across all +> targets), see the [connector index](index.md). + +## Overview + +The Java Keystore connector deploys certificates to JKS or +PKCS#12 keystores via the `keytool` CLI. This enables TLS cert +deployment for Tomcat, Jetty, Kafka, Elasticsearch, and any +JVM-based service. + +Flow: PEM → temp PKCS#12 → `keytool -importkeystore` into the +target keystore. The flow is engineered for atomicity and +rollback, not just convenience. + +Implementation lives at `internal/connector/target/javakeystore/`. + +## When to use this connector + +Use the Java Keystore connector when: + +- The target is a JVM-based service (Tomcat, Jetty, Kafka, + Elasticsearch, ZooKeeper) that reads TLS material from a + keystore file. +- You need PKCS#12 or JKS format support; the connector handles + both. + +Look elsewhere when: + +- The JVM service has been re-fronted with a non-Java reverse + proxy (NGINX, HAProxy) that handles TLS termination — deploy + to the proxy instead. +- The service uses PKCS#11 or a hardware token rather than a + keystore file — that's outside this connector's scope. + +## Configuration + +```json +{ + "keystore_path": "/opt/tomcat/conf/keystore.p12", + "keystore_password": "changeit", + "keystore_type": "PKCS12", + "alias": "server", + "reload_command": "systemctl restart tomcat" +} +``` + +| Field | Default | Description | +|---|---|---| +| `keystore_path` | (required) | Absolute path to the keystore file | +| `keystore_password` | (required) | Keystore password | +| `keystore_type` | `"PKCS12"` | `"PKCS12"` or `"JKS"` | +| `alias` | `"server"` | Key entry alias in the keystore | +| `reload_command` | — | Optional command to run after keystore update | +| `create_keystore` | `true` | Create keystore if it doesn't exist | +| `keytool_path` | `"keytool"` | Override keytool binary path | +| `backup_retention` | `3` | Number of `.certctl-bak..p12` snapshot files to keep after a successful deploy. `0` means use the default of 3; `-1` opts out of pruning entirely. | +| `backup_dir` | `dirname(keystore_path)` | Override directory where rollback snapshots are written and pruned from. Defaults to the keystore's own directory so snapshots land on the same filesystem. | + +## Atomic-rollback contract (Bundle 8) + +The deploy flow is **snapshot → delete → import → reload**. + +Before the irreversible `keytool -delete` step (which removes the +existing alias from the keystore), the connector runs `keytool +-exportkeystore` to write a sibling `.certctl-bak..p12` +file containing the prior alias. + +If the subsequent `keytool -importkeystore` fails for any reason, +the rollback path runs `keytool -delete` (best-effort cleanup of +any partial alias the failed import created) followed by +`keytool -importkeystore` from the snapshot PFX, restoring the +keystore to its pre-deploy state. + +If both the import AND the rollback fail, the connector returns +an operator-actionable wrapped error containing both error +strings AND the snapshot path so the operator can manually +`keytool -importkeystore` from the `.p12` file to recover. + +Successful deploys prune older `.certctl-bak.*.p12` files beyond +the configured `backup_retention` count; pruning sorts by file +ModTime and removes the oldest entries first. Operators that wire +their own archival/rotation logic can opt out via +`backup_retention: -1`. + +First-time deploys (no keystore file exists at the configured +path) skip the snapshot phase entirely — there's nothing to roll +back to. The same is true for "alias-not-present-in-existing- +keystore" deploys: `keytool -exportkeystore` returns "alias does +not exist" which the connector recognises as a normal first- +time-on-existing-keystore signal, not an outage. + +## Operator playbook: keytool argv password exposure + +Java's `keytool` accepts the keystore password via the +`-storepass` argv flag — there is no stdin or file-based password +mode in OpenJDK keytool. While the keytool subprocess is running, +the password is visible in `ps(1)` output to any user on the same +host who can read `/proc//cmdline`. **This is a standard +keytool limitation, not a certctl-specific issue**, but operators +in regulated environments should know about it. + +### What this means in practice + +- The password is visible for the duration of each keytool + invocation (typically <1s on modern hardware; the connector + runs 2-4 keytool calls per deploy: snapshot, optional + pre-import delete, import, optional rollback). +- A local user with shell access on the agent host who polls + `ps -ef` aggressively can capture the password. +- The exposure is local to the agent host; remote attackers + without shell access cannot see it. +- The same applies to the snapshot's transient `-deststorepass` + (which mirrors the operator's keystore password by design — + see "Why the snapshot reuses the keystore password" below). + +### Mitigations + +Layer one or more depending on threat model: + +- **Restrict shell access to the agent host.** Only the certctl + agent's service account should have a login shell. Other admins + SSH to a bastion that doesn't host the agent. +- **Use Linux user namespaces or AppArmor** to deny `ps`- + visibility into the keytool subprocess for non-root users. + systemd's `ProtectKernelTunables=yes` + `ProtectProc=invisible` + (kernel 5.8+) hides `/proc/` from non-owner users. +- **Run the certctl agent in a single-purpose container** so only + the agent's processes are visible to anyone who execs into the + container. The host's `ps` doesn't see container internals if + proper PID-namespace isolation is configured. +- **Rotate the keystore password post-deployment.** For + high-security environments where the brief exposure is + unacceptable, the rotation can itself be automated via a + post-deploy hook running `keytool -storepasswd`. The certctl + `reload_command` is the natural place for this; just be aware + the new password must be propagated to whatever service reads + the keystore (Tomcat's `server.xml`, Kafka's + `kafka.properties`, etc.). +- **For FIPS environments**, use the `BCFKS` (BouncyCastle FIPS) + keystore type which supports stronger password-derivation. Same + argv-exposure caveat applies; the keystore-format change + doesn't affect how keytool receives the password. + +For a fundamentally different password-handling model, switch to +a non-Java target (e.g. PEM-on-disk via the SSH connector + a +JCA-shim like `tomcat-native` reading PEMs directly) or a +PKCS#11 keystore (where the password is supplied to the cryptoki +library, not via argv). + +### Why the snapshot reuses the keystore password + +The snapshot's `keytool -exportkeystore` writes a PKCS#12 file +under a `-deststorepass`. The connector reuses the operator's +`keystore_password` for this rather than generating a separate +transient password. Two reasons: + +1. The operator already trusts the connector with this secret, + so the surface area doesn't grow. +2. The rollback's matching `keytool -importkeystore` needs to + know the password too, and threading a second random + password through the in-memory state machine adds complexity + (and another argv-exposure window) for no security gain. + +If you rotate the keystore password between deploys, the +rollback may fail to read the snapshot — keep stale +`.certctl-bak.*.p12` files on disk until the rotation completes, +and clean them up manually if rotation invalidates them. + +## Security baseline + +- Reload commands validated against shell injection via + `validation.ValidateShellCommand()`. +- Alias validated against injection (alphanumeric, hyphens, + underscores only). +- Path traversal prevention on keystore path. +- Transient PKCS#12 temp file cleaned up after import (even on + error). + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [Windows Certificate Store](wincertstore.md) — comparable cert-store deploy on Windows +- [SSH agentless](ssh.md) — alternative when the JVM target is reachable via SSH and you'd rather drop PEM files than maintain a keystore diff --git a/docs/reference/connectors/ssh.md b/docs/reference/connectors/ssh.md new file mode 100644 index 0000000..a94a069 --- /dev/null +++ b/docs/reference/connectors/ssh.md @@ -0,0 +1,194 @@ +# SSH (Agentless) Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the SSH agentless target +> connector. For the connector-development context (interface +> contract, registry, atomic deploy primitive shared across all +> targets), see the [connector index](index.md). + +## Overview + +The SSH connector enables agentless certificate deployment to any +Linux/Unix server via SSH/SFTP. Instead of installing the certctl +agent binary on every target, a single "proxy agent" in the same +network zone deploys certificates to remote servers over SSH. + +This is ideal for environments where installing agents on every +server is impractical — air-gapped servers, legacy fleets, or +brownfield environments where agent installation requires change- +control tickets per host. + +Implementation lives at `internal/connector/target/ssh/`. + +## When to use this connector + +Use the SSH connector when: + +- Installing the certctl agent on every target is impractical or + politically expensive. +- The agent-to-target network path is operator-controlled. +- You're deploying to known, registered infrastructure where the + operator implicitly trusts the host (you're already shipping it + a TLS cert). + +Look elsewhere when: + +- You're deploying across the public internet to dynamic / + multi-tenant hosts. The connector accepts any host key + (`InsecureIgnoreHostKey`); MITM resistance requires the + mitigations below. +- Your environment has strict regulatory MITM-resistance + requirements (PCI-DSS Level 1, FedRAMP High). The inline-comment + "out of scope" framing on host-key acceptance doesn't satisfy + auditors who want documented host-key verification at the + connector level. + +## Configuration + +### Key authentication (recommended) + +```json +{ + "host": "web-server.internal", + "port": 22, + "user": "certctl", + "auth_method": "key", + "private_key_path": "/home/certctl/.ssh/id_ed25519", + "cert_path": "/etc/ssl/certs/cert.pem", + "key_path": "/etc/ssl/private/key.pem", + "chain_path": "/etc/ssl/certs/chain.pem", + "reload_command": "systemctl reload nginx", + "timeout": 30 +} +``` + +### Password authentication + +```json +{ + "host": "legacy-server.internal", + "user": "deploy", + "auth_method": "password", + "password": "s3cret", + "cert_path": "/etc/ssl/cert.pem", + "key_path": "/etc/ssl/key.pem", + "reload_command": "systemctl reload apache2" +} +``` + +### Field reference + +| Field | Default | Description | +|---|---|---| +| `host` | (required) | SSH hostname or IP address | +| `port` | 22 | SSH port | +| `user` | (required) | SSH username | +| `auth_method` | `"key"` | `"key"` or `"password"` | +| `private_key_path` | — | Path to SSH private key file (key auth) | +| `private_key` | — | Inline SSH private key PEM (alternative to path) | +| `password` | — | SSH password (password auth) | +| `passphrase` | — | Passphrase for encrypted private keys | +| `cert_path` | (required) | Remote path for certificate file | +| `key_path` | (required) | Remote path for private key file | +| `chain_path` | — | Remote path for chain file (if empty, chain appended to cert) | +| `cert_mode` | `"0644"` | File permissions for cert (octal) | +| `key_mode` | `"0600"` | File permissions for private key (octal) | +| `reload_command` | — | Command to execute after deployment | +| `timeout` | 30 | SSH connection timeout in seconds | + +## Security baseline + +- **Key-based authentication is recommended** over password + authentication. Encrypted private keys are supported via + `passphrase`. +- **Reload commands are validated against shell injection** (same + validation as Postfix/Dovecot connectors). +- **Host field is regex-validated** to prevent shell metacharacters. +- **Private keys are written with 0600 permissions** by default. +- **Host key verification is intentionally skipped.** See the + threat model below. + +## 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, not an +oversight. + +### 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 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 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`. +- **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. + +### 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"). + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [F5 BIG-IP](f5.md) — comparable proxy-agent target where the agent doesn't run on the appliance itself +- [Kubernetes Secrets](k8s.md) — agent-in-cluster alternative when the targets are workloads rather than VMs diff --git a/docs/reference/connectors/wincertstore.md b/docs/reference/connectors/wincertstore.md new file mode 100644 index 0000000..44275e3 --- /dev/null +++ b/docs/reference/connectors/wincertstore.md @@ -0,0 +1,118 @@ +# Windows Certificate Store Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Windows Certificate Store +> target connector. For the connector-development context (interface +> contract, registry, atomic deploy primitive shared across all +> targets), see the [connector index](index.md). + +## Overview + +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, +LSA-protected services, etc. + +Same injectable `PowerShellExecutor` pattern as the IIS connector, +with optional WinRM proxy mode for agentless deployment to remote +Windows hosts. + +Implementation lives at `internal/connector/target/wincertstore/`. + +## When to use this connector + +Use the Windows Certificate Store connector when: + +- The target is a Windows service that reads certs from the + Windows cert store (Exchange transport TLS, RDP listener, SQL + Server SSL endpoint, ADFS token-signing cert, etc.). +- You don't want IIS-binding management (use the + [IIS connector](iis.md) for that). +- You're deploying via an in-host agent (`mode: local`) or via + WinRM from a proxy agent (`mode: winrm`). + +Look elsewhere when: + +- The target is IIS with site bindings — use the + [IIS connector](iis.md) for binding management. +- The target reads certs from a JKS / PKCS#12 keystore — use the + [Java Keystore](jks.md) connector. + +## Configuration + +```json +{ + "store_name": "My", + "store_location": "LocalMachine", + "friendly_name": "Production API Cert", + "remove_expired": true +} +``` + +| Field | Default | Description | +|---|---|---| +| `store_name` | `"My"` | Windows cert store name (My, Root, WebHosting, etc.) | +| `store_location` | `"LocalMachine"` | `"LocalMachine"` or `"CurrentUser"` | +| `friendly_name` | — | Optional friendly name for the imported certificate | +| `remove_expired` | `false` | Remove expired certs with same CN after import | +| `mode` | `"local"` | `"local"` (agent-local) or `"winrm"` (remote) | +| `winrm_host` | — | WinRM hostname (required for winrm mode) | +| `winrm_port` | 5985 | WinRM port (5985 HTTP, 5986 HTTPS) | +| `winrm_username` | — | WinRM username (required for winrm mode) | +| `winrm_password` | — | WinRM password (required for winrm mode) | +| `winrm_https` | `false` | Use HTTPS for WinRM | +| `winrm_insecure` | `false` | Skip TLS verification for WinRM | +| `exec_deadline` | `60s` | Per-PowerShell-subprocess cap that fires only when the caller's `ctx` has no deadline of its own. A caller-supplied deadline always wins; this is a safety net so a hung WinRM session or stuck `Cert:` provider call cannot block the deploy worker indefinitely. Operators on slow links can extend with e.g. `"exec_deadline": "5m"`. | + +## Deploy modes + +### `mode: local` + +Runs PowerShell in-process on the agent host. Requires the agent +to be installed on the Windows target itself. Best fit for +single-host services (a Windows server running Exchange or SQL +Server alone). + +### `mode: winrm` + +Runs PowerShell remotely via WinRM from a proxy agent. Best fit +for fleets where you don't want to install the certctl agent on +every Windows host. Use HTTPS WinRM (port 5986) with +`winrm_insecure: false` for production; HTTP WinRM (5985) is +acceptable on operator-controlled networks. + +## Operator playbook + +### Selecting the right store + +- `My` — personal cert store under LocalMachine. Default for + Exchange transport TLS, SQL Server, RDP, most service-account + workloads. +- `Root` — trusted root CA store. **Don't import leaves here.** + This is for adding trust anchors only. +- `WebHosting` — alternative store for IIS websites; the IIS + connector typically uses `My` instead. + +### Removing expired certs + +`remove_expired: true` cleans up old cert versions with the same +Subject CN after a successful import. Useful in long-running +fleets where the cert store accumulates dozens of expired entries +over years of rotations. + +### Handling private-key permissions + +Imported certs land with the Network Service account having read +access by default. For services running as a different account +(e.g. a domain user for SQL Server), the operator needs to grant +that account read access to the private key after import — this +isn't automated by the connector. Use the post-deploy +`reload_command` to run a `Set-Acl` step if you need it. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [IIS connector](iis.md) — IIS site-binding management on top of the cert store +- [Java Keystore](jks.md) — JVM-based service alternative