diff --git a/acme/db.go b/acme/db.go index 46e3fc97..5c490410 100644 --- a/acme/db.go +++ b/acme/db.go @@ -21,6 +21,7 @@ type DB interface { CreateExternalAccountKey(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error) GetExternalAccountKey(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error) + DeleteExternalAccountKey(ctx context.Context, keyID string) error UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error CreateNonce(ctx context.Context) (Nonce, error) @@ -53,6 +54,7 @@ type MockDB struct { MockCreateExternalAccountKey func(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error) MockGetExternalAccountKey func(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error) + MockDeleteExternalAccountKey func(ctx context.Context, keyID string) error MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error MockCreateNonce func(ctx context.Context) (Nonce, error) @@ -138,6 +140,17 @@ func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerName stri return m.MockRet1.(*ExternalAccountKey), m.MockError } +// DeleteExternalAccountKey mock +func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, keyID string) error { + if m.MockDeleteExternalAccountKey != nil { + return m.MockDeleteExternalAccountKey(ctx, keyID) + } else if m.MockError != nil { + return m.MockError + } + return m.MockError +} + +// UpdateExternalAccountKey mock func (m *MockDB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error { if m.MockUpdateExternalAccountKey != nil { return m.MockUpdateExternalAccountKey(ctx, provisionerName, eak) diff --git a/acme/db/nosql/account.go b/acme/db/nosql/account.go index dc909dde..a0a43ffd 100644 --- a/acme/db/nosql/account.go +++ b/acme/db/nosql/account.go @@ -221,6 +221,14 @@ func (db *DB) GetExternalAccountKey(ctx context.Context, provisionerName string, }, nil } +func (db *DB) DeleteExternalAccountKey(ctx context.Context, keyID string) error { + err := db.db.Del(externalAccountKeyTable, []byte(keyID)) + if err != nil { + return errors.Wrapf(err, "error deleting ACME EAB Key with Key ID: %s", keyID) + } + return nil +} + func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error { old, err := db.getDBExternalAccountKey(ctx, eak.ID) if err != nil { diff --git a/authority/admin/api/acme.go b/authority/admin/api/acme.go index 7772e435..c96b54b4 100644 --- a/authority/admin/api/acme.go +++ b/authority/admin/api/acme.go @@ -3,6 +3,7 @@ package api import ( "net/http" + "github.com/go-chi/chi" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/admin" "go.step.sm/linkedca" @@ -60,6 +61,18 @@ func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Reques api.ProtoJSONStatus(w, response, http.StatusCreated) } +// DeleteExternalAccountKey deletes an ACME External Account Key. +func (h *Handler) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + + if err := h.acmeDB.DeleteExternalAccountKey(r.Context(), id); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error deleting ACME EAB Key %s", id)) + return + } + + api.JSON(w, &DeleteResponse{Status: "ok"}) +} + // GetExternalAccountKeys returns a segment of ACME EAB Keys. func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) { // cursor, limit, err := api.ParseCursor(r) diff --git a/authority/admin/api/handler.go b/authority/admin/api/handler.go index 0e49dfd9..4dd21796 100644 --- a/authority/admin/api/handler.go +++ b/authority/admin/api/handler.go @@ -46,4 +46,5 @@ func (h *Handler) Route(r api.Router) { // ACME External Account Binding Keys r.MethodFunc("GET", "/acme/eab", authnz(h.GetExternalAccountKeys)) r.MethodFunc("POST", "/acme/eab", authnz(h.CreateExternalAccountKey)) + r.MethodFunc("DELETE", "/acme/eab/{id}", authnz(h.DeleteExternalAccountKey)) } diff --git a/ca/adminClient.go b/ca/adminClient.go index 06462051..c52fc38e 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -634,6 +634,34 @@ retry: return eabKey, nil } +// RemoveExternalAccountKey performs the DELETE /admin/acme/eab/{key_id} request to the CA. +func (c *AdminClient) RemoveExternalAccountKey(keyID string) error { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab", keyID)}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("DELETE", u.String(), nil) + if err != nil { + return errors.Wrapf(err, "create DELETE %s request failed", u) + } + req.Header.Add("Authorization", tok) +retry: + resp, err := c.client.Do(req) + if err != nil { + return errors.Wrapf(err, "client DELETE %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return readAdminError(resp.Body) + } + return nil +} + // GetExternalAccountKeys returns all ACME EAB Keys from the GET /admin/acme/eab request to the CA. func (c *AdminClient) GetExternalAccountKeys(opts ...AdminOption) ([]*linkedca.EABKey, error) { var (