Merge branch 'caa-flag-fix' of github.com:letsencrypt/boulder into caa-flag-fix

This commit is contained in:
Roland Shoemaker 2016-01-27 14:15:26 -08:00
commit 82c7d0b7a5
13 changed files with 328 additions and 7 deletions

View File

@ -11,6 +11,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
@ -410,7 +411,12 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
if err != nil {
err = core.InternalServerError(err.Error())
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
ca.log.Audit(fmt.Sprintf("Failed RPC to store at SA, orphaning certificate: pem=[%s] err=[%v]", certPEM, err))
ca.log.Audit(fmt.Sprintf(
"Failed RPC to store at SA, orphaning certificate: b64der=[%s] err=[%v], regID=[%d]",
base64.StdEncoding.EncodeToString(certDER),
err,
regID,
))
return emptyCert, err
}

View File

@ -113,11 +113,11 @@ type Config struct {
Mailer struct {
ServiceConfig
DBConfig
PasswordConfig
Server string
Port string
Username string
Password string
From string
Subject string
@ -217,6 +217,26 @@ func (config *Config) KeyPolicy() core.KeyPolicy {
}
}
// PasswordConfig either contains a password or the path to a file
// containing a password
type PasswordConfig struct {
Password string
PasswordFile string
}
// Pass returns a password, either directly from the configuration
// struct or by reading from a specified file
func (pc *PasswordConfig) Pass() (string, error) {
if pc.PasswordFile != "" {
contents, err := ioutil.ReadFile(pc.PasswordFile)
if err != nil {
return "", err
}
return strings.TrimRight(string(contents), "\n"), nil
}
return pc.Password, nil
}
// ServiceConfig contains config items that are common to all our services, to
// be embedded in other config structs.
type ServiceConfig struct {

25
cmd/config_test.go Normal file
View File

@ -0,0 +1,25 @@
package cmd
import (
"testing"
"github.com/letsencrypt/boulder/test"
)
func TestPasswordConfig(t *testing.T) {
tests := []struct {
pc PasswordConfig
expected string
}{
{pc: PasswordConfig{}, expected: ""},
{pc: PasswordConfig{Password: "config"}, expected: "config"},
{pc: PasswordConfig{Password: "config", PasswordFile: "testdata/test_secret"}, expected: "secret"},
{pc: PasswordConfig{PasswordFile: "testdata/test_secret"}, expected: "secret"},
}
for _, tc := range tests {
password, err := tc.pc.Pass()
test.AssertNotError(t, err, "Failed to retrieve password")
test.AssertEquals(t, password, tc.expected)
}
}

View File

@ -252,7 +252,9 @@ func main() {
_, err = netmail.ParseAddress(c.Mailer.From)
cmd.FailOnError(err, fmt.Sprintf("Could not parse from address: %s", c.Mailer.From))
mailClient := mail.New(c.Mailer.Server, c.Mailer.Port, c.Mailer.Username, c.Mailer.Password, c.Mailer.From)
smtpPassword, err := c.Mailer.PasswordConfig.Pass()
cmd.FailOnError(err, "Failed to load SMTP password")
mailClient := mail.New(c.Mailer.Server, c.Mailer.Port, c.Mailer.Username, smtpPassword, c.Mailer.From)
err = mailClient.Connect()
cmd.FailOnError(err, "Couldn't connect to mail server.")

View File

@ -0,0 +1,21 @@
{
"syslog": {
"network": "",
"server": "",
"stdoutlevel": 7
},
"statsd": {
"server": "localhost:8125",
"prefix": "Boulder"
},
"amqp": {
"serverURLFile": "test/secrets/amqp_url",
"insecure": true,
"SA": {
"server": "SA.server",
"rpcTimeout": "15s"
}
}
}

190
cmd/orphan-finder/main.go Normal file
View File

@ -0,0 +1,190 @@
package main
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"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"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
)
type config struct {
AMQP cmd.AMQPConfig
Statsd cmd.StatsdConfig
Syslog cmd.SyslogConfig
}
var (
b64derOrphan = regexp.MustCompile(`b64der=\[([a-zA-Z0-9+/]+)\]`)
regOrphan = regexp.MustCompile(`regID=\[(\d+)\]`)
)
func checkDER(sai core.StorageAuthority, der []byte) error {
cert, err := x509.ParseCertificate(der)
if err != nil {
return fmt.Errorf("Failed to parse DER: %s", err)
}
_, err = sai.GetCertificate(core.SerialToString(cert.SerialNumber))
if err == nil {
return fmt.Errorf("Existing certificate found with serial %s", core.SerialToString(cert.SerialNumber))
}
if _, ok := err.(core.NotFoundError); ok {
return nil
}
return fmt.Errorf("Existing certificate lookup failed: %s", err)
}
func parseLogLine(sa core.StorageAuthority, logger *blog.AuditLogger, line string) (found bool, added bool) {
if !strings.Contains(line, "b64der=") {
return false, false
}
derStr := b64derOrphan.FindStringSubmatch(line)
if len(derStr) <= 1 {
logger.Err(fmt.Sprintf("b64der variable is empty, [%s]", line))
return true, false
}
der, err := base64.StdEncoding.DecodeString(derStr[1])
if err != nil {
logger.Err(fmt.Sprintf("Couldn't decode b64: %s, [%s]", err, line))
return true, false
}
err = checkDER(sa, der)
if err != nil {
logger.Err(fmt.Sprintf("%s, [%s]", err, line))
return true, false
}
// extract the regID
regStr := regOrphan.FindStringSubmatch(line)
if len(regStr) <= 1 {
logger.Err(fmt.Sprintf("regID variable is empty, [%s]", line))
return true, false
}
regID, err := strconv.Atoi(regStr[1])
if err != nil {
logger.Err(fmt.Sprintf("Couldn't parse regID: %s, [%s]", err, line))
return true, false
}
_, err = sa.AddCertificate(der, int64(regID))
if err != nil {
logger.Err(fmt.Sprintf("Failed to store certificate: %s, [%s]", err, line))
return true, false
}
return true, true
}
func setup(c *cli.Context) (statsd.Statter, *blog.AuditLogger, *rpc.StorageAuthorityClient) {
configJSON, err := ioutil.ReadFile(c.GlobalString("config"))
cmd.FailOnError(err, "Failed to read config file")
var conf config
err = json.Unmarshal(configJSON, &conf)
cmd.FailOnError(err, "Failed to parse config file")
stats, logger := cmd.StatsAndLogging(conf.Statsd, conf.Syslog)
sa, err := rpc.NewStorageAuthorityClient("orphan-finder", &conf.AMQP, stats)
cmd.FailOnError(err, "Failed to create SA client")
return stats, logger, sa
}
func main() {
app := cli.NewApp()
app.Name = "orphan-finder"
app.Usage = "Reads orphaned certificates from a boulder-ca log or a der file and add them to the database"
app.Version = cmd.Version()
app.Author = "Boulder contributors"
app.Email = "ca-dev@letsencrypt.org"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "config.json",
EnvVar: "BOULDER_CONFIG",
Usage: "Path to Boulder JSON configuration file",
},
}
app.Commands = []cli.Command{
{
Name: "parse-ca-log",
Usage: "Parses boulder-ca logs to add multiple orphaned certificates",
Flags: []cli.Flag{
cli.StringFlag{
Name: "log-file",
Usage: "Path to boulder-ca log file to parse",
},
},
Action: func(c *cli.Context) {
stats, logger, sa := setup(c)
logPath := c.String("log-file")
if logPath == "" {
fmt.Println("log file path must be provided")
os.Exit(1)
}
logData, err := ioutil.ReadFile(logPath)
cmd.FailOnError(err, "Failed to read log file")
orphansFound := int64(0)
orphansAdded := int64(0)
for _, line := range strings.Split(string(logData), "\n") {
found, added := parseLogLine(sa, logger, line)
if found {
orphansFound++
if added {
orphansAdded++
}
}
}
logger.Info(fmt.Sprintf("Found %d orphans and added %d to the database\n", orphansFound, orphansAdded))
stats.Inc("orphaned-certificates.found", orphansFound, 1.0)
stats.Inc("orphaned-certificates.added", orphansAdded, 1.0)
stats.Inc("orphaned-certificates.adding-failed", orphansFound-orphansAdded, 1.0)
},
},
{
Name: "parse-der",
Usage: "Parses a single orphaned DER certificate file and adds it to the database",
Flags: []cli.Flag{
cli.StringFlag{
Name: "der-file",
Usage: "Path to DER certificate file",
},
cli.IntFlag{
Name: "regID",
Usage: "Registration ID of user who requested the certificate",
},
},
Action: func(c *cli.Context) {
_, _, sa := setup(c)
derPath := c.String("der-file")
if derPath == "" {
fmt.Println("der file path must be provided")
os.Exit(1)
}
regID := c.Int("regID")
if regID == 0 {
fmt.Println("--regID must be non-zero")
os.Exit(1)
}
der, err := ioutil.ReadFile(derPath)
cmd.FailOnError(err, "Failed to read DER file")
err = checkDER(sa, der)
cmd.FailOnError(err, "Pre-AddCertificate checks failed")
_, err = sa.AddCertificate(der, int64(regID))
cmd.FailOnError(err, "Failed to add certificate to database")
},
},
}
app.Run(os.Args)
}

