add goose as the migration tool

This has required some substantive changes to the tests. Where
previously the foreign key constraints did not exist in the tests, now
that we use the actual production schema, they do. This has mostly led
to having to create real Registrations in the sa, ca, and ra tests. Long
term, it would be nice to fake this out better instead of needing a real
sa in the ca and ra tests.

The "goose" being referred to is <https://bitbucket.org/liamstask/goose>.

Database migrations are stored in a _db directory inside the relevant
owner service (namely, ca/_db, and sa/_db, today).

An example of migrating up with goose:

    goose -path ./sa/_db -env test up

An example of creating a new migration with goose:

    goose -path ./sa/_db -env test create NameOfNewMigration sql

Notice the "sql" at the end. It would be easier for us to manage sql
migrations. I would like us to stick to only them. In case we do use Go
migrations in the future, the underscore at the beginning of "_db" will
at least prevent build errors when using "..." with goose-created Go
files. Goose-created Go migrations do not compile with the go tool but
only with goose.

Fixes #111
Unblocks #623
This commit is contained in:
Jeff Hodges 2015-08-21 13:40:51 -07:00
parent 54e7ac6b1b
commit 7b6f2894f7
26 changed files with 427 additions and 337 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")
@ -421,7 +395,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()
AuthzInitial, _ = sa.NewPendingAuthorization(AuthzInitial)
sa.UpdatePendingAuthorization(AuthzInitial)
@ -445,7 +419,7 @@ func TestUpdateAuthorization(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)
@ -468,7 +442,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)
@ -491,8 +465,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")
@ -500,7 +474,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)
@ -520,9 +494,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)
@ -537,7 +511,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

@ -118,8 +118,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()
}