Merge branch 'master' into fix-nonce-race

This commit is contained in:
Jacob Hoffman-Andrews 2015-11-11 12:35:43 -08:00
commit c6bb0ad45b
22 changed files with 240 additions and 80 deletions

View File

@ -30,6 +30,7 @@ recent version.
Also, Boulder requires Go 1.5. As of September 2015 this version is not yet Also, Boulder requires Go 1.5. As of September 2015 this version is not yet
available in OS repositories, so you will have to install from https://golang.org/dl/. available in OS repositories, so you will have to install from https://golang.org/dl/.
Add ```${GOPATH}/bin``` to your path.
Ubuntu: Ubuntu:
@ -50,17 +51,19 @@ or
(On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.) (On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.)
> go get bitbucket.org/liamstask/goose/cmd/goose > go get bitbucket.org/liamstask/goose/cmd/goose
> go get github.com/jsha/listenbuddy
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files > go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
> cd $GOPATH/src/github.com/letsencrypt/boulder > cd $GOPATH/src/github.com/letsencrypt/boulder
> ./test/create_db.sh > ./test/create_db.sh
# This starts each Boulder component with test configs. Ctrl-C kills all. # This starts each Boulder component with test configs. Ctrl-C kills all.
> ./start.py > ./start.py
# Run tests # Run tests
> go get -u github.com/golang/lint/golint
> ./test.sh > ./test.sh
Note: `create_db.sh` it uses the root MariaDB user, so if you Note: `create_db.sh` it uses the root MariaDB user with the default
have disabled that account you may have to adjust the file or password, so if you have disabled that account or changed the password
recreate the commands. you may have to adjust the file or recreate the commands.
You can also check out the official client from You can also check out the official client from
https://github.com/letsencrypt/letsencrypt/ and follow the setup https://github.com/letsencrypt/letsencrypt/ and follow the setup

View File

@ -132,7 +132,7 @@ func setup(t *testing.T) *testCtx {
paDbMap, err := sa.NewDbMap(vars.DBConnPolicy) paDbMap, err := sa.NewDbMap(vars.DBConnPolicy)
test.AssertNotError(t, err, "Could not construct dbMap") test.AssertNotError(t, err, "Could not construct dbMap")
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false) pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false, nil)
test.AssertNotError(t, err, "Couldn't create PADB") test.AssertNotError(t, err, "Couldn't create PADB")
paDBCleanUp := test.ResetPolicyTestDatabase(t) paDBCleanUp := test.ResetPolicyTestDatabase(t)

View File

@ -27,6 +27,10 @@ func main() {
cmd.FailOnError(err, "Could not connect to Syslog") cmd.FailOnError(err, "Could not connect to Syslog")
auditlogger.Info(app.VersionString()) auditlogger.Info(app.VersionString())
// Validate PA config and set defaults if needed
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
c.PA.SetDefaultChallengesIfEmpty()
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
defer auditlogger.AuditPanic() defer auditlogger.AuditPanic()
@ -36,7 +40,7 @@ func main() {
paDbMap, err := sa.NewDbMap(c.PA.DBConnect) paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
cmd.FailOnError(err, "Couldn't connect to policy database") cmd.FailOnError(err, "Couldn't connect to policy database")
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist) pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
cmd.FailOnError(err, "Couldn't create PA") cmd.FailOnError(err, "Couldn't create PA")
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert) cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert)

View File

@ -31,6 +31,10 @@ func main() {
cmd.FailOnError(err, "Could not connect to Syslog") cmd.FailOnError(err, "Could not connect to Syslog")
auditlogger.Info(app.VersionString()) auditlogger.Info(app.VersionString())
// Validate PA config and set defaults if needed
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
c.PA.SetDefaultChallengesIfEmpty()
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
defer auditlogger.AuditPanic() defer auditlogger.AuditPanic()
@ -40,7 +44,7 @@ func main() {
paDbMap, err := sa.NewDbMap(c.PA.DBConnect) paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
cmd.FailOnError(err, "Couldn't connect to policy database") cmd.FailOnError(err, "Couldn't connect to policy database")
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist) pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
cmd.FailOnError(err, "Couldn't create PA") cmd.FailOnError(err, "Couldn't create PA")
rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename) rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename)
@ -68,7 +72,7 @@ func main() {
var dc *ra.DomainCheck var dc *ra.DomainCheck
if c.RA.UseIsSafeDomain { if c.RA.UseIsSafeDomain {
dc = &ra.DomainCheck{&vac} dc = &ra.DomainCheck{VA: &vac}
} }
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats, rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats,

View File

@ -76,8 +76,8 @@ type certChecker struct {
issuedReport report issuedReport report
} }
func newChecker(saDbMap *gorp.DbMap, paDbMap *gorp.DbMap, clk clock.Clock, enforceWhitelist bool) certChecker { func newChecker(saDbMap *gorp.DbMap, paDbMap *gorp.DbMap, clk clock.Clock, enforceWhitelist bool, challengeTypes map[string]bool) certChecker {
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, enforceWhitelist) pa, err := policy.NewPolicyAuthorityImpl(paDbMap, enforceWhitelist, challengeTypes)
cmd.FailOnError(err, "Failed to create PA") cmd.FailOnError(err, "Failed to create PA")
c := certChecker{ c := certChecker{
pa: pa, pa: pa,
@ -235,6 +235,10 @@ func main() {
} }
app.Action = func(c cmd.Config) { app.Action = func(c cmd.Config) {
// Validate PA config and set defaults if needed
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
c.PA.SetDefaultChallengesIfEmpty()
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix) stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
cmd.FailOnError(err, "Couldn't connect to statsd") cmd.FailOnError(err, "Couldn't connect to statsd")
@ -250,7 +254,7 @@ func main() {
paDbMap, err := sa.NewDbMap(c.PA.DBConnect) paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
cmd.FailOnError(err, "Could not connect to policy database") cmd.FailOnError(err, "Could not connect to policy database")
checker := newChecker(saDbMap, paDbMap, clock.Default(), c.PA.EnforcePolicyWhitelist) checker := newChecker(saDbMap, paDbMap, clock.Default(), c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
auditlogger.Info("# Getting certificates issued in the last 90 days") auditlogger.Info("# Getting certificates issued in the last 90 days")
// Since we grab certificates in batches we don't want this to block, when it // Since we grab certificates in batches we don't want this to block, when it

View File

@ -47,7 +47,7 @@ func BenchmarkCheckCert(b *testing.B) {
fmt.Printf("Failed to truncate tables: %s\n", err) fmt.Printf("Failed to truncate tables: %s\n", err)
}() }()
checker := newChecker(saDbMap, paDbMap, clock.Default(), false) checker := newChecker(saDbMap, paDbMap, clock.Default(), false, nil)
testKey, _ := rsa.GenerateKey(rand.Reader, 1024) testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
expiry := time.Now().AddDate(0, 0, 1) expiry := time.Now().AddDate(0, 0, 1)
serial := big.NewInt(1337) serial := big.NewInt(1337)
@ -89,7 +89,7 @@ func TestCheckCert(t *testing.T) {
fc := clock.NewFake() fc := clock.NewFake()
fc.Add(time.Hour * 24 * 90) fc.Add(time.Hour * 24 * 90)
checker := newChecker(saDbMap, paDbMap, fc, false) checker := newChecker(saDbMap, paDbMap, fc, false, nil)
issued := checker.clock.Now().Add(-time.Hour * 24 * 45) issued := checker.clock.Now().Add(-time.Hour * 24 * 45)
goodExpiry := issued.Add(checkPeriod) goodExpiry := issued.Add(checkPeriod)
@ -181,7 +181,7 @@ func TestGetAndProcessCerts(t *testing.T) {
test.AssertNotError(t, err, "Couldn't connect to policy database") test.AssertNotError(t, err, "Couldn't connect to policy database")
fc := clock.NewFake() fc := clock.NewFake()
checker := newChecker(saDbMap, paDbMap, fc, false) checker := newChecker(saDbMap, paDbMap, fc, false, nil)
sa, err := sa.NewSQLStorageAuthority(saDbMap, fc) sa, err := sa.NewSQLStorageAuthority(saDbMap, fc)
test.AssertNotError(t, err, "Couldn't create SA to insert certificates") test.AssertNotError(t, err, "Couldn't create SA to insert certificates")
saCleanUp := test.ResetSATestDatabase(t) saCleanUp := test.ResetSATestDatabase(t)

View File

@ -260,10 +260,37 @@ type CAConfig struct {
} }
// PAConfig specifies how a policy authority should connect to its // PAConfig specifies how a policy authority should connect to its
// database, and what policies it should enforce. // database, what policies it should enforce, and what challenges
// it should offer.
type PAConfig struct { type PAConfig struct {
DBConnect string DBConnect string
EnforcePolicyWhitelist bool EnforcePolicyWhitelist bool
Challenges map[string]bool
}
// CheckChallenges checks whether the list of challenges in the PA config
// actually contains valid challenge names
func (pc PAConfig) CheckChallenges() error {
for name := range pc.Challenges {
if !core.ValidChallenge(name) {
return fmt.Errorf("Invalid challenge in PA config: %s", name)
}
}
return nil
}
// SetDefaultChallengesIfEmpty sets a default list of challenges if no
// challenges are enabled in the PA config. The set of challenges specified
// corresponds to the set that was hard-coded before these configuration
// options were added.
func (pc *PAConfig) SetDefaultChallengesIfEmpty() {
if len(pc.Challenges) == 0 {
pc.Challenges = map[string]bool{}
pc.Challenges[core.ChallengeTypeSimpleHTTP] = true
pc.Challenges[core.ChallengeTypeDVSNI] = true
pc.Challenges[core.ChallengeTypeHTTP01] = true
pc.Challenges[core.ChallengeTypeTLSSNI01] = true
}
} }
// KeyConfig should contain either a File path to a PEM-format private key, // KeyConfig should contain either a File path to a PEM-format private key,

44
cmd/shell_test.go Normal file
View File

@ -0,0 +1,44 @@
package cmd
import (
"encoding/json"
"testing"
"github.com/letsencrypt/boulder/test"
)
var (
validPAConfig = []byte(`{
"dbConnect": "dummyDBConnect",
"enforcePolicyWhitelist": false,
"challenges": { "simpleHttp": true }
}`)
invalidPAConfig = []byte(`{
"dbConnect": "dummyDBConnect",
"enforcePolicyWhitelist": false,
"challenges": { "nonsense": true }
}`)
noChallengesPAConfig = []byte(`{
"dbConnect": "dummyDBConnect",
"enforcePolicyWhitelist": false
}`)
)
func TestPAConfigUnmarshal(t *testing.T) {
var pc1 PAConfig
err := json.Unmarshal(validPAConfig, &pc1)
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
test.AssertNotError(t, pc1.CheckChallenges(), "Flagged valid challenges as bad")
var pc2 PAConfig
err = json.Unmarshal(invalidPAConfig, &pc2)
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
test.AssertError(t, pc2.CheckChallenges(), "Considered invalid challenges as good")
var pc3 PAConfig
err = json.Unmarshal(noChallengesPAConfig, &pc3)
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
test.AssertNotError(t, pc3.CheckChallenges(), "Somehow found a bad challenge among none")
pc3.SetDefaultChallengesIfEmpty()
test.Assert(t, len(pc3.Challenges) == 4, "Incorrect number of challenges in default set")
}

View File

@ -15,6 +15,7 @@ import (
"time" "time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/test"
) )
// challenges.go // challenges.go
@ -56,6 +57,15 @@ func TestChallenges(t *testing.T) {
if !dns01.IsSane(false) { if !dns01.IsSane(false) {
t.Errorf("New dns-01 challenge is not sane: %v", dns01) t.Errorf("New dns-01 challenge is not sane: %v", dns01)
} }
// TODO(#894): Remove these lines
test.Assert(t, ValidChallenge(ChallengeTypeSimpleHTTP), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeDVSNI), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeHTTP01), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeTLSSNI01), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeDNS01), "Refused valid challenge")
test.Assert(t, !ValidChallenge("nonsense-71"), "Accepted invalid challenge")
} }
// objects.go // objects.go

