Skip to content

Commit 949bbc3

Browse files
committed
Make landmarks mandatory; drop landmarks.enabled and landmark_url_path config
Landmark-relative certs are no longer opt-in. Removes the LandmarkConfig.Enabled gate (landmarks always run) and the landmark_url_path knob (the /landmarks route was already hardcoded in tile/server.go, so the field was dead config). The cadence and max-cert-lifetime knobs remain tunable. - config: drop Enabled/URLPath fields; validate cadence + lifetime unconditionally. - cmd/cactus: always build the landmark.Sequence and wire it into the log OnFlush hook, ACME, and tile server. - update config examples, README, and the binary integration test.
1 parent 6b263f2 commit 949bbc3

7 files changed

Lines changed: 64 additions & 82 deletions

File tree

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,13 @@ determined by which top-level config blocks are populated and
128128

129129
| Concern | Adds | Set |
130130
|---|---|---|
131-
| **CA** (default) | Issuance log + ACME server | `acme`, `log`, `ca_cosigner` |
132-
| **Landmarks** | Landmark-relative cert support | `landmarks.enabled = true` |
131+
| **CA** (default) | Issuance log + ACME server + landmark-relative certs | `acme`, `log`, `ca_cosigner` |
133132
| **CA-side mirror collection** | Multi-mirror cosignatures during issuance | `ca_cosigner_quorum.mirrors[]` |
134133
| **Mirror operating mode** | Follow an upstream + serve `/sign-subtree` | `mirror.enabled = true` (and `mirror.upstream`) |
135134

135+
Landmark-relative cert support is always on; only its cadence is
136+
tunable (see `landmarks` below).
137+
136138
Cactus operating modes are not enumerated; the binary just brings up
137139
whichever subsystems the config asks for. The validator does enforce
138140
some hygiene rules — chiefly, mirror + CA modes in the same binary
@@ -271,22 +273,23 @@ and an older toolchain simply won't compile cactus.
271273
valid; for tests) or `http-01` (real fetch of
272274
`http://identifier/.well-known/acme-challenge/<token>`).
273275

274-
### `landmarks` (optional)
276+
### `landmarks` (tuning only)
275277

276278
```json
277279
"landmarks": {
278-
"enabled": true,
279280
"time_between_landmarks_ms": 3600000,
280-
"max_cert_lifetime_ms": 604800000,
281-
"landmark_url_path": "/landmarks"
281+
"max_cert_lifetime_ms": 604800000
282282
}
283283
```
284284

285-
Landmark trust anchor IDs are derived from the CA ID and log number
286-
(`CA-ID.1.logNumber.L`, §6.3.1) — there's no separate `base_id`.
287-
Defaults: 1-hour landmark cadence, 7-day max cert lifetime ⇒
288-
`max_active_landmarks = ceil(168) + 1 = 169`~10 KiB of relying
289-
party state per CA. See §6.3.1 of the draft.
285+
Landmarks are always on; this block only tunes the cadence and the
286+
max cert lifetime (both optional, with the defaults shown). The
287+
§6.3.1 list is always served at `/landmarks`. Landmark trust anchor
288+
IDs are derived from the CA ID and log number (`CA-ID.1.logNumber.L`,
289+
§6.3.1) — there's no separate `base_id`. Defaults: 1-hour landmark
290+
cadence, 7-day max cert lifetime ⇒ `max_active_landmarks =
291+
ceil(168) + 1 = 169` ⇒ ~10 KiB of relying party state per CA. See
292+
§6.3.1 of the draft.
290293

291294
### `mirror` (optional, mirror mode)
292295

cmd/cactus/main.go

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -193,25 +193,22 @@ func run(cfg config.Config, logger *slog.Logger) error {
193193
return fmt.Errorf("build CA certificate: %w", err)
194194
}
195195

196-
// Optional landmark sequence. Built before the log so we
197-
// can pass the OnFlush hook to log.Config.
198-
var landmarkSeq *landmark.Sequence
199-
if cfg.Landmarks.Enabled {
200-
landmarkSeq, err = landmark.New(landmark.Config{
201-
CAID: caID,
202-
LogNumber: cfg.Log.Number,
203-
TimeBetweenLandmarks: cfg.Landmarks.TimeBetweenLandmarks(),
204-
MaxCertLifetime: cfg.Landmarks.MaxCertLifetime(),
205-
}, fsRoot, time.Now())
206-
if err != nil {
207-
return fmt.Errorf("landmark sequence: %w", err)
208-
}
209-
logger.Info("landmarks enabled",
210-
"ca_id", string(caID),
211-
"log_number", cfg.Log.Number,
212-
"interval", cfg.Landmarks.TimeBetweenLandmarks(),
213-
"max_active", landmarkSeq.MaxActive())
196+
// Landmark sequence. Built before the log so we can pass the
197+
// OnFlush hook to log.Config. Landmarks are mandatory.
198+
landmarkSeq, err := landmark.New(landmark.Config{
199+
CAID: caID,
200+
LogNumber: cfg.Log.Number,
201+
TimeBetweenLandmarks: cfg.Landmarks.TimeBetweenLandmarks(),
202+
MaxCertLifetime: cfg.Landmarks.MaxCertLifetime(),
203+
}, fsRoot, time.Now())
204+
if err != nil {
205+
return fmt.Errorf("landmark sequence: %w", err)
214206
}
207+
logger.Info("landmarks ready",
208+
"ca_id", string(caID),
209+
"log_number", cfg.Log.Number,
210+
"interval", cfg.Landmarks.TimeBetweenLandmarks(),
211+
"max_active", landmarkSeq.MaxActive())
215212

216213
// Issuance log. The MirrorRequester closure (CA-mode quorum)
217214
// needs `l` to compute consistency proofs, so we forward-declare
@@ -231,17 +228,15 @@ func run(cfg config.Config, logger *slog.Logger) error {
231228
SignatureDuration: m.SignatureDurationVec(),
232229
},
233230
}
234-
if landmarkSeq != nil {
235-
logCfg.OnFlush = func(treeSize uint64) {
236-
lm, ok, err := landmarkSeq.Append(ctx, treeSize, time.Now())
237-
if err != nil {
238-
logger.Error("landmark append", "err", err)
239-
return
240-
}
241-
if ok {
242-
logger.Info("landmark allocated",
243-
"number", lm.Number, "tree_size", lm.TreeSize)
244-
}
231+
logCfg.OnFlush = func(treeSize uint64) {
232+
lm, ok, err := landmarkSeq.Append(ctx, treeSize, time.Now())
233+
if err != nil {
234+
logger.Error("landmark append", "err", err)
235+
return
236+
}
237+
if ok {
238+
logger.Info("landmark allocated",
239+
"number", lm.Number, "tree_size", lm.TreeSize)
245240
}
246241
}
247242
if len(cfg.CACosignerQuorum.Mirrors) > 0 {
@@ -336,11 +331,9 @@ func run(cfg config.Config, logger *slog.Logger) error {
336331
LogID: logID,
337332
CAID: caID,
338333
}
339-
if landmarkSeq != nil {
340-
acmeCfg.Landmarks = landmarkSeq
341-
acmeCfg.SubtreeProof = l.SubtreeProof
342-
acmeCfg.LogNumber = cfg.Log.Number
343-
}
334+
acmeCfg.Landmarks = landmarkSeq
335+
acmeCfg.SubtreeProof = l.SubtreeProof
336+
acmeCfg.LogNumber = cfg.Log.Number
344337
acmeSrv, err := acme.New(acmeCfg)
345338
if err != nil {
346339
return fmt.Errorf("acme: %w", err)
@@ -368,10 +361,7 @@ func run(cfg config.Config, logger *slog.Logger) error {
368361
IdleTimeout: idleTimeout,
369362
MaxHeaderBytes: 16 * 1024,
370363
}
371-
tileSrv := tile.New(l, fsRoot)
372-
if landmarkSeq != nil {
373-
tileSrv = tileSrv.WithLandmarks(landmarkSeq)
374-
}
364+
tileSrv := tile.New(l, fsRoot).WithLandmarks(landmarkSeq)
375365
monMux := http.NewServeMux()
376366
monMux.HandleFunc("/ca-certificate", func(w http.ResponseWriter, r *http.Request) {
377367
w.Header().Set("Content-Type", "application/pem-certificate-chain")

config-example.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@
2525
"listen": "127.0.0.1:14090"
2626
},
2727
"landmarks": {
28-
"enabled": false,
2928
"time_between_landmarks_ms": 3600000,
30-
"max_cert_lifetime_ms": 604800000,
31-
"landmark_url_path": "/landmarks"
29+
"max_cert_lifetime_ms": 604800000
3230
},
3331
"ca_cosigner_quorum": {
3432
"mirrors": [],

config-witness-example.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@
2525
"listen": "127.0.0.1:14090"
2626
},
2727
"landmarks": {
28-
"enabled": true,
2928
"time_between_landmarks_ms": 60000,
30-
"max_cert_lifetime_ms": 3600000,
31-
"landmark_url_path": "/landmarks"
29+
"max_cert_lifetime_ms": 3600000
3230
},
3331
"mirror": {
3432
"enabled": true,

config/config.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ func (u UpstreamConfig) PollInterval() time.Duration {
8282
return time.Duration(u.PollIntervalMS) * time.Millisecond
8383
}
8484

85-
// LandmarkConfig configures the §6.3 landmark sequence + URL.
86-
// Disabled by default; set Enabled=true to opt in. In draft-04 landmark
87-
// trust anchor IDs are derived from the CA ID and log number
88-
// (CA-ID.1.logNumber.L), so there is no separate base_id parameter.
85+
// LandmarkConfig configures the §6.3 landmark sequence. Landmarks are
86+
// always on; only their cadence and the max cert lifetime (which sets
87+
// max_active_landmarks) are tunable. In draft-04 landmark trust anchor
88+
// IDs are derived from the CA ID and log number (CA-ID.1.logNumber.L),
89+
// so there is no separate base_id parameter. The §6.3.1 list is always
90+
// served at "/landmarks".
8991
type LandmarkConfig struct {
90-
Enabled bool `json:"enabled"`
91-
TimeBetweenLandmarksMS int `json:"time_between_landmarks_ms"`
92-
MaxCertLifetimeMS int `json:"max_cert_lifetime_ms"`
93-
URLPath string `json:"landmark_url_path"`
92+
TimeBetweenLandmarksMS int `json:"time_between_landmarks_ms"`
93+
MaxCertLifetimeMS int `json:"max_cert_lifetime_ms"`
9494
}
9595

9696
// TimeBetweenLandmarks returns the §6.3.2 interval as a time.Duration.
@@ -161,7 +161,6 @@ func Default() Config {
161161
Monitoring: ListenerConfig{Listen: ":14080"},
162162
Metrics: MetricsConfig{Listen: "127.0.0.1:14090"},
163163
Landmarks: LandmarkConfig{
164-
URLPath: "/landmarks",
165164
TimeBetweenLandmarksMS: 3600000, // 1 hour
166165
MaxCertLifetimeMS: 604800000, // 7 days
167166
},
@@ -256,16 +255,13 @@ func (c *Config) Validate() error {
256255
default:
257256
return fmt.Errorf("log_level %q invalid", c.LogLevel)
258257
}
259-
if c.Landmarks.Enabled {
260-
if c.Landmarks.TimeBetweenLandmarksMS <= 0 {
261-
return fmt.Errorf("landmarks.time_between_landmarks_ms must be > 0")
262-
}
263-
if c.Landmarks.MaxCertLifetimeMS <= 0 {
264-
return fmt.Errorf("landmarks.max_cert_lifetime_ms must be > 0")
265-
}
266-
if c.Landmarks.URLPath == "" {
267-
return fmt.Errorf("landmarks.landmark_url_path must be set")
268-
}
258+
// Landmarks are mandatory; their cadence and max cert lifetime
259+
// always apply.
260+
if c.Landmarks.TimeBetweenLandmarksMS <= 0 {
261+
return fmt.Errorf("landmarks.time_between_landmarks_ms must be > 0")
262+
}
263+
if c.Landmarks.MaxCertLifetimeMS <= 0 {
264+
return fmt.Errorf("landmarks.max_cert_lifetime_ms must be > 0")
269265
}
270266
if len(c.CACosignerQuorum.Mirrors) > 0 {
271267
if c.CACosignerQuorum.MinSignatures < 1 {

integration/binary_landmarks_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import (
1515
"time"
1616
)
1717

18-
// TestCactusBinaryWithLandmarks builds the cactus binary with
19-
// landmarks enabled in config, lets it allocate at least one landmark
20-
// (using a 50ms interval), then hits /landmarks and confirms the body
21-
// matches the §6.3.1 format with at least one allocated landmark.
18+
// TestCactusBinaryWithLandmarks builds the cactus binary, lets it
19+
// allocate at least one landmark (using a 50ms interval), then hits
20+
// /landmarks and confirms the body matches the §6.3.1 format with at
21+
// least one allocated landmark.
2222
func TestCactusBinaryWithLandmarks(t *testing.T) {
2323
if testing.Short() {
2424
t.Skip("skipping in -short mode")
@@ -57,10 +57,8 @@ func TestCactusBinaryWithLandmarks(t *testing.T) {
5757
"listen": fmt.Sprintf("127.0.0.1:%d", metricsPort),
5858
},
5959
"landmarks": map[string]any{
60-
"enabled": true,
6160
"time_between_landmarks_ms": 50, // 50ms so a landmark allocates fast
6261
"max_cert_lifetime_ms": 300,
63-
"landmark_url_path": "/landmarks",
6462
},
6563
"log_level": "info",
6664
}

tile/server.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ func New(l *log.Log, fs storage.FS) *Server {
4141
}
4242

4343
// WithLandmarks attaches a landmark.Sequence so the server exposes
44-
// the §6.3.1 /landmarks endpoint (path configurable; "/landmarks" by
45-
// default in cmd/cactus).
44+
// the §6.3.1 /landmarks endpoint.
4645
func (s *Server) WithLandmarks(seq *landmark.Sequence) *Server {
4746
s.landmarks = seq
4847
return s

0 commit comments

Comments
 (0)