View File

@ -0,0 +1,51 @@
package main
import (
"fmt"
"testing"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/sa"
"github.com/letsencrypt/boulder/sa/satest"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/test/vars"
)
var log = mocks.UseMockLog()
func TestParseLine(t *testing.T) {
dbMap, err := sa.NewDbMap(vars.DBConnSA)
if err != nil {
t.Fatalf("Failed to create dbMap: %s", err)
}
fc := clock.NewFake()
fc.Set(time.Date(2015, 3, 4, 5, 0, 0, 0, time.UTC))
sa, err := sa.NewSQLStorageAuthority(dbMap, fc)
if err != nil {
t.Fatalf("Failed to create SA: %s", err)
}
defer test.ResetSATestDatabase(t)()
logger := blog.GetAuditLogger()
found, added := parseLogLine(sa, logger, "")
test.AssertEquals(t, found, false)
test.AssertEquals(t, added, false)
found, added = parseLogLine(sa, logger, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: b64der=[] err=[AMQP-RPC timeout], regID=[1337]")
test.AssertEquals(t, found, true)
test.AssertEquals(t, added, false)
found, added = parseLogLine(sa, logger, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: b64der=[deadbeef] err=[AMQP-RPC timeout], regID=[]")
test.AssertEquals(t, found, true)
test.AssertEquals(t, added, false)
reg := satest.CreateWorkingRegistration(t, sa)
found, added = parseLogLine(sa, logger, fmt.Sprintf("0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: b64der=[MIIEWzCCA0OgAwIBAgITAP+gFgYw1hiy61wFEIJLFCdIVjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNTEwMDMwNTIxMDBaFw0xNjAxMDEwNTIxMDBaMBgxFjAUBgNVBAMTDWV4YW1wbGUuY28uYm4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCeo/HSH63lWW42pqdwlalHWOS3JGa3REraT3xM9v3psdRwuTtlwf3YlpF/JIzK5JtXyA3CHGSwEGmUMhMNBZ0tg5I0booXnHyUeDVUnGSnpWgMUY+vCly+pI5oT8pjBHdcj6kjnDTx1cstBjsJi9HBcYPHUh78iEZBsvC0FAKsh8cHaEjUNHzvWd1anBdK0lRn25M8le9IxXi6di9SeyFmahmPteH+LYKZtNzrF5HpatB14+ywV8d212T62PCCnUPDLd+YWjo2+t5pZs7IlGhyGh7EerOOrI2kUUBg3tUdKDp4e3xplxvaAfSfdrqkGx+bQ0iqQnng+lVkXWYWRB8NAgMBAAGjggGVMIIBkTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFDadDBAEUrnrP/566FLp6DmjrlrbMB8GA1UdIwQYMBaAFPt4TxL5YBWDLJ8XfzQZsy426kGJMGoGCCsGAQUFBwEBBF4wXDAmBggrBgEFBQcwAYYaaHR0cDovL2xvY2FsaG9zdDo0MDAyL29jc3AwMgYIKwYBBQUHMAKGJmh0dHA6Ly9sb2NhbGhvc3Q6NDAwMC9hY21lL2lzc3Vlci1jZXJ0MBgGA1UdEQQRMA+CDWV4YW1wbGUuY28uYm4wJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL2V4YW1wbGUuY29tL2NybDBjBgNVHSAEXDBaMAoGBmeBDAECATAAMEwGAyoDBDBFMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMB8GCCsGAQUFBwICMBMMEURvIFdoYXQgVGhvdSBXaWx0MA0GCSqGSIb3DQEBCwUAA4IBAQC7tLmUlxyvouVuIljbRtiL+zYdi/zXVSHAMXTkceqp8/8ucZBZu1fMBkB5SW2FUFd8EnuqhKGOeS3dNr9Pe4dLbUDR0UKIwV045Na+Jet4BbHDdWs3NXAutFhdGIa8ivLBQIbTzlBuVRhJE8g6qqjf5hYL0DXkLNptl2l+0+4xJMm/liCp/mYCGRwbdGUzwdSjACO76QLLSqZhkBF37ZJOuDbJTMBi3QzkOcTs6e4d/gSZpCy7yy6nJDxZ9N9P3XBYIpus+aZAYy29d2shYzE3st8cQfB2Wmb0SHd67sftTAzeudiiNW/4E4IKKH4R1S794apUO07y7pkqep1cz32k] err=[AMQP-RPC timeout], regID=[%d]", reg.ID))
test.AssertEquals(t, found, true)
test.AssertEquals(t, added, true)
}

1
cmd/testdata/test_secret vendored Normal file
View File

@ -0,0 +1 @@
secret

View File

@ -43,7 +43,7 @@ func NewNonceService() (*NonceService, error) {
if err != nil {
panic("Failure in NewCipher: " + err.Error())
}
gcm, _ := cipher.NewGCM(c)
gcm, err := cipher.NewGCM(c)
if err != nil {
panic("Failure in NewGCM: " + err.Error())
}

View File

@ -386,7 +386,7 @@ func (ssa *SQLStorageAuthority) GetCertificate(serial string) (core.Certificate,
}
if certObj == nil {
ssa.log.Debug(fmt.Sprintf("Nil cert for %s", serial))
return core.Certificate{}, fmt.Errorf("Certificate does not exist for %s", serial)
return core.Certificate{}, core.NotFoundError(fmt.Sprintf("No certificate found for %s", serial))
}
certPtr, ok := certObj.(*core.Certificate)

View File

@ -292,7 +292,7 @@
"port": "25",
"username": "cert-master@example.com",
"from": "Expiry bot <test@example.com>",
"password": "password",
"passwordFile": "test/secrets/smtp_password",
"dbConnectFile": "test/secrets/mailer_dburl",
"messageLimit": 0,
"nagTimes": ["24h", "72h", "168h", "336h"],

View File

@ -70,7 +70,11 @@ func deleteEverythingInAllTables(db CleanUpDB) error {
// rejecting the DELETE for not having a WHERE clause.
_, err := db.Exec("delete from `" + tn + "` where 1 = 1")
if err != nil {
return err
return fmt.Errorf("unable to delete all rows from table %#v: %s", tn, err)
}
_, err = db.Exec("alter table `" + tn + "` AUTO_INCREMENT = 1")
if err != nil {
return fmt.Errorf("unable to reset autoincrement on table %#v: %s", tn, err)
}
}
return err

View File

@ -0,0 +1 @@
password