Add lookup by reference and make reference optional
This commit is contained in:
parent
02cd3b6b3b
commit
9c0020352b
6 changed files with 88 additions and 26 deletions
22
acme/db.go
22
acme/db.go
|
@ -22,6 +22,7 @@ type DB interface {
|
||||||
CreateExternalAccountKey(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
|
CreateExternalAccountKey(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKey(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
|
GetExternalAccountKey(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
||||||
|
GetExternalAccountKeyByReference(ctx context.Context, provisionerName string, reference string) (*ExternalAccountKey, error)
|
||||||
DeleteExternalAccountKey(ctx context.Context, keyID string) error
|
DeleteExternalAccountKey(ctx context.Context, keyID string) error
|
||||||
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
||||||
|
|
||||||
|
@ -53,11 +54,12 @@ type MockDB struct {
|
||||||
MockGetAccountByKeyID func(ctx context.Context, kid string) (*Account, error)
|
MockGetAccountByKeyID func(ctx context.Context, kid string) (*Account, error)
|
||||||
MockUpdateAccount func(ctx context.Context, acc *Account) error
|
MockUpdateAccount func(ctx context.Context, acc *Account) error
|
||||||
|
|
||||||
MockCreateExternalAccountKey func(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
|
MockCreateExternalAccountKey func(ctx context.Context, provisionerName string, name string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKey func(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKey func(ctx context.Context, provisionerName string, keyID string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
||||||
MockDeleteExternalAccountKey func(ctx context.Context, keyID string) error
|
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerName string, reference string) (*ExternalAccountKey, error)
|
||||||
MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *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)
|
MockCreateNonce func(ctx context.Context) (Nonce, error)
|
||||||
MockDeleteNonce func(ctx context.Context, nonce Nonce) error
|
MockDeleteNonce func(ctx context.Context, nonce Nonce) error
|
||||||
|
@ -152,6 +154,16 @@ func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName str
|
||||||
return m.MockRet1.([]*ExternalAccountKey), m.MockError
|
return m.MockRet1.([]*ExternalAccountKey), m.MockError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExtrnalAccountKeyByReference mock
|
||||||
|
func (m *MockDB) GetExternalAccountKeyByReference(ctx context.Context, provisionerName string, reference string) (*ExternalAccountKey, error) {
|
||||||
|
if m.MockGetExternalAccountKeys != nil {
|
||||||
|
return m.GetExternalAccountKeyByReference(ctx, provisionerName, reference)
|
||||||
|
} else if m.MockError != nil {
|
||||||
|
return nil, m.MockError
|
||||||
|
}
|
||||||
|
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteExternalAccountKey mock
|
// DeleteExternalAccountKey mock
|
||||||
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, keyID string) error {
|
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, keyID string) error {
|
||||||
if m.MockDeleteExternalAccountKey != nil {
|
if m.MockDeleteExternalAccountKey != nil {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/acme"
|
"github.com/smallstep/certificates/acme"
|
||||||
|
"github.com/smallstep/nosql"
|
||||||
nosqlDB "github.com/smallstep/nosql"
|
nosqlDB "github.com/smallstep/nosql"
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +38,11 @@ type dbExternalAccountKey struct {
|
||||||
BoundAt time.Time `json:"boundAt"`
|
BoundAt time.Time `json:"boundAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dbExternalAccountKeyReference struct {
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
ExternalAccountKeyID string `json:"externalAccountKeyID"`
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, error) {
|
func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, error) {
|
||||||
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
|
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,6 +194,17 @@ func (db *DB) CreateExternalAccountKey(ctx context.Context, provisionerName stri
|
||||||
if err = db.save(ctx, keyID, dbeak, nil, "external_account_key", externalAccountKeyTable); err != nil {
|
if err = db.save(ctx, keyID, dbeak, nil, "external_account_key", externalAccountKeyTable); err != nil {
|
||||||
return nil, err
|
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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &acme.ExternalAccountKey{
|
return &acme.ExternalAccountKey{
|
||||||
ID: dbeak.ID,
|
ID: dbeak.ID,
|
||||||
Provisioner: dbeak.Provisioner,
|
Provisioner: dbeak.Provisioner,
|
||||||
|
@ -263,6 +280,21 @@ func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string
|
||||||
return keys, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
|
||||||
|
func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerName string, reference string) (*acme.ExternalAccountKey, error) {
|
||||||
|
k, err := db.db.Get(externalAccountKeysByReferenceTable, []byte(reference))
|
||||||
|
if nosql.IsErrNotFound(err) {
|
||||||
|
return nil, errors.Errorf("ACME EAB key for reference %s not found", reference)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error loading ACME EAB key for reference %s", reference)
|
||||||
|
}
|
||||||
|
dbExternalAccountKeyReference := new(dbExternalAccountKeyReference)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error {
|
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *acme.ExternalAccountKey) error {
|
||||||
old, err := db.getDBExternalAccountKey(ctx, eak.ID)
|
old, err := db.getDBExternalAccountKey(ctx, eak.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,15 +11,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accountTable = []byte("acme_accounts")
|
accountTable = []byte("acme_accounts")
|
||||||
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
|
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
|
||||||
authzTable = []byte("acme_authzs")
|
authzTable = []byte("acme_authzs")
|
||||||
challengeTable = []byte("acme_challenges")
|
challengeTable = []byte("acme_challenges")
|
||||||
nonceTable = []byte("nonces")
|
nonceTable = []byte("nonces")
|
||||||
orderTable = []byte("acme_orders")
|
orderTable = []byte("acme_orders")
|
||||||
ordersByAccountIDTable = []byte("acme_account_orders_index")
|
ordersByAccountIDTable = []byte("acme_account_orders_index")
|
||||||
certTable = []byte("acme_certs")
|
certTable = []byte("acme_certs")
|
||||||
externalAccountKeyTable = []byte("acme_external_account_keys")
|
externalAccountKeyTable = []byte("acme_external_account_keys")
|
||||||
|
externalAccountKeysByReferenceTable = []byte("acme_external_account_key_reference_index")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB is a struct that implements the AcmeDB interface.
|
// DB is a struct that implements the AcmeDB interface.
|
||||||
|
@ -30,7 +31,7 @@ type DB struct {
|
||||||
// New configures and returns a new ACME DB backend implemented using a nosql DB.
|
// New configures and returns a new ACME DB backend implemented using a nosql DB.
|
||||||
func New(db nosqlDB.DB) (*DB, error) {
|
func New(db nosqlDB.DB) (*DB, error) {
|
||||||
tables := [][]byte{accountTable, accountByKeyIDTable, authzTable,
|
tables := [][]byte{accountTable, accountByKeyIDTable, authzTable,
|
||||||
challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable, externalAccountKeyTable}
|
challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable, externalAccountKeyTable, externalAccountKeysByReferenceTable}
|
||||||
for _, b := range tables {
|
for _, b := range tables {
|
||||||
if err := db.CreateTable(b); err != nil {
|
if err := db.CreateTable(b); err != nil {
|
||||||
return nil, errors.Wrapf(err, "error creating table %s",
|
return nil, errors.Wrapf(err, "error creating table %s",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/api"
|
"github.com/smallstep/certificates/api"
|
||||||
"github.com/smallstep/certificates/authority/admin"
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
@ -23,9 +24,6 @@ func (r *CreateExternalAccountKeyRequest) Validate() error {
|
||||||
if r.Provisioner == "" {
|
if r.Provisioner == "" {
|
||||||
return admin.NewError(admin.ErrorBadRequestType, "provisioner name cannot be empty")
|
return admin.NewError(admin.ErrorBadRequestType, "provisioner name cannot be empty")
|
||||||
}
|
}
|
||||||
if r.Reference == "" {
|
|
||||||
return admin.NewError(admin.ErrorBadRequestType, "reference cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +122,7 @@ func (h *Handler) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Reques
|
||||||
// GetExternalAccountKeys returns a segment of ACME EAB Keys.
|
// GetExternalAccountKeys returns a segment of ACME EAB Keys.
|
||||||
func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
prov := chi.URLParam(r, "prov")
|
prov := chi.URLParam(r, "prov")
|
||||||
|
reference := chi.URLParam(r, "ref")
|
||||||
|
|
||||||
eabEnabled, err := h.provisionerHasEABEnabled(r.Context(), prov)
|
eabEnabled, err := h.provisionerHasEABEnabled(r.Context(), prov)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -144,10 +143,23 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
keys, err := h.acmeDB.GetExternalAccountKeys(r.Context(), prov)
|
var (
|
||||||
if err != nil {
|
key *acme.ExternalAccountKey
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account keys"))
|
keys []*acme.ExternalAccountKey
|
||||||
return
|
)
|
||||||
|
if reference != "" {
|
||||||
|
key, err = h.acmeDB.GetExternalAccountKeyByReference(r.Context(), prov, reference)
|
||||||
|
if err != nil {
|
||||||
|
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account key with reference %s", reference))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys = []*acme.ExternalAccountKey{key}
|
||||||
|
} else {
|
||||||
|
keys, err = h.acmeDB.GetExternalAccountKeys(r.Context(), prov)
|
||||||
|
if err != nil {
|
||||||
|
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account keys"))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eaks := make([]*linkedca.EABKey, len(keys))
|
eaks := make([]*linkedca.EABKey, len(keys))
|
||||||
|
|
|
@ -44,6 +44,7 @@ func (h *Handler) Route(r api.Router) {
|
||||||
r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin))
|
r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin))
|
||||||
|
|
||||||
// ACME External Account Binding Keys
|
// ACME External Account Binding Keys
|
||||||
|
r.MethodFunc("GET", "/acme/eab/{prov}/{ref}", authnz(h.GetExternalAccountKeys))
|
||||||
r.MethodFunc("GET", "/acme/eab/{prov}", authnz(h.GetExternalAccountKeys))
|
r.MethodFunc("GET", "/acme/eab/{prov}", authnz(h.GetExternalAccountKeys))
|
||||||
r.MethodFunc("POST", "/acme/eab", authnz(h.CreateExternalAccountKey))
|
r.MethodFunc("POST", "/acme/eab", authnz(h.CreateExternalAccountKey))
|
||||||
r.MethodFunc("DELETE", "/acme/eab/{id}", authnz(h.DeleteExternalAccountKey))
|
r.MethodFunc("DELETE", "/acme/eab/{id}", authnz(h.DeleteExternalAccountKey))
|
||||||
|
|
|
@ -559,14 +559,18 @@ retry:
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeysPaginate returns a page from the the GET /admin/acme/eab request to the CA.
|
// GetExternalAccountKeysPaginate returns a page from the the GET /admin/acme/eab request to the CA.
|
||||||
func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName string, opts ...AdminOption) (*adminAPI.GetExternalAccountKeysResponse, error) {
|
func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName string, reference string, opts ...AdminOption) (*adminAPI.GetExternalAccountKeysResponse, error) {
|
||||||
var retried bool
|
var retried bool
|
||||||
o := new(adminOptions)
|
o := new(adminOptions)
|
||||||
if err := o.apply(opts); err != nil {
|
if err := o.apply(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
p := path.Join(adminURLPrefix, "acme/eab", provisionerName)
|
||||||
|
if reference != "" {
|
||||||
|
p = path.Join(p, "/", reference)
|
||||||
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{
|
u := c.endpoint.ResolveReference(&url.URL{
|
||||||
Path: path.Join(adminURLPrefix, "acme/eab", provisionerName),
|
Path: p,
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
@ -662,13 +666,13 @@ retry:
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys returns all ACME EAB Keys from the GET /admin/acme/eab request to the CA.
|
// GetExternalAccountKeys returns all ACME EAB Keys from the GET /admin/acme/eab request to the CA.
|
||||||
func (c *AdminClient) GetExternalAccountKeys(provisionerName string, opts ...AdminOption) ([]*linkedca.EABKey, error) {
|
func (c *AdminClient) GetExternalAccountKeys(provisionerName string, reference string, opts ...AdminOption) ([]*linkedca.EABKey, error) {
|
||||||
var (
|
var (
|
||||||
cursor = ""
|
cursor = ""
|
||||||
eaks = []*linkedca.EABKey{}
|
eaks = []*linkedca.EABKey{}
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
resp, err := c.GetExternalAccountKeysPaginate(provisionerName, WithAdminCursor(cursor), WithAdminLimit(100))
|
resp, err := c.GetExternalAccountKeysPaginate(provisionerName, reference, WithAdminCursor(cursor), WithAdminLimit(100))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue