Skip to content

Commit fe5d16b

Browse files
authored
Merge pull request #56 from d-Rickyy-b/add-custom-logs
feat: ability to add own ct logs
2 parents 3717687 + aa15efb commit fe5d16b

3 files changed

Lines changed: 80 additions & 16 deletions

File tree

config.sample.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,14 @@ prometheus:
1717
real_ip: false
1818
whitelist:
1919
- "127.0.0.1/8"
20+
21+
general:
22+
# When you want to add logs that are not contained in the log list provided by
23+
# Google (https://www.gstatic.com/ct/log_list/v3/log_list.json), you can add them here.
24+
additional_logs:
25+
- url: https://ct.googleapis.com/logs/us1/mirrors/digicert_nessie2022/
26+
operator: "DigiCert"
27+
description: "DigiCert Nessie2022 log"
28+
- url: https://dodo.ct.comodo.com/
29+
operator: "Comodo"
30+
description: "Comodo Dodo"

internal/certificatetransparency/ct-watcher.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,35 @@ func getAllLogs() (loglist3.LogList, error) {
329329
return loglist3.LogList{}, parseErr
330330
}
331331

332+
// Add manually added logs from config to the allLogs list
333+
if config.AppConfig.General.AdditionalLogs == nil {
334+
return *allLogs, nil
335+
}
336+
337+
for _, additionalLog := range config.AppConfig.General.AdditionalLogs {
338+
customLog := loglist3.Log{
339+
URL: additionalLog.URL,
340+
Description: additionalLog.Description,
341+
}
342+
343+
operatorFound := false
344+
for _, operator := range allLogs.Operators {
345+
if operator.Name == additionalLog.Operator {
346+
operator.Logs = append(operator.Logs, &customLog)
347+
operatorFound = true
348+
break
349+
}
350+
}
351+
352+
if !operatorFound {
353+
newOperator := loglist3.Operator{
354+
Name: additionalLog.Operator,
355+
Logs: []*loglist3.Log{&customLog},
356+
}
357+
allLogs.Operators = append(allLogs.Operators, &newOperator)
358+
}
359+
}
360+
332361
// Add new ct logs to metrics
333362
for _, operator := range allLogs.Operators {
334363
for _, ctlog := range operator.Logs {

internal/config/config.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ type ServerConfig struct {
2525
Whitelist []string `yaml:"whitelist"`
2626
}
2727

28+
type LogConfig struct {
29+
Operator string `yaml:"operator"`
30+
URL string `yaml:"url"`
31+
Description string `yaml:"description"`
32+
}
33+
2834
type Config struct {
2935
Webserver struct {
3036
ServerConfig `yaml:",inline"`
@@ -39,6 +45,9 @@ type Config struct {
3945
MetricsURL string `yaml:"metrics_url"`
4046
ExposeSystemMetrics bool `yaml:"expose_system_metrics"`
4147
}
48+
General struct {
49+
AdditionalLogs []LogConfig `yaml:"additional_logs"`
50+
}
4251
}
4352

4453
// ReadConfig reads the config file and returns a filled Config struct.
@@ -53,14 +62,14 @@ func ReadConfig(configPath string) (Config, error) {
5362
if !validateConfig(conf) {
5463
log.Fatalln("Invalid config")
5564
}
56-
AppConfig = conf
65+
AppConfig = *conf
5766

58-
return conf, nil
67+
return *conf, nil
5968
}
6069

6170
// parseConfigFromFile reads the config file as bytes and passes it to parseConfigFromBytes.
6271
// It returns a filled Config struct.
63-
func parseConfigFromFile(configFile string) (Config, error) {
72+
func parseConfigFromFile(configFile string) (*Config, error) {
6473
if configFile == "" {
6574
configFile = "config.yml"
6675
}
@@ -69,7 +78,7 @@ func parseConfigFromFile(configFile string) (Config, error) {
6978
absPath, err := filepath.Abs(configFile)
7079
if err != nil {
7180
log.Printf("Couldn't convert to absolute path: '%s'\n", configFile)
72-
return Config{}, err
81+
return &Config{}, err
7382
}
7483

7584
if _, statErr := os.Stat(absPath); os.IsNotExist(statErr) {
@@ -84,45 +93,46 @@ func parseConfigFromFile(configFile string) (Config, error) {
8493
absPath += ".yaml"
8594
default:
8695
log.Printf("Config file '%s' does not have a valid extension\n", configFile)
87-
return Config{}, statErr
96+
return &Config{}, statErr
8897
}
8998

9099
if _, secondStatErr := os.Stat(absPath); os.IsNotExist(secondStatErr) {
91100
log.Printf("Config file '%s' does not exist\n", absPath)
92-
return Config{}, secondStatErr
101+
return &Config{}, secondStatErr
93102
}
94103
}
95104
log.Printf("File '%s' exists\n", absPath)
96105

97106
yamlFileContent, readErr := os.ReadFile(absPath)
98107
if readErr != nil {
99-
return Config{}, readErr
108+
return &Config{}, readErr
100109
}
101110

102111
conf, parseErr := parseConfigFromBytes(yamlFileContent)
103112
if parseErr != nil {
104-
return Config{}, parseErr
113+
return &Config{}, parseErr
105114
}
106115

107116
return conf, nil
108117
}
109118

110119
// parseConfigFromBytes parses the config bytes and returns a filled Config struct.
111-
func parseConfigFromBytes(data []byte) (Config, error) {
120+
func parseConfigFromBytes(data []byte) (*Config, error) {
112121
var config Config
113122

114123
err := yaml.Unmarshal(data, &config)
115124
if err != nil {
116-
return config, err
125+
return &config, err
117126
}
118127

119-
return config, nil
128+
return &config, nil
120129
}
121130

122131
// validateConfig validates the config values and sets defaults for missing values.
123-
func validateConfig(config Config) bool {
132+
func validateConfig(config *Config) bool {
124133
// Still matches invalid IP addresses but good enough for detecting completely wrong formats
125-
URLRegex := regexp.MustCompile(`^(/[a-zA-Z0-9\-._]+)+$`)
134+
URLPathRegex := regexp.MustCompile(`^(/[a-zA-Z0-9\-._]+)+$`)
135+
URLRegex := regexp.MustCompile(`^https?://[a-zA-Z0-9\-._]+(:[0-9]+)?(/[a-zA-Z0-9\-._]+)*$`)
126136

127137
// Check webserver config
128138
if config.Webserver.ListenAddr == "" || net.ParseIP(config.Webserver.ListenAddr) == nil {
@@ -135,17 +145,17 @@ func validateConfig(config Config) bool {
135145
return false
136146
}
137147

138-
if config.Webserver.FullURL == "" || !URLRegex.MatchString(config.Webserver.FullURL) {
148+
if config.Webserver.FullURL == "" || !URLPathRegex.MatchString(config.Webserver.FullURL) {
139149
log.Println("Webhook full URL is not set or does not match pattern '/...'")
140150
config.Webserver.FullURL = "/full-stream"
141151
}
142152

143-
if config.Webserver.LiteURL == "" || !URLRegex.MatchString(config.Webserver.FullURL) {
153+
if config.Webserver.LiteURL == "" || !URLPathRegex.MatchString(config.Webserver.FullURL) {
144154
log.Println("Webhook lite URL is not set or does not match pattern '/...'")
145155
config.Webserver.LiteURL = "/"
146156
}
147157

148-
if config.Webserver.DomainsOnlyURL == "" || !URLRegex.MatchString(config.Webserver.DomainsOnlyURL) {
158+
if config.Webserver.DomainsOnlyURL == "" || !URLPathRegex.MatchString(config.Webserver.DomainsOnlyURL) {
149159
log.Println("Webhook domains only URL is not set or does not match pattern '/...'")
150160
config.Webserver.FullURL = "/domains-only"
151161
}
@@ -187,5 +197,19 @@ func validateConfig(config Config) bool {
187197
}
188198
}
189199

200+
var validLogs []LogConfig
201+
if len(config.General.AdditionalLogs) > 0 {
202+
for _, ctLog := range config.General.AdditionalLogs {
203+
if !URLRegex.MatchString(ctLog.URL) {
204+
log.Println("Ignoring invalid additional log URL: ", ctLog.URL)
205+
continue
206+
}
207+
208+
validLogs = append(validLogs, ctLog)
209+
}
210+
}
211+
212+
config.General.AdditionalLogs = validLogs
213+
190214
return true
191215
}

0 commit comments

Comments
 (0)