Skip to content

Commit f11c0cd

Browse files
committed
Add endpoint for listing ACME EAB keys
1 parent a1afbce commit f11c0cd

File tree

5 files changed

+78
-21
lines changed

5 files changed

+78
-21
lines changed

acme/db.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type DB interface {
2121

2222
CreateExternalAccountKey(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
2323
GetExternalAccountKey(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
24+
GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
2425
DeleteExternalAccountKey(ctx context.Context, keyID string) error
2526
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
2627

@@ -54,6 +55,7 @@ type MockDB struct {
5455

5556
MockCreateExternalAccountKey func(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
5657
MockGetExternalAccountKey func(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
58+
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
5759
MockDeleteExternalAccountKey func(ctx context.Context, keyID string) error
5860
MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
5961

@@ -140,6 +142,16 @@ func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerName stri
140142
return m.MockRet1.(*ExternalAccountKey), m.MockError
141143
}
142144

145+
// GetExternalAccountKeys mock
146+
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error) {
147+
if m.MockGetExternalAccountKeys != nil {
148+
return m.MockGetExternalAccountKeys(ctx, provisionerName)
149+
} else if m.MockError != nil {
150+
return nil, m.MockError
151+
}
152+
return m.MockRet1.([]*ExternalAccountKey), m.MockError
153+
}
154+
143155
// DeleteExternalAccountKey mock
144156
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, keyID string) error {
145157
if m.MockDeleteExternalAccountKey != nil {

acme/db/nosql/account.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,33 @@ func (db *DB) DeleteExternalAccountKey(ctx context.Context, keyID string) error
233233
return nil
234234
}
235235

236+
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
237+
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
238+
entries, err := db.db.List(externalAccountKeyTable)
239+
if err != nil {
240+
return nil, err
241+
}
242+
243+
keys := make([]*acme.ExternalAccountKey, len(entries))
244+
for i, entry := range entries {
245+
dbeak := new(dbExternalAccountKey)
246+
if err = json.Unmarshal(entry.Value, dbeak); err != nil {
247+
return nil, errors.Wrapf(err, "error unmarshaling external account key %s into dbExternalAccountKey", string(entry.Key))
248+
}
249+
keys[i] = &acme.ExternalAccountKey{
250+
ID: dbeak.ID,
251+
KeyBytes: dbeak.KeyBytes,
252+
ProvisionerName: dbeak.ProvisionerName,
253+
Name: dbeak.Name,
254+
AccountID: dbeak.AccountID,
255+
CreatedAt: dbeak.CreatedAt,
256+
BoundAt: dbeak.BoundAt,
257+
}
258+
}
259+
260+
return keys, nil
261+
}
262+
236263
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error {
237264
old, err := db.getDBExternalAccountKey(ctx, eak.ID)
238265
if err != nil {

authority/admin/api/acme.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/smallstep/certificates/api"
88
"github.com/smallstep/certificates/authority/admin"
99
"go.step.sm/linkedca"
10+
"google.golang.org/protobuf/types/known/timestamppb"
1011
)
1112

1213
// CreateExternalAccountKeyRequest is the type for POST /admin/acme/eab requests
@@ -35,7 +36,7 @@ type GetExternalAccountKeysResponse struct {
3536
// CreateExternalAccountKey creates a new External Account Binding key
3637
func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
3738
var body CreateExternalAccountKeyRequest
38-
if err := api.ReadJSON(r.Body, &body); err != nil { // TODO: rewrite into protobuf json (likely)
39+
if err := api.ReadJSON(r.Body, &body); err != nil {
3940
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
4041
return
4142
}
@@ -75,20 +76,38 @@ func (h *Handler) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Reques
7576

7677
// GetExternalAccountKeys returns a segment of ACME EAB Keys.
7778
func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
79+
prov := chi.URLParam(r, "prov")
80+
81+
// TODO: support paging properly? It'll probably leak to the DB layer, as we have to loop through all keys
7882
// cursor, limit, err := api.ParseCursor(r)
7983
// if err != nil {
8084
// api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
8185
// "error parsing cursor and limit from query params"))
8286
// return
8387
// }
8488

85-
// eaks, nextCursor, err := h.acmeDB.GetExternalAccountKeys(cursor, limit)
86-
// if err != nil {
87-
// api.WriteError(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
88-
// return
89-
// }
90-
// api.JSON(w, &GetExternalAccountKeysResponse{
91-
// EAKs: eaks,
92-
// NextCursor: nextCursor,
93-
// })
89+
keys, err := h.acmeDB.GetExternalAccountKeys(r.Context(), prov)
90+
if err != nil {
91+
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account keys"))
92+
return
93+
}
94+
95+
eaks := make([]*linkedca.EABKey, len(keys))
96+
for i, k := range keys {
97+
eaks[i] = &linkedca.EABKey{
98+
EabKid: k.ID,
99+
EabHmacKey: []byte{},
100+
ProvisionerName: k.ProvisionerName,
101+
Name: k.Name,
102+
Account: k.AccountID,
103+
CreatedAt: timestamppb.New(k.CreatedAt),
104+
BoundAt: timestamppb.New(k.BoundAt),
105+
}
106+
}
107+
108+
nextCursor := ""
109+
api.JSON(w, &GetExternalAccountKeysResponse{
110+
EAKs: eaks,
111+
NextCursor: nextCursor,
112+
})
94113
}

authority/admin/api/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (h *Handler) Route(r api.Router) {
4444
r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin))
4545

4646
// ACME External Account Binding Keys
47-
r.MethodFunc("GET", "/acme/eab", authnz(h.GetExternalAccountKeys))
47+
r.MethodFunc("GET", "/acme/eab/{prov}", authnz(h.GetExternalAccountKeys))
4848
r.MethodFunc("POST", "/acme/eab", authnz(h.CreateExternalAccountKey))
4949
r.MethodFunc("DELETE", "/acme/eab/{id}", authnz(h.DeleteExternalAccountKey))
5050
}

ca/adminClient.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -559,14 +559,14 @@ retry:
559559
}
560560

561561
// GetExternalAccountKeysPaginate returns a page from the the GET /admin/acme/eab request to the CA.
562-
func (c *AdminClient) GetExternalAccountKeysPaginate(opts ...AdminOption) (*adminAPI.GetExternalAccountKeysResponse, error) {
562+
func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName string, opts ...AdminOption) (*adminAPI.GetExternalAccountKeysResponse, error) {
563563
var retried bool
564564
o := new(adminOptions)
565565
if err := o.apply(opts); err != nil {
566566
return nil, err
567567
}
568568
u := c.endpoint.ResolveReference(&url.URL{
569-
Path: "/admin/acme/eab",
569+
Path: path.Join(adminURLPrefix, "acme/eab", provisionerName),
570570
RawQuery: o.rawQuery(),
571571
})
572572
tok, err := c.generateAdminToken(u.Path)
@@ -590,12 +590,11 @@ retry:
590590
}
591591
return nil, readAdminError(resp.Body)
592592
}
593-
// var body = new(GetExternalAccountKeysResponse)
594-
// if err := readJSON(resp.Body, body); err != nil {
595-
// return nil, errors.Wrapf(err, "error reading %s", u)
596-
// }
597-
// return body, nil
598-
return nil, nil // TODO: fix correctly
593+
var body = new(adminAPI.GetExternalAccountKeysResponse)
594+
if err := readJSON(resp.Body, body); err != nil {
595+
return nil, errors.Wrapf(err, "error reading %s", u)
596+
}
597+
return body, nil
599598
}
600599

601600
// CreateExternalAccountKey performs the POST /admin/acme/eab request to the CA.
@@ -663,13 +662,13 @@ retry:
663662
}
664663

665664
// GetExternalAccountKeys returns all ACME EAB Keys from the GET /admin/acme/eab request to the CA.
666-
func (c *AdminClient) GetExternalAccountKeys(opts ...AdminOption) ([]*linkedca.EABKey, error) {
665+
func (c *AdminClient) GetExternalAccountKeys(provisionerName string, opts ...AdminOption) ([]*linkedca.EABKey, error) {
667666
var (
668667
cursor = ""
669668
eaks = []*linkedca.EABKey{}
670669
)
671670
for {
672-
resp, err := c.GetExternalAccountKeysPaginate(WithAdminCursor(cursor), WithAdminLimit(100))
671+
resp, err := c.GetExternalAccountKeysPaginate(provisionerName, WithAdminCursor(cursor), WithAdminLimit(100))
673672
if err != nil {
674673
return nil, err
675674
}

0 commit comments

Comments
 (0)