Merge master

This commit is contained in:
Richard Barnes 2015-08-25 19:21:02 -04:00
commit c552984784
28 changed files with 607 additions and 527 deletions

View File

@ -43,6 +43,7 @@ before_install:
- go get github.com/mattn/goveralls
- go get github.com/modocache/gover
- go get github.com/jcjones/github-pr-status
- go get bitbucket.org/liamstask/goose/cmd/goose
# Boulder consists of multiple Go packages, which
# refer to each other by their absolute GitHub path,

View File

@ -45,34 +45,41 @@ To run a single module, specifying the AMQP server, you might use something more
Quickstart
----------
Install RabbitMQ from https://rabbitmq.com/download.html. It's required to run
tests.
Install libtool-ltdl dev libraries, which are required for Boulder's PKCS11
support.
Boulder requires an installation of RabbitMQ, libtool-ltdl, and
MariaDB 10 to work correctly. On Ubuntu and CentOS, you may have to
install RabbitMQ from https://rabbitmq.com/download.html to get a
recent version.
Ubuntu:
`sudo apt-get install libltdl3-dev`
sudo apt-get install libltdl3-dev mariadb-server rabbitmq-server
CentOS:
`sudo yum install libtool-ltdl-devel`
sudo yum install libtool-ltdl-devel MariaDB-server MariaDB-client rabbitmq-server
OS X:
`sudo port install libtool` or `brew install libtool`
brew install libtool mariadb rabbitmq
or
sudo port install libtool mariadb-server rabbitmq-server
(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 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
# This starts each Boulder component with test configs. Ctrl-C kills all.
> python ./start.py
> cd test/js
> npm install
> nodejs test.js
> ./test.sh
```
The databases that boulder requires to operate in development and
testing can be created using test/create\_db.sh. It uses the root
MariaDB user, so if you have disabled that account you may have to
adjust the file or recreate the commands.
You can also check out the official client from
https://github.com/letsencrypt/lets-encrypt-preview/ and follow the setup
instructions there.

9
ca/_db/dbconf.yml Normal file
View File

@ -0,0 +1,9 @@
development:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_ca_development
test:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_ca_test
integration:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_ca_integration

View File

@ -0,0 +1,23 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE `serialNumber` (
`id` int(11) DEFAULT NULL,
`number` int(11) DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `serialNumber`
(`id`,
`number`,
`lastUpdated`)
VALUES (1,
1,
now()
);
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE `serialNumber`

View File

@ -9,7 +9,6 @@ import (
"fmt"
"time"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
@ -31,7 +30,7 @@ type SerialNumber struct {
// NewCertificateAuthorityDatabaseImpl constructs a Database for the
// Certificate Authority.
func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb core.CertificateAuthorityDatabase, err error) {
func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb *CertificateAuthorityDatabaseImpl, err error) {
logger := blog.GetAuditLogger()
dbMap.AddTableWithName(SerialNumber{}, "serialNumber").SetKeys(true, "ID")
@ -43,21 +42,6 @@ func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb core.Certifica
return cadb, nil
}
// CreateTablesIfNotExists builds the database tables and inserts the initial
// state, if the tables do not already exist. It is not an error for the tables
// to already exist.
func (cadb *CertificateAuthorityDatabaseImpl) CreateTablesIfNotExists() (err error) {
// Create serial number table
err = cadb.dbMap.CreateTablesIfNotExists()
if err != nil {
return
}
// Initialize the serial number
err = cadb.dbMap.Insert(&SerialNumber{ID: 1, Number: 1, LastUpdated: time.Now()})
return
}
// Begin starts a transaction at the GORP wrapper.
func (cadb *CertificateAuthorityDatabaseImpl) Begin() (*gorp.Transaction, error) {
return cadb.dbMap.Begin()

View File

@ -7,8 +7,8 @@ package ca
import (
"testing"
"time"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/sa"
"github.com/letsencrypt/boulder/test"
)
@ -46,8 +46,8 @@ func TestGetSetSequenceNumber(t *testing.T) {
test.AssertNotError(t, err, "Could not commit")
}
func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
dbMap, err := sa.NewDbMap(dbConnStr)
func caDBImpl(t *testing.T) (*CertificateAuthorityDatabaseImpl, func()) {
dbMap, err := sa.NewDbMap(caDBConnStr)
if err != nil {
t.Fatalf("Could not construct dbMap: %s", err)
}
@ -57,33 +57,16 @@ func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
t.Fatalf("Could not construct CA DB: %s", err)
}
// We intentionally call CreateTablesIfNotExists twice before
// returning because of the weird insert inside it. The
// CADatabaseImpl code expects the existence of a single row in
// its serialIds table or else it errors. CreateTablesIfNotExists
// currently inserts that row and TruncateTables will remove
// it. But we need to make sure the tables exist before
// TruncateTables can be called to reset the table. So, two calls
// to CreateTablesIfNotExists.
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
err = cadb.CreateTablesIfNotExists()
// This row is required to exist for caDBImpl to work correctly. We
// can no longer use dbMap.Insert(&SerialNumber{...}) for this
// because gorp will ignore the ID and insert a new row at a new
// autoincrement id.
// TODO(jmhodges): gen ids flickr-style, no row needed a head of time
_, err = dbMap.Db.Exec("insert into serialNumber (id, number, lastUpdated) VALUES (?, ?, ?)", 1, 1, time.Now())
if err != nil {
t.Fatalf("Could not construct tables: %s", err)
t.Fatalf("unable to create the serial number row: %s", err)
}
err = dbMap.TruncateTables()
if err != nil {
t.Fatalf("Could not truncate tables: %s", err)
}
err = cadb.CreateTablesIfNotExists()
if err != nil {
t.Fatalf("Could not construct tables: %s", err)
}
cleanUp := func() {
if err := dbMap.TruncateTables(); err != nil {
t.Fatalf("Could not truncate tables after the test: %s", err)
}
dbMap.Db.Close()
}
return cadb, cleanUp
}

View File

@ -17,6 +17,7 @@ import (
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/sa/satest"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/sa"
@ -337,12 +338,22 @@ const profileName = "ee"
const caKeyFile = "../test/test-ca.key"
const caCertFile = "../test/test-ca.pem"
// TODO(jmhodges): change this to boulder_ca_test database
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
const (
caDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_ca_test"
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
)
func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthority, Config, func()) {
type testCtx struct {
caDB core.CertificateAuthorityDatabase
sa core.StorageAuthority
caConfig Config
reg core.Registration
cleanUp func()
}
func setup(t *testing.T) *testCtx {
// Create an SA
dbMap, err := sa.NewDbMap(dbConnStr)
dbMap, err := sa.NewDbMap(saDBConnStr)
if err != nil {
t.Fatalf("Failed to create dbMap: %s", err)
}
@ -350,22 +361,16 @@ func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthori
if err != nil {
t.Fatalf("Failed to create SA: %s", err)
}
if err = ssa.CreateTablesIfNotExists(); err != nil {
t.Fatalf("Failed to create tables: %s", err)
}
if err = dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate tables: %s", err)
}
saDBCleanUp := test.ResetTestDatabase(t, dbMap.Db)
cadb, caDBCleanUp := caDBImpl(t)
cleanUp := func() {
if err = dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate tables after the test: %s", err)
}
dbMap.Db.Close()
saDBCleanUp()
caDBCleanUp()
}
// TODO(jmhodges): use of this pkg here is a bug caused by using a real SA
reg := satest.CreateWorkingRegistration(t, ssa)
// Create a CA
caConfig := Config{
Profile: profileName,
@ -412,32 +417,32 @@ func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthori
},
},
}
return cadb, ssa, caConfig, cleanUp
return &testCtx{cadb, ssa, caConfig, reg, cleanUp}
}
func TestFailNoSerial(t *testing.T) {
cadb, _, caConfig, cleanUp := setup(t)
defer cleanUp()
ctx := setup(t)
defer ctx.cleanUp()
caConfig.SerialPrefix = 0
_, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx.caConfig.SerialPrefix = 0
_, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
}
func TestRevoke(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
if err != nil {
return
}
ca.SA = storageAuthority
ca.SA = ctx.sa
ca.MaxKeySize = 4096
csrDER, _ := hex.DecodeString(CNandSANCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
certObj, err := ca.IssueCertificate(*csr, 1, FarFuture)
certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.AssertNotError(t, err, "Failed to sign certificate")
if err != nil {
return
@ -448,7 +453,7 @@ func TestRevoke(t *testing.T) {
err = ca.RevokeCertificate(serialString, 0)
test.AssertNotError(t, err, "Revocation failed")
status, err := storageAuthority.GetCertificateStatus(serialString)
status, err := ctx.sa.GetCertificateStatus(serialString)
test.AssertNotError(t, err, "Failed to get cert status")
test.AssertEquals(t, status.Status, core.OCSPStatusRevoked)
@ -458,11 +463,11 @@ func TestRevoke(t *testing.T) {
}
func TestIssueCertificate(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
ca.SA = ctx.sa
ca.MaxKeySize = 4096
/*
@ -480,7 +485,7 @@ func TestIssueCertificate(t *testing.T) {
csr, _ := x509.ParseCertificateRequest(csrDER)
// Sign CSR
issuedCert, err := ca.IssueCertificate(*csr, 1, FarFuture)
issuedCert, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.AssertNotError(t, err, "Failed to sign certificate")
if err != nil {
continue
@ -521,12 +526,12 @@ func TestIssueCertificate(t *testing.T) {
// Verify that the cert got stored in the DB
serialString := core.SerialToString(cert.SerialNumber)
storedCert, err := storageAuthority.GetCertificate(serialString)
storedCert, err := ctx.sa.GetCertificate(serialString)
test.AssertNotError(t, err,
fmt.Sprintf("Certificate %s not found in database", serialString))
test.Assert(t, bytes.Equal(issuedCert.DER, storedCert.DER), "Retrieved cert not equal to issued cert.")
certStatus, err := storageAuthority.GetCertificateStatus(serialString)
certStatus, err := ctx.sa.GetCertificateStatus(serialString)
test.AssertNotError(t, err,
fmt.Sprintf("Error fetching status for certificate %s", serialString))
test.Assert(t, certStatus.Status == core.OCSPStatusGood, "Certificate status was not good")
@ -535,48 +540,48 @@ func TestIssueCertificate(t *testing.T) {
}
func TestRejectNoName(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
ca.SA = ctx.sa
ca.MaxKeySize = 4096
// Test that the CA rejects CSRs with no names
csrDER, _ := hex.DecodeString(NoNameCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
if err == nil {
t.Errorf("CA improperly agreed to create a certificate with no name")
}
}
func TestRejectTooManyNames(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
ca.SA = ctx.sa
// Test that the CA rejects a CSR with too many names
csrDER, _ := hex.DecodeString(TooManyNameCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.Assert(t, err != nil, "Issued certificate with too many names")
}
func TestDeduplication(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
ca.SA = ctx.sa
ca.MaxKeySize = 4096
// Test that the CA collapses duplicate names
csrDER, _ := hex.DecodeString(DupeNameCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
cert, err := ca.IssueCertificate(*csr, 1, FarFuture)
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with duplicate names")
if err != nil {
return
@ -596,17 +601,17 @@ func TestDeduplication(t *testing.T) {
}
func TestRejectValidityTooLong(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
ca.SA = ctx.sa
ca.MaxKeySize = 4096
// Test that the CA rejects CSRs that would expire after the intermediate cert
csrDER, _ := hex.DecodeString(NoCNCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1, FarPast)
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarPast)
test.Assert(t, err == nil, "Can issue a certificate that expires after the underlying authorization.")
// Test that the CA rejects CSRs that would expire after the intermediate cert
@ -618,29 +623,29 @@ func TestRejectValidityTooLong(t *testing.T) {
}
func TestShortKey(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ca.SA = storageAuthority
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
ca.SA = ctx.sa
ca.MaxKeySize = 4096
// Test that the CA rejects CSRs that would expire after the intermediate cert
csrDER, _ := hex.DecodeString(ShortKeyCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.Assert(t, err != nil, "Issued a certificate with too short a key.")
}
func TestRejectBadAlgorithm(t *testing.T) {
cadb, storageAuthority, caConfig, cleanUp := setup(t)
defer cleanUp()
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
ca.SA = storageAuthority
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
ca.SA = ctx.sa
ca.MaxKeySize = 4096
// Test that the CA rejects CSRs that would expire after the intermediate cert
csrDER, _ := hex.DecodeString(BadAlgorithmCSRhex)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
test.Assert(t, err != nil, "Issued a certificate based on a CSR with a weak algorithm.")
}

View File

@ -37,11 +37,6 @@ func main() {
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(dbMap)
cmd.FailOnError(err, "Failed to create CA database")
if c.SQL.CreateTables {
err = cadb.CreateTablesIfNotExists()
cmd.FailOnError(err, "Failed to create CA tables")
}
cai, err := ca.NewCertificateAuthorityImpl(cadb, c.CA, c.Common.IssuerCert)
cmd.FailOnError(err, "Failed to create CA impl")
cai.MaxKeySize = c.Common.MaxKeySize

View File

@ -38,11 +38,6 @@ func main() {
cmd.FailOnError(err, "Failed to create SA impl")
sai.SetSQLDebug(c.SQL.SQLDebug)
if c.SQL.CreateTables {
err = sai.CreateTablesIfNotExists()
cmd.FailOnError(err, "Failed to create tables")
}
go cmd.ProfileCmd("SA", stats)
connectionHandler := func(*rpc.AmqpRPCServer) {}

View File

@ -140,42 +140,30 @@ var testKey = rsa.PrivateKey{
Primes: []*big.Int{p, q},
}
// TODO(jmhodges): Turn this into boulder_sa_test
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
const dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
func TestFindExpiringCertificates(t *testing.T) {
dbMap, err := sa.NewDbMap(dbConnStr)
if err != nil {
t.Fatalf("Couldn't connect the database: %s", err)
}
err = dbMap.CreateTablesIfNotExists()
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
ssa, err := sa.NewSQLStorageAuthority(dbMap)
if err != nil {
t.Fatalf("Couldn't create tables: %s", err)
t.Fatalf("unable to create SQLStorageAuthority: %s", err)
}
err = dbMap.TruncateTables()
if err != nil {
t.Fatalf("Couldn't truncate tables: %s", err)
}
defer func() {
err = dbMap.TruncateTables()
if err != nil {
t.Fatalf("Couldn't truncate tables after the test: %s", err)
}
dbMap.Db.Close()
}()
defer cleanUp()
tmpl, err := template.New("expiry-email").Parse(testTmpl)
test.AssertNotError(t, err, "Couldn't parse test email template")
stats, _ := statsd.NewNoopClient(nil)
mc := mockMail{}
rs := newFakeRegStore()
m := mailer{
log: blog.GetAuditLogger(),
stats: stats,
mailer: &mc,
emailTemplate: tmpl,
dbMap: dbMap,
rs: rs,
rs: ssa,
nagTimes: []time.Duration{time.Hour * 24, time.Hour * 24 * 4, time.Hour * 24 * 7},
limit: 100,
}
@ -208,6 +196,15 @@ func TestFindExpiringCertificates(t *testing.T) {
},
Key: keyB,
}
regA, err = ssa.NewRegistration(regA)
if err != nil {
t.Fatalf("Couldn't store regA: %s", err)
}
regB, err = ssa.NewRegistration(regB)
if err != nil {
t.Fatalf("Couldn't store regB: %s", err)
}
rawCertA := x509.Certificate{
Subject: pkix.Name{
CommonName: "happy A",
@ -218,7 +215,7 @@ func TestFindExpiringCertificates(t *testing.T) {
}
certDerA, _ := x509.CreateCertificate(rand.Reader, &rawCertA, &rawCertA, &testKey.PublicKey, &testKey)
certA := &core.Certificate{
RegistrationID: 1,
RegistrationID: regA.ID,
Status: core.StatusValid,
Serial: "001",
Expires: time.Now().AddDate(0, 0, 1),
@ -236,7 +233,7 @@ func TestFindExpiringCertificates(t *testing.T) {
}
certDerB, _ := x509.CreateCertificate(rand.Reader, &rawCertB, &rawCertB, &testKey.PublicKey, &testKey)
certB := &core.Certificate{
RegistrationID: 1,
RegistrationID: regA.ID,
Status: core.StatusValid,
Serial: "002",
Expires: time.Now().AddDate(0, 0, 3),
@ -254,15 +251,13 @@ func TestFindExpiringCertificates(t *testing.T) {
}
certDerC, _ := x509.CreateCertificate(rand.Reader, &rawCertC, &rawCertC, &testKey.PublicKey, &testKey)
certC := &core.Certificate{
RegistrationID: 2,
RegistrationID: regB.ID,
Status: core.StatusValid,
Serial: "003",
Expires: time.Now().AddDate(0, 0, 7),
DER: certDerC,
}
certStatusC := &core.CertificateStatus{Serial: "003"}
rs.RegById[regA.ID] = regA
rs.RegById[regB.ID] = regB
err = dbMap.Insert(certA)
test.AssertNotError(t, err, "Couldn't add certA")

View File

@ -164,8 +164,6 @@ func main() {
dbMap.AddTableWithName(core.ExternalCert{}, "externalCerts").SetKeys(false, "SHA1")
dbMap.AddTableWithName(core.IdentifierData{}, "identifierData").SetKeys(false, "CertSHA1")
err = dbMap.CreateTablesIfNotExists()
cmd.FailOnError(err, "Could not create the tables")
// Note that this order of operations is intentional: we first add
// new certs to the database. Then, since certs are identified by

View File

@ -104,8 +104,7 @@ type Config struct {
}
SQL struct {
CreateTables bool
SQLDebug bool
SQLDebug bool
}
Statsd struct {

View File

@ -134,7 +134,6 @@ type StorageAuthority interface {
// CertificateAuthorityDatabase represents an atomic sequence source
type CertificateAuthorityDatabase interface {
CreateTablesIfNotExists() error
IncrementAndGetSerial(*gorp.Transaction) (int64, error)
Begin() (*gorp.Transaction, error)
}

View File

@ -1,5 +1,12 @@
These `.sql` files define the table layout, indicies, relationships, and users default to Boulder. Implementors should use these as starting points for their own configuration.
The `sql` files here define the database user relationships between
the various databases and services. Implementors should use these as
starting points for their own configuration. The actual schemas are
managed by [goose](https://bitbucket.org/liamstask/goose) and can be
found in `./ca/_db` and `./sa/_db`.
The currently supported database is MariaDB 10.
mysql -u root -e "create database boulder_test; create database boulder_development; grant all privileges on boulder_test.* to 'boulder'@'localhost';"
The databases that boulder requires to operate in development and
testing can be created using test/create\_db.sh. It uses the root
MariaDB user, so if you have disabled that account you may have to
adjust the file or recreate the commands.

View File

@ -1,26 +0,0 @@
--
-- 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/.
--
-- This file defines the table schema, foreign keys and indicies of the
-- Certificate Authority database, which is logically separate from that which
-- is utilized by the Storage Authority and administrator tools.
--
CREATE TABLE `serialNumber` (
`id` int(11) DEFAULT NULL,
`number` int(11) DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `serialNumber`
(`id`,
`number`,
`lastUpdated`)
VALUES
(1,
1,
now() );

View File

@ -122,12 +122,14 @@ var (
AuthzFinal = core.Authorization{}
log = mocks.UseMockLog()
// TODO(jmhodges): Turn this into boulder_sa_test
dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
)
func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, func()) {
const (
caDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_ca_test"
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
)
func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, func()) {
err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA)
test.AssertNotError(t, err, "Failed to unmarshal public JWK")
err = json.Unmarshal(AccountKeyJSONB, &AccountKeyB)
@ -141,7 +143,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
err = json.Unmarshal(ShortKeyJSON, &ShortKey)
test.AssertNotError(t, err, "Failed to unmarshall JWK")
dbMap, err := sa.NewDbMap(dbConnStr)
dbMap, err := sa.NewDbMap(saDBConnStr)
if err != nil {
t.Fatalf("Failed to create dbMap: %s", err)
}
@ -150,14 +152,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
t.Fatalf("Failed to create SA: %s", err)
}
err = ssa.CreateTablesIfNotExists()
if err != nil {
t.Fatalf("Failed to create SA tables: %s", err)
}
if err = dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate SA tables: %s", err)
}
saDBCleanUp := test.ResetTestDatabase(t, dbMap.Db)
va := &DummyValidationAuthority{}
@ -193,17 +188,13 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
MaxKeySize: 4096,
}
cleanUp := func() {
if err = dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate tables after the test: %s", err)
}
dbMap.Db.Close()
saDBCleanUp()
caDBCleanUp()
}
csrDER, _ := hex.DecodeString(CSRhex)
ExampleCSR, _ = x509.ParseCertificateRequest(csrDER)
// This registration implicitly gets ID = 1
Registration, _ = ssa.NewRegistration(core.Registration{Key: AccountKeyA})
ra := NewRegistrationAuthorityImpl()
@ -225,7 +216,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
AuthzFinal.Expires = &exp
AuthzFinal.Challenges[0].Status = "valid"
return &ca, va, ssa, &ra, cleanUp
return va, ssa, &ra, cleanUp
}
// This is an unfortunate bit of tech debt that is being taken on in
@ -235,7 +226,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
// CertificateAuthorityClient, so this is only marginally worse.
// TODO(Issue #628): use a CAClient fake instead of a CAImpl instance
func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
dbMap, err := sa.NewDbMap(dbConnStr)
dbMap, err := sa.NewDbMap(caDBConnStr)
if err != nil {
t.Fatalf("Could not construct dbMap: %s", err)
}
@ -245,34 +236,17 @@ func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
t.Fatalf("Could not construct CA DB: %s", err)
}
// We intentionally call CreateTablesIfNotExists twice before
// returning because of the weird insert inside it. The
// CADatabaseImpl code expects the existence of a single row in
// its serialIds table or else it errors. CreateTablesIfNotExists
// currently inserts that row and TruncateTables will remove
// it. But we need to make sure the tables exist before
// TruncateTables can be called to reset the table. So, two calls
// to CreateTablesIfNotExists.
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
err = cadb.CreateTablesIfNotExists()
// This row is required to exist for caDBImpl to work
// correctly. We can no longer use
// dbMap.Insert(&SerialNumber{...}) for this because gorp will
// ignore the ID and insert a new row at a new autoincrement id.
// TODO(jmhodges): gen ids flickr-style, no row needed a head of time
_, err = dbMap.Db.Exec("insert into serialNumber (id, number, lastUpdated) VALUES (?, ?, ?)", 1, 1, time.Now())
if err != nil {
t.Fatalf("Could not construct tables: %s", err)
t.Fatalf("unable to create the serial number row: %s", err)
}
err = dbMap.TruncateTables()
if err != nil {
t.Fatalf("Could not truncate tables: %s", err)
}
err = cadb.CreateTablesIfNotExists()
if err != nil {
t.Fatalf("Could not construct tables: %s", err)
}
cleanUp := func() {
if err := dbMap.TruncateTables(); err != nil {
t.Fatalf("Could not truncate tables after the test: %s", err)
}
dbMap.Db.Close()
}
return cadb, cleanUp
}
@ -327,7 +301,7 @@ func TestValidateEmail(t *testing.T) {
}
func TestNewRegistration(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
input := core.Registration{
@ -352,7 +326,7 @@ func TestNewRegistration(t *testing.T) {
}
func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
_, _, _, ra, cleanUp := initAuthorities(t)
_, _, ra, cleanUp := initAuthorities(t)
defer cleanUp()
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
input := core.Registration{
@ -380,7 +354,7 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
}
func TestNewRegistrationBadKey(t *testing.T) {
_, _, _, ra, cleanUp := initAuthorities(t)
_, _, ra, cleanUp := initAuthorities(t)
defer cleanUp()
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
input := core.Registration{
@ -393,12 +367,12 @@ func TestNewRegistrationBadKey(t *testing.T) {
}
func TestNewAuthorization(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
_, err := ra.NewAuthorization(AuthzRequest, 0)
test.AssertError(t, err, "Authorization cannot have registrationID == 0")
authz, err := ra.NewAuthorization(AuthzRequest, 1)
authz, err := ra.NewAuthorization(AuthzRequest, Registration.ID)
test.AssertNotError(t, err, "NewAuthorization failed")
// Verify that returned authz same as DB
@ -407,7 +381,7 @@ func TestNewAuthorization(t *testing.T) {
assertAuthzEqual(t, authz, dbAuthz)
// Verify that the returned authz has the right information
test.Assert(t, authz.RegistrationID == 1, "Initial authz did not get the right registration ID")
test.Assert(t, authz.RegistrationID == Registration.ID, "Initial authz did not get the right registration ID")
test.Assert(t, authz.Identifier == AuthzRequest.Identifier, "Initial authz had wrong identifier")
test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
@ -424,7 +398,7 @@ func TestNewAuthorization(t *testing.T) {
}
func TestUpdateAuthorization(t *testing.T) {
_, va, sa, ra, cleanUp := initAuthorities(t)
va, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
// We know this is OK because of TestNewAuthorization
@ -472,7 +446,7 @@ func TestUpdateAuthorizationReject(t *testing.T) {
}
func TestOnValidationUpdate(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
AuthzUpdated, _ = sa.NewPendingAuthorization(AuthzUpdated)
sa.UpdatePendingAuthorization(AuthzUpdated)
@ -495,7 +469,7 @@ func TestOnValidationUpdate(t *testing.T) {
}
func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
authz := core.Authorization{}
authz, _ = sa.NewPendingAuthorization(authz)
@ -518,8 +492,8 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
CSR: parsedCSR,
}
// Registration id 1 has key == AccountKeyA
_, err = ra.NewCertificate(certRequest, 1)
// Registration has key == AccountKeyA
_, err = ra.NewCertificate(certRequest, Registration.ID)
test.AssertError(t, err, "Should have rejected cert with key = account key")
test.AssertEquals(t, err.Error(), "Certificate public key must be different than account key")
@ -527,7 +501,7 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
}
func TestAuthorizationRequired(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
AuthzFinal.RegistrationID = 1
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
@ -547,9 +521,9 @@ func TestAuthorizationRequired(t *testing.T) {
}
func TestNewCertificate(t *testing.T) {
_, _, sa, ra, cleanUp := initAuthorities(t)
_, sa, ra, cleanUp := initAuthorities(t)
defer cleanUp()
AuthzFinal.RegistrationID = 1
AuthzFinal.RegistrationID = Registration.ID
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
sa.UpdatePendingAuthorization(AuthzFinal)
sa.FinalizeAuthorization(AuthzFinal)
@ -564,7 +538,7 @@ func TestNewCertificate(t *testing.T) {
CSR: ExampleCSR,
}
cert, err := ra.NewCertificate(certRequest, 1)
cert, err := ra.NewCertificate(certRequest, Registration.ID)
test.AssertNotError(t, err, "Failed to issue certificate")
if err != nil {
return

9
sa/_db/dbconf.yml Normal file
View File

@ -0,0 +1,9 @@
development:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_sa_development
test:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_sa_test
integration:
driver: mysql
open: boulder@tcp(localhost:3306)/boulder_sa_integration

View File

@ -1,13 +1,6 @@
--
-- 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/.
--
-- This file defines the table schema, foreign keys and indicies of the
-- primary database, used by all the parts of Boulder except the Certificate
-- Authority module, which utilizes its own database.
--
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE `registrations` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
@ -127,3 +120,21 @@ CREATE TABLE `externalCerts` (
`rawDERCert` blob DEFAULT NULL,
UNIQUE INDEX (sha1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
ALTER TABLE `pending_authz` DROP FOREIGN KEY `regId_pending_authz`;
ALTER TABLE `certificates` DROP FOREIGN KEY `regId_certificates`;
ALTER TABLE `authz` DROP FOREIGN KEY `regId_authz`;
DROP TABLE `registrations`;
DROP TABLE `authz`;
DROP TABLE `certificates`;
DROP TABLE `certificateStatus`;
DROP TABLE `crls`;
DROP TABLE `deniedCSRs`;
DROP TABLE `ocspResponses`;
DROP TABLE `pending_authz`;
DROP TABLE `identifierData`;
DROP TABLE `externalCerts`;

View File

@ -85,6 +85,15 @@ func recombineURLForDB(dbConnect string) (string, error) {
// instead of the number of rows changed by the UPDATE.
dsnVals.Set("clientFoundRows", "true")
// Ensures that MySQL/MariaDB warnings are treated as errors. This
// avoids a number of nasty edge conditions we could wander
// into. Common things this discovers includes places where data
// being sent had a different type than what is in the schema,
// strings being truncated, writing null to a NOT NULL column, and
// so on. See
// <https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-strict>.
dsnVals.Set("strict", "true")
user := dbURL.User.Username()
passwd, hasPass := dbURL.User.Password()
dbConn := ""
@ -118,8 +127,7 @@ func (log *SQLLogger) Printf(format string, v ...interface{}) {
log.log.Debug(fmt.Sprintf(format, v...))
}
// initTables constructs the table map for the ORM. If you want to also create
// the tables, call CreateTablesIfNotExists on the DbMap.
// initTables constructs the table map for the ORM.
func initTables(dbMap *gorp.DbMap) {
regTable := dbMap.AddTableWithName(regModel{}, "registrations").SetKeys(true, "ID")
regTable.SetVersionCol("LockCol")

52
sa/satest/satest.go Normal file
View File

@ -0,0 +1,52 @@
package satest
import (
"encoding/json"
"testing"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core"
)
var theKey = `{
"kty": "RSA",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
"e": "AQAB"
}`
// GoodJWK returns a known-good JsonWebKey that is always the
// same. This a hack to allow both the CA and SA tests to benefit
// because the CA tests currently require a full-fledged
// SQLSAImpl. Long term, when the CA tests no longer need
// CreateWorkingRegistration, this and CreateWorkingRegistration can
// be pushed back into the SA tests proper.
func GoodJWK() jose.JsonWebKey {
var jwk jose.JsonWebKey
err := json.Unmarshal([]byte(theKey), &jwk)
if err != nil {
panic("known-good theKey is no longer known-good")
}
return jwk
}
// CreateWorkingRegistration inserts a new, correct Registration into
// SA using GoodKey under the hood. This a hack to allow both the CA
// and SA tests to benefit because the CA tests currently require a
// full-fledged SQLSAImpl. Long term, when the CA tests no longer need
// CreateWorkingRegistration, this and CreateWorkingRegistration can
// be pushed back into the SA tests proper.
func CreateWorkingRegistration(t *testing.T, sa core.StorageAuthority) core.Registration {
contact, err := core.ParseAcmeURL("mailto:foo@example.com")
if err != nil {
t.Fatalf("unable to parse contact link: %s", err)
}
contacts := []*core.AcmeURL{contact}
reg, err := sa.NewRegistration(core.Registration{
Key: GoodJWK(),
Contact: contacts,
})
if err != nil {
t.Fatalf("Unable to create new registration")
}
return reg
}

View File

@ -69,12 +69,6 @@ func (ssa *SQLStorageAuthority) SetSQLDebug(state bool) {
SetSQLDebug(ssa.dbMap, state)
}
// CreateTablesIfNotExists instructs the ORM to create any missing tables.
func (ssa *SQLStorageAuthority) CreateTablesIfNotExists() (err error) {
err = ssa.dbMap.CreateTablesIfNotExists()
return
}
func statusIsPending(status core.AcmeStatus) bool {
return status == core.StatusPending || status == core.StatusProcessing || status == core.StatusUnknown
}

View File

@ -19,13 +19,13 @@ import (
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/sa/satest"
"github.com/letsencrypt/boulder/test"
)
var log = mocks.UseMockLog()
const dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
// TODO(jmhodges): change this to boulder_sa_test database
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
var log = mocks.UseMockLog()
// initSA constructs a SQLStorageAuthority and a clean up function
// that should be defer'ed to the end of the test.
@ -39,26 +39,11 @@ func initSA(t *testing.T) (*SQLStorageAuthority, func()) {
if err != nil {
t.Fatalf("Failed to create SA: %s", err)
}
if err = sa.CreateTablesIfNotExists(); err != nil {
t.Fatalf("Failed to create tables: %s", err)
}
if err = sa.dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate tables: %s", err)
}
return sa, func() {
if err = sa.dbMap.TruncateTables(); err != nil {
t.Fatalf("Failed to truncate tables after the test: %s", err)
}
sa.dbMap.Db.Close()
}
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
return sa, cleanUp
}
var (
theKey = `{
"kty": "RSA",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
"e": "AQAB"
}`
anotherKey = `{
"kty":"RSA",
"n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw",
@ -70,12 +55,7 @@ func TestAddRegistration(t *testing.T) {
sa, cleanUp := initSA(t)
defer cleanUp()
var jwk jose.JsonWebKey
err := json.Unmarshal([]byte(theKey), &jwk)
if err != nil {
t.Errorf("JSON unmarshal error: %+v", err)
return
}
jwk := satest.GoodJWK()
contact, err := core.ParseAcmeURL("mailto:foo@example.com")
if err != nil {
@ -132,9 +112,7 @@ func TestNoSuchRegistrationErrors(t *testing.T) {
t.Errorf("GetRegistration: expected NoSuchRegistrationError, got %T type error (%s)", err, err)
}
var jwk jose.JsonWebKey
err = json.Unmarshal([]byte(theKey), &jwk)
test.AssertNotError(t, err, "Unmarshal")
jwk := satest.GoodJWK()
_, err = sa.GetRegistrationByKey(jwk)
if _, ok := err.(NoSuchRegistrationError); !ok {
t.Errorf("GetRegistrationByKey: expected a NoSuchRegistrationError, got %T type error (%s)", err, err)
@ -150,7 +128,8 @@ func TestAddAuthorization(t *testing.T) {
sa, cleanUp := initSA(t)
defer cleanUp()
PA := core.Authorization{}
reg := satest.CreateWorkingRegistration(t, sa)
PA := core.Authorization{RegistrationID: reg.ID}
PA, err := sa.NewPendingAuthorization(PA)
test.AssertNotError(t, err, "Couldn't create new pending authorization")
@ -163,19 +142,12 @@ func TestAddAuthorization(t *testing.T) {
expectedPa := core.Authorization{ID: PA.ID}
test.AssertMarshaledEquals(t, dbPa.ID, expectedPa.ID)
var jwk jose.JsonWebKey
err = json.Unmarshal([]byte(theKey), &jwk)
if err != nil {
t.Errorf("JSON unmarshal error: %+v", err)
return
}
combos := make([][]int, 1)
combos[0] = []int{0, 1}
exp := time.Now().AddDate(0, 0, 1)
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}
newPa := core.Authorization{ID: PA.ID, Identifier: identifier, RegistrationID: 0, Status: core.StatusPending, Expires: &exp, Combinations: combos}
newPa := core.Authorization{ID: PA.ID, Identifier: identifier, RegistrationID: reg.ID, Status: core.StatusPending, Expires: &exp, Combinations: combos}
err = sa.UpdatePendingAuthorization(newPa)
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+PA.ID)
@ -188,9 +160,16 @@ func TestAddAuthorization(t *testing.T) {
}
func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) {
return CreateDomainAuthWithRegId(t, domainName, sa, 42)
}
func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) {
// create pending auth
authz, err := sa.NewPendingAuthorization(core.Authorization{Challenges: []core.Challenge{core.Challenge{}}})
test.AssertNotError(t, err, "Couldn't create new pending authorization")
authz, err := sa.NewPendingAuthorization(core.Authorization{RegistrationID: regID, Challenges: []core.Challenge{core.Challenge{}}})
if err != nil {
t.Fatalf("Couldn't create new pending authorization: %s", err)
}
test.Assert(t, authz.ID != "", "ID shouldn't be blank")
// prepare challenge for auth
@ -204,7 +183,6 @@ func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority)
// validate pending auth
authz.Status = core.StatusPending
authz.Identifier = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domainName}
authz.RegistrationID = 42
authz.Expires = &exp
authz.Challenges = []core.Challenge{chall}
authz.Combinations = combos
@ -225,8 +203,10 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
authz, err := sa.GetLatestValidAuthorization(0, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
test.AssertError(t, err, "Should not have found a valid auth for example.org")
reg := satest.CreateWorkingRegistration(t, sa)
// authorize "example.org"
authz = CreateDomainAuth(t, "example.org", sa)
authz = CreateDomainAuthWithRegId(t, "example.org", sa, reg.ID)
// finalize auth
authz.Status = core.StatusValid
@ -238,12 +218,12 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
test.AssertError(t, err, "Should not have found a valid auth for example.org and regID 0")
// get authorized domain
authz, err = sa.GetLatestValidAuthorization(42, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
authz, err = sa.GetLatestValidAuthorization(reg.ID, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
test.AssertNotError(t, err, "Should have found a valid auth for example.org and regID 42")
test.AssertEquals(t, authz.Status, core.StatusValid)
test.AssertEquals(t, authz.Identifier.Type, core.IdentifierDNS)
test.AssertEquals(t, authz.Identifier.Value, "example.org")
test.AssertEquals(t, authz.RegistrationID, int64(42))
test.AssertEquals(t, authz.RegistrationID, reg.ID)
}
// Ensure we get the latest valid authorization for an ident
@ -253,11 +233,11 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
domain := "example.org"
ident := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
regID := int64(42)
var err error
reg := satest.CreateWorkingRegistration(t, sa)
// create invalid authz
authz := CreateDomainAuth(t, domain, sa)
authz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
exp := time.Now().AddDate(0, 0, 10) // expire in 10 day
authz.Expires = &exp
authz.Status = core.StatusInvalid
@ -265,11 +245,11 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
// should not get the auth
authz, err = sa.GetLatestValidAuthorization(regID, ident)
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
test.AssertError(t, err, "Should not have found a valid auth for "+domain)
// create valid auth
authz = CreateDomainAuth(t, domain, sa)
authz = CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
exp = time.Now().AddDate(0, 0, 1) // expire in 1 day
authz.Expires = &exp
authz.Status = core.StatusValid
@ -277,27 +257,27 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
// should get the valid auth even if it's expire date is lower than the invalid one
authz, err = sa.GetLatestValidAuthorization(regID, ident)
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
test.AssertEquals(t, authz.Status, core.StatusValid)
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
test.AssertEquals(t, authz.RegistrationID, regID)
test.AssertEquals(t, authz.RegistrationID, reg.ID)
// create a newer auth
newAuthz := CreateDomainAuth(t, domain, sa)
newAuthz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
exp = time.Now().AddDate(0, 0, 2) // expire in 2 day
newAuthz.Expires = &exp
newAuthz.Status = core.StatusValid
err = sa.FinalizeAuthorization(newAuthz)
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+newAuthz.ID)
authz, err = sa.GetLatestValidAuthorization(regID, ident)
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
test.AssertEquals(t, authz.Status, core.StatusValid)
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
test.AssertEquals(t, authz.RegistrationID, regID)
test.AssertEquals(t, authz.RegistrationID, reg.ID)
// make sure we got the latest auth
test.AssertEquals(t, authz.ID, newAuthz.ID)
}
@ -306,11 +286,13 @@ func TestAddCertificate(t *testing.T) {
sa, cleanUp := initSA(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
// An example cert taken from EFF's website
certDER, err := ioutil.ReadFile("www.eff.org.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")
digest, err := sa.AddCertificate(certDER, 1)
digest, err := sa.AddCertificate(certDER, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
test.AssertEquals(t, digest, "qWoItDZmR4P9eFbeYgXXP3SR4ApnkQj8x4LsB_ORKBo")
@ -333,7 +315,7 @@ func TestAddCertificate(t *testing.T) {
certDER2, err := ioutil.ReadFile("test-cert.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")
digest2, err := sa.AddCertificate(certDER2, 1)
digest2, err := sa.AddCertificate(certDER2, reg.ID)
test.AssertNotError(t, err, "Couldn't add test-cert.der")
test.AssertEquals(t, digest2, "CMVYqWzyqUW7pfBF2CxL0Uk6I0Upsk7p4EWSnd_vYx4")
@ -387,10 +369,11 @@ func TestUpdateOCSP(t *testing.T) {
sa, cleanUp := initSA(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
// Add a cert to the DB to test with.
certDER, err := ioutil.ReadFile("www.eff.org.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")
_, err = sa.AddCertificate(certDER, 1)
_, err = sa.AddCertificate(certDER, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
serial := "00000000000000000000000000021bd4"

View File

@ -48,7 +48,7 @@
"ca": {
"serialPrefix": 255,
"profile": "ee",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_ca_integration",
"debugAddr": "localhost:8001",
"testMode": true,
"_comment": "This should only be present in testMode. In prod use an HSM.",
@ -114,7 +114,7 @@
},
"sa": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"debugAddr": "localhost:8003"
},
@ -124,27 +124,22 @@
},
"sql": {
"SQLDebug": true,
"CreateTables": true
"SQLDebug": true
},
"revoker": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test"
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration"
},
"ocspResponder": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test"
},
"ocspResponder": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"path": "/",
"listenAddress": "localhost:4002",
"debugAddr": "localhost:8005"
},
"ocspUpdater": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"minTimeToExpiry": "72h",
"debugAddr": "localhost:8006"
},
@ -158,7 +153,7 @@
"port": "25",
"username": "cert-master@example.com",
"password": "password",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"messageLimit": 0,
"nagTimes": ["24h", "72h", "168h", "336h"],
"emailTemplate": "test/example-expiration-template",

View File

@ -114,8 +114,7 @@
},
"sql": {
"SQLDebug": true,
"CreateTables": true
"SQLDebug": true
},
"revoker": {

View File

@ -7,5 +7,21 @@ function die() {
exit 1
}
mysql -u root -e "create database boulder_test; grant all privileges on boulder_test.* to 'boulder'@'localhost'" || die "unable to create boulder_test"
echo "created boulder_test database"
SERVICES="ca
sa"
DBENVS="development
test
integration"
for svc in $SERVICES; do
for dbenv in $DBENVS; do
db="boulder_${svc}_${dbenv}"
mysql -u root -e "drop database if exists \`${db}\`; create database if not exists \`${db}\`; grant all privileges on ${db}.* to 'boulder'@'localhost'" || die "unable to create ${db}"
echo "created empty ${db} database"
goose -path=./$svc/_db/ -env=$dbenv up || die "unable to migrate ${db}"
echo "migrated ${db} database"
done
done
echo "created all databases"

84
test/db.go Normal file
View File

@ -0,0 +1,84 @@
package test
import (
"database/sql"
"io"
"testing"
)
var (
_ CleanUpDB = &sql.DB{}
)
// CleanUpDB is an interface with only what is needed to delete all
// rows in all tables in a database plus close the database
// connection. It is satisfied by *sql.DB.
type CleanUpDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
io.Closer
}
// ResetTestDatabase deletes all rows in all tables available to the
// passed in CleanUpDB, failing the tests if that errors and returning
// a clean up function that will attempt the same plus close the
// database. "Tables available" means all tables that can be seen in
// the MariaDB configuration by the database user except for ones that
// are configuration only like goose_db_version (for migrations) or
// the ones describing the internal configuration of the server.To be
// used only in test code.
func ResetTestDatabase(t *testing.T, db CleanUpDB) func() {
if err := deleteEverythingInAllTables(db); err != nil {
t.Fatalf("Failed to delete everything: %s", err)
}
return func() {
if err := deleteEverythingInAllTables(db); err != nil {
t.Fatalf("Failed to truncate tables after the test: %s", err)
}
db.Close()
}
}
// clearEverythingInAllTables deletes all rows in the tables
// available to the CleanUpDB passed in and resets the autoincrement
// counters. See allTableNamesInDB for what is meant by "all tables
// available". To be used only in test code.
func deleteEverythingInAllTables(db CleanUpDB) error {
ts, err := allTableNamesInDB(db)
if err != nil {
return nil
}
for _, tn := range ts {
// 1 = 1 here prevents the MariaDB i_am_a_dummy setting from
// rejecting the DELETE for not having a WHERE clause.
_, err := db.Exec("delete from `" + tn + "` where 1 = 1")
if err != nil {
return err
}
}
return nil
}
// allTableNamesInDB returns the names of the tables available to the
// CleanUpDB passed in. "Tables available" means all tables that can
// be seen in the MariaDB configuration by the database user except
// for ones that are configuration only like goose_db_version (for
// migrations) or the ones describing the internal configuration of
// the server. To be used only in test code.
func allTableNamesInDB(db CleanUpDB) ([]string, error) {
r, err := db.Query("select table_name from information_schema.tables t where t.table_schema = DATABASE() and t.table_name != 'goose_db_version';")
if err != nil {
return nil, err
}
var ts []string
for r.Next() {
tableName := ""
err = r.Scan(&tableName)
if err != nil {
return nil, err
}
ts = append(ts, tableName)
}
return ts, r.Err()
}

View File

@ -40,12 +40,14 @@ var ErrTooManyCNAME = errors.New("too many CNAME/DNAME lookups")
// ValidationAuthorityImpl represents a VA
type ValidationAuthorityImpl struct {
RA core.RegistrationAuthority
log *blog.AuditLogger
DNSResolver core.DNSResolver
IssuerDomain string
TestMode bool
UserAgent string
RA core.RegistrationAuthority
log *blog.AuditLogger
DNSResolver core.DNSResolver
IssuerDomain string
simpleHTTPPort int
simpleHTTPSPort int
dvsniPort int
UserAgent string
}
// NewValidationAuthorityImpl constructs a new VA, and may place it
@ -53,7 +55,23 @@ type ValidationAuthorityImpl struct {
func NewValidationAuthorityImpl(tm bool) ValidationAuthorityImpl {
logger := blog.GetAuditLogger()
logger.Notice("Validation Authority Starting")
return ValidationAuthorityImpl{log: logger, TestMode: tm}
// TODO(jsha): Remove TestMode entirely. Instead, the various validation ports
// should be exported, so the cmd file can set them based on a config.
if tm {
return ValidationAuthorityImpl{
log: logger,
simpleHTTPPort: 5001,
simpleHTTPSPort: 5001,
dvsniPort: 5001,
}
} else {
return ValidationAuthorityImpl{
log: logger,
simpleHTTPPort: 80,
simpleHTTPSPort: 443,
dvsniPort: 443,
}
}
}
// Used for audit logging
@ -155,10 +173,8 @@ func (d *dialer) Dial(_, _ string) (net.Conn, error) {
// resolveAndConstructDialer gets the prefered address using va.getAddr and returns
// the chosen address and dialer for that address and correct port.
func (va ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort string) (dialer, *core.ProblemDetails) {
port := "80"
if va.TestMode {
port = "5001"
} else if defaultPort != "" {
port := fmt.Sprintf("%d", va.simpleHTTPPort)
if defaultPort != "" {
port = defaultPort
}
d := dialer{
@ -192,18 +208,23 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier))
return challenge, challenge.Error
}
hostName := identifier.Value
host := identifier.Value
var scheme string
var port int
if input.TLS == nil || (input.TLS != nil && *input.TLS) {
scheme = "https"
port = va.simpleHTTPSPort
} else {
scheme = "http"
port = va.simpleHTTPPort
}
portString := fmt.Sprintf("%d", port)
hostPort := net.JoinHostPort(host, portString)
url := url.URL{
Scheme: scheme,
Host: hostName,
Host: hostPort,
Path: fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token),
}
@ -224,12 +245,8 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
httpRequest.Header["User-Agent"] = []string{va.UserAgent}
}
httpRequest.Host = hostName
var port string
if scheme == "https" {
port = "443"
}
dialer, prob := va.resolveAndConstructDialer(hostName, port)
httpRequest.Host = hostPort
dialer, prob := va.resolveAndConstructDialer(host, portString)
dialer.record.URL = url.String()
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
if prob != nil {
@ -255,15 +272,15 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
return fmt.Errorf("Too many redirects")
}
host := req.URL.Host
port = ""
if strings.Contains(host, ":") {
splitHost := strings.SplitN(host, ":", 2)
reqHost := req.URL.Host
reqPort := ""
if strings.Contains(reqHost, ":") {
splitHost := strings.SplitN(reqHost, ":", 2)
if len(splitHost) <= 1 {
return fmt.Errorf("Malformed host")
}
host, port = splitHost[0], splitHost[1]
portNum, err := strconv.Atoi(port)
reqHost, reqPort = splitHost[0], splitHost[1]
portNum, err := strconv.Atoi(reqPort)
if err != nil {
return err
}
@ -271,10 +288,10 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
return fmt.Errorf("Invalid port number in redirect")
}
} else if strings.ToLower(req.URL.Scheme) == "https" {
port = "443"
reqPort = "443"
}
dialer, err := va.resolveAndConstructDialer(host, port)
dialer, err := va.resolveAndConstructDialer(reqHost, reqPort)
dialer.record.URL = req.URL.String()
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
if err != nil {
@ -412,12 +429,9 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
}
// Make a connection with SNI = nonceName
hostPort := net.JoinHostPort(addr.String(), "443")
challenge.ValidationRecord[0].Port = "443"
if va.TestMode {
hostPort = net.JoinHostPort(addr.String(), "5001")
challenge.ValidationRecord[0].Port = "5001"
}
portString := fmt.Sprintf("%d", va.dvsniPort)
hostPort := net.JoinHostPort(addr.String(), portString)
challenge.ValidationRecord[0].Port = portString
va.log.Notice(fmt.Sprintf("DVSNI [%s] Attempting to validate DVSNI for %s %s",
identifier, hostPort, ZName))
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{

View File

@ -20,6 +20,9 @@ import (
"math/big"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"time"
@ -80,14 +83,14 @@ func createValidation(token string, enableTLS bool) string {
return obj.FullSerialize()
}
func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableTLS bool) {
func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
m := http.NewServeMux()
defaultToken := token
currentToken := defaultToken
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Host != "localhost" && r.Host != "other.valid" && r.Host != "other.valid:8080" {
if !strings.HasPrefix(r.Host, "localhost:") && r.Host != "other.valid" && r.Host != "other.valid:8080" {
t.Errorf("Bad Host header: " + r.Host)
}
if strings.HasSuffix(r.URL.Path, path404) {
@ -133,21 +136,10 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
}
})
server := &http.Server{Addr: "localhost:5001", Handler: m}
conn, err := net.Listen("tcp", server.Addr)
if err != nil {
waitChan <- true
t.Fatalf("Couldn't listen on %s: %s", server.Addr, err)
}
server := httptest.NewUnstartedServer(m)
go func() {
<-stopChan
conn.Close()
}()
var listener net.Listener
if !enableTLS {
listener = conn
server.Start()
} else {
template := &x509.Certificate{
SerialNumber: big.NewInt(1337),
@ -170,18 +162,17 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
PrivateKey: &TheKey,
}
tlsConfig := &tls.Config{
server.TLS = &tls.Config{
Certificates: []tls.Certificate{*cert},
}
listener = tls.NewListener(conn, tlsConfig)
server.StartTLS()
}
waitChan <- true
server.Serve(listener)
return server
}
func dvsniSrv(t *testing.T, chall core.Challenge, stopChan, waitChan chan bool) {
func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature)
h := sha256.New()
h.Write([]byte(encodedSig))
@ -222,43 +213,25 @@ func dvsniSrv(t *testing.T, chall core.Challenge, stopChan, waitChan chan bool)
NextProtos: []string{"http/1.1"},
}
httpsServer := &http.Server{Addr: "localhost:5001"}
conn, err := net.Listen("tcp", httpsServer.Addr)
if err != nil {
waitChan <- true
t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
}
tlsListener := tls.NewListener(conn, tlsConfig)
go func() {
<-stopChan
conn.Close()
}()
waitChan <- true
httpsServer.Serve(tlsListener)
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
hs.TLS = tlsConfig
hs.StartTLS()
return hs
}
func brokenTLSSrv(t *testing.T, stopChan, waitChan chan bool) {
httpsServer := &http.Server{Addr: "localhost:5001"}
conn, err := net.Listen("tcp", httpsServer.Addr)
if err != nil {
waitChan <- true
t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
func brokenTLSSrv() *httptest.Server {
server := httptest.NewUnstartedServer(http.DefaultServeMux)
server.TLS = &tls.Config{
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return nil, fmt.Errorf("Failing on purpose")
},
}
tlsListener := tls.NewListener(conn, &tls.Config{})
go func() {
<-stopChan
conn.Close()
}()
waitChan <- true
httpsServer.Serve(tlsListener)
server.StartTLS()
return server
}
func TestSimpleHttpTLS(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
chall := core.Challenge{
@ -268,11 +241,12 @@ func TestSimpleHttpTLS(t *testing.T) {
AccountKey: accountKey,
}
stopChan := make(chan bool, 1)
waitChan := make(chan bool, 1)
go simpleSrv(t, expectedToken, stopChan, waitChan, true)
defer func() { stopChan <- true }()
<-waitChan
hs := simpleSrv(t, expectedToken, true)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.simpleHTTPSPort = port
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
@ -284,7 +258,7 @@ func TestSimpleHttpTLS(t *testing.T) {
}
func TestSimpleHttp(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
tls := false
@ -296,17 +270,30 @@ func TestSimpleHttp(t *testing.T) {
AccountKey: accountKey,
}
// NOTE: We do not attempt to shut down the server. The problem is that the
// "wait-long" handler sleeps for ten seconds, but this test finishes in less
// than that. So if we try to call hs.Close() at the end of the test, we'll be
// closing the test server while a request is still pending. Unfortunately,
// there appears to be an issue in httptest that trips Go's race detector when
// that happens, failing the test. So instead, we live with leaving the server
// around till the process exits.
hs := simpleSrv(t, expectedToken, tls)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
// Attempt to fail a challenge by telling the VA to connect to a port we are
// not listening on.
va.simpleHTTPPort = port + 1
if va.simpleHTTPPort == 65536 {
va.simpleHTTPPort = port - 1
}
invalidChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Server's not up yet; expected refusal. Where did we connect?")
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
stopChan := make(chan bool, 1)
waitChan := make(chan bool, 1)
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
defer func() { stopChan <- true }()
<-waitChan
va.simpleHTTPPort = port
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
@ -352,12 +339,10 @@ func TestSimpleHttp(t *testing.T) {
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
va.TestMode = false
invalidChall, err = va.validateSimpleHTTP(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Domain name is invalid.")
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
va.TestMode = true
chall.Token = "wait-long"
started := time.Now()
@ -372,7 +357,7 @@ func TestSimpleHttp(t *testing.T) {
}
func TestSimpleHttpRedirectLookup(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
tls := false
@ -383,11 +368,11 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
AccountKey: accountKey,
}
stopChan := make(chan bool, 1)
waitChan := make(chan bool, 1)
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
defer func() { stopChan <- true }()
<-waitChan
hs := simpleSrv(t, expectedToken, tls)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.simpleHTTPPort = port
log.Clear()
chall.Token = pathMoved
@ -435,7 +420,7 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
}
func TestSimpleHttpRedirectLoop(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
tls := false
@ -445,11 +430,11 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
ValidationRecord: []core.ValidationRecord{},
}
stopChan := make(chan bool, 1)
waitChan := make(chan bool, 1)
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
defer func() { stopChan <- true }()
<-waitChan
hs := simpleSrv(t, expectedToken, tls)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.simpleHTTPPort = port
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
@ -458,24 +443,33 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
fmt.Println(finChall)
}
func TestDvsni(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va.DNSResolver = &mocks.MockDNS{}
func getPort(hs *httptest.Server) (int, error) {
url, err := url.Parse(hs.URL)
if err != nil {
return 0, err
}
_, portString, err := net.SplitHostPort(url.Host)
if err != nil {
return 0, err
}
port, err := strconv.ParseInt(portString, 10, 64)
if err != nil {
return 0, err
}
return int(port), nil
}
func TestDvsni(t *testing.T) {
chall := createChallenge(core.ChallengeTypeDVSNI)
log.Clear()
invalidChall, err := va.validateDvsni(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Server's not up yet; expected refusal. Where did we connect?")
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
hs := dvsniSrv(t, chall)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
waitChan := make(chan bool, 1)
stopChan := make(chan bool, 1)
go dvsniSrv(t, chall, stopChan, waitChan)
defer func() { stopChan <- true }()
<-waitChan
va := NewValidationAuthorityImpl(false)
va.dvsniPort = port
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateDvsni(ident, chall)
@ -484,20 +478,20 @@ func TestDvsni(t *testing.T) {
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
log.Clear()
invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
invalidChall, err := va.validateDvsni(core.AcmeIdentifier{
Type: core.IdentifierType("ip"),
Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)),
}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
log.Clear()
va.TestMode = false
invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Domain name is invalid.")
test.AssertError(t, err, "Domain name was supposed to be invalid.")
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
va.TestMode = true
// Need to re-sign to get an unknown SNI (from the signature value)
chall.Token = core.NewToken()
validationPayload, _ := json.Marshal(map[string]interface{}{
@ -518,18 +512,25 @@ func TestDvsni(t *testing.T) {
test.AssertError(t, err, "Connection should've timed out")
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
// Take down DVSNI validation server and check that validation fails.
hs.Close()
invalidChall, err = va.validateDvsni(ident, chall, AccountKey)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
}
func TestTLSError(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
chall := createChallenge(core.ChallengeTypeDVSNI)
waitChan := make(chan bool, 1)
stopChan := make(chan bool, 1)
go brokenTLSSrv(t, stopChan, waitChan)
defer func() { stopChan <- true }()
<-waitChan
hs := brokenTLSSrv()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.dvsniPort = port
invalidChall, err := va.validateDvsni(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
@ -538,7 +539,7 @@ func TestTLSError(t *testing.T) {
}
func TestValidateHTTP(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -549,17 +550,11 @@ func TestValidateHTTP(t *testing.T) {
challHTTP.ValidationRecord = []core.ValidationRecord{}
challHTTP.AccountKey = accountKey
stopChanHTTP := make(chan bool, 1)
waitChanHTTP := make(chan bool, 1)
go simpleSrv(t, challHTTP.Token, stopChanHTTP, waitChanHTTP, tls)
// Let them start
<-waitChanHTTP
// shutdown cleanly
defer func() {
stopChanHTTP <- true
}()
hs := simpleSrv(t, challHTTP.Token, tls)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.simpleHTTPPort = port
defer hs.Close()
var authz = core.Authorization{
ID: core.NewToken(),
@ -593,23 +588,18 @@ func createChallenge(challengeType string) core.Challenge {
}
func TestValidateDvsni(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chall := createChallenge(core.ChallengeTypeDVSNI)
waitChanDvsni := make(chan bool, 1)
stopChanDvsni := make(chan bool, 1)
go dvsniSrv(t, chall, stopChanDvsni, waitChanDvsni)
hs := dvsniSrv(t, chall)
defer hs.Close()
// Let them start
<-waitChanDvsni
// shutdown cleanly
defer func() {
stopChanDvsni <- true
}()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
va.dvsniPort = port
var authz = core.Authorization{
ID: core.NewToken(),
@ -623,23 +613,12 @@ func TestValidateDvsni(t *testing.T) {
}
func TestValidateDvsniNotSane(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chall := createChallenge(core.ChallengeTypeDVSNI)
waitChanDvsni := make(chan bool, 1)
stopChanDvsni := make(chan bool, 1)
go dvsniSrv(t, chall, stopChanDvsni, waitChanDvsni)
// Let them start
<-waitChanDvsni
// shutdown cleanly
defer func() {
stopChanDvsni <- true
}()
chall.Token = "not sane"
@ -655,7 +634,7 @@ func TestValidateDvsniNotSane(t *testing.T) {
}
func TestUpdateValidations(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -665,18 +644,6 @@ func TestUpdateValidations(t *testing.T) {
challHTTP.TLS = &tls
challHTTP.ValidationRecord = []core.ValidationRecord{}
stopChanHTTP := make(chan bool, 1)
waitChanHTTP := make(chan bool, 1)
go simpleSrv(t, challHTTP.Token, stopChanHTTP, waitChanHTTP, tls)
// Let them start
<-waitChanHTTP
// shutdown cleanly
defer func() {
stopChanHTTP <- true
}()
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
@ -722,7 +689,7 @@ func TestCAAChecking(t *testing.T) {
// CNAME to critical
}
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
va.IssuerDomain = "letsencrypt.org"
for _, caaTest := range tests {
@ -754,7 +721,7 @@ func TestCAAChecking(t *testing.T) {
}
func TestDNSValidationFailure(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -790,7 +757,7 @@ func TestDNSValidationInvalid(t *testing.T) {
Challenges: []core.Challenge{chalDNS},
}
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -803,7 +770,7 @@ func TestDNSValidationInvalid(t *testing.T) {
}
func TestDNSValidationNotSane(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -833,7 +800,7 @@ func TestDNSValidationNotSane(t *testing.T) {
}
func TestDNSValidationServFail(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
@ -858,7 +825,7 @@ func TestDNSValidationServFail(t *testing.T) {
}
func TestDNSValidationNoServer(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va := NewValidationAuthorityImpl(false)
va.DNSResolver = core.NewDNSResolverImpl(time.Second*5, []string{})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA