Skip to content

Commit 4bfea45

Browse files
committed
feat: add feature to create initial index file
This is useful in order to not start pulling each log after the first run at index 0 but still have newly discovered logs download from 0.
1 parent 44bd15c commit 4bfea45

4 files changed

Lines changed: 80 additions & 1 deletion

File tree

cmd/certstream-server-go/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
func main() {
1414
configFile := flag.String("config", "config.yml", "path to the config file")
1515
versionFlag := flag.Bool("version", false, "Print the version and exit")
16+
createIndexFile := flag.Bool("create-index-file", false, "Create the ct_index.json based on current STHs")
1617
flag.Parse()
1718

1819
if *versionFlag {
@@ -21,6 +22,22 @@ func main() {
2122
}
2223

2324
log.SetFlags(log.LstdFlags | log.Lshortfile)
25+
26+
// If the user only wants to create the index file, we don't need to start the server
27+
if *createIndexFile {
28+
conf, readConfErr := config.ReadConfig(*configFile)
29+
if readConfErr != nil {
30+
log.Fatalf("Error while reading config: %v", readConfErr)
31+
}
32+
cs := certstream.NewRawCertstream(conf)
33+
34+
createErr := cs.CreateIndexFile()
35+
if createErr != nil {
36+
log.Fatalf("Error while creating index file: %v", createErr)
37+
}
38+
return
39+
}
40+
2441
log.Printf("Starting certstream-server-go v%s\n", config.Version)
2542

2643
cs, err := certstream.NewCertstreamFromConfigFile(*configFile)

config.sample.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ general:
4747
# Options for resuming certificate downloads after restart
4848
recovery:
4949
# If enabled, the server will resume downloading certificates from the last processed and stored index for each log.
50-
# If there is no ct_index_file or for a specific log there is no index entry, the server will start from the latest STH.
50+
# If there is no ct_index_file or for a specific log there is no index entry, the server will start from index 0.
51+
# Be aware that this leads to a massive number of certificates being downloaded.
52+
# Depending on your server's performance and network connection, this could be up to 10.000 certificates per second.
53+
# Make sure your infrastructure can handle this!
5154
enabled: true
5255
# Path to the file where indices are stored. Be aware that a temp file in the same path with the same name and ".tmp" as suffix will be created.
5356
# If there are no write permissions to the path, the server will not be able to store the indices.

internal/certificatetransparency/ct-watcher.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,48 @@ func (w *Watcher) Stop() {
214214
w.cancelFunc()
215215
}
216216

217+
// CreateIndexFile creates a ct_index.json file based on the current STHs of all availble logs.
218+
func (w *Watcher) CreateIndexFile(filePath string) error {
219+
logs, err := getAllLogs()
220+
if err != nil {
221+
return err
222+
}
223+
224+
w.context, w.cancelFunc = context.WithCancel(context.Background())
225+
log.Println("Fetching current STH for all logs...")
226+
for _, operator := range logs.Operators {
227+
// Iterate over each log of the operator
228+
for _, transparencyLog := range operator.Logs {
229+
// Check if the log is already being watched
230+
metrics.Init(operator.Name, transparencyLog.URL)
231+
log.Println("Fetching STH for", transparencyLog.URL)
232+
233+
hc := http.Client{Timeout: 5 * time.Second}
234+
jsonClient, e := client.New(transparencyLog.URL, &hc, jsonclient.Options{UserAgent: userAgent})
235+
if e != nil {
236+
log.Printf("Error creating JSON client: %s\n", e)
237+
continue
238+
}
239+
240+
sth, getSTHerr := jsonClient.GetSTH(w.context)
241+
if getSTHerr != nil {
242+
// TODO this can happen due to a 429 error. We should retry the request
243+
log.Printf("Could not get STH for '%s': %s\n", transparencyLog.URL, getSTHerr)
244+
continue
245+
}
246+
247+
metrics.index[transparencyLog.URL] = int64(sth.TreeSize)
248+
}
249+
}
250+
w.cancelFunc()
251+
252+
tempFilePath := fmt.Sprintf("%s.tmp", filePath)
253+
metrics.SaveCertIndexes(tempFilePath, filePath)
254+
log.Println("Index file saved to", filePath)
255+
256+
return nil
257+
}
258+
217259
// A worker processes a single CT log.
218260
type worker struct {
219261
name string

internal/certstream/certstream.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ type Certstream struct {
2323
config config.Config
2424
}
2525

26+
func NewRawCertstream(config config.Config) *Certstream {
27+
cs := Certstream{}
28+
cs.config = config
29+
30+
return &cs
31+
}
32+
2633
// NewCertstreamServer creates a new Certstream server from a config struct.
2734
func NewCertstreamServer(config config.Config) (*Certstream, error) {
2835
cs := Certstream{}
@@ -108,6 +115,16 @@ func (cs *Certstream) Stop() {
108115
}
109116
}
110117

118+
// CreateIndexFile creates the index file for the certificate transparency logs.
119+
// It gets only called when the CLI flag --create-index-file is set.
120+
func (cs *Certstream) CreateIndexFile() error {
121+
// If there is no watcher initialized, create a new one
122+
if cs.watcher == nil {
123+
cs.watcher = &certificatetransparency.Watcher{}
124+
}
125+
return cs.watcher.CreateIndexFile(cs.config.General.Recovery.CTIndexFile)
126+
}
127+
111128
// signalHandler listens for signals in order to gracefully shut down the server.
112129
// Executes the callback function when a signal is received.
113130
func signalHandler(signals chan os.Signal, callback func()) {

0 commit comments

Comments
 (0)