Add ConfigSecret.

This allows secret values to be stored externally to the config file, so that
config files can be easily shared without revealing secret data.
This commit is contained in:
Jacob Hoffman-Andrews 2015-11-19 21:48:53 -08:00
parent 7ea3f5da08
commit 5dd212dd47
21 changed files with 105 additions and 30 deletions

View File

@ -48,7 +48,7 @@ func setupContext(context *cli.Context) (rpc.RegistrationAuthorityClient, *blog.
rac, err := rpc.NewRegistrationAuthorityClient(clientName, amqpConf, stats)
cmd.FailOnError(err, "Unable to create CA client")
dbMap, err := sa.NewDbMap(c.Revoker.DBConnect)
dbMap, err := sa.NewDbMap(string(c.Revoker.DBConnect))
cmd.FailOnError(err, "Couldn't setup database connection")
sac, err := rpc.NewStorageAuthorityClient(clientName, amqpConf, stats)

View File

@ -32,7 +32,7 @@ func main() {
go cmd.DebugServer(c.CA.DebugAddr)
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
paDbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Couldn't connect to policy database")
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
cmd.FailOnError(err, "Couldn't create PA")

View File

@ -31,7 +31,7 @@ func main() {
go cmd.DebugServer(c.RA.DebugAddr)
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
paDbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Couldn't connect to policy database")
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
cmd.FailOnError(err, "Couldn't create PA")

View File

@ -20,7 +20,7 @@ func main() {
saConf := c.SA
go cmd.DebugServer(saConf.DebugAddr)
dbMap, err := sa.NewDbMap(saConf.DBConnect)
dbMap, err := sa.NewDbMap(string(saConf.DBConnect))
cmd.FailOnError(err, "Couldn't connect to SA database")
sai, err := sa.NewSQLStorageAuthority(dbMap, clock.Default())

View File

@ -239,10 +239,10 @@ func main() {
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
c.PA.SetDefaultChallengesIfEmpty()
saDbMap, err := sa.NewDbMap(c.CertChecker.DBConnect)
saDbMap, err := sa.NewDbMap(string(c.CertChecker.DBConnect))
cmd.FailOnError(err, "Could not connect to database")
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
paDbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Could not connect to policy database")
checker := newChecker(saDbMap, paDbMap, clock.Default(), c.PA.EnforcePolicyWhitelist, c.PA.Challenges)

View File

@ -9,6 +9,8 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strings"
"time"
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
@ -65,7 +67,7 @@ type Config struct {
SA struct {
ServiceConfig
DBConnect string
DBConnect ConfigSecret
MaxConcurrentRPCServerRequests int64
}
@ -183,7 +185,7 @@ type ServiceConfig struct {
// AMQPConfig describes how to connect to AMQP, and how to speak to each of the
// RPC services we offer via AMQP.
type AMQPConfig struct {
Server string
Server ConfigSecret
Insecure bool
RA *RPCServerConfig
VA *RPCServerConfig
@ -386,3 +388,35 @@ func (d *ConfigDuration) UnmarshalYAML(unmarshal func(interface{}) error) error
d.Duration = dur
return nil
}
// A ConfigSecret represents a string-valued config field. It may be specified
// directly in the config or, if it starts with the string "secret:", its
// contents are read from the filename that comes after "secret:", with
// trailing newlines removed.
type ConfigSecret string
var errSecretMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigSecret")
const secretPrefix = "secret:"
// UnmarshalJSON unmarshals a ConfigSecret
func (d *ConfigSecret) UnmarshalJSON(b []byte) error {
s := ""
err := json.Unmarshal(b, &s)
if err != nil {
if _, ok := err.(*json.UnmarshalTypeError); ok {
return errSecretMustBeString
}
return err
}
if !strings.HasPrefix(s, secretPrefix) {
*d = ConfigSecret(s)
return nil
}
contents, err := ioutil.ReadFile(s[len(secretPrefix):])
if err != nil {
return err
}
*d = ConfigSecret(strings.TrimRight(string(contents), "\n"))
return nil
}

32
cmd/config_test.go Normal file
View File

@ -0,0 +1,32 @@
package cmd
import (
"encoding/json"
"os"
"path"
"testing"
)
func TestConfigSecret(t *testing.T) {
type hasASecret struct {
Value ConfigSecret
}
var twocankeep hasASecret
err := json.Unmarshal([]byte(`{"value": "hi"}`), &twocankeep)
if err != nil {
t.Fatalf("Error unmarshaling: %s", err)
}
if twocankeep.Value != "hi" {
t.Errorf("Expected parsed value to be \"hi\", got %q", twocankeep.Value)
}
os.Chdir(path.Base(os.Args[0]))
var oneofthemisdead hasASecret
err = json.Unmarshal([]byte(`{"value": "secret:testdata/secret"}`), &oneofthemisdead)
if err != nil {
t.Fatalf("Error unmarshaling: %s", err)
}
if oneofthemisdead.Value != "test secret" {
t.Errorf("Expected parsed value to be \"test secret\", got %q", twocankeep.Value)
}
}

View File

@ -231,7 +231,7 @@ func main() {
go cmd.DebugServer(c.Mailer.DebugAddr)
// Configure DB
dbMap, err := sa.NewDbMap(c.Mailer.DBConnect)
dbMap, err := sa.NewDbMap(string(c.Mailer.DBConnect))
cmd.FailOnError(err, "Could not connect to database")
amqpConf := c.SA.AMQP

View File

@ -150,7 +150,7 @@ func main() {
app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
// Configure DB
dbMap, err := sa.NewDbMap(c.PA.DBConnect)
dbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Could not connect to database")
dbMap.AddTableWithName(core.ExternalCert{}, "externalCerts").SetKeys(false, "SHA1")

View File

@ -560,7 +560,7 @@ func main() {
go cmd.ProfileCmd("OCSP-Updater", stats)
// Configure DB
dbMap, err := sa.NewDbMap(conf.DBConnect)
dbMap, err := sa.NewDbMap(string(conf.DBConnect))
cmd.FailOnError(err, "Could not connect to database")
cac, pubc, sac := setupClients(conf, stats)

View File

@ -110,7 +110,7 @@ func setupFromContext(context *cli.Context) (*policy.PolicyAuthorityDatabaseImpl
err = json.Unmarshal(configJSON, &c)
cmd.FailOnError(err, "Couldn't unmarshal configuration object")
dbMap, err := sa.NewDbMap(c.PA.DBConnect)
dbMap, err := sa.NewDbMap(string(c.PA.DBConnect))
cmd.FailOnError(err, "Failed to create DB map")
padb, err := policy.NewPolicyAuthorityDatabaseImpl(dbMap)

1
cmd/testdata/secret vendored Normal file
View File

@ -0,0 +1 @@
test secret

View File

@ -296,14 +296,15 @@ func AmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
log := blog.GetAuditLogger()
server := string(conf.Server)
if conf.Insecure == true {
// If the Insecure flag is true, then just go ahead and connect
conn, err = amqp.Dial(conf.Server)
conn, err = amqp.Dial(server)
} else {
// The insecure flag is false or not set, so we need to load up the options
log.Info("AMQPS: Loading TLS Options.")
if strings.HasPrefix(conf.Server, "amqps") == false {
if strings.HasPrefix(server, "amqps") == false {
err = fmt.Errorf("AMQPS: Not using an AMQPS URL. To use AMQP instead of AMQPS, set insecure=true")
return nil, err
}
@ -347,7 +348,7 @@ func AmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
log.Info("AMQPS: Configured CA certificate for AMQPS.")
}
conn, err = amqp.DialTLS(conf.Server, cfg)
conn, err = amqp.DialTLS(server, cfg)
}
if err != nil {

View File

@ -21,7 +21,7 @@
"shutdownKillTimeout": "1m",
"debugAddr": "localhost:8000",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"RA": {
"server": "RA.server",
@ -96,7 +96,7 @@
"maxConcurrentRPCServerRequests": 16,
"hsmFaultTimeout": "300s",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"serviceQueue": "CA.server",
"SA": {
@ -111,7 +111,7 @@
},
"pa": {
"dbConnect": "mysql+tcp://policy@localhost:3306/boulder_policy_integration",
"dbConnect": "secret:test/secrets/pa_dburl",
"challenges": {
"simpleHttp": true,
"dvsni": true,
@ -127,7 +127,7 @@
"maxContactsPerRegistration": 100,
"debugAddr": "localhost:8002",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"serviceQueue": "RA.server",
"VA": {
@ -147,11 +147,11 @@
},
"sa": {
"dbConnect": "mysql+tcp://sa@localhost:3306/boulder_sa_integration",
"dbConnect": "secret:test/secrets/sa_dburl",
"maxConcurrentRPCServerRequests": 16,
"debugAddr": "localhost:8003",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"serviceQueue": "SA.server"
}
@ -167,7 +167,7 @@
},
"maxConcurrentRPCServerRequests": 16,
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"serviceQueue": "VA.server",
"RA": {
@ -182,9 +182,9 @@
},
"revoker": {
"dbConnect": "mysql+tcp://revoker@localhost:3306/boulder_sa_integration",
"dbConnect": "secret:test/secrets/revoker_dburl",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"RA": {
"server": "RA.server",
@ -208,7 +208,7 @@
},
"ocspUpdater": {
"dbConnect": "mysql+tcp://ocsp_update@localhost:3306/boulder_sa_integration",
"dbConnect": "secret:test/secrets/ocsp_updater_dburl",
"newCertificateWindow": "1s",
"oldOCSPWindow": "2s",
"missingSCTWindow": "1m",
@ -223,7 +223,7 @@
"signFailureBackoffMax": "30m",
"debugAddr": "localhost:8006",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"SA": {
"server": "SA.server",
@ -243,7 +243,7 @@
"activityMonitor": {
"debugAddr": "localhost:8007",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true
}
},
@ -253,7 +253,7 @@
"port": "25",
"username": "cert-master@example.com",
"password": "password",
"dbConnect": "mysql+tcp://mailer@localhost:3306/boulder_sa_integration",
"dbConnect": "secret:test/secrets/mailer_dburl",
"messageLimit": 0,
"nagTimes": ["24h", "72h", "168h", "336h"],
"nagCheckInterval": "24h",
@ -265,7 +265,7 @@
"maxConcurrentRPCServerRequests": 16,
"debugAddr": "localhost:8009",
"amqp": {
"server": "amqp://guest:guest@localhost:5673",
"server": "secret:test/secrets/amqp",
"insecure": true,
"serviceQueue": "Publisher.server",
"SA": {
@ -295,7 +295,7 @@
},
"certChecker": {
"dbConnect": "mysql+tcp://cert_checker@localhost:3306/boulder_sa_integration"
"dbConnect": "secret:test/secrets/cert_checker_dburl"
},
"subscriberAgreementURL": "http://127.0.0.1:4001/terms/v1"

1
test/secrets/amqp Normal file
View File

@ -0,0 +1 @@
amqp://guest:guest@localhost:5673

View File

@ -0,0 +1 @@
mysql+tcp://cert_checker@localhost:3306/boulder_sa_integration

View File

@ -0,0 +1 @@
mysql+tcp://mailer@localhost:3306/boulder_sa_integration

View File

@ -0,0 +1 @@
mysql+tcp://ocsp_update@localhost:3306/boulder_sa_integration

1
test/secrets/pa_dburl Normal file
View File

@ -0,0 +1 @@
mysql+tcp://policy@localhost:3306/boulder_policy_integration

View File

@ -0,0 +1 @@
mysql+tcp://revoker@localhost:3306/boulder_sa_integration

1
test/secrets/sa_dburl Normal file
View File

@ -0,0 +1 @@
mysql+tcp://sa@localhost:3306/boulder_sa_integration