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:
parent
54e7ac6b1b
commit
7b6f2894f7
|
@ -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,
|
||||
|
|
33
README.md
33
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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`
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -104,8 +104,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
SQL struct {
|
||||
CreateTables bool
|
||||
SQLDebug bool
|
||||
SQLDebug bool
|
||||
}
|
||||
|
||||
Statsd struct {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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() );
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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`;
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -114,8 +114,7 @@
|
|||
},
|
||||
|
||||
"sql": {
|
||||
"SQLDebug": true,
|
||||
"CreateTables": true
|
||||
"SQLDebug": true
|
||||
},
|
||||
|
||||
"revoker": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue