Commit b304c8c
Add
* Add `trellis vm trust` for self-signed SSL certs
Automates extracting the per-site self-signed cert and key from the Lima
VM and trusting the cert on the host (macOS login keychain + Firefox NSS
on macOS and Linux). Replaces the manual 6-step ritual previously
documented for Lima users.
Adds `vm trust`, `vm untrust`, and `vm trust paths` subcommands. Trust
state is recorded in ~/.local/share/trellis/state/trusted_certs.json
with a process-level flock so concurrent runs serialize. Verify checks
the live trust setting (macOS verify-cert, Linux system bundle, NSS
fingerprint) so drift triggers a re-trust instead of silently passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Simplify fingerprinting
Compute macOS specific one within macOS specific code and only expose a
single `Fingerprint` field.
* Move per-site trust orchestration into pkg/trust
Lift the read-cert / fingerprint / decide-skip-or-retrust / untrust-old /
trust-new / record-state sequence out of cmd/vm_trust.go into
trust.ApplySite, plus the symmetric trust.RevokeSite for vm_untrust.go.
Path/label helpers (ProjectID, Label, ExportDir) move alongside since
the package owns the on-disk layout.
The cmd files now hold only CLI concerns: flag parsing, site selection,
VM cert fetching, and UI formatting. vm_trust.go Run drops from ~230
to ~100 lines; vm_untrust.go from ~135 to ~75. Trust logic, state
mutation, and path conventions are now testable without spawning
security/certutil from a cmd test.
Drive-by: the private key is now written via a temp-file + rename
helper, closing a brief window where an existing key file at 0o644
would hold the new private bytes before Chmod ran.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop the trust-state flock
The lock guarded a vanishingly rare race: two concurrent `trellis vm
trust` runs against the same state file. State.Save already does
temp-write + atomic rename, so concurrent saves can't corrupt the file
— the realistic hazard is a lost update across a load/modify/save
window, which requires the user to run the command twice in parallel
by hand. The Linux user CA bundle rebuild has the same shape.
If lost updates ever show up in practice the right fix is a short-lived
flock inside State.Save, not a multi-second lock around the whole trust
loop (which on Linux can include blocking sudo prompts).
Removing it deletes lock_unix.go, the windows no-op stub, and the
AcquireLock calls in both cmd files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add unit tests for trust path helpers and ApplySite/RevokeSite
Covers the deterministic part of the package:
- paths_test: ProjectID stable + distinct, Label format, ExportDir
separates forks of the same instance name
- format_test: FormatLocation across macOS / Linux / NSS / unknown
- store_test: VerifyResult.AllAccounted (Missing breaks, Unknown counts,
lengths must match)
- linux_test: safeFilename (dot/dash/underscore preserved, slashes and
control chars and unicode replaced)
- apply_test: ApplySite via a recordingStore Store fake — fresh trust,
fingerprint-match skip, drift re-trust, fingerprint-changed re-trust,
untrust-error state-preservation, trust-error partial state,
empty-cert early exit, key file mode 0o600, key file removal when
KeyPEM is empty, CertPath refresh on skip; RevokeSite success + error;
writeFileAtomic mode invariant on existing 0o644 file
Coverage for pkg/trust goes from ~7% to ~27%, but the relevant number is
that ApplySite is now at 87% and RevokeSite at 100%. Remaining 0% is in
macos/linux/nss.go where exec-binding makes them Tier 4 work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add filesystem-only tests for state, linux bundle, NSS, macOS helpers
Covers the platform store layer's pure-FS and pure-string parts. Uses
t.Setenv(\"XDG_DATA_HOME\") and t.Setenv(\"HOME\") to point app_paths
and Firefox profile lookup at tempdirs — no production refactor needed
since both already respect those env vars.
- state_test: Load on missing/empty/corrupt JSON, Save+Load round-trip
preserves all fields including AddedAt, Save leaves no temp files
behind from the atomic-rename path
- linux_test: rebuildBundle concatenates user CAs in dir order, injects
trailing newlines so adjacent PEM blocks parse, removes the bundle
when the dir is empty; pemFileContainsFingerprint scans all PEM
blocks; fileFingerprintMatches handles match/mismatch/missing-file
- nss_test: isNSSNicknameNotFound covers the certutil error variants;
isNSSProfile detects cert9.db (modern) and cert8.db (legacy);
firefoxProfileDirs walks the platform-appropriate parent
- macos_test: isKeychainNotFound covers the security CLI error
variants; sha1HexFromCertFile matches crypto/sha1 over DER and
returns empty on missing/invalid input
Coverage for pkg/trust now 47.6% (was ~27% after Tier 2, ~7% baseline).
The remaining 0% surface is exec-bound: linuxStore.Trust/Untrust/
Verify, macOSStore.Trust/Untrust/Verify, nssTrust/Untrust/Verify,
keychainTrustsCertForSSL, keychainHasFingerprint. Those are Tier 4
work that needs an exec runner shim.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Test platform stores via injectable exec runner
Add a runner interface (Run, RunStdin, Lookup) and a default execRunner
wrapping os/exec. Each platform store and a new nssHelper take a runner
so tests can inject a recording fake instead of spawning real binaries.
The fake (runner_test.go) registers single-shot canned responses keyed
by program name + an args prefix and records every call so tests can
assert on argument shape and call sequence.
Tests cover the previously-zero-coverage branches:
- macOS: Trust calls add-trusted-cert with -p ssl / -r trustRoot;
Untrust deletes when find-certificate confirms presence; skips
delete when fingerprint is already gone; treats "could not be
found" as success; surfaces other delete errors; Verify maps
verify-cert outcomes to Present / Missing (not-trusted) / Unknown
- Linux: Trust writes user CA + bundle without sudo; --trust-system
invokes sudo tee + sudo update-ca-certificates; system location is
recorded even when update-ca-certificates fails so untrust can clean
up; Untrust removes user CA file; system Untrust runs sudo rm -f and
update-ca-certificates --fresh; Verify requires both file and bundle
- NSS: disabled is a no-op; reports CertutilMissing without erroring;
reports FirefoxFound=false when no profiles exist; happy path runs
-D then -A in order; -A failure surfaces; Untrust treats nickname-
not-found as success; certutil-missing-but-records-exist returns
error; Verify classifies present / missing / unknown
Drive-by fix: rebuildBundle would error when called with the user CA
dir absent and no bundle present (os.Remove on missing file). Now
guarded with os.IsNotExist so untrust paths that bypass the user CA
location don't panic on a fresh install.
Coverage for pkg/trust now 81.2%, up from 47.6%. Remaining 0% is
execRunner itself (the prod-only os/exec wrapper) and Default()
(GOOS dispatcher).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Scott Walkinshaw <scott.walkinshaw@gmail.com>trellis vm trust for self-signed SSL certs (#682)1 parent aaf00ca commit b304c8c
30 files changed
Lines changed: 4032 additions & 0 deletions
File tree
- cmd
- pkg
- lima
- trust
- vm
- trellis
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
0 commit comments