Add aws-sso setup ecs ssl --self-signed - #1470
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1470 +/- ##
==========================================
+ Coverage 86.04% 87.30% +1.27%
==========================================
Files 77 79 +2
Lines 4971 5228 +257
==========================================
+ Hits 4277 4564 +287
+ Misses 470 448 -22
+ Partials 224 216 -8
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🔵 Needs a closer look
It generates a long-lived private CA and instructs users to add it to their OS system trust store, a security-sensitive change that warrants final human review.
Pull request overview
This PR makes ECS Server TLS practical by adding aws-sso setup ecs ssl --self-signed, which generates a local private CA plus a short-lived leaf certificate, stores both in the SecureStore, and prints per-runtime trust instructions. It reframes the previous documentation stance (that self-signed/private CAs are unusable) to reflect that only Python/botocore truly can't trust a private CA for this endpoint. It fits into the existing setup ecs ssl command and the three secure-storage backends.
Changes:
- New stdlib-only
internal/certutilpackage that generates a long-lived CA and a 397-day leaf (with default SANs for localhost/loopback/169.254.170.2), plus fingerprinting. - SecureStorage gains CA key/cert persistence across all three backends (keyring, 1Password, JSON), with matching tests.
setup ecs sslgains--self-signed,--print-ca,--rotate-ca, and--san, plus printed OS/runtime trust instructions and refreshed docs.
File summaries
| File | Description |
|---|---|
| Makefile | Bumps project version 2.3.2 → 2.4.0. |
| internal/certutil/certutil.go | New CA/leaf generation, SAN merging, and fingerprint helpers. |
| internal/certutil/certutil_test.go | Unit tests for CA/leaf generation, chain verification, SANs, fingerprint. |
| internal/storage/secure_store.go | Adds CA key-pair methods to the SecureStorage interface. |
| internal/storage/keyring.go | Implements CA key-pair storage and adds StorageData fields. |
| internal/storage/onepassword.go | Implements CA key-pair storage (reuses StorageData). |
| internal/storage/json_store.go | Implements CA key-pair storage and adds JsonStore fields. |
| internal/storage/*_test.go | Round-trip tests for CA key-pair storage per backend. |
| cmd/aws-sso/setup_cmd.go | New flags and self-signed/print-ca/rotate flow; CA export to ~/.aws-sso/ecs-ca.pem. |
| cmd/aws-sso/setup_cmd_ecs_ssl_trust.go | Renders per-runtime trust instructions. |
| cmd/aws-sso/setup_cmd_test.go, setup_cmd_e2e_test.go | Unit + e2e coverage incl. real chain-of-trust HTTPS. |
| docs/ecs-server.md, docs/ecs-threats.md, docs/remote-ssh.md | Documentation updates for the new flow. |
Review details
- Files reviewed: 17/17 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Adds targeted unit tests for the error/edge-case branches Codecov flagged on PR #1470: certutil's CA/key parsing failures (invalid PEM, unparsable key, non-ECDSA key), setup_cmd's Store-error and filesystem-error paths in loadOrGenerateCa/runSelfSigned/printCaAndInstructions (via a small errStore test double), and direct tests of warnIfCertExpiringSoon using flexlog's test logger to assert on the actual warning message. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
b92a9db to
ff5c95c
Compare
The ECS Server's TLS story was a dead end: docs told users the AWS SDKs "do NOT support self-signed certificates or private CA's," and `setup ecs ssl` only accepted a certificate from a public CA -- which no public CA will issue for localhost/loopback/169.254.170.2. Tracing the actual SDK sources shows this is stale for every SDK except Python/botocore: Go SDK v2, Node, Java, and .NET all honor the OS trust store (or NODE_EXTRA_CA_CERTS for Node), so a private CA trusted once per machine/runtime makes ECS Server TLS work everywhere except the AWS CLI. botocore's container-credentials fetcher hardcodes verification against certifi/its own vendored bundle, ignoring both AWS_CA_BUNDLE and the OS trust store (tracked upstream: aws/aws-sdk#9016) -- the trust instructions call this out explicitly with an installation-method-agnostic workaround. This adds `--self-signed`, which generates a local CA + leaf certificate via a new internal/certutil package, stores both in the SecureStore (mirroring the existing leaf-only storage across the keyring, 1Password, and JSON backends), and prints per-runtime trust instructions. The CA private key is retained and reused across reruns, so trust only needs to be established once per machine -- rerunning `--self-signed` rotates only the short-lived leaf. `--print-ca` re-displays the instructions on a new machine without regenerating anything, and `--rotate-ca` forces a fresh CA when actually needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
The BYO-certificate path was already labeled experimental and gated behind `--force` (issue #936). Now that `--self-signed` exists and is the recommended, fully-supported path, there's no reason to keep the manual load-your-own-cert flow around -- it only adds surface area for a case `--self-signed` already covers (private CA for localhost/loopback, which is the only reason a public CA can't be used here in the first place). `--self-signed` is now the default action of `setup ecs ssl` when no other flag is given, since it's the only certificate-issuing action left. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
It's a legitimate, occasionally-needed operation (leaf SANs changing in a way that needs a fresh CA, suspected key compromise, etc.), not an internal/debug-only flag, so it should show up in --help. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
`--self-signed`/`--print-ca` printed a long block of per-OS/per-runtime trust instructions on every run. Move the full instructions (including the Python/AWS CLI caveat) into docs/ecs-server.md and have the CLI print only a short summary (CA path, fingerprint, SANs) plus a link to the docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
Add certutil.NotAfter and check both the leaf and CA certificates when
'ecs server' starts with SSL/TLS enabled, warning if either expires within
30 days (or already has) and naming the fix ('--self-signed' for the leaf,
'--rotate-ca' for the CA). The CA check is skipped in --docker mode, since
the mounted security config file only carries the leaf cert/key, not the CA.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
Adds targeted unit tests for the error/edge-case branches Codecov flagged on PR #1470: certutil's CA/key parsing failures (invalid PEM, unparsable key, non-ECDSA key), setup_cmd's Store-error and filesystem-error paths in loadOrGenerateCa/runSelfSigned/printCaAndInstructions (via a small errStore test double), and direct tests of warnIfCertExpiringSoon using flexlog's test logger to assert on the actual warning message. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
--print-ca's help text and docs said it prints the CA certificate, but printCaAndInstructions only ever printed the path/fingerprint/trust instructions, never the PEM itself -- unlike --print, which does print the leaf certificate. printCaAndInstructions now takes a printCert bool so --print-ca prints the CA PEM (matching --print's behavior) while --self-signed's internal call keeps its existing summary-only output. fix: export the ECS CA cert under config.ConfigDir() instead of a fixed ~/.aws-sso path EcsCaExportPath was hardcoded to "~/.aws-sso/ecs-ca.pem". config.ConfigDir() treats the mere existence of ~/.aws-sso as a directory as a signal to switch the whole tool into legacy (non-XDG) mode, so on a fresh XDG install this would silently create that directory and flip every other aws-sso config/ store/cache file over to the legacy path on the next run. Write the CA export next to the rest of aws-sso's config/store files instead, using whichever location config.ConfigDir() actually resolves to.
The CA (10-year, ECDSA P-256) is still generated once and persisted in the SecureStore -- that's what avoids re-trusting anything on every rerun. The leaf it signs no longer is: it's now minted fresh, in memory, wherever it's about to be used -- native `ecs server` startup, or `ecs docker start` / `write-config` right before the container starts -- and validity drops from 397 days to 30 since there's nothing left to rotate by hand. Custom SAN support (`--san`) is dropped; the default SANs already cover every documented use case. Because every leaf mint now touches the CA private key (not just the rare `--self-signed`/`--rotate-ca` run), add best-effort memory scrubbing: certutil.ZeroSecret for the PEM string, zeroPrivateKey for the parsed scalar in GenerateLeaf. Both are explicitly defense-in-depth, not a secure-erase guarantee. Fetching the CA key from a SecureStore backend returns its cached string directly (shared backing array), so every mint call site clones it via strings.Clone before mutating/zeroing it -- zeroing an aliased string would otherwise corrupt the store's own in-memory copy (and crash on read-only-backed literals). setup_cmd.go's EcsSSLCmd narrows to CA-only management (Delete, PrintCa, SelfSigned, RotateCa); its reuse path no longer reads the CA key at all. ecs_client_cmd.go switches to CA-based trust so it validates whichever leaf is actually being served rather than pinning one that no longer persists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
Rewrite the ECS Server SSL section for the new certificate lifecycle: the CA is generated/reused via --self-signed and is the only thing that's persisted; a 30-day leaf is minted automatically in memory wherever it's needed and never rotated or printed by hand. Drop the --print/--san documentation, narrow the expiry-warning paragraph to CA-only, and note the long-running-compose-without-restart limitation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
ecs-threats.md proposed a hosted CSR-signing web service that was never built; --self-signed's local CA solved the underlying problem differently, leaving the doc's Solution/Attacks/Suggestions sections describing a design that won't be pursued. It was only reachable via its own mkdocs nav entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
…SSL test flake TestEcsSSLCmdRun_PrintCa_EnsureDirExistsError and ...WriteFileError used assert.Error(t, err) (a soft assertion) immediately followed by err.Error(), so on the rare occasion err came back nil the very next line dereferenced a nil interface and panicked, crashing the whole test binary instead of just failing the one test -- this is what took down the full `make coverage` run in CI. Switch both to require.Error so a recurrence fails cleanly. Also derive the CA export path via config.ConfigDir() instead of manually reconstructing it, so the test can never target a different directory than what printCaAndInstructions actually resolves at call time. Root cause of the CI-only failures: config.ConfigDir() prefers XDG_CONFIG_HOME over $HOME, with a heuristic that only re-derives it from a changed $HOME when the old XDG_CONFIG_HOME exactly matches the old $HOME/.config. GitHub Actions' runners export a real XDG_CONFIG_HOME (e.g. /home/runner/.config), so tests that override $HOME via t.Setenv without also touching XDG_CONFIG_HOME still resolve config.ConfigDir() to the real, un-isolated directory. One of the errpath tests then deletes and overwrites that real shared directory, corrupting it for every other test in the process (the cascade of "storage.lock: not a directory" failures seen in CI). Reproduced locally by setting XDG_CONFIG_HOME=$HOME/.config before running the suite -- confirmed the same corruption occurs, and that clearing XDG_CONFIG_HOME alongside the $HOME override (t.Setenv can't express "unset", so this is a manual save/unset/restore) fixes it, by adding a shared unsetXDGConfigHomeForTest helper and calling it everywhere a test overrides $HOME for config.ConfigDir() isolation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
There was a problem hiding this comment.
🔵 Needs a closer look
It is a broad, security-sensitive change (private-CA generation, persisted key material, TLS trust) that warrants human review, and it also contains documentation inaccuracies that should be corrected first.
Review details
Suppressed comments (1)
docs/ecs-commands.md:41
- These flag docs are out of sync with the implemented command. The current
EcsSSLCmdonly definesDelete,PrintCa,SelfSigned, andRotateCa(cmd/aws-sso/setup_cmd.go), so--san(line 36) and--print(line 40) no longer exist —--sanwas dropped and there is no leaf-printing flag. Also,--self-signedno longer "issue[s] a new leaf certificate"; it only generates/reuses the CA (the leaf is minted in memory at server/docker startup). Please remove the stale flags and correct the wording.
* `--self-signed` -- Generate (or reuse) a local CA and issue a new leaf certificate for the
ECS Server. This is the default action when no other flag is given.
* `--san` -- Additional DNS name or IP address to include in the self-signed leaf certificate (repeatable)
* `--rotate-ca` -- Force generation of a brand new CA instead of reusing the existing one (requires
re-trusting on every client)
* `--print-ca` -- Prints the local self-signed CA certificate and re-prints trust instructions
* `--print` -- Prints the current SSL (leaf) certificate
* `--delete` -- Disables SSL and deletes the current SSL certificate/private key and CA from the Secure Store
- Files reviewed: 27/27 changed files
- Comments generated: 3
- Review effort level: Balanced
3672b00 to
f375ef5
Compare
… key Two hardening fixes to the self-signed CA code. Name constraints: GenerateCA now emits a critical nameConstraints extension (RFC 5280) permitting only localhost, 127.0.0.0/8, ::1/128, and 169.254.170.2/32. This matters because the documented setup installs this CA as a trust root -- OS trust store, JVM cacerts, NODE_EXTRA_CA_CERTS. Without constraints, anyone who obtained the CA private key could forge a valid certificate for any name at all (sts.amazonaws.com, a bank, a corporate SSO endpoint), turning a localhost convenience CA into a universal MITM capability for that machine. Constrained, a leaked key can only forge certs for the endpoints the ECS Server itself listens on. Both a DNS and an IP constraint are set deliberately: under RFC 5280 a name type carrying no constraint of that type is entirely unconstrained, so permitting only DNS names would have left IP SANs wide open. The extension is marked critical so a validator that does not understand nameConstraints rejects the CA outright instead of silently treating it as unconstrained. Key scrubbing: zeroPrivateKey used big.Int.SetInt64(0), which only reslices the scalar's backing word array to length zero -- every word of the secret survives intact in memory behind it. It never scrubbed anything. Overwrite the words through the slice, then normalize via SetBits. Tests cover both. The name-constraint tests assert the extension is present and critical, that every default leaf SAN still verifies against the CA (the constraints must stay a superset of the SANs or the CA cannot sign its own leaves), and that certificates forged with the real CA key for foreign names fail verification. TestZeroPrivateKey now holds the backing array and asserts it is actually zeroed; its previous key.D.Sign() check passed against the broken implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh
Reports whether a leaf was issued by a given CA and is still valid for server authentication. Callers that hold an already-persisted leaf need this to tell "still good, reuse it" from "the CA was rotated out from under it, re-mint". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
Serve() was writing the certificate and private key to a temp directory purely because http.Server.ServeTLS takes file paths. ServeTLS only reads those paths when TLSConfig has no certificate of its own, so loading the PEM with tls.X509KeyPair and passing empty paths keeps the key off disk entirely. Also sets MinVersion to TLS 1.2, which the file-based path never specified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
An expired leaf breaks every TLS client regardless of what credentials are loaded, and the container cannot fix it itself. Returning 503 surfaces that through `docker ps` and blocks `depends_on: condition: service_healthy`, instead of letting dependent containers start and fail with opaque TLS errors. The expiry is parsed once in NewEcsServer rather than per probe. A parse failure there is not fatal -- Serve() validates the pair properly and fails loudly -- so the healthcheck just treats an unparseable cert as unexpired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
The CA certificate was exported to ecs-ca.pem under the config directory on every --self-signed run, so the trust instructions could name a path. That put a second copy of the CA on disk that nothing cleaned up, and left the stored copy and the exported one able to drift after a rotation. The CA now lives only in the secure store. --print-ca writes the PEM to stdout and nothing else, so it can be redirected straight to a file the trust-store commands consume. The fingerprint and the docs link would corrupt that redirect, so they moved to the --self-signed summary instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
`docker compose` owns its own container lifecycle, so the ECS Server has to come back up with the same security posture after a restart or recreation without aws-sso being in the loop. Three changes make that work. Rename `ecs docker write-config` to `ecs docker secrets`. The old name described the mechanism and read like it wrote config.yaml; what it emits is a bearer token and a private key. It never shipped, so there is no alias. Persist the leaf certificate and refresh it as it ages. `ecs docker secrets` writes the leaf into the security file rather than relying on a fresh one per start, and reuses a still-valid one so a running container's certificate is not invalidated by a re-run. The host-side `ecs load`/`list`/`profile` commands top up an aging leaf, so a compose deployment does not silently lapse once the 30-day validity runs out. Add `ecs --secrets-dir` (env AWS_SSO_ECS_SECRETS_DIR). The directory was fixed at <config dir>/ecs, and compose.yaml advertised an AWS_SSO_ECS_DIR variable the binary never read -- the two could not be pointed at the same place. The flag sits on the `ecs` parent so one setting covers writing the files, bind-mounting them, and refreshing the leaf. Relative paths are made absolute because docker bind mounts reject a relative source. That last change forces a split in internal/ecs: once the host side takes a directory, the ECSFileMode dispatch no longer holds a common signature, so SecurityFilePath/OpenSecurityFile become OpenHostSecurityFile(dir) plus ContainerSecurityFilePath/OpenContainerSecurityFile. Only the container half still needs a path override for tests -- its path is baked into the image and cannot be created on the host -- so host-side tests pass a temp dir instead of mutating a package global. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
09fbced to
fe02c44
Compare
Adds compose.yaml as a working example, describes the CA-persists / leaf-refreshed model, and covers `ecs docker secrets`, `--secrets-dir`, and AWS_SSO_ECS_SECRETS_DIR. Also audited every `ecs` subcommand's documented flags against the binary. That turned up an `--image` default naming an image that does not exist (`...-ecs-version`), a `--slotted` flag documented on `ecs profile` which does not accept it, and missing entries for `--sts-refresh`, `--bind-ip`, `--port`, `-d/--default`, `ecs docker stop --version`, and the AWS_SSO_ECS_SERVER environment variable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpzTc3khofuwL3uLRH3oE8
fe02c44 to
4e94779
Compare
The KeyPair doc comment still named SaveEcsSslKeyPair, which this branch renamed to SaveEcsCaKeyPair. Only SaveEcsCaKeyPair validates the key now, so name just that one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LDKQDAmBV12j7zi2LvFP8E
… for --docker Empty PrivateKey/CertChain/BearerToken in the security file used to mean either "explicitly disabled" or "never configured" indistinguishably, so `ecs server --docker` could only fail closed when the file itself was missing -- an empty-but-present file silently started serving credentials with no TLS or HTTP Auth. ECSSecurity now records DisableSSL/DisableAuth at write time, so the Docker server can fail closed on an unexplained gap and start open only when the file says so. --docker no longer reads its own --disable-auth/--disable-ssl flags at all; ecs docker secrets is now the sole place to request that posture. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LDKQDAmBV12j7zi2LvFP8E
Summary
The ECS Server's TLS story was a dead end: docs told users the AWS SDKs "do NOT support self-signed certificates or private CA's," and
setup ecs sslonly accepted a certificate from a public CA — which no public CA will issue forlocalhost/loopback/169.254.170.2.Tracing the actual SDK sources shows this is stale for every SDK except Python/botocore:
NODE_EXTRA_CA_CERTSfor Node), so a private CA trusted once per machine/runtime makes ECS Server TLS work.certifi/its own vendoredcacert.pem, ignoring bothAWS_CA_BUNDLEand the OS trust store (tracked upstream: aws/aws-cli#9016). The printed trust instructions call this out explicitly, along with an installation-method-agnostic workaround (Homebrew/pipx/the official installer each bundle their own isolated Python, so a barepython3on$PATHis often the wrong interpreter).This adds
aws-sso setup ecs ssl --self-signed, which generates (or reuses) a local CA (newinternal/certutilpackage, stdlib only, P-256 ECDSA, 10-year validity) and prints per-runtime trust instructions (macOS/Linux/Windows OS store, Node, JVM, .NET, Python/AWS CLI caveat). The CA private key is the only thing persisted, in the SecureStore, mirrored across the keyring, 1Password, and JSON backends.The leaf certificate is never persisted
An earlier revision of this PR also generated a leaf certificate up front and stored it in the SecureStore, rotated by manually rerunning
--self-signed. That's been replaced with a stronger model: the leaf is minted fresh, in memory, at whichever CLI invocation is the natural "about to start" moment — nativeaws-sso ecs serverstartup, oraws-sso ecs docker start/write-configright before the container starts (which already has to run before everydocker compose up, since the container deletes its security file after one read — so this costs nothing new operationally). Validity dropped from 397 days to 30 since there's nothing left to rotate by hand, and custom SAN support (--san) was dropped since the fixed defaults already cover every documented use case.Because every leaf mint now touches the CA private key (previously only
--self-signed/--rotate-cadid), added best-effort memory scrubbing (certutil.ZeroSecretfor the PEM string,zeroPrivateKeyfor the parsed scalar) at every mint call site — explicitly defense-in-depth, not a secure-erase guarantee, since Go's string immutability, a moving GC, and the SecureStore backends' own internal buffers all limit what's reachable to zero.setup ecs sslnarrows to CA-only management:--self-signed(generate/reuse the CA),--print-ca,--delete,--rotate-ca.ecs_client_cmd.go(and the e2e/polling helpers) validate via the CA rather than pinning an exact leaf, since the served leaf is now minted independently on each run.Test plan
go build ./...,go vet ./...,gofmt -l .go test ./internal/certutil/... ./internal/storage/...— CA/leaf generation (30-day leaf validity), chain verification viax509.Verify,ZeroSecret/zeroPrivateKeyscrubbing, CA-only storage round-trips across all three backendsgo test ./cmd/aws-sso/...(unit) — nativeecs serverandecs docker start/write-configeach mint a fresh leaf from the stored CA (chain-verified, non-identical across runs), fail fast on a malformed CAgo test -tags e2etests ./cmd/aws-sso/...— full CA generate/reuse/print-ca/delete cycle, a realServeTLSchain-of-trust HTTPS request against a client trusting only the CA (and a negative case against an unrelated CA), andecs serverminting its own leaf from a directly-seeded CAnpx markdownlint-cli2on changed docsecs servertwice and confirmed the served leaf's serial changes on restart while the CA fingerprint stays fixed, ranecs docker write-configand confirmed a freshly minted leaf lands in the security file, confirmed--deleteclears the CAgolangci-lint run— currently panics in this environment on a pre-existing go1.26/go1.27 toolchain mismatch (reproduces identically onmain, unrelated to this change)🤖 Generated with Claude Code
https://claude.ai/code/session_01UDJ1csupGfLzVyuy2qEfYh