Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SA: store and return certificate profile name #7352

Merged
merged 15 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ type Config struct {
// a 'orderID' matching the finalized order to true. This will occur
// inside of the finalize (order) transaction.
TrackReplacementCertificatesARI bool

// MultipleCertificateProfiles, when enabled, triggers the following
// behavior:
// - SA.NewOrderAndAuthzs: upon receiving a NewOrderRequest with a
// `certificateProfileName` value, will add that value to the database's
// `orders.certificateProfileName` column. Values in this column are
// allowed to be empty.
MultipleCertificateProfiles bool
}

var fMu = new(sync.RWMutex)
Expand Down
24 changes: 13 additions & 11 deletions mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,9 @@ func (sa *StorageAuthority) NewOrderAndAuthzs(_ context.Context, req *sapb.NewOr
Id: rand.Int63(),
Created: timestamppb.Now(),
// A new order is never processing because it can't have been finalized yet.
BeganProcessing: false,
Status: string(core.StatusPending),
BeganProcessing: false,
Status: string(core.StatusPending),
CertificateProfileName: req.NewOrder.CertificateProfileName,
}
return response, nil
}
Expand Down Expand Up @@ -420,15 +421,16 @@ func (sa *StorageAuthorityReadOnly) GetOrder(_ context.Context, req *sapb.OrderR
created := now.AddDate(-30, 0, 0)
exp := now.AddDate(30, 0, 0)
validOrder := &corepb.Order{
Id: req.Id,
RegistrationID: 1,
Created: timestamppb.New(created),
Expires: timestamppb.New(exp),
Names: []string{"example.com"},
Status: string(core.StatusValid),
V2Authorizations: []int64{1},
CertificateSerial: "serial",
Error: nil,
Id: req.Id,
RegistrationID: 1,
Created: timestamppb.New(created),
Expires: timestamppb.New(exp),
Names: []string{"example.com"},
Status: string(core.StatusValid),
V2Authorizations: []int64{1},
CertificateSerial: "serial",
Error: nil,
CertificateProfileName: "defaultBoulderCertificateProfile",
}

// Order ID doesn't have a certificate serial yet
Expand Down
10 changes: 8 additions & 2 deletions sa/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"time"

"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/borp"
"github.com/prometheus/client_golang/prometheus"

"github.com/letsencrypt/borp"

"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
boulderDB "github.com/letsencrypt/boulder/db"
"github.com/letsencrypt/boulder/features"
blog "github.com/letsencrypt/boulder/log"
)

Expand Down Expand Up @@ -271,7 +273,11 @@ func initTables(dbMap *borp.DbMap) {
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(true, "ID")
dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(true, "ID")
dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
dbMap.AddTableWithName(orderModel{}, "orders").SetKeys(true, "ID")
if features.Get().MultipleCertificateProfiles {
dbMap.AddTableWithName(orderModelv2{}, "orders").SetKeys(true, "ID")
} else {
dbMap.AddTableWithName(orderModelv1{}, "orders").SetKeys(true, "ID")
}
dbMap.AddTableWithName(orderToAuthzModel{}, "orderToAuthz").SetKeys(false, "OrderID", "AuthzID")
dbMap.AddTableWithName(requestedNameModel{}, "requestedNames").SetKeys(false, "OrderID")
dbMap.AddTableWithName(orderFQDNSet{}, "orderFqdnSets").SetKeys(true, "ID")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied

ALTER TABLE `orders` ADD COLUMN `certificateProfileName` varchar(32) DEFAULT NULL;

-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back

ALTER TABLE `orders` DROP COLUMN `certificateProfileName`;
70 changes: 66 additions & 4 deletions sa/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ type precertificateModel struct {
Expires time.Time
}

type orderModel struct {
// TODO(#7324) orderModelv1 is deprecated, use orderModelv2 moving forward.
type orderModelv1 struct {
ID int64
RegistrationID int64
Expires time.Time
Expand All @@ -383,6 +384,17 @@ type orderModel struct {
BeganProcessing bool
}

type orderModelv2 struct {
ID int64
RegistrationID int64
Expires time.Time
Created time.Time
Error []byte
CertificateSerial string
BeganProcessing bool
pgporada marked this conversation as resolved.
Show resolved Hide resolved
CertificateProfileName string
}

type requestedNameModel struct {
ID int64
OrderID int64
Expand All @@ -394,8 +406,9 @@ type orderToAuthzModel struct {
AuthzID int64
}

func orderToModel(order *corepb.Order) (*orderModel, error) {
om := &orderModel{
// TODO(#7324) orderToModelv1 is deprecated, use orderModelv2 moving forward.
func orderToModelv1(order *corepb.Order) (*orderModelv1, error) {
om := &orderModelv1{
ID: order.Id,
RegistrationID: order.RegistrationID,
Expires: order.Expires.AsTime(),
Expand All @@ -417,7 +430,8 @@ func orderToModel(order *corepb.Order) (*orderModel, error) {
return om, nil
}

func modelToOrder(om *orderModel) (*corepb.Order, error) {
// TODO(#7324) modelToOrderv1 is deprecated, use orderModelv2 moving forward.
func modelToOrderv1(om *orderModelv1) (*corepb.Order, error) {
order := &corepb.Order{
Id: om.ID,
RegistrationID: om.RegistrationID,
Expand All @@ -440,6 +454,54 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) {
return order, nil
}

func orderToModelv2(order *corepb.Order) (*orderModelv2, error) {
om := &orderModelv2{
ID: order.Id,
RegistrationID: order.RegistrationID,
Expires: order.Expires.AsTime(),
Created: order.Created.AsTime(),
BeganProcessing: order.BeganProcessing,
CertificateSerial: order.CertificateSerial,
CertificateProfileName: order.CertificateProfileName,
}

if order.Error != nil {
errJSON, err := json.Marshal(order.Error)
if err != nil {
return nil, err
}
if len(errJSON) > mediumBlobSize {
return nil, fmt.Errorf("Error object is too large to store in the database")
}
om.Error = errJSON
}
return om, nil
}

func modelToOrderv2(om *orderModelv2) (*corepb.Order, error) {
order := &corepb.Order{
Id: om.ID,
RegistrationID: om.RegistrationID,
Expires: timestamppb.New(om.Expires),
Created: timestamppb.New(om.Created),
CertificateSerial: om.CertificateSerial,
BeganProcessing: om.BeganProcessing,
CertificateProfileName: om.CertificateProfileName,
}
if len(om.Error) > 0 {
var problem corepb.ProblemDetails
err := json.Unmarshal(om.Error, &problem)
if err != nil {
return &corepb.Order{}, badJSONError(
"failed to unmarshal order model's error",
om.Error,
err)
}
order.Error = &problem
}
return order, nil
}

var challTypeToUint = map[string]uint8{
"http-01": 0,
"dns-01": 1,
Expand Down
42 changes: 39 additions & 3 deletions sa/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import (
"time"

"github.com/jmhodges/clock"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/letsencrypt/boulder/db"
"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/grpc"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/test/vars"
"google.golang.org/protobuf/types/known/timestamppb"

pgporada marked this conversation as resolved.
Show resolved Hide resolved
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
Expand Down Expand Up @@ -248,15 +249,50 @@ func TestAuthzModel(t *testing.T) {
// validation error JSON field to an Order produces the expected bad JSON error.
func TestModelToOrderBadJSON(t *testing.T) {
badJSON := []byte(`{`)
_, err := modelToOrder(&orderModel{
_, err := modelToOrderv2(&orderModelv2{
Error: badJSON,
})
test.AssertError(t, err, "expected error from modelToOrder")
test.AssertError(t, err, "expected error from modelToOrderv2")
var badJSONErr errBadJSON
test.AssertErrorWraps(t, err, &badJSONErr)
test.AssertEquals(t, string(badJSONErr.json), string(badJSON))
}

func TestOrderModelThereAndBackAgain(t *testing.T) {
clk := clock.New()
now := clk.Now()
order := &corepb.Order{
Id: 0,
RegistrationID: 2016,
Expires: timestamppb.New(now.Add(24 * time.Hour)),
Created: timestamppb.New(now),
Error: nil,
CertificateSerial: "1",
BeganProcessing: true,
}
model1, err := orderToModelv1(order)
test.AssertNotError(t, err, "orderToModelv1 should not have errored")
returnOrder, err := modelToOrderv1(model1)
test.AssertNotError(t, err, "modelToOrderv1 should not have errored")
test.AssertDeepEquals(t, order, returnOrder)

anotherOrder := &corepb.Order{
Id: 1,
RegistrationID: 2024,
Expires: timestamppb.New(now.Add(24 * time.Hour)),
Created: timestamppb.New(now),
Error: nil,
CertificateSerial: "2",
BeganProcessing: true,
CertificateProfileName: "phljny",
}
model2, err := orderToModelv2(anotherOrder)
test.AssertNotError(t, err, "orderToModelv2 should not have errored")
returnOrder, err = modelToOrderv2(model2)
test.AssertNotError(t, err, "modelToOrderv2 should not have errored")
test.AssertDeepEquals(t, anotherOrder, returnOrder)
}

// TestPopulateAttemptedFieldsBadJSON tests that populating a challenge from an
// authz2 model with an invalid validation error or an invalid validation record
// produces the expected bad JSON error.
Expand Down
45 changes: 32 additions & 13 deletions sa/sa.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,12 +498,27 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
}

// Second, insert the new order.
order := &orderModel{
RegistrationID: req.NewOrder.RegistrationID,
Expires: req.NewOrder.Expires.AsTime(),
Created: ssa.clk.Now(),
var orderID int64
var err error
created := ssa.clk.Now()
if features.Get().MultipleCertificateProfiles {
omv2 := orderModelv2{
RegistrationID: req.NewOrder.RegistrationID,
Expires: req.NewOrder.Expires.AsTime(),
Created: created,
CertificateProfileName: req.NewOrder.CertificateProfileName,
}
err = tx.Insert(ctx, &omv2)
orderID = omv2.ID
} else {
omv1 := orderModelv1{
RegistrationID: req.NewOrder.RegistrationID,
Expires: req.NewOrder.Expires.AsTime(),
Created: created,
}
err = tx.Insert(ctx, &omv1)
orderID = omv1.ID
}
err := tx.Insert(ctx, order)
if err != nil {
return nil, err
}
Expand All @@ -514,13 +529,13 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, err
}
for _, id := range req.NewOrder.V2Authorizations {
err = inserter.Add([]interface{}{order.ID, id})
err := inserter.Add([]interface{}{orderID, id})
if err != nil {
return nil, err
}
}
for _, id := range newAuthzIDs {
err = inserter.Add([]interface{}{order.ID, id})
err := inserter.Add([]interface{}{orderID, id})
if err != nil {
return nil, err
}
Expand All @@ -536,7 +551,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, err
}
for _, name := range req.NewOrder.Names {
err = inserter.Add([]interface{}{order.ID, ReverseName(name)})
err := inserter.Add([]interface{}{orderID, ReverseName(name)})
if err != nil {
return nil, err
}
Expand All @@ -547,16 +562,16 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
}

// Fifth, insert the FQDNSet entry for the order.
err = addOrderFQDNSet(ctx, tx, req.NewOrder.Names, order.ID, order.RegistrationID, order.Expires)
err = addOrderFQDNSet(ctx, tx, req.NewOrder.Names, orderID, req.NewOrder.RegistrationID, req.NewOrder.Expires.AsTime())
if err != nil {
return nil, err
}

// Finally, build the overall Order PB.
res := &corepb.Order{
// ID and Created were auto-populated on the order model when it was inserted.
Id: order.ID,
Created: timestamppb.New(order.Created),
Id: orderID,
Created: timestamppb.New(created),
// These are carried over from the original request unchanged.
RegistrationID: req.NewOrder.RegistrationID,
Expires: req.NewOrder.Expires,
Expand All @@ -565,12 +580,16 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
V2Authorizations: append(req.NewOrder.V2Authorizations, newAuthzIDs...),
// A new order is never processing because it can't be finalized yet.
BeganProcessing: false,
// An empty string is allowed. When the RA retrieves the order and
// transmits it to the CA, the empty string will take the value of
// DefaultCertProfileName from the //issuance package.
CertificateProfileName: req.NewOrder.CertificateProfileName,
}

if req.NewOrder.ReplacesSerial != "" {
// Update the replacementOrders table to indicate that this order
// replaces the provided certificate serial.
err := addReplacementOrder(ctx, tx, req.NewOrder.ReplacesSerial, order.ID, order.Expires)
err := addReplacementOrder(ctx, tx, req.NewOrder.ReplacesSerial, orderID, req.NewOrder.Expires.AsTime())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -643,7 +662,7 @@ func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.Set
return nil, errIncompleteRequest
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
om, err := orderToModel(&corepb.Order{
om, err := orderToModelv2(&corepb.Order{
Id: req.Id,
Error: req.Error,
})
Expand Down
Loading