Skip to content

Commit 9b3b093

Browse files
authored
Merge branch 'dev' into static-ct-backoff
2 parents 00e7250 + 27bb5c6 commit 9b3b093

13 files changed

Lines changed: 718 additions & 130 deletions

File tree

.github/goreleaser.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ builds:
1515
- darwin
1616
- windows
1717
goarch:
18-
- 386
18+
- "386"
1919
- amd64
2020
- arm
2121
- arm64
2222
ignore:
2323
- goos: darwin
24-
goarch: 386
24+
goarch: "386"
2525
- goos: darwin
2626
goarch: arm
2727
- goos: windows
2828
goarch: arm
2929
- goos: windows
3030
goarch: arm64
3131
- goos: windows
32-
goarch: 386
32+
goarch: "386"
3333
checksum:
34-
name_template: '{{.ProjectName}}_{{.Version}}_checksums.txt'
34+
name_template: "{{.ProjectName}}_{{.Version}}_checksums.txt"
3535
changelog:
3636
disable: true
3737

@@ -41,8 +41,8 @@ dockers_v2:
4141
- "ghcr.io/d-rickyy-b/{{.ProjectName}}"
4242
dockerfile: docker/Dockerfile.goreleaser
4343
tags:
44-
- "{{.Tag}}"
45-
- "{{ if not .Prerelease }}latest{{ end }}"
44+
- "{{.Tag}}"
45+
- "{{ if not .Prerelease }}latest{{ end }}"
4646
platforms:
4747
- linux/amd64
4848
- linux/arm64
@@ -58,7 +58,7 @@ dockers_v2:
5858
"org.opencontainers.image.source": "https://github.com/d-Rickyy-b/certstream-server-go"
5959

6060
archives:
61-
- formats: [ "binary" ]
61+
- formats: ["binary"]
6262
name_template: >-
6363
{{- .ProjectName }}_
6464
{{- .Version}}_

.github/workflows/release_build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
push:
55
tags:
66
- "*"
7+
branches:
8+
- dev
79

810
jobs:
911
build:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Updated http client settings to prevent timeouts and other connectivity issues
1414
- Updated http server settings to allow for higher delays
1515
- Minor code improvements and refactoring, mostly style related
16+
- Updated batch size from 100 to 256
1617
### Removed
1718
### Fixed
19+
- Calculate correct starting tile index for static ct logs
1820
- Use proper websocket close code (1008) instead of 1005, which wasn't sent to the client
21+
- Respect ct_index path in config file for the `create-index` command
1922
### Docs
2023

2124
## [1.9.0] - 2026-04-03

config.sample.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,19 @@ webserver:
33
listen_addr: "0.0.0.0"
44
listen_port: 8080
55
# If you want to use a reverse proxy in front of the server, set this to true
6-
# It will use the X-Forwarded-For header to get the real IP of the client
6+
# It will use the X-Forwarded-For / X-Real-IP headers to get the real IP of the client
77
real_ip: false
8+
# List of trusted proxy IPs or CIDR ranges whose X-Forwarded-For / X-Real-IP headers
9+
# are honored when real_ip is true. When left empty every connecting IP is trusted.
10+
# Restricting this to your actual proxy IPs prevents clients from spoofing their address
11+
# by injecting a forged forwarded header. Note: X-Forwarded-For can contain a chain of
12+
# IPs (e.g. "client, proxy1, proxy2"); only the first (leftmost) entry is used as the real client IP.
13+
trusted_proxies:
14+
- "172.16.0.0/12"
15+
# If you want to restrict access to the server to specific IPs or CIDR ranges, you can add them to the whitelist.
16+
# If the whitelist is empty, all IPs are allowed to access the server.
17+
whitelist:
18+
- "127.0.0.1/8"
819
full_url: "/full-stream"
920
lite_url: "/"
1021
domains_only_url: "/domains-only"
@@ -20,6 +31,9 @@ prometheus:
2031
metrics_url: "/metrics"
2132
expose_system_metrics: false
2233
real_ip: false
34+
# List of trusted proxy IPs or CIDR ranges (see webserver.trusted_proxies for details).
35+
#trusted_proxies:
36+
# - "127.0.0.1"
2337
whitelist:
2438
- "127.0.0.1/8"
2539

internal/certificatetransparency/ct-tiled.go

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -118,43 +118,6 @@ func FetchCheckpoint(ctx context.Context, client *http.Client, baseURL string) (
118118
}, nil
119119
}
120120

