Skip to content

Commit 814e1f2

Browse files
authored
refactor configuration and fix SMTP mailer (#254)
* refactor configuration and fix SMTP mailer (BREAKING CHANGE) * fix go version in lint job * remove go 1.16
1 parent 83f49a4 commit 814e1f2

File tree

19 files changed

+512
-194
lines changed

19 files changed

+512
-194
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v2
1414
- uses: actions/setup-python@v2
15+
- name: Setup go
16+
uses: actions/setup-go@v2
17+
with:
18+
go-version: 1.17
1519
- name: Lint
1620
uses: pre-commit/[email protected]
1721
with:
@@ -45,7 +49,7 @@ jobs:
4549
runs-on: ubuntu-latest
4650
strategy:
4751
matrix:
48-
go: [ '1.16', '1.17' ]
52+
go: [ '1.17' ]
4953
steps:
5054
- uses: actions/checkout@v2
5155
- name: Setup go

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ artifacts/
33
bin/
44
dist/
55
ct-monitor
6+
*.out

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ lint:
1919

2020
.PHONY: test
2121
test:
22-
go test -race -v $$(go list ./... | grep -v test)
22+
go test -coverprofile cover.out -count=1 -race -p 4 -v ./...
2323

2424
.PHONY: setup-container-structure-test
2525
setup-container-structure-test:
@@ -33,8 +33,8 @@ container-structure-test: setup-container-structure-test
3333

3434
.PHONY: setup-kind
3535
setup-kind:
36-
curl -sSLf -O https://storage.googleapis.com/kubernetes-release/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl
37-
sudo install kubectl /usr/local/bin/kubectl
36+
curl -sSLf -o /tmp/kubectl -O https://storage.googleapis.com/kubernetes-release/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl
37+
sudo install /tmp/kubectl /usr/local/bin/kubectl
3838
go install sigs.k8s.io/kind@v$(KIND_VERSION)
3939

4040
.PHONY: start-kind
@@ -47,7 +47,7 @@ stop-kind:
4747

4848
.PHONY: kindtest
4949
kindtest: clean stop-kind start-kind build
50-
go test -race -v ./test
50+
go test --tags=e2e -count=1 -coverprofile e2e.out -race -p 4 -v ./...
5151

5252
.PHONY: verify
5353
verify:

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,21 @@ Usage:
1111

1212
Flags:
1313
-c, --config string path to configuration file (default "/etc/ct-monitor/config.toml")
14-
-d, --domains strings domains to query certspotter for issuances
15-
-e, --endpoint string API endpoint (default "https://api.certspotter.com/v1/issuances")
1614
-h, --help help for ct-monitor
17-
-p, --position string path to position file (default "/var/log/ct-monitor/positions.toml")
18-
-s, --subdomains include subdomains (default true)
19-
-t, --token string API token
20-
-w, --wildcard match wildcards (default true)
2115
```
2216

2317
## Example config
2418
```toml
2519
[alert_config]
26-
27-
recipient = "[email protected]"
2820
mailer_config = "sendgrid"
2921

3022
[sendgrid]
23+
24+
3125
apiKey = "your-api-key"
3226

3327
[position_config]
3428
filename = "/var/log/ct-monitor/positions.toml"
3529
```
30+
31+
For more details, check the documentation.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.2
1+
0.3.0

cmd/root.go

Lines changed: 36 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,29 @@ import (
88
"strings"
99

1010
"github.com/Hsn723/certspotter-client/api"
11+
"github.com/Hsn723/ct-monitor/config"
1112
"github.com/Hsn723/ct-monitor/mailer"
1213
"github.com/cybozu-go/log"
13-
"github.com/mitchellh/mapstructure"
1414
"github.com/spf13/cobra"
1515
"github.com/spf13/viper"
1616
)
1717

18-
const (
19-
positionFileConfigKey = "position_config.filename"
20-
mailFromConfigKey = "alert_config.from"
21-
mailToConfigKey = "alert_config.recipient"
22-
mailerConfigKey = "alert_config.mailer_config"
23-
domainsConfigKey = "domains"
24-
endpointConfigKey = "endpoint"
25-
certspotterTokenConfigKey = "certspotter_token"
26-
sendgridTokenConfigKey = "sendgrid.apiKey"
27-
28-
defaultEndpoint = "https://api.certspotter.com/v1/issuances"
29-
defaultConfigFile = "/etc/ct-monitor/config.toml"
30-
defaultPositionFile = "/var/log/ct-monitor/positions.toml"
31-
tokenEnv = "CERTSPOTTER_TOKEN"
32-
sendgridTokenEnv = "SENDGRID_TOKEN"
33-
)
34-
3518
var (
3619
rootCmd = &cobra.Command{
3720
Use: "ct-monitor",
3821
Short: "ct-monitor queries the certspotter API for new certificate issuances",
3922
RunE: runRoot,
4023
}
4124
position = viper.New()
42-
config = viper.New()
4325

44-
configFile string
45-
endpoint string
46-
matchWildcards bool
47-
includeSubdomains bool
48-
mailSender mailer.Mailer
26+
configFile string
4927

5028
version string
5129
commit string
5230
date string
5331
builtBy string
5432
)
5533

56-
func getPositionFilePath() string {
57-
positionFile := config.GetString(positionFileConfigKey)
58-
if positionFile == "" {
59-
return defaultPositionFile
60-
}
61-
return positionFile
62-
}
63-
6434
func createFile(path string) error {
6535
if _, err := os.Stat(path); os.IsNotExist(err) {
6636
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
@@ -75,35 +45,9 @@ func createFile(path string) error {
7545
return nil
7646
}
7747

78-
func initConfig() {
79-
_ = log.Info("ct-monitor", map[string]interface{}{
80-
"version": version,
81-
"commit": commit,
82-
"date": date,
83-
"built_by": builtBy,
84-
})
85-
config.SetConfigFile(configFile)
86-
if err := createFile(configFile); err != nil {
87-
_ = log.Critical(err.Error(), nil)
88-
}
89-
if err := config.ReadInConfig(); err != nil {
90-
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
91-
_ = log.Critical(err.Error(), nil)
92-
}
93-
}
94-
_ = log.Info("using config file", map[string]interface{}{
95-
"path": config.ConfigFileUsed(),
96-
})
97-
config.WatchConfig()
98-
99-
initPosition()
100-
initMailer()
101-
}
102-
103-
func initPosition() {
104-
positionFile := getPositionFilePath()
105-
position.SetConfigFile(positionFile)
106-
if err := createFile(positionFile); err != nil {
48+
func initPosition(pc config.PositionConfig) {
49+
position.SetConfigFile(pc.Filename)
50+
if err := createFile(pc.Filename); err != nil {
10751
_ = log.Critical(err.Error(), nil)
10852
}
10953
if err := position.ReadInConfig(); err != nil {
@@ -117,74 +61,15 @@ func initPosition() {
11761
position.WatchConfig()
11862
}
11963

120-
func initMailer() {
121-
from := config.GetString(mailFromConfigKey)
122-
to := config.GetString(mailToConfigKey)
123-
if to == "" {
124-
_ = log.Warn("alert mail recipient missing", nil)
125-
return
126-
}
127-
if from == "" {
128-
_ = log.Warn("alert mail from address missing", nil)
129-
return
130-
}
131-
mailerName := config.GetString(mailerConfigKey)
132-
switch mailerName {
133-
case "smtp":
134-
mailSender = &mailer.SMTPMailer{}
135-
case "sendgrid":
136-
mailSender = &mailer.SendgridMailer{
137-
APIKey: config.GetString(sendgridTokenConfigKey),
138-
Logger: log.DefaultLogger(),
139-
}
140-
case "amazonses":
141-
mailSender = &mailer.AmazonSESMailer{
142-
Logger: log.DefaultLogger(),
143-
}
144-
default:
145-
_ = log.Warn("no mailer configured, email report will not be sent", nil)
146-
return
147-
}
148-
149-
senderConf := config.GetStringMap(mailerName)
150-
if err := mapstructure.Decode(senderConf, &mailSender); err != nil {
151-
_ = log.Critical(err.Error(), nil)
152-
}
153-
154-
if err := mailSender.Init(from, to); err != nil {
155-
_ = log.Critical(err.Error(), nil)
156-
}
157-
}
158-
15964
func init() {
160-
cobra.OnInitialize(initConfig)
161-
rootCmd.Flags().StringVarP(&configFile, "config", "c", defaultConfigFile, "path to configuration file")
162-
rootCmd.Flags().StringVarP(&endpoint, "endpoint", "e", defaultEndpoint, "API endpoint")
163-
164-
rootCmd.Flags().StringP("position", "p", defaultPositionFile, "path to position file")
165-
rootCmd.Flags().StringP("token", "t", "", "API token")
166-
rootCmd.Flags().StringSliceP("domains", "d", []string{}, "domains to query certspotter for issuances")
167-
168-
rootCmd.Flags().BoolVarP(&matchWildcards, "wildcard", "w", true, "match wildcards")
169-
rootCmd.Flags().BoolVarP(&includeSubdomains, "subdomains", "s", true, "include subdomains")
170-
171-
_ = config.BindEnv(certspotterTokenConfigKey, tokenEnv)
172-
_ = config.BindEnv(sendgridTokenConfigKey, sendgridTokenEnv)
173-
174-
_ = config.BindPFlag(certspotterTokenConfigKey, rootCmd.Flags().Lookup("token"))
175-
_ = config.BindPFlag(domainsConfigKey, rootCmd.Flags().Lookup(domainsConfigKey))
176-
_ = config.BindPFlag(positionFileConfigKey, rootCmd.Flags().Lookup("position"))
177-
_ = config.BindPFlag(endpointConfigKey, rootCmd.Flags().Lookup("endpoint"))
65+
rootCmd.Flags().StringVarP(&configFile, "config", "c", config.DefaultConfigFile, "path to configuration file")
17866
}
17967

18068
func getDomainConfigName(domain string) string {
18169
return strings.ReplaceAll(domain, ".", "-")
18270
}
18371

184-
func sendMail(domain string, issuances []api.Issuance) error {
185-
if mailSender == nil {
186-
return nil
187-
}
72+
func sendMail(mailSender mailer.Mailer, domain string, issuances []api.Issuance) error {
18873
subject := fmt.Sprintf("Certificate Transparency Notification for %s", domain)
18974
body := fmt.Sprintf("ct-monitor has observed the issuance of the following certificate(s) for the %s domain:\n", domain)
19075
for _, i := range issuances {
@@ -196,7 +81,7 @@ func sendMail(domain string, issuances []api.Issuance) error {
19681
return mailSender.Send(subject, body)
19782
}
19883

199-
func checkIssuances(domain string, wildcards, subdomains bool, c api.CertspotterClient) error {
84+
func checkIssuances(domain string, wildcards, subdomains bool, c api.CertspotterClient, mailSender mailer.Mailer) error {
20085
key := getDomainConfigName(domain)
20186
lastIssuance := position.GetUint64(key)
20287
issuances, err := c.GetIssuances(domain, wildcards, subdomains, lastIssuance)
@@ -217,7 +102,7 @@ func checkIssuances(domain string, wildcards, subdomains bool, c api.Certspotter
217102
"sha256": issuance.Cert.SHA256,
218103
})
219104
}
220-
if err := sendMail(domain, issuances); err != nil {
105+
if err := sendMail(mailSender, domain, issuances); err != nil {
221106
return err
222107
}
223108
position.Set(key, lastIssuance)
@@ -227,9 +112,8 @@ func checkIssuances(domain string, wildcards, subdomains bool, c api.Certspotter
227112
return nil
228113
}
229114

230-
func atomicWritePosition() error {
231-
positionFile := getPositionFilePath()
232-
tmpFile, err := ioutil.TempFile(filepath.Dir(positionFile), "position.*.toml")
115+
func atomicWritePosition(pc config.PositionConfig) error {
116+
tmpFile, err := ioutil.TempFile(filepath.Dir(pc.Filename), "position.*.toml")
233117
if err != nil {
234118
return err
235119
}
@@ -243,23 +127,41 @@ func atomicWritePosition() error {
243127
if fi.Size() == 0 {
244128
return nil
245129
}
246-
return os.Rename(tmpFile.Name(), positionFile)
130+
return os.Rename(tmpFile.Name(), pc.Filename)
247131
}
248132

249133
func runRoot(cmd *cobra.Command, args []string) error {
134+
_ = log.Info("ct-monitor", map[string]interface{}{
135+
"version": version,
136+
"commit": commit,
137+
"date": date,
138+
"built_by": builtBy,
139+
})
140+
conf, err := config.Load(configFile)
141+
if err != nil {
142+
return err
143+
}
144+
_ = log.Info("loaded configuration", map[string]interface{}{
145+
"config": configFile,
146+
})
147+
initPosition(conf.PositionConfig)
148+
mailSender := conf.GetMailer()
149+
if err := mailSender.Init(); err != nil {
150+
return err
151+
}
250152
csp := api.CertspotterClient{
251-
Endpoint: endpoint,
252-
Token: config.GetString(certspotterTokenConfigKey),
153+
Endpoint: conf.Endpoint,
154+
Token: conf.Token,
253155
}
254-
for _, domain := range config.GetStringSlice(domainsConfigKey) {
255-
if err := checkIssuances(domain, matchWildcards, includeSubdomains, csp); err != nil {
156+
for _, domain := range conf.Domains {
157+
if err := checkIssuances(domain.Name, domain.MatchWildcards, domain.IncludeSubdomains, csp, mailSender); err != nil {
256158
_ = log.Error(err.Error(), map[string]interface{}{
257-
"domain": domain,
159+
"domain": domain.Name,
258160
})
259161
}
260162
}
261163

262-
return atomicWritePosition()
164+
return atomicWritePosition(conf.PositionConfig)
263165
}
264166

265167
// Execute runs the root command.

0 commit comments

Comments
 (0)