View File

@ -97,6 +97,27 @@ const (
ChallengeTypeDNS01 = "dns-01" ChallengeTypeDNS01 = "dns-01"
) )
// ValidChallenge tests whether the provided string names a known challenge
func ValidChallenge(name string) bool {
switch name {
// TODO(#894): Delete these lines
case ChallengeTypeSimpleHTTP:
fallthrough
case ChallengeTypeDVSNI:
fallthrough
case ChallengeTypeHTTP01:
fallthrough
case ChallengeTypeTLSSNI01:
fallthrough
case ChallengeTypeDNS01:
return true
default:
return false
}
}
// TLSSNISuffix is appended to pseudo-domain names in DVSNI challenges // TLSSNISuffix is appended to pseudo-domain names in DVSNI challenges
const TLSSNISuffix = "acme.invalid" const TLSSNISuffix = "acme.invalid"

View File

@ -24,10 +24,11 @@ type PolicyAuthorityImpl struct {
DB *PolicyAuthorityDatabaseImpl DB *PolicyAuthorityDatabaseImpl
EnforceWhitelist bool EnforceWhitelist bool
enabledChallenges map[string]bool
} }
// NewPolicyAuthorityImpl constructs a Policy Authority. // NewPolicyAuthorityImpl constructs a Policy Authority.
func NewPolicyAuthorityImpl(dbMap *gorp.DbMap, enforceWhitelist bool) (*PolicyAuthorityImpl, error) { func NewPolicyAuthorityImpl(dbMap *gorp.DbMap, enforceWhitelist bool, challengeTypes map[string]bool) (*PolicyAuthorityImpl, error) {
logger := blog.GetAuditLogger() logger := blog.GetAuditLogger()
logger.Notice("Policy Authority Starting") logger.Notice("Policy Authority Starting")
@ -36,10 +37,12 @@ func NewPolicyAuthorityImpl(dbMap *gorp.DbMap, enforceWhitelist bool) (*PolicyAu
if err != nil { if err != nil {
return nil, err return nil, err
} }
pa := PolicyAuthorityImpl{ pa := PolicyAuthorityImpl{
log: logger, log: logger,
DB: padb, DB: padb,
EnforceWhitelist: enforceWhitelist, EnforceWhitelist: enforceWhitelist,
enabledChallenges: challengeTypes,
} }
return &pa, nil return &pa, nil
@ -204,13 +207,34 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64
// //
// Note: Current implementation is static, but future versions may not be. // Note: Current implementation is static, but future versions may not be.
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) { func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines challenges = []core.Challenge{}
challenges = []core.Challenge{ combinations = [][]int{}
core.SimpleHTTPChallenge(accountKey),
core.DvsniChallenge(accountKey), // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
core.HTTPChallenge01(accountKey), if pa.enabledChallenges[core.ChallengeTypeSimpleHTTP] {
core.TLSSNIChallenge01(accountKey), challenges = append(challenges, core.SimpleHTTPChallenge(accountKey))
}
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
if pa.enabledChallenges[core.ChallengeTypeDVSNI] {
challenges = append(challenges, core.DvsniChallenge(accountKey))
}
if pa.enabledChallenges[core.ChallengeTypeHTTP01] {
challenges = append(challenges, core.HTTPChallenge01(accountKey))
}
if pa.enabledChallenges[core.ChallengeTypeTLSSNI01] {
challenges = append(challenges, core.TLSSNIChallenge01(accountKey))
}
if pa.enabledChallenges[core.ChallengeTypeDNS01] {
challenges = append(challenges, core.DNSChallenge01(accountKey))
}
combinations = make([][]int, len(challenges))
for i := range combinations {
combinations[i] = []int{i}
} }
combinations = [][]int{[]int{0}, []int{1}, []int{2}, []int{3}}
return return
} }

View File

@ -21,9 +21,17 @@ import (
var log = mocks.UseMockLog() var log = mocks.UseMockLog()
var enabledChallenges = map[string]bool{
core.ChallengeTypeSimpleHTTP: true,
core.ChallengeTypeDVSNI: true,
core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: true,
}
func paImpl(t *testing.T) (*PolicyAuthorityImpl, func()) { func paImpl(t *testing.T) (*PolicyAuthorityImpl, func()) {
dbMap, cleanUp := paDBMap(t) dbMap, cleanUp := paDBMap(t)
pa, err := NewPolicyAuthorityImpl(dbMap, false) pa, err := NewPolicyAuthorityImpl(dbMap, false, enabledChallenges)
if err != nil { if err != nil {
cleanUp() cleanUp()
t.Fatalf("Couldn't create policy implementation: %s", err) t.Fatalf("Couldn't create policy implementation: %s", err)
@ -207,25 +215,19 @@ func TestChallengesFor(t *testing.T) {
t.Errorf("Error generating challenges: %v", err) t.Errorf("Error generating challenges: %v", err)
} }
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these tests test.Assert(t, len(challenges) == len(enabledChallenges), "Wrong number of challenges returned")
if len(challenges) != 4 || test.Assert(t, len(combinations) == len(enabledChallenges), "Wrong number of combinations returned")
challenges[0].Type != core.ChallengeTypeSimpleHTTP || for i, challenge := range challenges {
challenges[1].Type != core.ChallengeTypeDVSNI || test.Assert(t, enabledChallenges[challenge.Type], "Unsupported challenge returned")
challenges[2].Type != core.ChallengeTypeHTTP01 || test.AssertEquals(t, len(combinations[i]), 1)
challenges[3].Type != core.ChallengeTypeTLSSNI01 { test.AssertEquals(t, combinations[i][0], i)
t.Error("Incorrect challenges returned")
}
if len(combinations) != 4 ||
combinations[0][0] != 0 || combinations[1][0] != 1 ||
combinations[2][0] != 2 || combinations[3][0] != 3 {
t.Error("Incorrect combinations returned")
} }
} }
func TestWillingToIssueWithWhitelist(t *testing.T) { func TestWillingToIssueWithWhitelist(t *testing.T) {
dbMap, cleanUp := paDBMap(t) dbMap, cleanUp := paDBMap(t)
defer cleanUp() defer cleanUp()
pa, err := NewPolicyAuthorityImpl(dbMap, true) pa, err := NewPolicyAuthorityImpl(dbMap, true, nil)
test.AssertNotError(t, err, "Couldn't create policy implementation") test.AssertNotError(t, err, "Couldn't create policy implementation")
googID := core.AcmeIdentifier{ googID := core.AcmeIdentifier{
Type: core.IdentifierDNS, Type: core.IdentifierDNS,

View File

@ -56,10 +56,15 @@ func (dva *DummyValidationAuthority) IsSafeDomain(req *core.IsSafeDomainRequest)
if dva.IsSafeDomainErr != nil { if dva.IsSafeDomainErr != nil {
return nil, dva.IsSafeDomainErr return nil, dva.IsSafeDomainErr
} }
return &core.IsSafeDomainResponse{!dva.IsNotSafe}, nil return &core.IsSafeDomainResponse{IsSafe: !dva.IsNotSafe}, nil
} }
var ( var (
SupportedChallenges = map[string]bool{
core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
}
// These values we simulate from the client // These values we simulate from the client
AccountKeyJSONA = []byte(`{ AccountKeyJSONA = []byte(`{
"kty":"RSA", "kty":"RSA",
@ -155,10 +160,6 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
err = json.Unmarshal(ShortKeyJSON, &ShortKey) err = json.Unmarshal(ShortKeyJSON, &ShortKey)
test.AssertNotError(t, err, "Failed to unmarshal JWK") test.AssertNotError(t, err, "Failed to unmarshal JWK")
simpleHTTP := core.SimpleHTTPChallenge(&AccountKeyA)
dvsni := core.DvsniChallenge(&AccountKeyA)
AuthzInitial.Challenges = []core.Challenge{simpleHTTP, dvsni}
fc := clock.NewFake() fc := clock.NewFake()
dbMap, err := sa.NewDbMap(vars.DBConnSA) dbMap, err := sa.NewDbMap(vars.DBConnSA)
@ -198,7 +199,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
t.Fatalf("Failed to create dbMap: %s", err) t.Fatalf("Failed to create dbMap: %s", err)
} }
policyDBCleanUp := test.ResetPolicyTestDatabase(t) policyDBCleanUp := test.ResetPolicyTestDatabase(t)
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false) pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false, SupportedChallenges)
test.AssertNotError(t, err, "Couldn't create PA") test.AssertNotError(t, err, "Couldn't create PA")
ca := ca.CertificateAuthorityImpl{ ca := ca.CertificateAuthorityImpl{
Signer: signer, Signer: signer,
@ -242,6 +243,10 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
AuthzInitial.RegistrationID = Registration.ID AuthzInitial.RegistrationID = Registration.ID
challenges, combinations, err := pa.ChallengesFor(AuthzInitial.Identifier, &Registration.Key)
AuthzInitial.Challenges = challenges
AuthzInitial.Combinations = combinations
AuthzFinal = AuthzInitial AuthzFinal = AuthzInitial
AuthzFinal.Status = "valid" AuthzFinal.Status = "valid"
exp := time.Now().Add(365 * 24 * time.Hour) exp := time.Now().Add(365 * 24 * time.Hour)
@ -403,22 +408,12 @@ func TestNewAuthorization(t *testing.T) {
test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending") test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
// TODO Verify that challenges are correct // TODO Verify that challenges are correct
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines test.Assert(t, len(authz.Challenges) == len(SupportedChallenges), "Incorrect number of challenges returned")
test.Assert(t, len(authz.Challenges) == 4, "Incorrect number of challenges returned") test.Assert(t, SupportedChallenges[authz.Challenges[0].Type], fmt.Sprintf("Unsupported challenge: %s", authz.Challenges[0].Type))
test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP") test.Assert(t, SupportedChallenges[authz.Challenges[1].Type], fmt.Sprintf("Unsupported challenge: %s", authz.Challenges[1].Type))
test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI")
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
test.Assert(t, authz.Challenges[2].Type == core.ChallengeTypeHTTP01, "Challenge 2 not http-00")
test.Assert(t, authz.Challenges[3].Type == core.ChallengeTypeTLSSNI01, "Challenge 3 not tlssni-00")
test.Assert(t, authz.Challenges[0].IsSane(false), "Challenge 0 is not sane") test.Assert(t, authz.Challenges[0].IsSane(false), "Challenge 0 is not sane")
test.Assert(t, authz.Challenges[1].IsSane(false), "Challenge 1 is not sane") test.Assert(t, authz.Challenges[1].IsSane(false), "Challenge 1 is not sane")
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
test.Assert(t, authz.Challenges[2].IsSane(false), "Challenge 2 is not sane")
test.Assert(t, authz.Challenges[3].IsSane(false), "Challenge 3 is not sane")
t.Log("DONE TestNewAuthorization") t.Log("DONE TestNewAuthorization")
} }

View File

@ -25,7 +25,7 @@ func (d *DomainCheck) IsSafe(domain string) (bool, error) {
return true, nil return true, nil
} }
resp, err := d.VA.IsSafeDomain(&core.IsSafeDomainRequest{domain}) resp, err := d.VA.IsSafeDomain(&core.IsSafeDomainRequest{Domain: domain})
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/cmd" "github.com/letsencrypt/boulder/cmd"
@ -170,6 +171,8 @@ type AmqpRPCServer struct {
currentGoroutines int64 currentGoroutines int64
maxConcurrentRPCServerRequests int64 maxConcurrentRPCServerRequests int64
tooManyRequestsResponse []byte tooManyRequestsResponse []byte
stats statsd.Statter
clk clock.Clock
} }
// NewAmqpRPCServer creates a new RPC server for the given queue and will begin // NewAmqpRPCServer creates a new RPC server for the given queue and will begin
@ -186,12 +189,19 @@ func NewAmqpRPCServer(serverQueue string, maxConcurrentRPCServerRequests int64,
reconnectMax = time.Minute reconnectMax = time.Minute
} }
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
if err != nil {
return nil, err
}
return &AmqpRPCServer{ return &AmqpRPCServer{
serverQueue: serverQueue, serverQueue: serverQueue,
connection: newAMQPConnector(serverQueue, reconnectBase, reconnectMax), connection: newAMQPConnector(serverQueue, reconnectBase, reconnectMax),
log: log, log: log,
dispatchTable: make(map[string]func([]byte) ([]byte, error)), dispatchTable: make(map[string]func([]byte) ([]byte, error)),
maxConcurrentRPCServerRequests: maxConcurrentRPCServerRequests, maxConcurrentRPCServerRequests: maxConcurrentRPCServerRequests,
clk: clock.Default(),
stats: stats,
}, nil }, nil
} }
@ -426,14 +436,19 @@ func (rpc *AmqpRPCServer) Start(c cmd.Config) error {
select { select {
case msg, ok := <-rpc.connection.messages(): case msg, ok := <-rpc.connection.messages():
if ok { if ok {
rpc.stats.TimingDuration(fmt.Sprintf("RPC.MessageLag.%s", rpc.serverQueue), rpc.clk.Now().Sub(msg.Timestamp), 1.0)
if rpc.maxConcurrentRPCServerRequests > 0 && atomic.LoadInt64(&rpc.currentGoroutines) >= rpc.maxConcurrentRPCServerRequests { if rpc.maxConcurrentRPCServerRequests > 0 && atomic.LoadInt64(&rpc.currentGoroutines) >= rpc.maxConcurrentRPCServerRequests {
rpc.replyTooManyRequests(msg) rpc.replyTooManyRequests(msg)
rpc.stats.Inc(fmt.Sprintf("RPC.CallsDropped.%s", rpc.serverQueue), 1, 1.0)
break // this breaks the select, not the for break // this breaks the select, not the for
} }
rpc.stats.Inc(fmt.Sprintf("RPC.Traffic.Rx.%s", rpc.serverQueue), int64(len(msg.Body)), 1.0)
go func() { go func() {
atomic.AddInt64(&rpc.currentGoroutines, 1) atomic.AddInt64(&rpc.currentGoroutines, 1)
defer atomic.AddInt64(&rpc.currentGoroutines, -1) defer atomic.AddInt64(&rpc.currentGoroutines, -1)
startedProcessing := rpc.clk.Now()
rpc.processMessage(msg) rpc.processMessage(msg)
rpc.stats.TimingDuration(fmt.Sprintf("RPC.ServerProcessingLatency.%s", msg.Type), time.Since(startedProcessing), 1.0)
}() }()
} else { } else {
rpc.mu.RLock() rpc.mu.RLock()
@ -623,10 +638,7 @@ func (rpc *AmqpRPCCLient) dispatch(method string, body []byte) (string, chan []b
// DispatchSync sends a body to the destination, and blocks waiting on a response. // DispatchSync sends a body to the destination, and blocks waiting on a response.
func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []byte, err error) { func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []byte, err error) {
rpc.stats.Inc(fmt.Sprintf("RPC.Rate.%s", method), 1, 1.0) rpc.stats.Inc(fmt.Sprintf("RPC.Traffic.Tx.%s", rpc.serverQueue), int64(len(body)), 1.0)
rpc.stats.Inc("RPC.Traffic", int64(len(body)), 1.0)
rpc.stats.GaugeDelta("RPC.CallsWaiting", 1, 1.0)
defer rpc.stats.GaugeDelta("RPC.CallsWaiting", -1, 1.0)
callStarted := time.Now() callStarted := time.Now()
corrID, responseChan := rpc.dispatch(method, body) corrID, responseChan := rpc.dispatch(method, body)
select { select {
@ -638,16 +650,14 @@ func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []b
} }
err = unwrapError(rpcResponse.Error) err = unwrapError(rpcResponse.Error)
if err != nil { if err != nil {
rpc.stats.Inc(fmt.Sprintf("RPC.Latency.%s.Error", method), 1, 1.0) rpc.stats.Inc(fmt.Sprintf("RPC.ClientCallLatency.%s.Error", method), 1, 1.0)
return return
} }
rpc.stats.Inc("RPC.Rate.Success", 1, 1.0) rpc.stats.TimingDuration(fmt.Sprintf("RPC.ClientCallLatency.%s.Success", method), time.Since(callStarted), 1.0)
rpc.stats.TimingDuration(fmt.Sprintf("RPC.Latency.%s.Success", method), time.Since(callStarted), 1.0)
response = rpcResponse.ReturnVal response = rpcResponse.ReturnVal
return return
case <-time.After(rpc.timeout): case <-time.After(rpc.timeout):
rpc.stats.TimingDuration(fmt.Sprintf("RPC.Latency.%s.Timeout", method), time.Since(callStarted), 1.0) rpc.stats.TimingDuration(fmt.Sprintf("RPC.ClientCallLatency.%s.Timeout", method), time.Since(callStarted), 1.0)
rpc.stats.Inc("RPC.Rate.Timeouts", 1, 1.0)
rpc.log.Warning(fmt.Sprintf(" [c!][%s] AMQP-RPC timeout [%s]", rpc.clientQueue, method)) rpc.log.Warning(fmt.Sprintf(" [c!][%s] AMQP-RPC timeout [%s]", rpc.clientQueue, method))
rpc.mu.Lock() rpc.mu.Lock()
delete(rpc.pending, corrID) delete(rpc.pending, corrID)

View File

@ -129,6 +129,7 @@ func (ac *amqpConnector) publish(queueName, corrId, expiration, replyTo, msgType
Expiration: expiration, Expiration: expiration,
ReplyTo: replyTo, ReplyTo: replyTo,
Type: msgType, Type: msgType,
Timestamp: ac.clk.Now(),
}) })
} }

View File

@ -31,6 +31,7 @@ func setup(t *testing.T) (*amqpConnector, *MockamqpChannel, func()) {
}, },
queueName: "fooqueue", queueName: "fooqueue",
retryTimeoutBase: time.Second, retryTimeoutBase: time.Second,
clk: clock.NewFake(),
} }
return &ac, mockChannel, func() { mockCtrl.Finish() } return &ac, mockChannel, func() { mockCtrl.Finish() }
} }
@ -125,6 +126,7 @@ func TestPublish(t *testing.T) {
Expiration: "3000", Expiration: "3000",
ReplyTo: "replyTo", ReplyTo: "replyTo",
Type: "testMsg", Type: "testMsg",
Timestamp: ac.clk.Now(),
}) })
ac.publish("fooqueue", "03c52e", "3000", "replyTo", "testMsg", []byte("body")) ac.publish("fooqueue", "03c52e", "3000", "replyTo", "testMsg", []byte("body"))
} }

