yamine 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db8822f4d49064d2cb61c2219c43c7049eafc7e44a2bb4b918981ef7a4bd8d11
4
- data.tar.gz: 31c30cc196665056aab7f711c092003f775af0d684fc891adcf039da1603a96b
3
+ metadata.gz: 06f020588bc63954dd44b5a99b598995ec3e6f562c03756b867d26ff44178af8
4
+ data.tar.gz: 97f1613f7ad01d1362e4098651048addd3a4a738912c6bd83427e3ea9453ad8d
5
5
  SHA512:
6
- metadata.gz: 85e04f77e692d458765ef87714bb83dac175f387dec1bba92bb46e4ac405d292d693a0292187bf427b6fc56bbbbf1263635bdcbcbd3426295531235aab51dc67
7
- data.tar.gz: 226f812a9accf7265fcae4f1ae74328bb6229ee9199af790e2d744085fed517f7445a6c501c94b6ec188d8f0575677f91753831b9ef502349b4bf2f873b5dbf3
6
+ metadata.gz: 3f03809157db470e905542751e16d8a6cb6edd6b91a78637f20e85fb5670e6b30e15f717d1bbaed714c70b077a756fe97744bfb27521304606b2d661dea57eab
7
+ data.tar.gz: 83bb42d5eb49f6ee4007d04fa3e1b13024a46044d1afc7756a126f43384f60cb9c6ca57d7440d77ffcbacd37b9d95b04dbf8eb933bbf1676a10440e4951bdba7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.2] — 2026-09-10
4
+
5
+ ### Added
6
+
7
+ - `doctor` reports trusted CAs that are no longer the one on disk
8
+ (`[warn] ca: CA trusted, but N superseded CA(s) are still trusted —
9
+ run: yamine trust`). Pruning only runs inside `trust`, so after a CA
10
+ regeneration (including the rename) a superseded root would sit in the
11
+ keychain waiting for the next missing-CA event — possibly forever.
12
+ Unrelated trusted roots are never counted, and an unreadable keychain
13
+ stays silent: doctor is a read-only probe and must not fail.
14
+
15
+
16
+ ## [0.10.1] — 2026-09-10
17
+
18
+ ### Fixed
19
+
20
+ - **The CA is regenerated before trust is judged, not after.** Both
21
+ trust paths asked `Certs.trusted?` first, which compares the trust
22
+ marker against the certificate ON DISK. A proxy regenerates the CA at
23
+ boot when the on-disk one is missing, expiring, or renamed — so after
24
+ a rename the marker still matched the OLD certificate, trust was
25
+ skipped as "already done", and the proxy then came up serving a CA the
26
+ keychain did not trust: TLS verification fails for every route. The
27
+ check now brings the CA up to date first (`ca_current_and_trusted?`,
28
+ used by both paths).
29
+
30
+
3
31
  ## [0.10.0] — 2026-09-10
4
32
 
5
33
  The ask-local rename is finished: the two user-visible names it left
@@ -334,13 +334,29 @@ module Yamine
334
334
  puts "Installed root LaunchDaemon on port 443 (state: #{state_dir})."
335
335
  end
336
336
 
337
+ # True when the trust store covers the CA this machine should be
338
+ # serving with — regenerating the CA first if it is missing,
339
+ # expiring, or renamed.
340
+ #
341
+ # The ordering is the whole point, so it lives in one place rather
342
+ # than at each call site. `trusted?` compares a marker against the
343
+ # fingerprint of the certificate ON DISK, and a proxy regenerates
344
+ # the CA at boot when the name changes. Checking the marker before
345
+ # the CA is current therefore reads a stale match, skips trust, and
346
+ # lets the proxy come up serving a CA the keychain does not trust —
347
+ # breaking TLS for every route.
348
+ def ca_current_and_trusted?(dir = Certs.state_dir)
349
+ Certs.ensure_ca(dir)
350
+ Certs.trusted?(dir)
351
+ end
352
+
337
353
  # Root-only CA trust: the System keychain (all users, no prompt).
338
354
  # Safe to call repeatedly — once the marker is set it is a no-op.
339
355
  # Prints before doing work: the System keychain add can take a few
340
356
  # seconds, and this runs in the root half of setup where silence
341
357
  # reads as a hang.
342
358
  def ensure_system_ca_trust
343
- return if Certs.trusted?(Certs.state_dir)
359
+ return if ca_current_and_trusted?(Certs.state_dir)
344
360
 
345
361
  puts " Trusting the CA into the System keychain..."
346
362
  result = Trust.trust
@@ -760,7 +776,7 @@ module Yamine
760
776
  # those users had already chosen to avoid.
761
777
  def ensure_workstation!(ctx)
762
778
  # 1. CA
763
- unless Yamine::Certs.trusted?(ctx.store.dir)
779
+ unless ca_current_and_trusted?(ctx.store.dir)
764
780
  result = Yamine::Trust.trust
765
781
  unless result[:trusted]
766
782
  abort_setup("CA trust failed: #{result[:error]}",
data/lib/yamine/doctor.rb CHANGED
@@ -174,11 +174,45 @@ module Yamine
174
174
  unless File.file?(paths[:cert])
175
175
  return Check.new(name: "ca", ok: false, message: "no CA yet — run: yamine trust")
176
176
  end
177
- if Certs.trusted?(dir)
178
- Check.new(name: "ca", ok: true, message: "CA trusted")
177
+ unless Certs.trusted?(dir)
178
+ return Check.new(name: "ca", ok: false, message: "CA not trusted — run: yamine trust")
179
+ end
180
+
181
+ # Trusted CAs that are not the one now on disk. They accumulate from
182
+ # CA regeneration (missing, expiring, or renamed — the ask-local
183
+ # rename regenerates every machine's CA), and a trusted root whose
184
+ # superseded private key is still on disk is a liability. Pruning
185
+ # only happens when `trust` runs, so after a rename it would wait
186
+ # for the next missing-CA event — i.e. possibly forever. Surface it
187
+ # here with the one command that clears it.
188
+ stale = stale_ca_count(dir)
189
+ if stale.positive?
190
+ Check.new(name: "ca", ok: true, warn: true,
191
+ message: "CA trusted, but #{stale} superseded CA(s) are still trusted" \
192
+ " — run: yamine trust")
179
193
  else
180
- Check.new(name: "ca", ok: false, message: "CA not trusted — run: yamine trust")
194
+ Check.new(name: "ca", ok: true, message: "CA trusted")
195
+ end
196
+ end
197
+
198
+ # How many trusted certificates carry one of our CA names without
199
+ # being the CA currently on disk. Best-effort: 0 when the keychain
200
+ # cannot be read (doctor must never fail on a read-only probe).
201
+ def stale_ca_count(dir = Certs.state_dir)
202
+ return 0 unless Trust.respond_to?(:keychain_certs)
203
+
204
+ current = Trust.fingerprint_of(Certs.ca_paths(dir)[:cert])
205
+ return 0 unless current
206
+
207
+ names = Certs.ca_common_names
208
+ Trust.keychains.sum do |keychain|
209
+ Trust.keychain_certs(keychain).count do |entry|
210
+ entry[:fingerprint] != current &&
211
+ names.any? { |n| entry[:subject].include?(n) }
212
+ end
181
213
  end
214
+ rescue StandardError
215
+ 0
182
216
  end
183
217
 
184
218
  def print(checks, out: $stdout, json: false)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yamine
4
- VERSION = "0.10.0"
4
+ VERSION = "0.10.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yamine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto