Refactor ACME EAB queries

The ACME EAB keys are now also indexed by the provisioner. This
solves part of the issue in which too many EAB keys may be in
memory at a given time.
pull/650/head
Herman Slatman 2 years ago
parent 30859d3c83
commit ef16febf40
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -45,13 +45,13 @@ func KeyToID(jwk *jose.JSONWebKey) (string, error) {
// ExternalAccountKey is an ACME External Account Binding key.
type ExternalAccountKey struct {
ID string `json:"id"`
Provisioner string `json:"provisioner"`
Reference string `json:"reference"`
AccountID string `json:"-"`
KeyBytes []byte `json:"-"`
CreatedAt time.Time `json:"createdAt"`
BoundAt time.Time `json:"boundAt,omitempty"`
ID string `json:"id"`
ProvisionerID string `json:"provisionerID"`
Reference string `json:"reference"`
AccountID string `json:"-"`
KeyBytes []byte `json:"-"`
CreatedAt time.Time `json:"createdAt"`
BoundAt time.Time `json:"boundAt,omitempty"`
}
// AlreadyBound returns whether this EAK is already bound to

@ -92,10 +92,10 @@ func TestExternalAccountKey_BindTo(t *testing.T) {
{
name: "ok",
eak: &ExternalAccountKey{
ID: "eakID",
Provisioner: "prov",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
ID: "eakID",
ProvisionerID: "provID",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
},
acct: &Account{
ID: "accountID",
@ -105,12 +105,12 @@ func TestExternalAccountKey_BindTo(t *testing.T) {
{
name: "fail/already-bound",
eak: &ExternalAccountKey{
ID: "eakID",
Provisioner: "prov",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
AccountID: "someAccountID",
BoundAt: boundAt,
ID: "eakID",
ProvisionerID: "provID",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
AccountID: "someAccountID",
BoundAt: boundAt,
},
acct: &Account{
ID: "accountID",

@ -144,7 +144,7 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
api.WriteError(w, err)
return
}
if err := h.db.UpdateExternalAccountKey(ctx, prov.Name, eak); err != nil {
if err := h.db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error updating external account binding key"))
return
}
@ -274,7 +274,7 @@ func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAc
return nil, acmeErr
}
externalAccountKey, err := h.db.GetExternalAccountKey(ctx, acmeProv.Name, keyID)
externalAccountKey, err := h.db.GetExternalAccountKey(ctx, acmeProv.ID, keyID)
if err != nil {
if _, ok := err.(*acme.Error); ok {
return nil, acme.WrapError(acme.ErrorUnauthorizedType, err, "the field 'kid' references an unknown key")

@ -351,6 +351,7 @@ func TestHandler_NewAccount(t *testing.T) {
prov := newProv()
escProvName := url.PathEscape(prov.GetName())
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
provID := prov.GetID()
type test struct {
db acme.DB
@ -554,11 +555,11 @@ func TestHandler_NewAccount(t *testing.T) {
ctx = context.WithValue(ctx, provisionerContextKey, prov)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
eak := &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
}
return test{
db: &acme.MockDB{
@ -731,11 +732,11 @@ func TestHandler_NewAccount(t *testing.T) {
},
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
}, nil
},
MockUpdateExternalAccountKey: func(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error {
@ -1056,6 +1057,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
acmeProv := newACMEProv(t)
escProvName := url.PathEscape(acmeProv.GetName())
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
provID := acmeProv.GetID()
type test struct {
db acme.DB
ctx context.Context
@ -1128,11 +1130,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
}, nil
},
},
@ -1142,11 +1144,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
ExternalAccountBinding: eab,
},
eak: &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
},
err: nil,
}
@ -1492,12 +1494,12 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
CreatedAt: createdAt,
AccountID: "some-account-id",
BoundAt: boundAt,
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
CreatedAt: createdAt,
AccountID: "some-account-id",
BoundAt: boundAt,
}, nil
},
},
@ -1549,11 +1551,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 2, 3, 4},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 2, 3, 4},
CreatedAt: time.Now(),
}, nil
},
},
@ -1607,11 +1609,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
}, nil
},
},
@ -1662,11 +1664,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
}, nil
},
},
@ -1718,11 +1720,11 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
ID: "eakID",
ProvisionerID: provID,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(),
}, nil
},
},
@ -1765,7 +1767,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
assert.NotNil(t, tc.eak)
assert.Equals(t, got.ID, tc.eak.ID)
assert.Equals(t, got.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, got.Provisioner, tc.eak.Provisioner)
assert.Equals(t, got.ProvisionerID, tc.eak.ProvisionerID)
assert.Equals(t, got.Reference, tc.eak.Reference)
assert.Equals(t, got.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, got.AccountID, tc.eak.AccountID)

@ -19,12 +19,12 @@ type DB interface {
GetAccountByKeyID(ctx context.Context, kid string) (*Account, error)
UpdateAccount(ctx context.Context, acc *Account) error
CreateExternalAccountKey(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
GetExternalAccountKey(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
GetExternalAccountKeyByReference(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
DeleteExternalAccountKey(ctx context.Context, provisionerName, keyID string) error
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error)
GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error
UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
CreateNonce(ctx context.Context) (Nonce, error)
DeleteNonce(ctx context.Context, nonce Nonce) error
@ -56,12 +56,12 @@ type MockDB struct {
MockGetAccountByKeyID func(ctx context.Context, kid string) (*Account, error)
MockUpdateAccount func(ctx context.Context, acc *Account) error
MockCreateExternalAccountKey func(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
MockGetExternalAccountKey func(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
MockDeleteExternalAccountKey func(ctx context.Context, provisionerName, keyID string) error
MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
MockCreateExternalAccountKey func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
MockGetExternalAccountKey func(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
MockGetExternalAccountKeys func(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error)
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
MockDeleteExternalAccountKey func(ctx context.Context, provisionerID, keyID string) error
MockUpdateExternalAccountKey func(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
MockCreateNonce func(ctx context.Context) (Nonce, error)
MockDeleteNonce func(ctx context.Context, nonce Nonce) error
@ -129,9 +129,9 @@ func (m *MockDB) UpdateAccount(ctx context.Context, acc *Account) error {
}
// CreateExternalAccountKey mock
func (m *MockDB) CreateExternalAccountKey(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error) {
func (m *MockDB) CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error) {
if m.MockCreateExternalAccountKey != nil {
return m.MockCreateExternalAccountKey(ctx, provisionerName, reference)
return m.MockCreateExternalAccountKey(ctx, provisionerID, reference)
} else if m.MockError != nil {
return nil, m.MockError
}
@ -139,9 +139,9 @@ func (m *MockDB) CreateExternalAccountKey(ctx context.Context, provisionerName,
}
// GetExternalAccountKey mock
func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error) {
func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error) {
if m.MockGetExternalAccountKey != nil {
return m.MockGetExternalAccountKey(ctx, provisionerName, keyID)
return m.MockGetExternalAccountKey(ctx, provisionerID, keyID)
} else if m.MockError != nil {
return nil, m.MockError
}
@ -149,9 +149,9 @@ func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerName, key
}
// GetExternalAccountKeys mock
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error) {
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error) {
if m.MockGetExternalAccountKeys != nil {
return m.MockGetExternalAccountKeys(ctx, provisionerName)
return m.MockGetExternalAccountKeys(ctx, provisionerID)
} else if m.MockError != nil {
return nil, m.MockError
}
@ -159,9 +159,9 @@ func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName str
}
// GetExternalAccountKeyByReference mock
func (m *MockDB) GetExternalAccountKeyByReference(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error) {
func (m *MockDB) GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error) {
if m.MockGetExternalAccountKeyByReference != nil {
return m.MockGetExternalAccountKeyByReference(ctx, provisionerName, reference)
return m.MockGetExternalAccountKeyByReference(ctx, provisionerID, reference)
} else if m.MockError != nil {
return nil, m.MockError
}
@ -169,9 +169,9 @@ func (m *MockDB) GetExternalAccountKeyByReference(ctx context.Context, provision
}
// DeleteExternalAccountKey mock
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, provisionerName, keyID string) error {
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error {
if m.MockDeleteExternalAccountKey != nil {
return m.MockDeleteExternalAccountKey(ctx, provisionerName, keyID)
return m.MockDeleteExternalAccountKey(ctx, provisionerID, keyID)
} else if m.MockError != nil {
return m.MockError
}
@ -179,9 +179,9 @@ func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, provisionerName,
}
// UpdateExternalAccountKey mock
func (m *MockDB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error {
func (m *MockDB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error {
if m.MockUpdateExternalAccountKey != nil {
return m.MockUpdateExternalAccountKey(ctx, provisionerName, eak)
return m.MockUpdateExternalAccountKey(ctx, provisionerID, eak)
} else if m.MockError != nil {
return m.MockError
}

@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/json"
"sync"
"time"
"github.com/pkg/errors"
@ -12,6 +13,9 @@ import (
"go.step.sm/crypto/jose"
)
// Mutex for locking referencesByProvisioner index operations.
var referencesByProvisionerIndexMux sync.Mutex
// dbAccount represents an ACME account.
type dbAccount struct {
ID string `json:"id"`
@ -28,13 +32,13 @@ func (dba *dbAccount) clone() *dbAccount {
}
type dbExternalAccountKey struct {
ID string `json:"id"`
Provisioner string `json:"provisioner"`
Reference string `json:"reference"`
AccountID string `json:"accountID,omitempty"`
KeyBytes []byte `json:"key"`
CreatedAt time.Time `json:"createdAt"`
BoundAt time.Time `json:"boundAt"`
ID string `json:"id"`
ProvisionerID string `json:"provisionerID"`
Reference string `json:"reference"`
AccountID string `json:"accountID,omitempty"`
KeyBytes []byte `json:"key"`
CreatedAt time.Time `json:"createdAt"`
BoundAt time.Time `json:"boundAt"`
}
type dbExternalAccountKeyReference struct {
@ -170,7 +174,7 @@ func (db *DB) UpdateAccount(ctx context.Context, acc *acme.Account) error {
}
// CreateExternalAccountKey creates a new External Account Binding key with a name
func (db *DB) CreateExternalAccountKey(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
func (db *DB) CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
keyID, err := randID()
if err != nil {
return nil, err
@ -183,106 +187,125 @@ func (db *DB) CreateExternalAccountKey(ctx context.Context, provisionerName, ref
}
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: provisionerName,
Reference: reference,
KeyBytes: random,
CreatedAt: clock.Now(),
ID: keyID,
ProvisionerID: provisionerID,
Reference: reference,
KeyBytes: random,
CreatedAt: clock.Now(),
}
if err := db.save(ctx, keyID, dbeak, nil, "external_account_key", externalAccountKeyTable); err != nil {
return nil, err
}
if err := db.addEAKID(ctx, provisionerID, dbeak.ID); err != nil {
return nil, err
}
if dbeak.Reference != "" {
dbExternalAccountKeyReference := &dbExternalAccountKeyReference{
Reference: dbeak.Reference,
ExternalAccountKeyID: dbeak.ID,
}
if err := db.save(ctx, dbeak.Reference, dbExternalAccountKeyReference, nil, "external_account_key_reference", externalAccountKeysByReferenceTable); err != nil {
if err := db.save(ctx, referenceKey(provisionerID, dbeak.Reference), dbExternalAccountKeyReference, nil, "external_account_key_reference", externalAccountKeysByReferenceTable); err != nil {
return nil, err
}
}
return &acme.ExternalAccountKey{
ID: dbeak.ID,
Provisioner: dbeak.Provisioner,
Reference: dbeak.Reference,
AccountID: dbeak.AccountID,
KeyBytes: dbeak.KeyBytes,
CreatedAt: dbeak.CreatedAt,
BoundAt: dbeak.BoundAt,
ID: dbeak.ID,
ProvisionerID: dbeak.ProvisionerID,
Reference: dbeak.Reference,
AccountID: dbeak.AccountID,
KeyBytes: dbeak.KeyBytes,
CreatedAt: dbeak.CreatedAt,
BoundAt: dbeak.BoundAt,
}, nil
}
// GetExternalAccountKey retrieves an External Account Binding key by KeyID
func (db *DB) GetExternalAccountKey(ctx context.Context, provisionerName, keyID string) (*acme.ExternalAccountKey, error) {
func (db *DB) GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*acme.ExternalAccountKey, error) {
dbeak, err := db.getDBExternalAccountKey(ctx, keyID)
if err != nil {
return nil, err
}
if dbeak.Provisioner != provisionerName {
return nil, acme.NewError(acme.ErrorUnauthorizedType, "name of provisioner does not match provisioner for which the EAB key was created")
if dbeak.ProvisionerID != provisionerID {
return nil, acme.NewError(acme.ErrorUnauthorizedType, "provisioner does not match provisioner for which the EAB key was created")
}
return &acme.ExternalAccountKey{
ID: dbeak.ID,
Provisioner: dbeak.Provisioner,
Reference: dbeak.Reference,
AccountID: dbeak.AccountID,
KeyBytes: dbeak.KeyBytes,
CreatedAt: dbeak.CreatedAt,
BoundAt: dbeak.BoundAt,
ID: dbeak.ID,
ProvisionerID: dbeak.ProvisionerID,
Reference: dbeak.Reference,
AccountID: dbeak.AccountID,
KeyBytes: dbeak.KeyBytes,
CreatedAt: dbeak.CreatedAt,
BoundAt: dbeak.BoundAt,
}, nil
}
func (db *DB) DeleteExternalAccountKey(ctx context.Context, provisionerName, keyID string) error {
func (db *DB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error {
dbeak, err := db.getDBExternalAccountKey(ctx, keyID)
if err != nil {
return errors.Wrapf(err, "error loading ACME EAB Key with Key ID %s", keyID)
}
if dbeak.Provisioner != provisionerName {
return errors.New("name of provisioner does not match provisioner for which the EAB key was created")
if dbeak.ProvisionerID != provisionerID {
return errors.New("provisioner does not match provisioner for which the EAB key was created")
}
if dbeak.Reference != "" {
err = db.db.Del(externalAccountKeysByReferenceTable, []byte(dbeak.Reference))
if err != nil {
return errors.Wrapf(err, "error deleting ACME EAB Key Reference with Key ID %s and reference %s", keyID, dbeak.Reference)
if err := db.db.Del(externalAccountKeysByReferenceTable, []byte(referenceKey(provisionerID, dbeak.Reference))); err != nil {
return errors.Wrapf(err, "error deleting ACME EAB Key reference with Key ID %s and reference %s", keyID, dbeak.Reference)
}
}
if err = db.db.Del(externalAccountKeyTable, []byte(keyID)); err != nil {
if err := db.db.Del(externalAccountKeyTable, []byte(keyID)); err != nil {
return errors.Wrapf(err, "error deleting ACME EAB Key with Key ID %s", keyID)
}
if err := db.deleteEAKID(ctx, provisionerID, keyID); err != nil {
return errors.Wrapf(err, "error removing ACME EAB Key ID %s", keyID)
}
return nil
}
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
// TODO: mutex?
// TODO: lookup by provisioner based on index
entries, err := db.db.List(externalAccountKeyTable)
var eakIDs []string
r, err := db.db.Get(externalAccountKeysByProvisionerIDTable, []byte(provisionerID))
if err != nil {
return nil, err
if !nosqlDB.IsErrNotFound(err) {
return nil, errors.Wrapf(err, "error loading ACME EAB Key IDs for provisioner %s", provisionerID)
}
} else {
if err := json.Unmarshal(r, &eakIDs); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling ACME EAB Key IDs for provisioner %s", provisionerID)
}
}
keys := []*acme.ExternalAccountKey{}
for _, entry := range entries { // entries is sorted alphabetically on the key (ID) of the EAK; no need to sort this again.
dbeak := new(dbExternalAccountKey)
if err = json.Unmarshal(entry.Value, dbeak); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling external account key %s into ExternalAccountKey", string(entry.Key))
for _, eakID := range eakIDs {
if eakID == "" {
continue // shouldn't happen; just in case
}
if dbeak.Provisioner != provisionerName {
continue
eak, err := db.getDBExternalAccountKey(ctx, eakID)
if err != nil {
if !nosqlDB.IsErrNotFound(err) {
return nil, errors.Wrapf(err, "error retrieving ACME EAB Key for provisioner %s and keyID %s", provisionerID, eakID)
}
}
keys = append(keys, &acme.ExternalAccountKey{
ID: dbeak.ID,
KeyBytes: dbeak.KeyBytes,
Provisioner: dbeak.Provisioner,
Reference: dbeak.Reference,
AccountID: dbeak.AccountID,
CreatedAt: dbeak.CreatedAt,
BoundAt: dbeak.BoundAt,
ID: eak.ID,
KeyBytes: eak.KeyBytes,
ProvisionerID: eak.ProvisionerID,
Reference: eak.Reference,
AccountID: eak.AccountID,
CreatedAt: eak.CreatedAt,
BoundAt: eak.BoundAt,
})
}
@ -290,11 +313,12 @@ func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string
}
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
if reference == "" {
return nil, nil
}
k, err := db.db.Get(externalAccountKeysByReferenceTable, []byte(reference))
k, err := db.db.Get(externalAccountKeysByReferenceTable, []byte(referenceKey(provisionerID, reference)))
if nosqlDB.IsErrNotFound(err) {
return nil, acme.ErrNotFound
} else if err != nil {
@ -304,28 +328,139 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerN
if err := json.Unmarshal(k, dbExternalAccountKeyReference); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling ACME EAB key for reference %s", reference)
}
return db.GetExternalAccountKey(ctx, provisionerName, dbExternalAccountKeyReference.ExternalAccountKeyID)
return db.GetExternalAccountKey(ctx, provisionerID, dbExternalAccountKeyReference.ExternalAccountKeyID)
}
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error {
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *acme.ExternalAccountKey) error {
old, err := db.getDBExternalAccountKey(ctx, eak.ID)
if err != nil {
return err
}
if old.Provisioner != provisionerName {
return errors.New("name of provisioner does not match provisioner for which the EAB key was created")
if old.ProvisionerID != provisionerID {
return errors.New("provisioner does not match provisioner for which the EAB key was created")
}
if old.ProvisionerID != eak.ProvisionerID {
return errors.New("cannot change provisioner for an existing ACME EAB Key")
}
if old.Reference != eak.Reference {
return errors.New("cannot change reference for an existing ACME EAB Key")
}
nu := dbExternalAccountKey{
ID: eak.ID,
Provisioner: eak.Provisioner,
Reference: eak.Reference,
AccountID: eak.AccountID,
KeyBytes: eak.KeyBytes,
CreatedAt: eak.CreatedAt,
BoundAt: eak.BoundAt,
ID: eak.ID,
ProvisionerID: eak.ProvisionerID,
Reference: eak.Reference,
AccountID: eak.AccountID,
KeyBytes: eak.KeyBytes,
CreatedAt: eak.CreatedAt,
BoundAt: eak.BoundAt,
}
return db.save(ctx, nu.ID, nu, old, "external_account_key", externalAccountKeyTable)
}
func (db *DB) addEAKID(ctx context.Context, provisionerID, eakID string) error {
referencesByProvisionerIndexMux.Lock()
defer referencesByProvisionerIndexMux.Unlock()
var eakIDs []string
b, err := db.db.Get(externalAccountKeysByProvisionerIDTable, []byte(provisionerID))
if err != nil {
if !nosqlDB.IsErrNotFound(err) {
return errors.Wrapf(err, "error loading eakIDs for provisioner %s", provisionerID)
}
} else {
if err := json.Unmarshal(b, &eakIDs); err != nil {
return errors.Wrapf(err, "error unmarshaling eakIDs for provisioner %s", provisionerID)
}
}
var newEAKIDs []string
newEAKIDs = append(newEAKIDs, eakIDs...)
newEAKIDs = append(newEAKIDs, eakID)
var (
_old interface{} = eakIDs
_new interface{} = newEAKIDs
)
if len(eakIDs) == 0 {
_old = nil
}
if err = db.save(ctx, provisionerID, _new, _old, "externalAccountKeysByProvisionerID", externalAccountKeysByProvisionerIDTable); err != nil {
return errors.Wrapf(err, "error saving eakIDs index for provisioner %s", provisionerID)
}
return nil
}
func (db *DB) deleteEAKID(ctx context.Context, provisionerID, eakID string) error {
referencesByProvisionerIndexMux.Lock()
defer referencesByProvisionerIndexMux.Unlock()
var eakIDs []string
b, err := db.db.Get(externalAccountKeysByProvisionerIDTable, []byte(provisionerID))
if err != nil {
if !nosqlDB.IsErrNotFound(err) {
return errors.Wrapf(err, "error loading reference IDs for provisioner %s", provisionerID)
}
} else {
if err := json.Unmarshal(b, &eakIDs); err != nil {
return errors.Wrapf(err, "error unmarshaling eakIDs for provisioner %s", provisionerID)
}
}
newEAKIDs := removeElement(eakIDs, eakID)
var (
_old interface{} = eakIDs
_new interface{} = newEAKIDs
)
switch {
case len(eakIDs) == 0:
_old = nil
case len(newEAKIDs) == 0:
_new = nil
}
if err = db.save(ctx, provisionerID, _new, _old, "externalAccountKeysByProvisionerID", externalAccountKeysByProvisionerIDTable); err != nil {
return errors.Wrapf(err, "error saving referenceIDs index for provisioner %s", provisionerID)
}
return nil
}
// referenceKey returns a unique key for a reference per provisioner
func referenceKey(provisionerID, reference string) string {
return provisionerID + "." + reference
}
// sliceIndex finds the index of item in slice
func sliceIndex(slice []string, item string) int {
for i := range slice {
if slice[i] == item {
return i
}
}
return -1
}
// removeElement deletes the item if it exists in the
// slice. It returns a new slice, keeping the old one intact.
func removeElement(slice []string, item string) []string {
newSlice := make([]string, 0)
index := sliceIndex(slice, item)
if index < 0 {
newSlice = append(newSlice, slice...)
return newSlice
}
newSlice = append(newSlice, slice[:index]...)
return append(newSlice, slice[index+1:]...)
}

@ -3,6 +3,7 @@ package nosql
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
@ -307,7 +308,7 @@ func TestDB_GetAccountByKeyID(t *testing.T) {
assert.Equals(t, string(key), accID)
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
@ -340,7 +341,7 @@ func TestDB_GetAccountByKeyID(t *testing.T) {
assert.Equals(t, string(key), accID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
@ -462,7 +463,7 @@ func TestDB_CreateAccount(t *testing.T) {
assert.True(t, dbacc.DeactivatedAt.IsZero())
return nil, false, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
@ -506,7 +507,7 @@ func TestDB_CreateAccount(t *testing.T) {
assert.True(t, dbacc.DeactivatedAt.IsZero())
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
@ -699,6 +700,7 @@ func TestDB_UpdateAccount(t *testing.T) {
func TestDB_getDBExternalAccountKey(t *testing.T) {
keyID := "keyID"
provID := "provID"
type test struct {
db nosql.DB
err error
@ -709,12 +711,12 @@ func TestDB_getDBExternalAccountKey(t *testing.T) {
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "prov",
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
@ -790,7 +792,7 @@ func TestDB_getDBExternalAccountKey(t *testing.T) {
} else if assert.Nil(t, tc.err) {
assert.Equals(t, dbeak.ID, tc.dbeak.ID)
assert.Equals(t, dbeak.KeyBytes, tc.dbeak.KeyBytes)
assert.Equals(t, dbeak.Provisioner, tc.dbeak.Provisioner)
assert.Equals(t, dbeak.ProvisionerID, tc.dbeak.ProvisionerID)
assert.Equals(t, dbeak.Reference, tc.dbeak.Reference)
assert.Equals(t, dbeak.CreatedAt, tc.dbeak.CreatedAt)
assert.Equals(t, dbeak.AccountID, tc.dbeak.AccountID)
@ -802,7 +804,7 @@ func TestDB_getDBExternalAccountKey(t *testing.T) {
func TestDB_GetExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
provID := "provID"
type test struct {
db nosql.DB
err error
@ -813,12 +815,12 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
@ -831,12 +833,12 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
}
},
@ -856,12 +858,12 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
"fail/non-matching-provisioner": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "aDifferentProv",
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: "aDifferentProvID",
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
@ -874,14 +876,14 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
acmeErr: acme.NewError(acme.ErrorUnauthorizedType, "name of provisioner does not match provisioner for which the EAB key was created"),
acmeErr: acme.NewError(acme.ErrorUnauthorizedType, "provisioner does not match provisioner for which the EAB key was created"),
}
},
}
@ -889,7 +891,7 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eak, err := d.GetExternalAccountKey(context.Background(), prov, keyID); err != nil {
if eak, err := d.GetExternalAccountKey(context.Background(), provID, keyID); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
@ -907,7 +909,7 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
} else if assert.Nil(t, tc.err) {
assert.Equals(t, eak.ID, tc.eak.ID)
assert.Equals(t, eak.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, eak.ProvisionerID, tc.eak.ProvisionerID)
assert.Equals(t, eak.Reference, tc.eak.Reference)
assert.Equals(t, eak.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, eak.AccountID, tc.eak.AccountID)
@ -919,7 +921,7 @@ func TestDB_GetExternalAccountKey(t *testing.T) {
func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
provID := "provID"
ref := "ref"
type test struct {
db nosql.DB
@ -932,12 +934,12 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
@ -953,24 +955,24 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
err: nil,
}
@ -988,7 +990,7 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return nil, nosqldb.ErrNotFound
},
},
@ -1001,7 +1003,7 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return nil, errors.New("force")
},
},
@ -1014,7 +1016,7 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return []byte{0}, nil
},
},
@ -1034,13 +1036,13 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
@ -1053,7 +1055,7 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eak, err := d.GetExternalAccountKeyByReference(context.Background(), prov, tc.ref); err != nil {
if eak, err := d.GetExternalAccountKeyByReference(context.Background(), provID, tc.ref); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
@ -1074,7 +1076,7 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
assert.Equals(t, eak.BoundAt, tc.eak.BoundAt)
assert.Equals(t, eak.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, eak.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, eak.ProvisionerID, tc.eak.ProvisionerID)
assert.Equals(t, eak.Reference, tc.eak.Reference)
}
})
@ -1085,7 +1087,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
keyID1 := "keyID1"
keyID2 := "keyID2"
keyID3 := "keyID3"
prov := "acmeProv"
provID := "provID"
ref := "ref"
type test struct {
db nosql.DB
@ -1097,105 +1099,147 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak1 := &dbExternalAccountKey{
ID: keyID1,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID1,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b1, err := json.Marshal(dbeak1)
assert.FatalError(t, err)
dbeak2 := &dbExternalAccountKey{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID2,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b2, err := json.Marshal(dbeak2)
assert.FatalError(t, err)
dbeak3 := &dbExternalAccountKey{
ID: keyID3,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID3,
ProvisionerID: "aDifferentProvID",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b3, err := json.Marshal(dbeak3)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return b, nil
case string(externalAccountKeyTable):
switch string(key) {
case keyID1:
return b1, nil
case keyID2:
return b2, nil
default:
assert.FatalError(t, errors.Errorf("unexpected key %s", string(key)))
return nil, errors.New("force")
}
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
// TODO: remove the MList
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: b1,
},
{
Bucket: bucket,
Key: []byte(keyID2),
Value: b2,
},
{
Bucket: bucket,
Key: []byte(keyID3),
Value: b3,
},
}, nil
switch string(bucket) {
case string(externalAccountKeyTable):
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: b1,
},
{
Bucket: bucket,
Key: []byte(keyID2),
Value: b2,
},
{
Bucket: bucket,
Key: []byte(keyID3),
Value: b3,
},
}, nil
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(provID),
Value: b,
},
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
},
eaks: []*acme.ExternalAccountKey{
{
ID: keyID1,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID1,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID2,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
},
}
},
"fail/db.List-error": func(t *testing.T) test {
"fail/db.Get-externalAccountKeysByProvisionerIDTable": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, string(bucket), string(externalAccountKeyTable))
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
return nil, errors.New("force")
},
},
err: errors.New("force"),
err: errors.New("error loading ACME EAB Key IDs for provisioner provID: force"),
}
},
"fail/unmarshal-error": func(t *testing.T) test {
"fail/db.getDBExternalAccountKey": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: []byte("foo"),
},
}, nil
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return b, nil
case string(externalAccountKeyTable):
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force bucket")
}
},
},
eaks: []*acme.ExternalAccountKey{},
err: errors.Errorf("error unmarshaling external account key %s into ExternalAccountKey", keyID1),
err: errors.New("error retrieving ACME EAB Key for provisioner provID and keyID keyID1: error loading external account key keyID1: force"),
}
},
}
@ -1203,7 +1247,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eaks, err := d.GetExternalAccountKeys(context.Background(), prov); err != nil {
if eaks, err := d.GetExternalAccountKeys(context.Background(), provID); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
@ -1215,7 +1259,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
}
default:
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
assert.Equals(t, tc.err.Error(), err.Error())
}
}
} else if assert.Nil(t, tc.err) {
@ -1223,7 +1267,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
for i, eak := range eaks {
assert.Equals(t, eak.ID, tc.eaks[i].ID)
assert.Equals(t, eak.KeyBytes, tc.eaks[i].KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eaks[i].Provisioner)
assert.Equals(t, eak.ProvisionerID, tc.eaks[i].ProvisionerID)
assert.Equals(t, eak.Reference, tc.eaks[i].Reference)
assert.Equals(t, eak.CreatedAt, tc.eaks[i].CreatedAt)
assert.Equals(t, eak.AccountID, tc.eaks[i].AccountID)
@ -1236,7 +1280,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
func TestDB_DeleteExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
provID := "provID"
ref := "ref"
type test struct {
db nosql.DB
@ -1247,12 +1291,12 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
@ -1267,27 +1311,46 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
b, err := json.Marshal([]string{keyID})
assert.FatalError(t, err)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return errors.New("force")
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force default")
}
},
MCmpAndSwap: func(bucket, key, old, new []byte) ([]byte, bool, error) {
fmt.Println(string(bucket))
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, provID+"."+ref, string(key))
return nil, true, nil
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nil, true, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force default")
}
},
},
@ -1302,18 +1365,18 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
return nil, nosqldb.ErrNotFound
},
},
err: errors.New("error loading ACME EAB Key with Key ID keyID"),
err: errors.New("error loading ACME EAB Key with Key ID keyID: not found"),
}
},
"fail/non-matching-provisioner": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: "aDifferentProvID",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
@ -1325,18 +1388,18 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
return b, nil
},
},
err: errors.New("name of provisioner does not match provisioner for which the EAB key was created"),
err: errors.New("provisioner does not match provisioner for which the EAB key was created"),
}
},
"fail/delete-reference": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
@ -1357,36 +1420,36 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return errors.New("force")
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return errors.New("force")
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force default")
}
},
},
err: errors.New("error deleting ACME EAB Key Reference with Key ID keyID and reference ref"),
err: errors.New("error deleting ACME EAB Key reference with Key ID keyID and reference ref: force"),
}
},
"fail/delete-eak": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
@ -1407,25 +1470,25 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, string(key), provID+"."+ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force")
}
},
},
err: errors.New("error deleting ACME EAB Key with Key ID keyID"),
err: errors.New("error deleting ACME EAB Key with Key ID keyID: force"),
}
},
}
@ -1433,7 +1496,7 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.DeleteExternalAccountKey(context.Background(), prov, keyID); err != nil {
if err := d.DeleteExternalAccountKey(context.Background(), provID, keyID); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
@ -1445,7 +1508,7 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
}
default:
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
assert.Equals(t, err.Error(), tc.err.Error())
}
}
} else {
@ -1457,7 +1520,7 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) {
func TestDB_CreateExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
provID := "provID"
ref := "ref"
type test struct {
db nosql.DB
@ -1473,30 +1536,38 @@ func TestDB_CreateExternalAccountKey(t *testing.T) {
)
now := clock.Now()
eak := &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
CreatedAt: now,
}
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
assert.Equals(t, provID, string(key))
b, _ := json.Marshal([]string{})
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nu, true, nil
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, old, nil)
assert.Equals(t, provID+"."+ref, string(key))
assert.Equals(t, nil, old)
return nu, true, nil
case string(externalAccountKeyTable):
assert.Equals(t, old, nil)
assert.Equals(t, nil, old)
id = string(key)
dbeak := new(dbExternalAccountKey)
assert.FatalError(t, json.Unmarshal(nu, dbeak))
assert.Equals(t, string(key), dbeak.ID)
assert.Equals(t, eak.Provisioner, dbeak.Provisioner)
assert.Equals(t, eak.ProvisionerID, dbeak.ProvisionerID)
assert.Equals(t, eak.Reference, dbeak.Reference)
assert.Equals(t, 32, len(dbeak.KeyBytes))
assert.False(t, dbeak.CreatedAt.IsZero())
@ -1504,8 +1575,8 @@ func TestDB_CreateExternalAccountKey(t *testing.T) {
assert.True(t, dbeak.BoundAt.IsZero())
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force default")
}
},
},
@ -1527,34 +1598,42 @@ func TestDB_CreateExternalAccountKey(t *testing.T) {
assert.Equals(t, old, nil)
return nu, true, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key"),
err: errors.New("error saving acme external_account_key: force"),
}
},
"fail/externalAccountKeyReference-cmpAndSwap-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
assert.Equals(t, provID, string(key))
b, _ := json.Marshal([]string{})
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nu, true, nil
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, provID+"."+ref, string(key))
assert.Equals(t, old, nil)
return nu, true, errors.New("force")
case string(externalAccountKeyTable):
assert.Equals(t, old, nil)
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key"),
err: errors.New("error saving acme external_account_key_reference: force"),
}
},
}
@ -1562,14 +1641,15 @@ func TestDB_CreateExternalAccountKey(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
eak, err := d.CreateExternalAccountKey(context.Background(), prov, ref)
eak, err := d.CreateExternalAccountKey(context.Background(), provID, ref)
fmt.Println(name, err)
if err != nil {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
assert.Equals(t, err.Error(), tc.err.Error())
}
} else if assert.Nil(t, tc.err) {
assert.Equals(t, *tc._id, eak.ID)
assert.Equals(t, prov, eak.Provisioner)
assert.Equals(t, provID, eak.ProvisionerID)
assert.Equals(t, ref, eak.Reference)
assert.Equals(t, "", eak.AccountID)
assert.False(t, eak.CreatedAt.IsZero())
@ -1582,16 +1662,16 @@ func TestDB_CreateExternalAccountKey(t *testing.T) {
func TestDB_UpdateExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
provID := "provID"
ref := "ref"
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
@ -1604,12 +1684,12 @@ func TestDB_UpdateExternalAccountKey(t *testing.T) {
"ok": func(t *testing.T) test {
eak := &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
return test{
eak: eak,
@ -1627,7 +1707,7 @@ func TestDB_UpdateExternalAccountKey(t *testing.T) {
dbNew := new(dbExternalAccountKey)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbeak.ID)
assert.Equals(t, dbNew.Provisioner, dbeak.Provisioner)
assert.Equals(t, dbNew.ProvisionerID, dbeak.ProvisionerID)
assert.Equals(t, dbNew.Reference, dbeak.Reference)
assert.Equals(t, dbNew.AccountID, dbeak.AccountID)
assert.Equals(t, dbNew.CreatedAt, dbeak.CreatedAt)
@ -1640,12 +1720,12 @@ func TestDB_UpdateExternalAccountKey(t *testing.T) {
},
"fail/provisioner-mismatch": func(t *testing.T) test {
newDBEAK := &dbExternalAccountKey{
ID: keyID,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: keyID,
ProvisionerID: "aDifferentProvID",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(newDBEAK)
assert.FatalError(t, err)
@ -1661,7 +1741,7 @@ func TestDB_UpdateExternalAccountKey(t *testing.T) {
return b, nil
},
},
err: errors.New("name of provisioner does not match provisioner for which the EAB key was created"),
err: errors.New("provisioner does not match provisioner for which the EAB key was created"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
@ -1685,13 +1765,13 @@ func TestDB_UpdateExternalAccountKey(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.UpdateExternalAccountKey(context.Background(), prov, tc.eak); err != nil {
if err := d.UpdateExternalAccountKey(context.Background(), provID, tc.eak); err != nil {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else if assert.Nil(t, tc.err) {
assert.Equals(t, dbeak.ID, tc.eak.ID)
assert.Equals(t, dbeak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, dbeak.ProvisionerID, tc.eak.ProvisionerID)
assert.Equals(t, dbeak.Reference, tc.eak.Reference)
assert.Equals(t, dbeak.AccountID, tc.eak.AccountID)
assert.Equals(t, dbeak.CreatedAt, tc.eak.CreatedAt)

@ -11,17 +11,18 @@ import (
)
var (
accountTable = []byte("acme_accounts")
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
authzTable = []byte("acme_authzs")
challengeTable = []byte("acme_challenges")
nonceTable = []byte("nonces")
orderTable = []byte("acme_orders")
ordersByAccountIDTable = []byte("acme_account_orders_index")
certTable = []byte("acme_certs")
certBySerialTable = []byte("acme_serial_certs_index")
externalAccountKeyTable = []byte("acme_external_account_keys")
externalAccountKeysByReferenceTable = []byte("acme_external_account_key_reference_index")
accountTable = []byte("acme_accounts")
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
authzTable = []byte("acme_authzs")
challengeTable = []byte("acme_challenges")
nonceTable = []byte("nonces")
orderTable = []byte("acme_orders")
ordersByAccountIDTable = []byte("acme_account_orders_index")
certTable = []byte("acme_certs")
certBySerialTable = []byte("acme_serial_certs_index")
externalAccountKeyTable = []byte("acme_external_account_keys")
externalAccountKeysByReferenceTable = []byte("acme_external_account_key_reference_index")
externalAccountKeysByProvisionerIDTable = []byte("acme_external_account_keyID_provisionerID_index")
)
// DB is a struct that implements the AcmeDB interface.
@ -33,7 +34,8 @@ type DB struct {
func New(db nosqlDB.DB) (*DB, error) {
tables := [][]byte{accountTable, accountByKeyIDTable, authzTable,
challengeTable, nonceTable, orderTable, ordersByAccountIDTable,
certTable, certBySerialTable, externalAccountKeyTable, externalAccountKeysByReferenceTable,
certTable, certBySerialTable, externalAccountKeyTable,
externalAccountKeysByReferenceTable, externalAccountKeysByProvisionerIDTable,
}
for _, b := range tables {
if err := db.CreateTable(b); err != nil {

@ -15,6 +15,11 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
// provisionerContextKey provisioner key
provisionerContextKey = ContextKey("provisioner")
)
// CreateExternalAccountKeyRequest is the type for POST /admin/acme/eab requests
type CreateExternalAccountKeyRequest struct {
Reference string `json:"reference"`
@ -37,47 +42,63 @@ type GetExternalAccountKeysResponse struct {
// before serving requests that act on ACME EAB credentials.
func (h *Handler) requireEABEnabled(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
prov := chi.URLParam(r, "prov")
eabEnabled, err := h.provisionerHasEABEnabled(r.Context(), prov)
ctx := r.Context()
provName := chi.URLParam(r, "prov")
eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName)
if err != nil {
api.WriteError(w, err)
return
}
if !eabEnabled {
api.WriteError(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner %s", prov))
api.WriteError(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner %s", prov.GetName()))
return
}
next(w, r)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
next(w, r.WithContext(ctx))
}
}
// provisionerHasEABEnabled determines if the "requireEAB" setting for an ACME
// provisioner is set to true and thus has EAB enabled.
func (h *Handler) provisionerHasEABEnabled(ctx context.Context, provisionerName string) (bool, error) {
func (h *Handler) provisionerHasEABEnabled(ctx context.Context, provisionerName string) (bool, *linkedca.Provisioner, error) {
var (
p provisioner.Interface
err error
)
if p, err = h.auth.LoadProvisionerByName(provisionerName); err != nil {
return false, admin.WrapErrorISE(err, "error loading provisioner %s", provisionerName)
return false, nil, admin.WrapErrorISE(err, "error loading provisioner %s", provisionerName)
}
prov, err := h.db.GetProvisioner(ctx, p.GetID())
if err != nil {
return false, admin.WrapErrorISE(err, "error getting provisioner with ID: %s", p.GetID())
return false, nil, admin.WrapErrorISE(err, "error getting provisioner with ID: %s", p.GetID())
}
details := prov.GetDetails()
if details == nil {
return false, admin.NewErrorISE("error getting details for provisioner with ID: %s", p.GetID())
return false, nil, admin.NewErrorISE("error getting details for provisioner with ID: %s", p.GetID())
}
acmeProvisioner := details.GetACME()
if acmeProvisioner == nil {
return false, admin.NewErrorISE("error getting ACME details for provisioner with ID: %s", p.GetID())
return false, nil, admin.NewErrorISE("error getting ACME details for provisioner with ID: %s", p.GetID())
}
return acmeProvisioner.GetRequireEab(), nil
return acmeProvisioner.GetRequireEab(), prov, nil
}
// provisionerFromContext searches the context for a provisioner. Returns the
// provisioner or an error.
func provisionerFromContext(ctx context.Context) (*linkedca.Provisioner, error) {
val := ctx.Value(provisionerContextKey)
if val == nil {
return nil, admin.NewErrorISE("provisioner expected in request context")
}
pval, ok := val.(*linkedca.Provisioner)
if !ok || pval == nil {
return nil, admin.NewErrorISE("provisioner in context is not a linkedca.Provisioner")
}
return pval, nil
}
// CreateExternalAccountKey creates a new External Account Binding key
@ -93,12 +114,17 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques
return
}
prov := chi.URLParam(r, "prov")
reference := body.Reference
ctx := r.Context()
prov, err := provisionerFromContext(ctx)
if err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error getting provisioner from context"))
return
}
// check if a key with the reference does not exist (only when a reference was in the request)
reference := body.Reference
if reference != "" {
k, err := h.acmeDB.GetExternalAccountKeyByReference(r.Context(), prov, reference)
k, err := h.acmeDB.GetExternalAccountKeyByReference(ctx, prov.GetId(), reference)
// retrieving an EAB key from DB results in an error if it doesn't exist, which is what we're looking for,
// but other errors can also happen. Return early if that happens; continuing if it was acme.ErrNotFound.
if shouldWriteError := err != nil && !errors.Is(err, acme.ErrNotFound); shouldWriteError {
@ -107,7 +133,7 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques
}
// if a key was found, return HTTP 409 conflict
if k != nil {
err := admin.NewError(admin.ErrorBadRequestType, "an ACME EAB key for provisioner %s with reference %s already exists", prov, reference)
err := admin.NewError(admin.ErrorBadRequestType, "an ACME EAB key for provisioner '%s' with reference '%s' already exists", prov.GetName(), reference)
err.Status = 409
api.WriteError(w, err)
return
@ -115,9 +141,9 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques
// continue execution if no key was found for the reference
}
eak, err := h.acmeDB.CreateExternalAccountKey(r.Context(), prov, reference)
eak, err := h.acmeDB.CreateExternalAccountKey(ctx, prov.GetId(), reference)
if err != nil {
msg := fmt.Sprintf("error creating ACME EAB key for provisioner '%s'", prov)
msg := fmt.Sprintf("error creating ACME EAB key for provisioner '%s'", prov.GetName())
if reference != "" {
msg += fmt.Sprintf(" and reference '%s'", reference)
}
@ -128,7 +154,7 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques
response := &linkedca.EABKey{
Id: eak.ID,
HmacKey: eak.KeyBytes,
Provisioner: eak.Provisioner,
Provisioner: prov.GetName(),
Reference: eak.Reference,
}
@ -137,10 +163,17 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques
// DeleteExternalAccountKey deletes an ACME External Account Key.
func (h *Handler) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
prov := chi.URLParam(r, "prov")
keyID := chi.URLParam(r, "id")
if err := h.acmeDB.DeleteExternalAccountKey(r.Context(), prov, keyID); err != nil {
ctx := r.Context()
prov, err := provisionerFromContext(ctx)
if err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error getting provisioner from context"))
return
}
if err := h.acmeDB.DeleteExternalAccountKey(ctx, prov.GetId(), keyID); err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error deleting ACME EAB Key '%s'", keyID))
return
}
@ -152,8 +185,6 @@ func (h *Handler) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Reques
// only the ExternalAccountKey with that reference is returned. Otherwise all
// ExternalAccountKeys in the system for a specific provisioner are returned.
func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
prov := chi.URLParam(r, "prov")
reference := chi.URLParam(r, "ref")
var (
key *acme.ExternalAccountKey
@ -161,8 +192,16 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
err error
)
ctx := r.Context()
prov, err := provisionerFromContext(ctx)
if err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error getting provisioner from context"))
return
}
reference := chi.URLParam(r, "ref")
if reference != "" {
if key, err = h.acmeDB.GetExternalAccountKeyByReference(r.Context(), prov, reference); err != nil {
if key, err = h.acmeDB.GetExternalAccountKeyByReference(ctx, prov.GetId(), reference); err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving external account key with reference '%s'", reference))
return
}
@ -170,18 +209,19 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
keys = []*acme.ExternalAccountKey{key}
}
} else {
if keys, err = h.acmeDB.GetExternalAccountKeys(r.Context(), prov); err != nil {
if keys, err = h.acmeDB.GetExternalAccountKeys(ctx, prov.GetId()); err != nil {
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving external account keys"))
return
}
}
provisionerName := prov.GetName()
eaks := make([]*linkedca.EABKey, len(keys))
for i, k := range keys {
eaks[i] = &linkedca.EABKey{
Id: k.ID,
HmacKey: []byte{},
Provisioner: k.Provisioner,
Provisioner: provisionerName,
Reference: k.Reference,
Account: k.AccountID,
CreatedAt: timestamppb.New(k.CreatedAt),

@ -368,12 +368,13 @@ func TestHandler_provisionerHasEABEnabled(t *testing.T) {
auth: tc.auth,
acmeDB: nil,
}
got, err := h.provisionerHasEABEnabled(context.TODO(), tc.provisionerName)
got, prov, err := h.provisionerHasEABEnabled(context.TODO(), tc.provisionerName)
if (err != nil) != (tc.err != nil) {
t.Errorf("Handler.provisionerHasEABEnabled() error = %v, want err %v", err, tc.err)
return
}
if tc.err != nil {
assert.Type(t, &linkedca.Provisioner{}, prov)
assert.Type(t, &admin.Error{}, err)
adminError, _ := err.(*admin.Error)
assert.Equals(t, tc.err.Type, adminError.Type)
@ -434,6 +435,10 @@ func TestCreateExternalAccountKeyRequest_Validate(t *testing.T) {
}
func TestHandler_CreateExternalAccountKey(t *testing.T) {
prov := &linkedca.Provisioner{
Id: "provID",
Name: "provName",
}
type test struct {
ctx context.Context
db acme.DB
@ -487,14 +492,15 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "an-external-key-reference",
}
body, err := json.Marshal(req)
assert.FatalError(t, err)
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, errors.New("force")
},
@ -517,22 +523,23 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "an-external-key-reference",
}
body, err := json.Marshal(req)
assert.FatalError(t, err)
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
past := time.Now().Add(-24 * time.Hour)
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: "provName",
Reference: "an-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: past,
ID: "eakID",
ProvisionerID: "provID",
Reference: "an-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: past,
}, nil
},
}
@ -546,7 +553,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
Type: admin.ErrorBadRequestType.String(),
Status: 409,
Detail: "bad request",
Message: "an ACME EAB key for provisioner provName with reference an-external-key-reference already exists",
Message: "an ACME EAB key for provisioner 'provName' with reference 'an-external-key-reference' already exists",
},
}
},
@ -554,14 +561,15 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "",
}
body, err := json.Marshal(req)
assert.FatalError(t, err)
db := &acme.MockDB{
MockCreateExternalAccountKey: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockCreateExternalAccountKey: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "", reference)
return nil, errors.New("force")
},
@ -583,19 +591,20 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "an-external-key-reference",
}
body, err := json.Marshal(req)
assert.FatalError(t, err)
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, acme.ErrNotFound // simulating not found; skipping 409 conflict
},
MockCreateExternalAccountKey: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockCreateExternalAccountKey: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, errors.New("force")
},
@ -617,6 +626,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "",
}
@ -624,15 +634,15 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
assert.FatalError(t, err)
now := time.Now()
db := &acme.MockDB{
MockCreateExternalAccountKey: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockCreateExternalAccountKey: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "", reference)
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: "provName",
Reference: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: "eakID",
ProvisionerID: "provID",
Reference: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}, nil
},
}
@ -653,6 +663,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
req := CreateExternalAccountKeyRequest{
Reference: "an-external-key-reference",
}
@ -660,20 +671,20 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
assert.FatalError(t, err)
now := time.Now()
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, acme.ErrNotFound // simulating not found; skipping 409 conflict
},
MockCreateExternalAccountKey: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockCreateExternalAccountKey: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: "provName",
Reference: "an-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
ID: "eakID",
ProvisionerID: "provID",
Reference: "an-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}, nil
},
}
@ -737,6 +748,10 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
}
func TestHandler_DeleteExternalAccountKey(t *testing.T) {
prov := &linkedca.Provisioner{
Id: "provID",
Name: "provName",
}
type test struct {
ctx context.Context
db acme.DB
@ -749,9 +764,10 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
chiCtx.URLParams.Add("prov", "provName")
chiCtx.URLParams.Add("id", "keyID")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
db := &acme.MockDB{
MockDeleteExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) error {
assert.Equals(t, "provName", provisionerName)
MockDeleteExternalAccountKey: func(ctx context.Context, provisionerID, keyID string) error {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "keyID", keyID)
return errors.New("force")
},
@ -773,9 +789,10 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
chiCtx.URLParams.Add("prov", "provName")
chiCtx.URLParams.Add("id", "keyID")
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
db := &acme.MockDB{
MockDeleteExternalAccountKey: func(ctx context.Context, provisionerName, keyID string) error {
assert.Equals(t, "provName", provisionerName)
MockDeleteExternalAccountKey: func(ctx context.Context, provisionerID, keyID string) error {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "keyID", keyID)
return nil
},
@ -831,6 +848,10 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
}
func TestHandler_GetExternalAccountKeys(t *testing.T) {
prov := &linkedca.Provisioner{
Id: "provID",
Name: "provName",
}
type test struct {
ctx context.Context
db acme.DB
@ -846,9 +867,10 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
chiCtx.URLParams.Add("ref", "an-external-key-reference")
req := httptest.NewRequest("GET", "/foo", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, errors.New("force")
},
@ -871,9 +893,10 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
chiCtx.URLParams.Add("prov", "provName")
req := httptest.NewRequest("GET", "/foo", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
db := &acme.MockDB{
MockGetExternalAccountKeys: func(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
return nil, errors.New("force")
},
}
@ -896,9 +919,10 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
chiCtx.URLParams.Add("ref", "an-external-key-reference")
req := httptest.NewRequest("GET", "/foo", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return nil, nil // returning nil; no key found
},
@ -920,17 +944,18 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
chiCtx.URLParams.Add("ref", "an-external-key-reference")
req := httptest.NewRequest("GET", "/foo", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
createdAt := time.Now().Add(-24 * time.Hour)
var boundAt time.Time
db := &acme.MockDB{
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerName, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeyByReference: func(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
assert.Equals(t, "an-external-key-reference", reference)
return &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: "provName",
Reference: "an-external-key-reference",
CreatedAt: createdAt,
ID: "eakID",
ProvisionerID: "provID",
Reference: "an-external-key-reference",
CreatedAt: createdAt,
}, nil
},
}
@ -958,107 +983,36 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
chiCtx.URLParams.Add("prov", "provName")
req := httptest.NewRequest("GET", "/foo", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
createdAt := time.Now().Add(-24 * time.Hour)
var boundAt time.Time
boundAtSet := time.Now().Add(-12 * time.Hour)
db := &acme.MockDB{
MockGetExternalAccountKeys: func(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
return []*acme.ExternalAccountKey{
{
ID: "eakID1",
Provisioner: "provName",
Reference: "some-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
},
{
ID: "eakID2",
Provisioner: "provName",
Reference: "some-other-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt.Add(1 * time.Hour),
},
{
ID: "eakID3",
Provisioner: "provName",
Reference: "another-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
BoundAt: boundAtSet,
AccountID: "accountID",
},
}, nil
},
}
return test{
ctx: ctx,
statusCode: 200,
req: req,
resp: GetExternalAccountKeysResponse{
EAKs: []*linkedca.EABKey{
{
Id: "eakID1",
Provisioner: "provName",
Reference: "some-external-key-reference",
CreatedAt: timestamppb.New(createdAt),
BoundAt: timestamppb.New(boundAt),
},
{
Id: "eakID2",
Provisioner: "provName",
Reference: "some-other-external-key-reference",
CreatedAt: timestamppb.New(createdAt.Add(1 * time.Hour)),
BoundAt: timestamppb.New(boundAt),
},
{
Id: "eakID3",
Provisioner: "provName",
Reference: "another-external-key-reference",
CreatedAt: timestamppb.New(createdAt),
BoundAt: timestamppb.New(boundAtSet),
Account: "accountID",
},
},
},
db: db,
err: nil,
}
},
"ok/multiple-keys-with-cursor-and-limit": func(t *testing.T) test {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("prov", "provName")
req := httptest.NewRequest("GET", "/foo?cursor=eakID1&limit=10", nil)
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
createdAt := time.Now().Add(-24 * time.Hour)
var boundAt time.Time
boundAtSet := time.Now().Add(-12 * time.Hour)
db := &acme.MockDB{
MockGetExternalAccountKeys: func(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
assert.Equals(t, "provName", provisionerName)
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
assert.Equals(t, "provID", provisionerID)
return []*acme.ExternalAccountKey{
{
ID: "eakID1",
Provisioner: "provName",
Reference: "some-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
ID: "eakID1",
ProvisionerID: "provID",
Reference: "some-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
},
{
ID: "eakID2",
Provisioner: "provName",
Reference: "some-other-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt.Add(1 * time.Hour),
ID: "eakID2",
ProvisionerID: "provID",
Reference: "some-other-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt.Add(1 * time.Hour),
},
{
ID: "eakID3",
Provisioner: "provName",
Reference: "another-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
BoundAt: boundAtSet,
AccountID: "accountID",
ID: "eakID3",
ProvisionerID: "provID",
Reference: "another-external-key-reference",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
BoundAt: boundAtSet,
AccountID: "accountID",
},
}, nil
},

Loading…
Cancel
Save