121-
// FetchTile fetches a tile from the tiled CT log using the provided client.
122-
// If partialWidth > 0, fetches a partial tile with that width (1-255).
123-
func FetchTile(ctx context.Context, client *http.Client, baseURL string, tileIndex, partialWidth uint64) ([]TileLeaf, error) {
124-
baseURL = strings.TrimRight(baseURL, "/")
125-
tilePath := encodeTilePath(tileIndex)
126-
127-
if partialWidth > 0 {
128-
tilePath = fmt.Sprintf("%s.p/%d", tilePath, partialWidth)
129-
}
130-
131-
url := fmt.Sprintf("%s/tile/data/%s", baseURL, tilePath)
132-
133-
req, newReqErr := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
134-
if newReqErr != nil {
135-
return nil, fmt.Errorf("failed to create tile request: %w", newReqErr)
136-
}
137-
138-
req.Header.Set("User-Agent", UserAgent)
139-
140-
resp, reqErr := client.Do(req)
141-
if reqErr != nil {
142-
return nil, fmt.Errorf("fetching tile %d: %w", tileIndex, reqErr)
143-
}
144-
defer resp.Body.Close()
145-
146-
if resp.StatusCode != http.StatusOK {
147-
return nil, fmt.Errorf("%w: unexpected status code %d", ErrRequestFailed, resp.StatusCode)
148-
}
149-
150-
data, err := io.ReadAll(resp.Body)
151-
if err != nil {
152-
return nil, fmt.Errorf("reading tile data: %w", err)
153-
}
154-
155-
return ParseTileData(data)
156-
}
157-
158121
// ParseTileData parses the binary tile data into TileLeaf entries using cryptobyte.
159122
func ParseTileData(data []byte) ([]TileLeaf, error) {
160123
var leaves []TileLeaf
@@ -330,7 +293,7 @@ func (s *StaticCTClient) Monitor(ctx context.Context, foundCert func(*ct.RawLogE
330293
// It returns true if at least one full tile was fetched.
331294
func (s *StaticCTClient) fetchAndProcessTiles(ctx context.Context, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) (bool, error) {
332295
// Fetch current checkpoint
333-
checkpoint, fetchErr := s.fetchCheckpoint(ctx)
296+
checkpoint, fetchErr := s.FetchCheckpoint(ctx)
334297
if fetchErr != nil {
335298
return false, fmt.Errorf("fetching checkpoint: %w", fetchErr)
336299
}
@@ -471,8 +434,8 @@ func (s *StaticCTClient) fetchTile(ctx context.Context, tileIndex, partialWidth
471434
return ParseTileData(data)
472435
}
473436

474-
// fetchCheckpoint fetches the checkpoint from a tiled CT log using the provided client.
475-
func (s *StaticCTClient) fetchCheckpoint(ctx context.Context) (*TiledCheckpoint, error) {
437+
// FetchCheckpoint fetches the checkpoint from a tiled CT log using the provided client.
438+
func (s *StaticCTClient) FetchCheckpoint(ctx context.Context) (*TiledCheckpoint, error) {
476439
url := s.url + "/checkpoint"
477440

478441
req, newReqErr := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

internal/certificatetransparency/ct-watcher.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (w *Watcher) Start() {
7575

7676
// Start background job to save CTIndexes at regular intervals
7777
storageInterval := time.Second * 30
78-
go metrics.Metrics.SaveCertIndexesAtInterval(storageInterval, ctIndexFilePath)
78+
go metrics.Metrics.SaveCertIndexesAtInterval(w.context, storageInterval, ctIndexFilePath)
7979
}
8080

8181
// initialize the watcher with currently available logs
@@ -309,7 +309,8 @@ func (w *Watcher) CreateIndexFile(filePath string) error {
309309
metrics.Metrics.Init(operator.Name, normalizedURL)
310310
log.Println("Fetching checkpoint for", normalizedURL)
311311

312-
checkpoint, fetchErr := FetchCheckpoint(w.context, httpClient, transparencyLog.MonitoringURL)
312+
staticCTClient := NewStaticCTClient(transparencyLog.MonitoringURL, httpClient, UserAgent, 0)
313+
checkpoint, fetchErr := staticCTClient.FetchCheckpoint(w.context)
313314
if fetchErr != nil {
314315
log.Printf("Could not get checkpoint for '%s': %s\n", transparencyLog.MonitoringURL, fetchErr)
315316
return ErrFetchingSTHFailed
@@ -444,7 +445,7 @@ func (w *worker) runStandardWorker(ctx context.Context) error {
444445

445446
certScanner := scanner.NewScanner(jsonClient, scanner.ScannerOptions{
446447
FetcherOptions: scanner.FetcherOptions{
447-
BatchSize: 100,
448+
BatchSize: 256,
448449
ParallelFetch: 1,
449450
StartIndex: int64(w.ctIndex),
450451
Continuous: true,
@@ -469,19 +470,20 @@ func (w *worker) runStandardWorker(ctx context.Context) error {
469470
func (w *worker) runTiledWorker(ctx context.Context) error {
470471
httpClient := newHTTPClient()
471472

473+
staticCTClient := NewStaticCTClient(w.ctURL, httpClient, UserAgent, w.ctIndex)
474+
472475
// If recovery is enabled and the CT index is set, we start at the saved index. Otherwise, we start at the latest checkpoint.
473476
validSavedCTIndexExists := config.AppConfig.General.Recovery.Enabled
474477
if !validSavedCTIndexExists {
475-
checkpoint, err := FetchCheckpoint(ctx, httpClient, w.ctURL)
478+
checkpoint, err := staticCTClient.FetchCheckpoint(ctx)
476479
if err != nil {
477480
log.Printf("Could not get checkpoint for '%s': %s\n", w.ctURL, err)
478481
return ErrFetchingSTHFailed
479482
}
480483
// Start at the latest checkpoint to skip all the past certificates
481-
w.ctIndex = checkpoint.Size
484+
staticCTClient.ctIndex = checkpoint.Size
482485
}
483486

484-
staticCTClient := NewStaticCTClient(w.ctURL, httpClient, UserAgent, w.ctIndex)
485487
err := staticCTClient.Monitor(ctx, w.foundCertCallback, w.foundPrecertCallback)
486488
if err != nil {
487489
return fmt.Errorf("error scanning for certificates: %w", err)

internal/metrics/logmetrics.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package metrics
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"log"
@@ -197,6 +198,13 @@ func (m *LogMetrics) LoadCTIndex(ctIndexFilePath string) {
197198
}
198199
}
199200

201+
m.index = make(CTCertIndex)
202+
203+
if len(bytes) == 0 || string(bytes) == "" {
204+
log.Panicln("CT index file is empty!")
205+
return
206+
}
207+
200208
jerr := json.Unmarshal(bytes, &m.index)
201209
if jerr != nil {
202210
log.Printf("Error unmarshalling CT index file: '%s'\n", ctIndexFilePath)
@@ -207,8 +215,7 @@ func (m *LogMetrics) LoadCTIndex(ctIndexFilePath string) {
207215
}
208216

209217
func (m *LogMetrics) createCTIndexFile(ctIndexFilePath string) error {
210-
log.Printf("Specified CT index file does not exist: '%s'\n", ctIndexFilePath)
211-
log.Println("Creating CT index file now!")
218+
log.Printf("Specified CT index file does not exist, creating now: '%s'\n", ctIndexFilePath)
212219

213220
file, createErr := os.Create(ctIndexFilePath)
214221
if createErr != nil {
@@ -239,13 +246,18 @@ func (m *LogMetrics) createCTIndexFile(ctIndexFilePath string) error {
239246
// We first create a temp file and write the index data to it. Only then do we move the temp file to the actual
240247
// permanent index file. This prevents the last good index file from being clobbered if the program was shutdown/killed
241248
// in-between the write operation.
242-
func (m *LogMetrics) SaveCertIndexesAtInterval(interval time.Duration, ctIndexFilePath string) {
249+
func (m *LogMetrics) SaveCertIndexesAtInterval(ctx context.Context, interval time.Duration, ctIndexFilePath string) {
243250
ticker := time.NewTicker(interval)
244251
defer ticker.Stop()
245252

246-
for range ticker.C {
247-
if err := m.SaveCertIndexes(ctIndexFilePath); err != nil {
248-
log.Printf("Error saving CT indexes at '%s': %s\n", ctIndexFilePath, err)
253+
for {
254+
select {
255+
case <-ctx.Done():
256+
return
257+
case <-ticker.C:
258+
if err := m.SaveCertIndexes(ctIndexFilePath); err != nil {
259+
log.Printf("Error saving CT indexes at '%s': %s\n", ctIndexFilePath, err)
260+
}
249261
}
250262
}
251263
}

0 commit comments

Comments
 (0)