@@ -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-
3518var (
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-
6434func 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-
15964func 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
18068func 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
249133func 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