diff --git a/acme/account.go b/acme/account.go index 14a707e9..027d7be1 100644 --- a/acme/account.go +++ b/acme/account.go @@ -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 diff --git a/acme/account_test.go b/acme/account_test.go index 44b815b9..33524d87 100644 --- a/acme/account_test.go +++ b/acme/account_test.go @@ -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", diff --git a/acme/api/account.go b/acme/api/account.go index 658c40a8..bf478d2a 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -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") diff --git a/acme/api/account_test.go b/acme/api/account_test.go index aa8d44ba..e54e3c1a 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -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) diff --git a/acme/db.go b/acme/db.go index bed55c85..708bce9e 100644 --- a/acme/db.go +++ b/acme/db.go @@ -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 } diff --git a/acme/db/nosql/account.go b/acme/db/nosql/account.go index d340ec5c..d3b9949a 100644 --- a/acme/db/nosql/account.go +++ b/acme/db/nosql/account.go @@ -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: lookup by provisioner based on index - entries, err := db.db.List(externalAccountKeyTable) + // TODO: mutex? + + 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:]...) +} diff --git a/acme/db/nosql/account_test.go b/acme/db/nosql/account_test.go index 4b94e40f..77937c2e 100644 --- a/acme/db/nosql/account_test.go +++ b/acme/db/nosql/account_test.go @@ -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) diff --git a/acme/db/nosql/nosql.go b/acme/db/nosql/nosql.go index 3c7019e2..8343c196 100644 --- a/acme/db/nosql/nosql.go +++ b/acme/db/nosql/nosql.go @@ -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 { diff --git a/authority/admin/api/acme.go b/authority/admin/api/acme.go index dbcca15b..82850010 100644 --- a/authority/admin/api/acme.go +++ b/authority/admin/api/acme.go @@ -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), diff --git a/authority/admin/api/acme_test.go b/authority/admin/api/acme_test.go index 7162ea98..f7de9290 100644 --- a/authority/admin/api/acme_test.go +++ b/authority/admin/api/acme_test.go @@ -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) + 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", - }, - }, 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) - 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", + ID: "eakID3", + ProvisionerID: "provID", + Reference: "another-external-key-reference", + KeyBytes: []byte{1, 3, 3, 7}, + CreatedAt: createdAt, + BoundAt: boundAtSet, + AccountID: "accountID", }, }, nil },