boulder/cmd/external-cert-importer/main.go

171 lines
5.6 KiB
Go

// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"encoding/csv"
"encoding/hex"
"fmt"
"io"
"math"
"os"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/codegangsta/cli"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/sa"
)
const datestampFormat string = "2006-01-02 15:04:05"
func addCerts(csvFilename string, dbMap *gorp.DbMap, stats statsd.Statter, statsRate float32) {
file, err := os.Open(csvFilename)
cmd.FailOnError(err, "Could not open the file for reading")
csvReader := csv.NewReader(file)
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
fmt.Println("Error:", err)
return
}
notAfter, err := time.Parse(datestampFormat, record[3])
spkiBytes, err := hex.DecodeString(record[4])
certDER, err := hex.DecodeString(record[7])
externalCert := core.ExternalCert{
SHA1: record[0],
Issuer: record[1],
Subject: record[2],
NotAfter: notAfter,
SPKI: spkiBytes,
Valid: record[5] == "1",
EV: record[6] == "1",
CertDER: certDER,
}
importStart := time.Now()
err = dbMap.Insert(&externalCert)
stats.TimingDuration("ExistingCert.Certs.ImportLatency", time.Since(importStart), statsRate)
stats.Inc("ExistingCert.Certs.Imported", 1, statsRate)
}
}
func addIdentifiers(csvFilename string, dbMap *gorp.DbMap, stats statsd.Statter, statsRate float32) {
file, err := os.Open(csvFilename)
cmd.FailOnError(err, "Could not open the file for reading")
csvReader := csv.NewReader(file)
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
fmt.Println("Error:", err)
return
}
identifierData := core.IdentifierData{
ReversedName: record[1],
CertSHA1: record[0],
}
importStart := time.Now()
err = dbMap.Insert(&identifierData)
stats.TimingDuration("ExistingCert.Domains.ImportLatency", time.Since(importStart), statsRate)
stats.Inc("ExistingCert.Domains.Imported", 1, statsRate)
}
}
func removeInvalidCerts(csvFilename string, dbMap *gorp.DbMap, stats statsd.Statter, statsRate float32) {
file, err := os.Open(csvFilename)
cmd.FailOnError(err, "Could not open the file for reading")
csvReader := csv.NewReader(file)
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
fmt.Println("Error:", err)
return
}
identifierData := core.IdentifierData{
CertSHA1: record[0],
}
externalCert := core.ExternalCert{
SHA1: record[0],
}
deleteStart := time.Now()
_, err = dbMap.Delete(&identifierData)
stats.TimingDuration("ExistingCert.Domains.DeleteLatency", time.Since(deleteStart), statsRate)
_, err = dbMap.Delete(&externalCert)
stats.TimingDuration("ExistingCert.Certs.DeleteLatency", time.Since(deleteStart), statsRate)
stats.Inc("ExistingCert.Removed", 1, statsRate)
}
}
func main() {
app := cmd.NewAppShell("external-cert-importer", "Imports external certificates for POP checks")
app.App.Flags = append(app.App.Flags, cli.StringFlag{
Name: "a, valid-certs-file",
Value: "ssl-observatory-valid-certs.csv",
Usage: "The CSV file containing the valid certs to import.",
}, cli.StringFlag{
Name: "d, domains-file",
Value: "ssl-observatory-domains.csv",
Usage: "The CSV file containing the domains associated with the certs that are being imported.",
}, cli.StringFlag{
Name: "r, invalid-certs-file",
Value: "ssl-observatory-invalid-certs.csv",
Usage: "The CSV file Containing now invalid certs which should be removed.",
}, cli.Float64Flag{
Name: "statsd-rate",
Value: 1.0,
Usage: "A floating point number between 0 and 1 representing the rate at which the statsd client will send data.",
})
app.Config = func(c *cli.Context, config cmd.Config) cmd.Config {
fmt.Println(c.Args())
config.ExternalCertImporter.CertsToImportCSVFilename = c.GlobalString("a")
config.ExternalCertImporter.DomainsToImportCSVFilename = c.GlobalString("d")
config.ExternalCertImporter.CertsToRemoveCSVFilename = c.GlobalString("r")
config.ExternalCertImporter.StatsdRate = float32(math.Min(math.Max(c.Float64("statsd-rate"), 0.0), 1.0))
return config
}
app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
// Configure DB
dbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Could not connect to database")
dbMap.AddTableWithName(core.ExternalCert{}, "externalCerts").SetKeys(false, "SHA1")
dbMap.AddTableWithName(core.IdentifierData{}, "identifierData").SetKeys(false, "CertSHA1")
// Note that this order of operations is intentional: we first add
// new certs to the database. Then, since certs are identified by
// the entries in the identifiers table, we add those. Then, we
// can remove invalid certs (which first removes the associated
// identifiers).
addCerts(c.ExternalCertImporter.CertsToImportCSVFilename, dbMap, stats, c.ExternalCertImporter.StatsdRate)
addIdentifiers(c.ExternalCertImporter.DomainsToImportCSVFilename, dbMap, stats, c.ExternalCertImporter.StatsdRate)
removeInvalidCerts(c.ExternalCertImporter.CertsToRemoveCSVFilename, dbMap, stats, c.ExternalCertImporter.StatsdRate)
}
app.Run()
}