1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
package nosql
import (
"context"
"encoding/json"
"sync"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/nosql"
)
// Mutex for locking ordersByAccount index operations.
var ordersByAccountMux sync.Mutex
type dbOrder struct {
ID string `json:"id"`
AccountID string `json:"accountID"`
ProvisionerID string `json:"provisionerID"`
Identifiers []acme.Identifier `json:"identifiers"`
AuthorizationIDs []string `json:"authorizationIDs"`
Status acme.Status `json:"status"`
NotBefore time.Time `json:"notBefore,omitempty"`
NotAfter time.Time `json:"notAfter,omitempty"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt,omitempty"`
CertificateID string `json:"certificate,omitempty"`
Error *acme.Error `json:"error,omitempty"`
}
func (a *dbOrder) clone() *dbOrder {
b := *a
return &b
}
// getDBOrder retrieves and unmarshals an ACME Order type from the database.
func (db *DB) getDBOrder(ctx context.Context, id string) (*dbOrder, error) {
b, err := db.db.Get(orderTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "order %s not found", id)
} else if err != nil {
return nil, errors.Wrapf(err, "error loading order %s", id)
}
o := new(dbOrder)
if err := json.Unmarshal(b, &o); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling order %s into dbOrder", id)
}
return o, nil
}
// GetOrder retrieves an ACME Order from the database.
func (db *DB) GetOrder(ctx context.Context, id string) (*acme.Order, error) {
dbo, err := db.getDBOrder(ctx, id)
if err != nil {
return nil, err
}
o := &acme.Order{
ID: dbo.ID,
AccountID: dbo.AccountID,
ProvisionerID: dbo.ProvisionerID,
CertificateID: dbo.CertificateID,
Status: dbo.Status,
ExpiresAt: dbo.ExpiresAt,
Identifiers: dbo.Identifiers,
NotBefore: dbo.NotBefore,
NotAfter: dbo.NotAfter,
AuthorizationIDs: dbo.AuthorizationIDs,
Error: dbo.Error,
}
return o, nil
}
// CreateOrder creates ACME Order resources and saves them to the DB.
func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
var err error
o.ID, err = randID()
if err != nil {
return err
}
now := clock.Now()
dbo := &dbOrder{
ID: o.ID,
AccountID: o.AccountID,
ProvisionerID: o.ProvisionerID,
Status: o.Status,
CreatedAt: now,
ExpiresAt: o.ExpiresAt,
Identifiers: o.Identifiers,
NotBefore: o.NotBefore,
NotAfter: o.NotAfter,
AuthorizationIDs: o.AuthorizationIDs,
}
if err := db.save(ctx, o.ID, dbo, nil, "order", orderTable); err != nil {
return err
}
_, err = db.updateAddOrderIDs(ctx, o.AccountID, o.ID)
if err != nil {
return err
}
return nil
}
// UpdateOrder saves an updated ACME Order to the database.
func (db *DB) UpdateOrder(ctx context.Context, o *acme.Order) error {
old, err := db.getDBOrder(ctx, o.ID)
if err != nil {
return err
}
nu := old.clone()
nu.Status = o.Status
nu.Error = o.Error
nu.CertificateID = o.CertificateID
return db.save(ctx, old.ID, nu, old, "order", orderTable)
}
func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...string) ([]string, error) {
ordersByAccountMux.Lock()
defer ordersByAccountMux.Unlock()
var oldOids []string
b, err := db.db.Get(ordersByAccountIDTable, []byte(accID))
if err != nil {
if !nosql.IsErrNotFound(err) {
return nil, errors.Wrapf(err, "error loading orderIDs for account %s", accID)
}
} else {
if err := json.Unmarshal(b, &oldOids); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling orderIDs for account %s", accID)
}
}
// Remove any order that is not in PENDING state and update the stored list
// before returning.
//
// According to RFC 8555:
// The server SHOULD include pending orders and SHOULD NOT include orders
// that are invalid in the array of URLs.
pendOids := []string{}
for _, oid := range oldOids {
o, err := db.GetOrder(ctx, oid)
if err != nil {
return nil, acme.WrapErrorISE(err, "error loading order %s for account %s", oid, accID)
}
if err = o.UpdateStatus(ctx, db); err != nil {
return nil, acme.WrapErrorISE(err, "error updating order %s for account %s", oid, accID)
}
if o.Status == acme.StatusPending {
pendOids = append(pendOids, oid)
}
}
pendOids = append(pendOids, addOids...)
var (
_old interface{} = oldOids
_new interface{} = pendOids
)
switch {
case len(oldOids) == 0 && len(pendOids) == 0:
// If list has not changed from empty, then no need to write the DB.
return []string{}, nil
case len(oldOids) == 0:
_old = nil
case len(pendOids) == 0:
_new = nil
}
if err = db.save(ctx, accID, _new, _old, "orderIDsByAccountID", ordersByAccountIDTable); err != nil {
// Delete all orders that may have been previously stored if orderIDsByAccountID update fails.
for _, oid := range addOids {
// Ignore error from delete -- we tried our best.
// TODO when we have logging w/ request ID tracking, logging this error.
db.db.Del(orderTable, []byte(oid))
}
return nil, errors.Wrapf(err, "error saving orderIDs index for account %s", accID)
}
return pendOids, nil
}
// GetOrdersByAccountID returns a list of order IDs owned by the account.
func (db *DB) GetOrdersByAccountID(ctx context.Context, accID string) ([]string, error) {
return db.updateAddOrderIDs(ctx, accID)
}
|