View File

@ -127,6 +127,8 @@ function build_letsencrypt() {
run ./venv/bin/pip install -U pip run ./venv/bin/pip install -U pip
run ./venv/bin/pip install -e acme -e . -e letsencrypt-apache -e letsencrypt-nginx run ./venv/bin/pip install -e acme -e . -e letsencrypt-apache -e letsencrypt-nginx
source ./venv/bin/activate
cd - cd -
} }

View File

@ -116,7 +116,14 @@
}, },
"pa": { "pa": {
"dbConnect": "mysql+tcp://policy@localhost:3306/boulder_policy_integration" "dbConnect": "mysql+tcp://policy@localhost:3306/boulder_policy_integration",
"challenges": {
"simpleHttp": true,
"dvsni": true,
"http-01": true,
"tls-sni-01": true,
"dns-01": true
}
}, },
"ra": { "ra": {

View File

@ -209,7 +209,7 @@ def run_client_tests():
"Please set LETSENCRYPT_PATH env variable to point at " "Please set LETSENCRYPT_PATH env variable to point at "
"initialized (virtualenv) client repo root") "initialized (virtualenv) client repo root")
test_script_path = os.path.join(root, 'tests', 'boulder-integration.sh') test_script_path = os.path.join(root, 'tests', 'boulder-integration.sh')
cmd = "source %s/venv/bin/activate && SIMPLE_HTTP_PORT=5002 %s" % (root, test_script_path) cmd = "SIMPLE_HTTP_PORT=5002 %s" % (test_script_path)
if subprocess.Popen(cmd, shell=True, cwd=root, executable='/bin/bash').wait() != 0: if subprocess.Popen(cmd, shell=True, cwd=root, executable='/bin/bash').wait() != 0:
die(ExitStatus.PythonFailure) die(ExitStatus.PythonFailure)

View File

@ -29,7 +29,7 @@ func (va *ValidationAuthorityImpl) IsSafeDomain(req *core.IsSafeDomainRequest) (
va.stats.Inc("VA.IsSafeDomain.Requests", 1, 1.0) va.stats.Inc("VA.IsSafeDomain.Requests", 1, 1.0)
if va.SafeBrowsing == nil { if va.SafeBrowsing == nil {
va.stats.Inc("VA.IsSafeDomain.Skips", 1, 1.0) va.stats.Inc("VA.IsSafeDomain.Skips", 1, 1.0)
return &core.IsSafeDomainResponse{true}, nil return &core.IsSafeDomainResponse{IsSafe: true}, nil
} }
list, err := va.SafeBrowsing.IsListed(req.Domain) list, err := va.SafeBrowsing.IsListed(req.Domain)
@ -37,7 +37,7 @@ func (va *ValidationAuthorityImpl) IsSafeDomain(req *core.IsSafeDomainRequest) (
va.stats.Inc("VA.IsSafeDomain.Errors", 1, 1.0) va.stats.Inc("VA.IsSafeDomain.Errors", 1, 1.0)
if err == safebrowsing.ErrOutOfDateHashes { if err == safebrowsing.ErrOutOfDateHashes {
va.stats.Inc("VA.IsSafeDomain.OutOfDateHashErrors", 1, 1.0) va.stats.Inc("VA.IsSafeDomain.OutOfDateHashErrors", 1, 1.0)
return &core.IsSafeDomainResponse{true}, nil return &core.IsSafeDomainResponse{IsSafe: true}, nil
} }
return nil, err return nil, err
} }
@ -48,5 +48,5 @@ func (va *ValidationAuthorityImpl) IsSafeDomain(req *core.IsSafeDomainRequest) (
} else { } else {
va.stats.Inc("VA.IsSafeDomain.Status.Bad", 1, 1.0) va.stats.Inc("VA.IsSafeDomain.Status.Bad", 1, 1.0)
} }
return &core.IsSafeDomainResponse{status}, nil return &core.IsSafeDomainResponse{IsSafe: status}, nil
} }

View File

@ -34,25 +34,25 @@ func TestIsSafeDomain(t *testing.T) {
sbc.EXPECT().IsListed("outofdate.com").Return("", safebrowsing.ErrOutOfDateHashes) sbc.EXPECT().IsListed("outofdate.com").Return("", safebrowsing.ErrOutOfDateHashes)
va := NewValidationAuthorityImpl(&PortConfig{}, sbc, stats, clock.NewFake()) va := NewValidationAuthorityImpl(&PortConfig{}, sbc, stats, clock.NewFake())
resp, err := va.IsSafeDomain(&core.IsSafeDomainRequest{"good.com"}) resp, err := va.IsSafeDomain(&core.IsSafeDomainRequest{Domain: "good.com"})
if err != nil { if err != nil {
t.Errorf("good.com: want no error, got '%s'", err) t.Errorf("good.com: want no error, got '%s'", err)
} }
if !resp.IsSafe { if !resp.IsSafe {
t.Errorf("good.com: want true, got %t", resp.IsSafe) t.Errorf("good.com: want true, got %t", resp.IsSafe)
} }
resp, err = va.IsSafeDomain(&core.IsSafeDomainRequest{"bad.com"}) resp, err = va.IsSafeDomain(&core.IsSafeDomainRequest{Domain: "bad.com"})
if err != nil { if err != nil {
t.Errorf("bad.com: want no error, got '%s'", err) t.Errorf("bad.com: want no error, got '%s'", err)
} }
if resp.IsSafe { if resp.IsSafe {
t.Errorf("bad.com: want false, got %t", resp.IsSafe) t.Errorf("bad.com: want false, got %t", resp.IsSafe)
} }
_, err = va.IsSafeDomain(&core.IsSafeDomainRequest{"errorful.com"}) _, err = va.IsSafeDomain(&core.IsSafeDomainRequest{Domain: "errorful.com"})
if err == nil { if err == nil {
t.Errorf("errorful.com: want error, got none") t.Errorf("errorful.com: want error, got none")
} }
resp, err = va.IsSafeDomain(&core.IsSafeDomainRequest{"outofdate.com"}) resp, err = va.IsSafeDomain(&core.IsSafeDomainRequest{Domain: "outofdate.com"})
if err != nil { if err != nil {
t.Errorf("outofdate.com: want no error, got '%s'", err) t.Errorf("outofdate.com: want no error, got '%s'", err)
} }
@ -67,7 +67,7 @@ func TestAllowNilInIsSafeDomain(t *testing.T) {
// Be cool with a nil SafeBrowsing. This will happen in prod when we have // Be cool with a nil SafeBrowsing. This will happen in prod when we have
// flag mismatch between the VA and RA. // flag mismatch between the VA and RA.
resp, err := va.IsSafeDomain(&core.IsSafeDomainRequest{"example.com"}) resp, err := va.IsSafeDomain(&core.IsSafeDomainRequest{Domain: "example.com"})
if err != nil { if err != nil {
t.Errorf("nil SafeBrowsing, unexpected error: %s", err) t.Errorf("nil SafeBrowsing, unexpected error: %s", err)
} else if !resp.IsSafe { } else if !resp.IsSafe {