Merge pull request #2575 from letsencrypt/master
Merge master to staging
This commit is contained in:
commit
f445cf3b32
|
|
@ -46,7 +46,6 @@ env:
|
|||
install:
|
||||
- ./test/travis-before-install.sh
|
||||
- docker-compose pull
|
||||
- docker pull letsencrypt/boulder-tools
|
||||
- docker-compose build
|
||||
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ container for service boulder" you should double check that your `$GOPATH`
|
|||
exists and doesn't contain any characters other than letters, numbers, `-`
|
||||
and `_`.
|
||||
|
||||
If you have problems with Docker, you may want to try [removing all containers
|
||||
and volumes](https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes).
|
||||
|
||||
By default, Boulder uses a fake DNS resolver that resolves all hostnames to
|
||||
127.0.0.1. This is suitable for running integration tests inside the Docker
|
||||
container. If you want Boulder to be able to communicate with a client running
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func TestParseAnswer(t *testing.T) {
|
|||
|
||||
func TestQueryCAA(t *testing.T) {
|
||||
testServ := httptest.NewServer(http.HandlerFunc(mocks.GPDNSHandler))
|
||||
// TODO(#1989): Close testServ
|
||||
defer testServ.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", testServ.URL, nil)
|
||||
test.AssertNotError(t, err, "Failed to create request")
|
||||
|
|
@ -63,7 +63,7 @@ func TestQueryCAA(t *testing.T) {
|
|||
|
||||
func TestLookupCAA(t *testing.T) {
|
||||
testSrv := httptest.NewServer(http.HandlerFunc(mocks.GPDNSHandler))
|
||||
// TODO(#1989): Close testServ
|
||||
defer testSrv.Close()
|
||||
|
||||
cpr := CAADistributedResolver{
|
||||
logger: log,
|
||||
|
|
@ -119,7 +119,7 @@ func (sbh *slightlyBrokenHandler) Handler(w http.ResponseWriter, r *http.Request
|
|||
func TestHTTPQuorum(t *testing.T) {
|
||||
sbh := &slightlyBrokenHandler{}
|
||||
testSrv := httptest.NewServer(http.HandlerFunc(sbh.Handler))
|
||||
// TODO(#1989): Close testServ
|
||||
defer testSrv.Close()
|
||||
|
||||
cpr := CAADistributedResolver{
|
||||
logger: log,
|
||||
|
|
|
|||
|
|
@ -260,11 +260,9 @@ func (m *mailer) findExpiringCertificates() error {
|
|||
// sequentially fetch the certificate details. This avoids an expensive
|
||||
// JOIN.
|
||||
var serials []string
|
||||
var err error
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
_, err = m.dbMap.Select(
|
||||
&serials,
|
||||
`SELECT
|
||||
_, err := m.dbMap.Select(
|
||||
&serials,
|
||||
`SELECT
|
||||
cs.serial
|
||||
FROM certificateStatus AS cs
|
||||
WHERE cs.notAfter > :cutoffA
|
||||
|
|
@ -273,35 +271,13 @@ func (m *mailer) findExpiringCertificates() error {
|
|||
AND COALESCE(TIMESTAMPDIFF(SECOND, cs.lastExpirationNagSent, cs.notAfter) > :nagCutoff, 1)
|
||||
ORDER BY cs.notAfter ASC
|
||||
LIMIT :limit`,
|
||||
map[string]interface{}{
|
||||
"cutoffA": left,
|
||||
"cutoffB": right,
|
||||
"nagCutoff": expiresIn.Seconds(),
|
||||
"limit": m.limit,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
_, err = m.dbMap.Select(
|
||||
&serials,
|
||||
`SELECT
|
||||
cert.serial
|
||||
FROM certificates AS cert
|
||||
JOIN certificateStatus AS cs
|
||||
ON cs.serial = cert.serial
|
||||
AND cert.expires > :cutoffA
|
||||
AND cert.expires <= :cutoffB
|
||||
AND cs.status != "revoked"
|
||||
AND COALESCE(TIMESTAMPDIFF(SECOND, cs.lastExpirationNagSent, cert.expires) > :nagCutoff, 1)
|
||||
ORDER BY cert.expires ASC
|
||||
LIMIT :limit`,
|
||||
map[string]interface{}{
|
||||
"cutoffA": left,
|
||||
"cutoffB": right,
|
||||
"nagCutoff": expiresIn.Seconds(),
|
||||
"limit": m.limit,
|
||||
},
|
||||
)
|
||||
}
|
||||
map[string]interface{}{
|
||||
"cutoffA": left,
|
||||
"cutoffB": right,
|
||||
"nagCutoff": expiresIn.Seconds(),
|
||||
"limit": m.limit,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.log.AuditErr(fmt.Sprintf("expiration-mailer: Error loading certificate serials: %s", err))
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ func addExpiringCerts(t *testing.T, ctx *testCtx) []core.Certificate {
|
|||
Serial: serial1String,
|
||||
LastExpirationNagSent: ctx.fc.Now().AddDate(0, 0, -3),
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertA.NotAfter,
|
||||
}
|
||||
|
||||
// Expires in 3d, already sent 4d nag at 4.5d
|
||||
|
|
@ -335,6 +336,7 @@ func addExpiringCerts(t *testing.T, ctx *testCtx) []core.Certificate {
|
|||
Serial: serial2String,
|
||||
LastExpirationNagSent: ctx.fc.Now().Add(-36 * time.Hour),
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertB.NotAfter,
|
||||
}
|
||||
|
||||
// Expires in 7d and change, no nag sent at all yet
|
||||
|
|
@ -354,8 +356,9 @@ func addExpiringCerts(t *testing.T, ctx *testCtx) []core.Certificate {
|
|||
DER: certDerC,
|
||||
}
|
||||
certStatusC := &core.CertificateStatus{
|
||||
Serial: serial3String,
|
||||
Status: core.OCSPStatusGood,
|
||||
Serial: serial3String,
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertC.NotAfter,
|
||||
}
|
||||
|
||||
// Expires in 3d, renewed
|
||||
|
|
@ -375,8 +378,9 @@ func addExpiringCerts(t *testing.T, ctx *testCtx) []core.Certificate {
|
|||
DER: certDerD,
|
||||
}
|
||||
certStatusD := &core.CertificateStatus{
|
||||
Serial: serial4String,
|
||||
Status: core.OCSPStatusGood,
|
||||
Serial: serial4String,
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertD.NotAfter,
|
||||
}
|
||||
fqdnStatusD := &core.FQDNSet{
|
||||
SetHash: []byte("hash of D"),
|
||||
|
|
@ -628,8 +632,9 @@ func TestLifetimeOfACert(t *testing.T) {
|
|||
}
|
||||
|
||||
certStatusA := &core.CertificateStatus{
|
||||
Serial: serial1String,
|
||||
Status: core.OCSPStatusGood,
|
||||
Serial: serial1String,
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertA.NotAfter,
|
||||
}
|
||||
|
||||
setupDBMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
|
|
@ -788,6 +793,7 @@ func TestDedupOnRegistration(t *testing.T) {
|
|||
Serial: serial1String,
|
||||
LastExpirationNagSent: time.Unix(0, 0),
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertA.NotAfter,
|
||||
}
|
||||
|
||||
rawCertB := newX509Cert("happy B",
|
||||
|
|
@ -806,6 +812,7 @@ func TestDedupOnRegistration(t *testing.T) {
|
|||
Serial: serial2String,
|
||||
LastExpirationNagSent: time.Unix(0, 0),
|
||||
Status: core.OCSPStatusGood,
|
||||
NotAfter: rawCertB.NotAfter,
|
||||
}
|
||||
|
||||
setupDBMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
|
|
|
|||
|
|
@ -1,247 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
)
|
||||
|
||||
type dbAccess interface {
|
||||
SelectOne(holder interface{}, query string, args ...interface{}) error
|
||||
Select(holder interface{}, query string, args ...interface{}) ([]interface{}, error)
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
type backfiller struct {
|
||||
dbMap dbAccess
|
||||
log blog.Logger
|
||||
clk clock.Clock
|
||||
dryRun bool
|
||||
batchSize uint
|
||||
numBatches uint
|
||||
sleep time.Duration
|
||||
}
|
||||
|
||||
func (b backfiller) printStatus(
|
||||
serial string, notAfter time.Time, cur, total int, start time.Time) {
|
||||
// Should never happen
|
||||
if total <= 0 || cur < 0 || cur > total {
|
||||
b.log.AuditErr(fmt.Sprintf(
|
||||
"invalid cur (%d) or total (%d)\n", cur, total))
|
||||
}
|
||||
completion := (float32(cur) / float32(total)) * 100
|
||||
now := b.clk.Now()
|
||||
elapsed := now.Sub(start)
|
||||
b.log.Info(
|
||||
fmt.Sprintf("Updating %q notAfter to %q. Cert. %d of %d [%.2f%%]. Elapsed: %s",
|
||||
serial, notAfter, cur+1, total, completion, elapsed.String()))
|
||||
}
|
||||
|
||||
func (b backfiller) backfill(certStatus *core.CertificateStatus) error {
|
||||
// We explicitly use `Exec` over `Update` to avoid contention on the
|
||||
// `LockCol` field that Gorp uses for optimistic locking. With an
|
||||
// `ocsp-updater` running at the same time as a backfill there is a pretty
|
||||
// good chance they would clobber each others `LockCol` values if we used
|
||||
// `Update()` instead of a raw `Exec()`.
|
||||
_, err := b.dbMap.Exec(
|
||||
`UPDATE certificateStatus
|
||||
SET notAfter=?
|
||||
WHERE serial=?`,
|
||||
certStatus.NotAfter,
|
||||
certStatus.Serial,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b backfiller) findEmpty() ([]*core.CertificateStatus, error) {
|
||||
var certs []*core.CertificateStatus
|
||||
|
||||
_, err := b.dbMap.Select(&certs,
|
||||
`SELECT
|
||||
serial
|
||||
FROM certificateStatus
|
||||
WHERE notAfter IS NULL
|
||||
LIMIT :batchSize`,
|
||||
map[string]interface{}{
|
||||
"batchSize": b.batchSize,
|
||||
},
|
||||
)
|
||||
return certs, err
|
||||
}
|
||||
|
||||
func (b backfiller) populateNotAfter(certs []*core.CertificateStatus) error {
|
||||
for _, cs := range certs {
|
||||
var c core.Certificate
|
||||
|
||||
err := b.dbMap.SelectOne(&c,
|
||||
`SELECT expires
|
||||
FROM certificates
|
||||
WHERE serial = :serial`,
|
||||
map[string]interface{}{
|
||||
"serial": cs.Serial,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs.NotAfter = c.Expires
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b backfiller) processBatch() (int, error) {
|
||||
certs, err := b.findEmpty()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
b.log.Info(fmt.Sprintf("Found %d certificates for this batch", len(certs)))
|
||||
if len(certs) == 0 {
|
||||
return 0, nil // Nothing to backfill!
|
||||
}
|
||||
|
||||
err = b.populateNotAfter(certs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
startTime := b.clk.Now()
|
||||
for i, c := range certs {
|
||||
b.printStatus(c.Serial, c.NotAfter, i, len(certs), startTime)
|
||||
if !b.dryRun {
|
||||
err := b.backfill(c)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(certs), nil
|
||||
}
|
||||
|
||||
func (b backfiller) processForever() error {
|
||||
var batchNum uint
|
||||
for {
|
||||
start := b.clk.Now()
|
||||
b.log.Info(fmt.Sprintf("Starting to process batch %d", batchNum+1))
|
||||
processed, err := b.processBatch()
|
||||
now := b.clk.Now()
|
||||
elapsed := now.Sub(start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.log.Info(fmt.Sprintf("Batch %d finished. Processed %d certificates in %s",
|
||||
batchNum+1, processed, elapsed))
|
||||
if processed == 0 {
|
||||
b.log.Info("No more certificates to process. Terminating.")
|
||||
break
|
||||
}
|
||||
batchNum++
|
||||
if batchNum >= b.numBatches {
|
||||
b.log.Info(fmt.Sprintf("Reached numBatches (%d). Terminating.", b.numBatches))
|
||||
break
|
||||
}
|
||||
b.log.Info(fmt.Sprintf("Sleeping for %s before next batch", b.sleep))
|
||||
b.clk.Sleep(b.sleep)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const usageIntro = `
|
||||
Introduction:
|
||||
|
||||
The "20160817143417_AddCertStatusNotAfter.sql" db migration adds a "notAfter"
|
||||
column to the certificateStatus database table. This field duplicates the
|
||||
contents of the certificates table "expires" column. This enables performance
|
||||
improvements[0] for both the ocsp-updater and the expiration-mailer utilities.
|
||||
|
||||
Since existing rows will have a NULL value in the new field the
|
||||
notafter-backfill utility exists to perform a one-time update of the existing
|
||||
certificateStatus rows to set their notAfter column based on the data that
|
||||
exists in the certificates table.
|
||||
|
||||
[0] https://github.com/letsencrypt/boulder/issues/1864
|
||||
|
||||
Examples:
|
||||
|
||||
Process 50 certificates at a time, printing the updates but not performing
|
||||
them:
|
||||
|
||||
notafter-backfill -config test/config/notafter-backfiller.json -batchSize=50
|
||||
-dryRun=true
|
||||
|
||||
Process 1000 certificates at a time, quitting after 5 batches (5000
|
||||
certificates) and sleeping 10 minutes between batches:
|
||||
|
||||
notafter-backfill -config test/config/notafter-backfiller.json -batchSize=1000
|
||||
-numBatches=5 -sleep=5m -dryRun=false
|
||||
|
||||
Required arguments:
|
||||
|
||||
- config
|
||||
|
||||
`
|
||||
|
||||
func main() {
|
||||
dryRun := flag.Bool("dryRun", true, "Whether to do a dry run.")
|
||||
sleep := flag.Duration("sleep", 60*time.Second, "How long to sleep between batches.")
|
||||
batchSize := flag.Uint("batchSize", 1000, "Number of certificates to process between sleeps.")
|
||||
numBatches := flag.Uint("numBatches", 999999, "Stop processing after N batches.")
|
||||
type config struct {
|
||||
NotAfterBackFiller struct {
|
||||
cmd.DBConfig
|
||||
}
|
||||
Statsd cmd.StatsdConfig
|
||||
Syslog cmd.SyslogConfig
|
||||
}
|
||||
configFile := flag.String("config", "", "File containing a JSON config.")
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "%s\n\n", usageIntro)
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
if *configFile == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadFile(*configFile)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Reading %q", *configFile))
|
||||
var cfg config
|
||||
err = json.Unmarshal(configData, &cfg)
|
||||
cmd.FailOnError(err, "Unmarshaling config")
|
||||
|
||||
stats, log := cmd.StatsAndLogging(cfg.Statsd, cfg.Syslog)
|
||||
defer log.AuditPanic()
|
||||
|
||||
dbURL, err := cfg.NotAfterBackFiller.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL, 10)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
go sa.ReportDbConnCount(dbMap, metrics.NewStatsdScope(stats, "NotAfterBackfiller"))
|
||||
|
||||
b := backfiller{
|
||||
dbMap: dbMap,
|
||||
log: log,
|
||||
clk: cmd.Clock(),
|
||||
dryRun: *dryRun,
|
||||
batchSize: *batchSize,
|
||||
numBatches: *numBatches,
|
||||
sleep: *sleep,
|
||||
}
|
||||
|
||||
err = b.processForever()
|
||||
cmd.FailOnError(err, "Could not process certificate batches")
|
||||
}
|
||||
|
|
@ -269,14 +269,9 @@ func (updater *OCSPUpdater) findStaleOCSPResponses(oldestLastUpdatedTime time.Ti
|
|||
now := updater.clk.Now()
|
||||
maxAgeCutoff := now.Add(-updater.ocspStaleMaxAge)
|
||||
|
||||
// If CertStatusOptimizationsMigrated is enabled then we can do this query
|
||||
// using only the `certificateStatus` table, saving an expensive JOIN and
|
||||
// improving performance substantially
|
||||
var err error
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
_, err = updater.dbMap.Select(
|
||||
&statuses,
|
||||
`SELECT
|
||||
_, err := updater.dbMap.Select(
|
||||
&statuses,
|
||||
`SELECT
|
||||
cs.serial,
|
||||
cs.status,
|
||||
cs.revokedDate,
|
||||
|
|
@ -287,37 +282,12 @@ func (updater *OCSPUpdater) findStaleOCSPResponses(oldestLastUpdatedTime time.Ti
|
|||
AND NOT cs.isExpired
|
||||
ORDER BY cs.ocspLastUpdated ASC
|
||||
LIMIT :limit`,
|
||||
map[string]interface{}{
|
||||
"lastUpdate": oldestLastUpdatedTime,
|
||||
"maxAge": maxAgeCutoff,
|
||||
"limit": batchSize,
|
||||
},
|
||||
)
|
||||
// If the migration hasn't been applied we don't have the `isExpired` or
|
||||
// `notAfter` fields on the certificate status table to use and must do the
|
||||
// expensive JOIN on `certificates`
|
||||
} else {
|
||||
_, err = updater.dbMap.Select(
|
||||
&statuses,
|
||||
`SELECT
|
||||
cs.serial,
|
||||
cs.status,
|
||||
cs.revokedDate
|
||||
FROM certificateStatus AS cs
|
||||
JOIN certificates AS cert
|
||||
ON cs.serial = cert.serial
|
||||
WHERE cs.ocspLastUpdated > :maxAge
|
||||
AND cs.ocspLastUpdated < :lastUpdate
|
||||
AND cert.expires > now()
|
||||
ORDER BY cs.ocspLastUpdated ASC
|
||||
LIMIT :limit`,
|
||||
map[string]interface{}{
|
||||
"lastUpdate": oldestLastUpdatedTime,
|
||||
"maxAge": maxAgeCutoff,
|
||||
"limit": batchSize,
|
||||
},
|
||||
)
|
||||
}
|
||||
map[string]interface{}{
|
||||
"lastUpdate": oldestLastUpdatedTime,
|
||||
"maxAge": maxAgeCutoff,
|
||||
"limit": batchSize,
|
||||
},
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return statuses, nil
|
||||
}
|
||||
|
|
@ -326,21 +296,11 @@ func (updater *OCSPUpdater) findStaleOCSPResponses(oldestLastUpdatedTime time.Ti
|
|||
|
||||
func (updater *OCSPUpdater) getCertificatesWithMissingResponses(batchSize int) ([]core.CertificateStatus, error) {
|
||||
const query = "WHERE ocspLastUpdated = 0 LIMIT ?"
|
||||
var statuses []core.CertificateStatus
|
||||
var err error
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
statuses, err = sa.SelectCertificateStatusesv2(
|
||||
updater.dbMap,
|
||||
query,
|
||||
batchSize,
|
||||
)
|
||||
} else {
|
||||
statuses, err = sa.SelectCertificateStatuses(
|
||||
updater.dbMap,
|
||||
query,
|
||||
batchSize,
|
||||
)
|
||||
}
|
||||
statuses, err := sa.SelectCertificateStatuses(
|
||||
updater.dbMap,
|
||||
query,
|
||||
batchSize,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return statuses, nil
|
||||
}
|
||||
|
|
@ -456,23 +416,12 @@ func (updater *OCSPUpdater) newCertificateTick(ctx context.Context, batchSize in
|
|||
|
||||
func (updater *OCSPUpdater) findRevokedCertificatesToUpdate(batchSize int) ([]core.CertificateStatus, error) {
|
||||
const query = "WHERE status = ? AND ocspLastUpdated <= revokedDate LIMIT ?"
|
||||
var statuses []core.CertificateStatus
|
||||
var err error
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
statuses, err = sa.SelectCertificateStatusesv2(
|
||||
updater.dbMap,
|
||||
query,
|
||||
string(core.OCSPStatusRevoked),
|
||||
batchSize,
|
||||
)
|
||||
} else {
|
||||
statuses, err = sa.SelectCertificateStatuses(
|
||||
updater.dbMap,
|
||||
query,
|
||||
string(core.OCSPStatusRevoked),
|
||||
batchSize,
|
||||
)
|
||||
}
|
||||
statuses, err := sa.SelectCertificateStatuses(
|
||||
updater.dbMap,
|
||||
query,
|
||||
string(core.OCSPStatusRevoked),
|
||||
batchSize,
|
||||
)
|
||||
return statuses, err
|
||||
}
|
||||
|
||||
|
|
@ -559,16 +508,11 @@ func (updater *OCSPUpdater) oldOCSPResponsesTick(ctx context.Context, batchSize
|
|||
tickEnd := updater.clk.Now()
|
||||
updater.stats.TimingDuration("oldOCSPResponsesTick.QueryTime", tickEnd.Sub(tickStart))
|
||||
|
||||
// If the CertStatusOptimizationsMigrated flag is set then we need to
|
||||
// opportunistically update the certificateStatus `isExpired` column for expired
|
||||
// certificates we come across
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
for _, s := range statuses {
|
||||
if !s.IsExpired && tickStart.After(s.NotAfter) {
|
||||
err := updater.markExpired(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if !s.IsExpired && tickStart.After(s.NotAfter) {
|
||||
err := updater.markExpired(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,25 @@ While Boulder attempts to implement the ACME specification as strictly as possib
|
|||
|
||||
This document details these differences, since ACME is not yet finalized it will be updated as numbered drafts are published.
|
||||
|
||||
Current draft: [`draft-ietf-acme-acme-04`](https://tools.ietf.org/html/draft-ietf-acme-acme-04).
|
||||
Current draft: [`draft-ietf-acme-acme-05`](https://tools.ietf.org/html/draft-ietf-acme-acme-05).
|
||||
|
||||
## [Section 5.2](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-5.2)
|
||||
## [Section 5](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5)
|
||||
|
||||
Boulder does not implement the [general JWS syntax](https://tools.ietf.org/html/rfc7515#page-20), but only accepts the [flattened syntax](https://tools.ietf.org/html/rfc7515#page-21).
|
||||
|
||||
## [Section 5.2](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.2)
|
||||
|
||||
Boulder enforces the presence of the `jwk` field in JWS objects, and does not support the `kid` field.
|
||||
|
||||
## [Section 5.4.1](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-5.4.1)
|
||||
## [Section 5.4.1](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.4.1)
|
||||
|
||||
Boulder does not use the `url` field from the JWS protected resource. Instead Boulder will validate the `resource` field from the JWS payload matches the resource being requested. Boulder implements the resource types described in [draft-ietf-acme-02 Section 6.1](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1) plus the additional "KeyChange" resource. Boulder verifies the `resource` field contains the `/directory` URI for the requested resource.
|
||||
|
||||
## [Section 5.6.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-5.6)
|
||||
## [Section 5.6.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6)
|
||||
|
||||
Boulder does not provide a `Retry-After` header when a user hits a rate-limit, nor does it provide `Link` headers to further documentation on rate-limiting.
|
||||
|
||||
## [Section 5.7.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-5.7)
|
||||
## [Section 5.7.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.7)
|
||||
|
||||
Boulder doesn't return errors under the `urn:ietf:params:acme:error:` namespace but instead uses the `urn:acme:error:` namespace from [draft-ietf-acme-01 Section 5.4](https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-5.4).
|
||||
|
||||
|
|
@ -26,52 +30,64 @@ Boulder uses `invalidEmail` in place of the error `invalidContact` defined in [d
|
|||
|
||||
Boulder does not implement the `caa` and `dnssec` errors.
|
||||
|
||||
## [Section 6.1.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.1)
|
||||
## [Section 6.1.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.1)
|
||||
|
||||
Boulder does not implement the `new-application` resource. Instead of `new-application` Boulder implements the `new-cert` resource that is defined in [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5). Boulder also doesn't implement the `new-nonce` endpoint.
|
||||
Boulder does not implement the `new-order` resource. Instead of `new-order` Boulder implements the `new-cert` resource that is defined in [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5).
|
||||
|
||||
## [Section 6.1.1.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.1.1)
|
||||
Boulder also doesn't implement the `new-nonce` endpoint.
|
||||
|
||||
Boulder implements the `new-account` ressource only under the `new-reg` key.
|
||||
|
||||
## [Section 6.1.1.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.1.1)
|
||||
|
||||
Boulder does not implement the `meta` field returned by the `directory` endpoint.
|
||||
|
||||
## [Section 6.1.2.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.1.2)
|
||||
## [Section 6.1.2.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.1.2)
|
||||
|
||||
Boulder does not implement the `terms-of-service-agreed` or `applications` fields in the registration object (nor the endpoints the latter links to).
|
||||
Boulder does not implement the `terms-of-service-agreed` or `orders` fields in the registration object (nor the endpoints the latter links to).
|
||||
|
||||
## [Section 6.1.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.1.3)
|
||||
## [Section 6.1.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.1.3)
|
||||
|
||||
Boulder does not implement applications, instead it implements the `new-cert` flow from [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5). Instead of application requirements Boulder currently uses authorizations that are created using the `new-authz` flow from [draft-ietf-acme-02 Section 6.4](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4).
|
||||
Boulder does not implement orders, instead it implements the `new-cert` flow from [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5). Instead of authorizations in the order response, Boulder currently uses authorizations that are created using the `new-authz` flow from [draft-ietf-acme-02 Section 6.4](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4).
|
||||
|
||||
## [Section 6.1.4.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.1.4)
|
||||
## [Section 6.1.4.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.1.4)
|
||||
|
||||
Boulder does not implement the `scope` field in authorization objects.
|
||||
|
||||
## [Section 6.2.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.2)
|
||||
## [Section 6.2.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.2)
|
||||
|
||||
Boulder doesn't implement the `new-nonce` endpoint, instead it responds to `HEAD` requests with a valid `Replay-Nonce` header per [draft-ietf-acme-03 Section 5.4](https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-5.4).
|
||||
|
||||
## [Section 6.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.3)
|
||||
## [Section 6.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.3)
|
||||
|
||||
Boulder only allows `mailto` URIs in the registrations `contact` list.
|
||||
|
||||
Boulder uses a HTTP status code 409 (Conflict) response for an already existing registration instead of 200 (OK). Boulder returns the URI of the already existing registration in a `Location` header field instead of a `Content-Location` header field.
|
||||
|
||||
## [Section 6.3.2.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.3.2)
|
||||
## [Section 6.3.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.3.3)
|
||||
|
||||
Boulder implements draft-04 style key roll-over with a few divergences. Since Boulder doesn't currently use the registration URL to identify users we do not check for that field in the JWS protected headers but do check for it in the inner payload. Boulder also requires the outer JWS payload contains the `"resource": "key-change"` field.
|
||||
Boulder implements draft-05 style key roll-over with a few divergences. Since Boulder doesn't currently use the registration URL to identify users we do not check for that field in the JWS protected headers but do check for it in the inner payload. Boulder also requires the outer JWS payload contains the `"resource": "key-change"` field.
|
||||
|
||||
## [Section 6.4.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-6.4)
|
||||
## [Section 6.4.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.4)
|
||||
|
||||
Boulder does not implement applications, instead it implements the `new-cert` flow from [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5). Instead of application requirements Boulder currently uses authorizations that are created using the `new-authz` flow from [draft-ietf-acme-02 Section 6.4](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4). Certificates are not proactively issued, a user must request issuance via the `new-cert` endpoint instead of assuming a certificate will be created once all required authorizations are validated.
|
||||
Boulder does not implement orders, instead it implements the `new-cert` flow from [draft-ietf-acme-02 Section 6.5](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5). Instead of authorizations in the order response, Boulder currently uses authorizations that are created using the `new-authz` flow from [draft-ietf-acme-02 Section 6.4](https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4). Certificates are not proactively issued, a user must request issuance via the `new-cert` endpoint instead of assuming a certificate will be created once all required authorizations are validated.
|
||||
|
||||
## [Section 7.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-7.3)
|
||||
## [Section 6.4.1.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-6.4.1)
|
||||
|
||||
Boulder ignores the `existing` field in authorization request objects.
|
||||
|
||||
## [Section 7.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7)
|
||||
|
||||
Boulder returns an `uri` instead of an `url` field in challenge objects.
|
||||
|
||||
## [Section 7.3.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.3)
|
||||
|
||||
Boulder implements `tls-sni-01` from [draft-ietf-acme-01 Section 7.3](https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3) instead of the `tls-sni-02` validation method.
|
||||
|
||||
## [Section 7.5.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-7.5)
|
||||
## [Section 7.5.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.5)
|
||||
|
||||
Boulder does not implement the `oob-01` validation method.
|
||||
|
||||
## [Section 8.5.](https://tools.ietf.org/html/draft-ietf-acme-acme-04#section-8.5)
|
||||
## [Section 8.5.](https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-8.5)
|
||||
|
||||
Boulder uses the `urn:acme:` namespace from [draft-ietf-acme-01 Section 5.4](https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-5.4) for errors instead of `urn:ietf:params:acme:`.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ package features
|
|||
|
||||
import "fmt"
|
||||
|
||||
const _FeatureFlag_name = "unusedIDNASupportAllowAccountDeactivationCertStatusOptimizationsMigratedAllowKeyRolloverResubmitMissingSCTsOnlyGoogleSafeBrowsingV4UseAIAIssuerURL"
|
||||
const _FeatureFlag_name = "unusedIDNASupportAllowAccountDeactivationAllowKeyRolloverResubmitMissingSCTsOnlyGoogleSafeBrowsingV4UseAIAIssuerURL"
|
||||
|
||||
var _FeatureFlag_index = [...]uint8{0, 6, 17, 41, 72, 88, 111, 131, 146}
|
||||
var _FeatureFlag_index = [...]uint8{0, 6, 17, 41, 57, 80, 100, 115}
|
||||
|
||||
func (i FeatureFlag) String() string {
|
||||
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ const (
|
|||
unused FeatureFlag = iota // unused is used for testing
|
||||
IDNASupport
|
||||
AllowAccountDeactivation
|
||||
CertStatusOptimizationsMigrated
|
||||
AllowKeyRollover
|
||||
ResubmitMissingSCTsOnly
|
||||
GoogleSafeBrowsingV4
|
||||
|
|
@ -23,14 +22,13 @@ const (
|
|||
|
||||
// List of features and their default value, protected by fMu
|
||||
var features = map[FeatureFlag]bool{
|
||||
unused: false,
|
||||
IDNASupport: false,
|
||||
AllowAccountDeactivation: false,
|
||||
CertStatusOptimizationsMigrated: false,
|
||||
AllowKeyRollover: false,
|
||||
ResubmitMissingSCTsOnly: false,
|
||||
GoogleSafeBrowsingV4: false,
|
||||
UseAIAIssuerURL: false,
|
||||
unused: false,
|
||||
IDNASupport: false,
|
||||
AllowAccountDeactivation: false,
|
||||
AllowKeyRollover: false,
|
||||
ResubmitMissingSCTsOnly: false,
|
||||
GoogleSafeBrowsingV4: false,
|
||||
UseAIAIssuerURL: false,
|
||||
}
|
||||
|
||||
var fMu = new(sync.RWMutex)
|
||||
|
|
|
|||
|
|
@ -219,11 +219,5 @@ func initTables(dbMap *gorp.DbMap) {
|
|||
dbMap.AddTableWithName(core.CRL{}, "crls").SetKeys(false, "Serial")
|
||||
dbMap.AddTableWithName(core.SignedCertificateTimestamp{}, "sctReceipts").SetKeys(true, "ID").SetVersionCol("LockCol")
|
||||
dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
|
||||
|
||||
// TODO(@cpu): Delete these table maps when the `CertStatusOptimizationsMigrated` feature flag is removed
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
dbMap.AddTableWithName(certStatusModelv2{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
|
||||
} else {
|
||||
dbMap.AddTableWithName(certStatusModelv1{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
|
||||
}
|
||||
dbMap.AddTableWithName(certStatusModel{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
|
||||
}
|
||||
|
|
|
|||
45
sa/model.go
45
sa/model.go
|
|
@ -116,12 +116,11 @@ func SelectCertificates(s dbSelector, q string, args map[string]interface{}) ([]
|
|||
return models, err
|
||||
}
|
||||
|
||||
const certStatusFields = "serial, subscriberApproved, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent, ocspResponse, LockCol"
|
||||
const certStatusFieldsv2 = certStatusFields + ", notAfter, isExpired"
|
||||
const certStatusFields = "serial, subscriberApproved, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent, ocspResponse, LockCol, notAfter, isExpired"
|
||||
|
||||
// SelectCertificateStatus selects all fields of one certificate status model
|
||||
func SelectCertificateStatus(s dbOneSelector, q string, args ...interface{}) (certStatusModelv1, error) {
|
||||
var model certStatusModelv1
|
||||
func SelectCertificateStatus(s dbOneSelector, q string, args ...interface{}) (certStatusModel, error) {
|
||||
var model certStatusModel
|
||||
err := s.SelectOne(
|
||||
&model,
|
||||
"SELECT "+certStatusFields+" FROM certificateStatus "+q,
|
||||
|
|
@ -130,17 +129,6 @@ func SelectCertificateStatus(s dbOneSelector, q string, args ...interface{}) (ce
|
|||
return model, err
|
||||
}
|
||||
|
||||
// SelectCertificateStatusv2 selects all fields (including the v2 migrated fields) of one certificate status model
|
||||
func SelectCertificateStatusv2(s dbOneSelector, q string, args ...interface{}) (certStatusModelv2, error) {
|
||||
var model certStatusModelv2
|
||||
err := s.SelectOne(
|
||||
&model,
|
||||
"SELECT "+certStatusFieldsv2+" FROM certificateStatus "+q,
|
||||
args...,
|
||||
)
|
||||
return model, err
|
||||
}
|
||||
|
||||
// SelectCertificateStatuses selects all fields of multiple certificate status objects
|
||||
func SelectCertificateStatuses(s dbSelector, q string, args ...interface{}) ([]core.CertificateStatus, error) {
|
||||
var models []core.CertificateStatus
|
||||
|
|
@ -152,17 +140,6 @@ func SelectCertificateStatuses(s dbSelector, q string, args ...interface{}) ([]c
|
|||
return models, err
|
||||
}
|
||||
|
||||
// SelectCertificateStatusesv2 selects all fields (including the v2 migrated fields) of multiple certificate status objects
|
||||
func SelectCertificateStatusesv2(s dbSelector, q string, args ...interface{}) ([]core.CertificateStatus, error) {
|
||||
var models []core.CertificateStatus
|
||||
_, err := s.Select(
|
||||
&models,
|
||||
"SELECT "+certStatusFieldsv2+" FROM certificateStatus "+q,
|
||||
args...,
|
||||
)
|
||||
return models, err
|
||||
}
|
||||
|
||||
var mediumBlobSize = int(math.Pow(2, 24))
|
||||
|
||||
type issuedNameModel struct {
|
||||
|
|
@ -194,13 +171,7 @@ type regModelv2 struct {
|
|||
Status string `db:"status"`
|
||||
}
|
||||
|
||||
// We need two certStatus model structs, one for when boulder does *not* have
|
||||
// the 20160817143417_CertStatusOptimizations.sql migration applied
|
||||
// (certStatusModelv1) and one for when it does (certStatusModelv2)
|
||||
//
|
||||
// TODO(@cpu): Collapse into one struct once the migration has been applied
|
||||
// & feature flag set.
|
||||
type certStatusModelv1 struct {
|
||||
type certStatusModel struct {
|
||||
Serial string `db:"serial"`
|
||||
SubscriberApproved bool `db:"subscriberApproved"`
|
||||
Status core.OCSPStatus `db:"status"`
|
||||
|
|
@ -210,12 +181,8 @@ type certStatusModelv1 struct {
|
|||
LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`
|
||||
OCSPResponse []byte `db:"ocspResponse"`
|
||||
LockCol int64 `json:"-"`
|
||||
}
|
||||
|
||||
type certStatusModelv2 struct {
|
||||
certStatusModelv1
|
||||
NotAfter time.Time `db:"notAfter"`
|
||||
IsExpired bool `db:"isExpired"`
|
||||
NotAfter time.Time `db:"notAfter"`
|
||||
IsExpired bool `db:"isExpired"`
|
||||
}
|
||||
|
||||
// challModel is the description of a core.Challenge in the database
|
||||
|
|
|
|||
123
sa/sa.go
123
sa/sa.go
|
|
@ -439,48 +439,26 @@ func (ssa *SQLStorageAuthority) GetCertificateStatus(ctx context.Context, serial
|
|||
}
|
||||
|
||||
var status core.CertificateStatus
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
statusObj, err := ssa.dbMap.Get(certStatusModelv2{}, serial)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
if statusObj == nil {
|
||||
return status, nil
|
||||
}
|
||||
statusModel := statusObj.(*certStatusModelv2)
|
||||
status = core.CertificateStatus{
|
||||
Serial: statusModel.Serial,
|
||||
SubscriberApproved: statusModel.SubscriberApproved,
|
||||
Status: statusModel.Status,
|
||||
OCSPLastUpdated: statusModel.OCSPLastUpdated,
|
||||
RevokedDate: statusModel.RevokedDate,
|
||||
RevokedReason: statusModel.RevokedReason,
|
||||
LastExpirationNagSent: statusModel.LastExpirationNagSent,
|
||||
OCSPResponse: statusModel.OCSPResponse,
|
||||
NotAfter: statusModel.NotAfter,
|
||||
IsExpired: statusModel.IsExpired,
|
||||
LockCol: statusModel.LockCol,
|
||||
}
|
||||
} else {
|
||||
statusObj, err := ssa.dbMap.Get(certStatusModelv1{}, serial)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
if statusObj == nil {
|
||||
return status, nil
|
||||
}
|
||||
statusModel := statusObj.(*certStatusModelv1)
|
||||
status = core.CertificateStatus{
|
||||
Serial: statusModel.Serial,
|
||||
SubscriberApproved: statusModel.SubscriberApproved,
|
||||
Status: statusModel.Status,
|
||||
OCSPLastUpdated: statusModel.OCSPLastUpdated,
|
||||
RevokedDate: statusModel.RevokedDate,
|
||||
RevokedReason: statusModel.RevokedReason,
|
||||
LastExpirationNagSent: statusModel.LastExpirationNagSent,
|
||||
OCSPResponse: statusModel.OCSPResponse,
|
||||
LockCol: statusModel.LockCol,
|
||||
}
|
||||
statusObj, err := ssa.dbMap.Get(certStatusModel{}, serial)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
if statusObj == nil {
|
||||
return status, nil
|
||||
}
|
||||
statusModel := statusObj.(*certStatusModel)
|
||||
status = core.CertificateStatus{
|
||||
Serial: statusModel.Serial,
|
||||
SubscriberApproved: statusModel.SubscriberApproved,
|
||||
Status: statusModel.Status,
|
||||
OCSPLastUpdated: statusModel.OCSPLastUpdated,
|
||||
RevokedDate: statusModel.RevokedDate,
|
||||
RevokedReason: statusModel.RevokedReason,
|
||||
LastExpirationNagSent: statusModel.LastExpirationNagSent,
|
||||
OCSPResponse: statusModel.OCSPResponse,
|
||||
NotAfter: statusModel.NotAfter,
|
||||
IsExpired: statusModel.IsExpired,
|
||||
LockCol: statusModel.LockCol,
|
||||
}
|
||||
|
||||
return status, nil
|
||||
|
|
@ -520,13 +498,7 @@ func (ssa *SQLStorageAuthority) MarkCertificateRevoked(ctx context.Context, seri
|
|||
}
|
||||
|
||||
const statusQuery = "WHERE serial = ?"
|
||||
var statusObj interface{}
|
||||
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
statusObj, err = SelectCertificateStatusv2(tx, statusQuery, serial)
|
||||
} else {
|
||||
statusObj, err = SelectCertificateStatus(tx, statusQuery, serial)
|
||||
}
|
||||
statusObj, err := SelectCertificateStatus(tx, statusQuery, serial)
|
||||
if err == sql.ErrNoRows {
|
||||
err = fmt.Errorf("No certificate with serial %s", serial)
|
||||
err = Rollback(tx, err)
|
||||
|
|
@ -539,19 +511,10 @@ func (ssa *SQLStorageAuthority) MarkCertificateRevoked(ctx context.Context, seri
|
|||
|
||||
var n int64
|
||||
now := ssa.clk.Now()
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
status := statusObj.(certStatusModelv2)
|
||||
status.Status = core.OCSPStatusRevoked
|
||||
status.RevokedDate = now
|
||||
status.RevokedReason = reasonCode
|
||||
n, err = tx.Update(&status)
|
||||
} else {
|
||||
status := statusObj.(certStatusModelv1)
|
||||
status.Status = core.OCSPStatusRevoked
|
||||
status.RevokedDate = now
|
||||
status.RevokedReason = reasonCode
|
||||
n, err = tx.Update(&status)
|
||||
}
|
||||
statusObj.Status = core.OCSPStatusRevoked
|
||||
statusObj.RevokedDate = now
|
||||
statusObj.RevokedReason = reasonCode
|
||||
n, err = tx.Update(&statusObj)
|
||||
if err != nil {
|
||||
err = Rollback(tx, err)
|
||||
return err
|
||||
|
|
@ -807,32 +770,16 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, certDER []by
|
|||
Expires: parsedCertificate.NotAfter,
|
||||
}
|
||||
|
||||
var certStatusOb interface{}
|
||||
if features.Enabled(features.CertStatusOptimizationsMigrated) {
|
||||
certStatusOb = &certStatusModelv2{
|
||||
certStatusModelv1: certStatusModelv1{
|
||||
SubscriberApproved: false,
|
||||
Status: core.OCSPStatus("good"),
|
||||
OCSPLastUpdated: time.Time{},
|
||||
OCSPResponse: []byte{},
|
||||
Serial: serial,
|
||||
RevokedDate: time.Time{},
|
||||
RevokedReason: 0,
|
||||
LockCol: 0,
|
||||
},
|
||||
NotAfter: parsedCertificate.NotAfter,
|
||||
}
|
||||
} else {
|
||||
certStatusOb = &certStatusModelv1{
|
||||
SubscriberApproved: false,
|
||||
Status: core.OCSPStatus("good"),
|
||||
OCSPLastUpdated: time.Time{},
|
||||
OCSPResponse: []byte{},
|
||||
Serial: serial,
|
||||
RevokedDate: time.Time{},
|
||||
RevokedReason: 0,
|
||||
LockCol: 0,
|
||||
}
|
||||
certStatusOb := &certStatusModel{
|
||||
SubscriberApproved: false,
|
||||
Status: core.OCSPStatus("good"),
|
||||
OCSPLastUpdated: time.Time{},
|
||||
OCSPResponse: []byte{},
|
||||
Serial: serial,
|
||||
RevokedDate: time.Time{},
|
||||
RevokedReason: 0,
|
||||
LockCol: 0,
|
||||
NotAfter: parsedCertificate.NotAfter,
|
||||
}
|
||||
|
||||
tx, err := ssa.dbMap.Begin()
|
||||
|
|
|
|||
|
|
@ -426,15 +426,6 @@ func TestGetValidAuthorizationsMultiple(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAddCertificate(t *testing.T) {
|
||||
// Enable the feature for the `CertStatusOptimizationsMigrated` flag so that
|
||||
// adding a new certificate will populate the `certificateStatus.NotAfter`
|
||||
// field correctly. This will let the unit test assertion for `NotAfter`
|
||||
// pass provided everything is working as intended. Note: this must be done
|
||||
// **before** the DbMap is created in `initSA()` or the feature flag won't be
|
||||
// set correctly at the time the table maps are set up.
|
||||
_ = features.Set(map[string]bool{"CertStatusOptimizationsMigrated": true})
|
||||
defer features.Reset()
|
||||
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,6 @@
|
|||
"saService": {
|
||||
"serverAddresses": ["sa.boulder:9095"],
|
||||
"timeout": "15s"
|
||||
},
|
||||
"features": {
|
||||
"CertStatusOptimizationsMigrated": true
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@
|
|||
"timeout": "15s"
|
||||
},
|
||||
"features": {
|
||||
"ResubmitMissingSCTsOnly": true,
|
||||
"CertStatusOptimizationsMigrated": true
|
||||
"ResubmitMissingSCTsOnly": true
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@
|
|||
"serviceQueue": "SA.server"
|
||||
},
|
||||
"features": {
|
||||
"AllowAccountDeactivation": true,
|
||||
"CertStatusOptimizationsMigrated": true
|
||||
"AllowAccountDeactivation": true
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1841,7 +1841,7 @@ func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
|
|||
|
||||
mux := wfe.Handler()
|
||||
s := httptest.NewServer(mux)
|
||||
// TODO(#1989): Close s
|
||||
defer s.Close()
|
||||
req, _ := http.NewRequest("HEAD", s.URL+"/acme/cert/0000000000000000000000000000000000b2", nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue