Add cursor and limit to ACME EAB DB interface
This commit is contained in:
parent
c3f2fd8ef0
commit
fd9845e9c7
11 changed files with 1313 additions and 1295 deletions
|
@ -1,7 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -9,16 +8,8 @@ import (
|
||||||
"github.com/smallstep/certificates/acme"
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/api"
|
"github.com/smallstep/certificates/api"
|
||||||
"github.com/smallstep/certificates/logging"
|
"github.com/smallstep/certificates/logging"
|
||||||
"go.step.sm/crypto/jose"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExternalAccountBinding represents the ACME externalAccountBinding JWS
|
|
||||||
type ExternalAccountBinding struct {
|
|
||||||
Protected string `json:"protected"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Sig string `json:"signature"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccountRequest represents the payload for a new account request.
|
// NewAccountRequest represents the payload for a new account request.
|
||||||
type NewAccountRequest struct {
|
type NewAccountRequest struct {
|
||||||
Contact []string `json:"contact"`
|
Contact []string `json:"contact"`
|
||||||
|
@ -241,142 +232,3 @@ func (h *Handler) GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
|
||||||
api.JSON(w, orders)
|
api.JSON(w, orders)
|
||||||
logOrdersByAccount(w, orders)
|
logOrdersByAccount(w, orders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateExternalAccountBinding validates the externalAccountBinding property in a call to new-account.
|
|
||||||
func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) (*acme.ExternalAccountKey, error) {
|
|
||||||
acmeProv, err := acmeProvisionerFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, acme.WrapErrorISE(err, "could not load ACME provisioner from context")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !acmeProv.RequireEAB {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if nar.ExternalAccountBinding == nil {
|
|
||||||
return nil, acme.NewError(acme.ErrorExternalAccountRequiredType, "no external account binding provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
eabJSONBytes, err := json.Marshal(nar.ExternalAccountBinding)
|
|
||||||
if err != nil {
|
|
||||||
return nil, acme.WrapErrorISE(err, "error marshaling externalAccountBinding into bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
eabJWS, err := jose.ParseJWS(string(eabJSONBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, acme.WrapErrorISE(err, "error parsing externalAccountBinding jws")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(hs): implement strategy pattern to allow for different ways of verification (i.e. webhook call) based on configuration?
|
|
||||||
|
|
||||||
keyID, acmeErr := validateEABJWS(ctx, eabJWS)
|
|
||||||
if acmeErr != nil {
|
|
||||||
return nil, acmeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
return nil, acme.WrapErrorISE(err, "error retrieving external account key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAccountKey.AlreadyBound() {
|
|
||||||
return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := eabJWS.Verify(externalAccountKey.KeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
jwk, err := jwkFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var payloadJWK *jose.JSONWebKey
|
|
||||||
if err = json.Unmarshal(payload, &payloadJWK); err != nil {
|
|
||||||
return nil, acme.WrapError(acme.ErrorMalformedType, err, "error unmarshaling payload into jwk")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !keysAreEqual(jwk, payloadJWK) {
|
|
||||||
return nil, acme.NewError(acme.ErrorUnauthorizedType, "keys in jws and eab payload do not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
return externalAccountKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// keysAreEqual performs an equality check on two JWKs by comparing
|
|
||||||
// the (base64 encoding) of the Key IDs.
|
|
||||||
func keysAreEqual(x, y *jose.JSONWebKey) bool {
|
|
||||||
if x == nil || y == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
digestX, errX := acme.KeyToID(x)
|
|
||||||
digestY, errY := acme.KeyToID(y)
|
|
||||||
if errX != nil || errY != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return digestX == digestY
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateEABJWS verifies the contents of the External Account Binding JWS.
|
|
||||||
// The protected header of the JWS MUST meet the following criteria:
|
|
||||||
// o The "alg" field MUST indicate a MAC-based algorithm
|
|
||||||
// o The "kid" field MUST contain the key identifier provided by the CA
|
|
||||||
// o The "nonce" field MUST NOT be present
|
|
||||||
// o The "url" field MUST be set to the same value as the outer JWS
|
|
||||||
func validateEABJWS(ctx context.Context, jws *jose.JSONWebSignature) (string, *acme.Error) {
|
|
||||||
|
|
||||||
if jws == nil {
|
|
||||||
return "", acme.NewErrorISE("no JWS provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(jws.Signatures) != 1 {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "JWS must have one signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
header := jws.Signatures[0].Protected
|
|
||||||
algorithm := header.Algorithm
|
|
||||||
keyID := header.KeyID
|
|
||||||
nonce := header.Nonce
|
|
||||||
|
|
||||||
if !(algorithm == jose.HS256 || algorithm == jose.HS384 || algorithm == jose.HS512) {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'alg' field set to invalid algorithm '%s'", algorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyID == "" {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'kid' field is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if nonce != "" {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'nonce' must not be present")
|
|
||||||
}
|
|
||||||
|
|
||||||
jwsURL, ok := header.ExtraHeaders["url"]
|
|
||||||
if !ok {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'url' field is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
outerJWS, err := jwsFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", acme.WrapErrorISE(err, "could not retrieve outer JWS from context")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outerJWS.Signatures) != 1 {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "outer JWS must have one signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
outerJWSURL, ok := outerJWS.Signatures[0].Protected.ExtraHeaders["url"]
|
|
||||||
if !ok {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'url' field must be set in outer JWS")
|
|
||||||
}
|
|
||||||
|
|
||||||
if jwsURL != outerJWSURL {
|
|
||||||
return "", acme.NewError(acme.ErrorMalformedType, "'url' field is not the same value as the outer JWS")
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyID, nil
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
155
acme/api/eab.go
Normal file
155
acme/api/eab.go
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/acme"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExternalAccountBinding represents the ACME externalAccountBinding JWS
|
||||||
|
type ExternalAccountBinding struct {
|
||||||
|
Protected string `json:"protected"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Sig string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateExternalAccountBinding validates the externalAccountBinding property in a call to new-account.
|
||||||
|
func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) (*acme.ExternalAccountKey, error) {
|
||||||
|
acmeProv, err := acmeProvisionerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, acme.WrapErrorISE(err, "could not load ACME provisioner from context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !acmeProv.RequireEAB {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if nar.ExternalAccountBinding == nil {
|
||||||
|
return nil, acme.NewError(acme.ErrorExternalAccountRequiredType, "no external account binding provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
eabJSONBytes, err := json.Marshal(nar.ExternalAccountBinding)
|
||||||
|
if err != nil {
|
||||||
|
return nil, acme.WrapErrorISE(err, "error marshaling externalAccountBinding into bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
eabJWS, err := jose.ParseJWS(string(eabJSONBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, acme.WrapErrorISE(err, "error parsing externalAccountBinding jws")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hs): implement strategy pattern to allow for different ways of verification (i.e. webhook call) based on configuration?
|
||||||
|
|
||||||
|
keyID, acmeErr := validateEABJWS(ctx, eabJWS)
|
||||||
|
if acmeErr != nil {
|
||||||
|
return nil, acmeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
return nil, acme.WrapErrorISE(err, "error retrieving external account key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if externalAccountKey.AlreadyBound() {
|
||||||
|
return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := eabJWS.Verify(externalAccountKey.KeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk, err := jwkFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadJWK *jose.JSONWebKey
|
||||||
|
if err = json.Unmarshal(payload, &payloadJWK); err != nil {
|
||||||
|
return nil, acme.WrapError(acme.ErrorMalformedType, err, "error unmarshaling payload into jwk")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keysAreEqual(jwk, payloadJWK) {
|
||||||
|
return nil, acme.NewError(acme.ErrorUnauthorizedType, "keys in jws and eab payload do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalAccountKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysAreEqual performs an equality check on two JWKs by comparing
|
||||||
|
// the (base64 encoding) of the Key IDs.
|
||||||
|
func keysAreEqual(x, y *jose.JSONWebKey) bool {
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
digestX, errX := acme.KeyToID(x)
|
||||||
|
digestY, errY := acme.KeyToID(y)
|
||||||
|
if errX != nil || errY != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return digestX == digestY
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateEABJWS verifies the contents of the External Account Binding JWS.
|
||||||
|
// The protected header of the JWS MUST meet the following criteria:
|
||||||
|
// o The "alg" field MUST indicate a MAC-based algorithm
|
||||||
|
// o The "kid" field MUST contain the key identifier provided by the CA
|
||||||
|
// o The "nonce" field MUST NOT be present
|
||||||
|
// o The "url" field MUST be set to the same value as the outer JWS
|
||||||
|
func validateEABJWS(ctx context.Context, jws *jose.JSONWebSignature) (string, *acme.Error) {
|
||||||
|
|
||||||
|
if jws == nil {
|
||||||
|
return "", acme.NewErrorISE("no JWS provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(jws.Signatures) != 1 {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "JWS must have one signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
header := jws.Signatures[0].Protected
|
||||||
|
algorithm := header.Algorithm
|
||||||
|
keyID := header.KeyID
|
||||||
|
nonce := header.Nonce
|
||||||
|
|
||||||
|
if !(algorithm == jose.HS256 || algorithm == jose.HS384 || algorithm == jose.HS512) {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'alg' field set to invalid algorithm '%s'", algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyID == "" {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'kid' field is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonce != "" {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'nonce' must not be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
jwsURL, ok := header.ExtraHeaders["url"]
|
||||||
|
if !ok {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'url' field is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
outerJWS, err := jwsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", acme.WrapErrorISE(err, "could not retrieve outer JWS from context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outerJWS.Signatures) != 1 {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "outer JWS must have one signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
outerJWSURL, ok := outerJWS.Signatures[0].Protected.ExtraHeaders["url"]
|
||||||
|
if !ok {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'url' field must be set in outer JWS")
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwsURL != outerJWSURL {
|
||||||
|
return "", acme.NewError(acme.ErrorMalformedType, "'url' field is not the same value as the outer JWS")
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyID, nil
|
||||||
|
}
|
1068
acme/api/eab_test.go
Normal file
1068
acme/api/eab_test.go
Normal file
File diff suppressed because it is too large
Load diff
12
acme/db.go
12
acme/db.go
|
@ -21,7 +21,7 @@ type DB interface {
|
||||||
|
|
||||||
CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error)
|
GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error
|
DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error
|
||||||
UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
||||||
|
@ -58,7 +58,7 @@ type MockDB struct {
|
||||||
|
|
||||||
MockCreateExternalAccountKey func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
MockCreateExternalAccountKey func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKey func(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKey func(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKeys func(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error)
|
MockGetExternalAccountKeys func(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
MockDeleteExternalAccountKey func(ctx context.Context, provisionerID, keyID string) error
|
MockDeleteExternalAccountKey func(ctx context.Context, provisionerID, keyID string) error
|
||||||
MockUpdateExternalAccountKey func(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
MockUpdateExternalAccountKey func(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
||||||
|
@ -149,13 +149,13 @@ func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerID, keyID
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys mock
|
// GetExternalAccountKeys mock
|
||||||
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*ExternalAccountKey, error) {
|
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error) {
|
||||||
if m.MockGetExternalAccountKeys != nil {
|
if m.MockGetExternalAccountKeys != nil {
|
||||||
return m.MockGetExternalAccountKeys(ctx, provisionerID)
|
return m.MockGetExternalAccountKeys(ctx, provisionerID, cursor, limit)
|
||||||
} else if m.MockError != nil {
|
} else if m.MockError != nil {
|
||||||
return nil, m.MockError
|
return nil, "", m.MockError
|
||||||
}
|
}
|
||||||
return m.MockRet1.([]*ExternalAccountKey), m.MockError
|
return m.MockRet1.([]*ExternalAccountKey), "", m.MockError
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeyByReference mock
|
// GetExternalAccountKeyByReference mock
|
||||||
|
|
|
@ -159,20 +159,22 @@ func (db *DB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
|
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
|
||||||
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
|
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*acme.ExternalAccountKey, string, error) {
|
||||||
externalAccountKeyMutex.RLock()
|
externalAccountKeyMutex.RLock()
|
||||||
defer externalAccountKeyMutex.RUnlock()
|
defer externalAccountKeyMutex.RUnlock()
|
||||||
|
|
||||||
|
// cursor and limit are ignored in open source, at least for now.
|
||||||
|
|
||||||
var eakIDs []string
|
var eakIDs []string
|
||||||
r, err := db.db.Get(externalAccountKeyIDsByProvisionerIDTable, []byte(provisionerID))
|
r, err := db.db.Get(externalAccountKeyIDsByProvisionerIDTable, []byte(provisionerID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !nosqlDB.IsErrNotFound(err) {
|
if !nosqlDB.IsErrNotFound(err) {
|
||||||
return nil, errors.Wrapf(err, "error loading ACME EAB Key IDs for provisioner %s", provisionerID)
|
return nil, "", errors.Wrapf(err, "error loading ACME EAB Key IDs for provisioner %s", provisionerID)
|
||||||
}
|
}
|
||||||
// it may happen that no record is found; we'll continue with an empty slice
|
// it may happen that no record is found; we'll continue with an empty slice
|
||||||
} else {
|
} else {
|
||||||
if err := json.Unmarshal(r, &eakIDs); err != nil {
|
if err := json.Unmarshal(r, &eakIDs); err != nil {
|
||||||
return nil, errors.Wrapf(err, "error unmarshaling ACME EAB Key IDs for provisioner %s", provisionerID)
|
return nil, "", errors.Wrapf(err, "error unmarshaling ACME EAB Key IDs for provisioner %s", provisionerID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +186,7 @@ func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID string)
|
||||||
eak, err := db.getDBExternalAccountKey(ctx, eakID)
|
eak, err := db.getDBExternalAccountKey(ctx, eakID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !nosqlDB.IsErrNotFound(err) {
|
if !nosqlDB.IsErrNotFound(err) {
|
||||||
return nil, errors.Wrapf(err, "error retrieving ACME EAB Key for provisioner %s and keyID %s", provisionerID, eakID)
|
return nil, "", errors.Wrapf(err, "error retrieving ACME EAB Key for provisioner %s and keyID %s", provisionerID, eakID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keys = append(keys, &acme.ExternalAccountKey{
|
keys = append(keys, &acme.ExternalAccountKey{
|
||||||
|
@ -198,7 +200,7 @@ func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID string)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys, nil
|
return keys, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
|
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
|
||||||
|
|
|
@ -576,7 +576,9 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
tc := run(t)
|
tc := run(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
d := DB{db: tc.db}
|
d := DB{db: tc.db}
|
||||||
if eaks, err := d.GetExternalAccountKeys(context.Background(), provID); err != nil {
|
cursor, limit := "", 0
|
||||||
|
if eaks, nextCursor, err := d.GetExternalAccountKeys(context.Background(), provID, cursor, limit); err != nil {
|
||||||
|
assert.Equals(t, "", nextCursor)
|
||||||
switch k := err.(type) {
|
switch k := err.(type) {
|
||||||
case *acme.Error:
|
case *acme.Error:
|
||||||
if assert.NotNil(t, tc.acmeErr) {
|
if assert.NotNil(t, tc.acmeErr) {
|
||||||
|
@ -593,6 +595,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
} else if assert.Nil(t, tc.err) {
|
} else if assert.Nil(t, tc.err) {
|
||||||
assert.Equals(t, len(eaks), len(tc.eaks))
|
assert.Equals(t, len(eaks), len(tc.eaks))
|
||||||
|
assert.Equals(t, "", nextCursor)
|
||||||
for i, eak := range eaks {
|
for i, eak := range eaks {
|
||||||
assert.Equals(t, eak.ID, tc.eaks[i].ID)
|
assert.Equals(t, eak.ID, tc.eaks[i].ID)
|
||||||
assert.Equals(t, eak.KeyBytes, tc.eaks[i].KeyBytes)
|
assert.Equals(t, eak.KeyBytes, tc.eaks[i].KeyBytes)
|
||||||
|
|
|
@ -36,6 +36,7 @@ func (r *CreateExternalAccountKeyRequest) Validate() error {
|
||||||
// GetExternalAccountKeysResponse is the type for GET /admin/acme/eab responses
|
// GetExternalAccountKeysResponse is the type for GET /admin/acme/eab responses
|
||||||
type GetExternalAccountKeysResponse struct {
|
type GetExternalAccountKeysResponse struct {
|
||||||
EAKs []*linkedca.EABKey `json:"eaks"`
|
EAKs []*linkedca.EABKey `json:"eaks"`
|
||||||
|
NextCursor string `json:"nextCursor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireEABEnabled is a middleware that ensures ACME EAB is enabled
|
// requireEABEnabled is a middleware that ensures ACME EAB is enabled
|
||||||
|
@ -43,7 +44,7 @@ type GetExternalAccountKeysResponse struct {
|
||||||
func (h *Handler) requireEABEnabled(next nextHTTP) nextHTTP {
|
func (h *Handler) requireEABEnabled(next nextHTTP) nextHTTP {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
provName := chi.URLParam(r, "prov")
|
provName := chi.URLParam(r, "provisionerName")
|
||||||
eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName)
|
eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, err)
|
api.WriteError(w, err)
|
||||||
|
@ -190,6 +191,9 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
key *acme.ExternalAccountKey
|
key *acme.ExternalAccountKey
|
||||||
keys []*acme.ExternalAccountKey
|
keys []*acme.ExternalAccountKey
|
||||||
err error
|
err error
|
||||||
|
cursor string
|
||||||
|
nextCursor string
|
||||||
|
limit int
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
@ -199,7 +203,13 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reference := chi.URLParam(r, "ref")
|
if cursor, limit, err = api.ParseCursor(r); err != nil {
|
||||||
|
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||||
|
"error parsing cursor and limit from query params"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reference := chi.URLParam(r, "reference")
|
||||||
if reference != "" {
|
if reference != "" {
|
||||||
if key, err = h.acmeDB.GetExternalAccountKeyByReference(ctx, prov.GetId(), 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))
|
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving external account key with reference '%s'", reference))
|
||||||
|
@ -209,7 +219,7 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
keys = []*acme.ExternalAccountKey{key}
|
keys = []*acme.ExternalAccountKey{key}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if keys, err = h.acmeDB.GetExternalAccountKeys(ctx, prov.GetId()); err != nil {
|
if keys, nextCursor, err = h.acmeDB.GetExternalAccountKeys(ctx, prov.GetId(), cursor, limit); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving external account keys"))
|
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving external account keys"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,5 +241,6 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
api.JSON(w, &GetExternalAccountKeysResponse{
|
api.JSON(w, &GetExternalAccountKeysResponse{
|
||||||
EAKs: eaks,
|
EAKs: eaks,
|
||||||
|
NextCursor: nextCursor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ func TestHandler_requireEABEnabled(t *testing.T) {
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"fail/h.provisionerHasEABEnabled": func(t *testing.T) test {
|
"fail/h.provisionerHasEABEnabled": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
auth := &mockAdminAuthority{
|
auth := &mockAdminAuthority{
|
||||||
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
||||||
|
@ -65,7 +65,7 @@ func TestHandler_requireEABEnabled(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/eab-disabled": func(t *testing.T) test {
|
"ok/eab-disabled": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
auth := &mockAdminAuthority{
|
auth := &mockAdminAuthority{
|
||||||
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
||||||
|
@ -105,7 +105,7 @@ func TestHandler_requireEABEnabled(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/eab-enabled": func(t *testing.T) test {
|
"ok/eab-enabled": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
auth := &mockAdminAuthority{
|
auth := &mockAdminAuthority{
|
||||||
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) {
|
||||||
|
@ -498,7 +498,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"fail/ReadJSON": func(t *testing.T) test {
|
"fail/ReadJSON": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
body := []byte("{!?}")
|
body := []byte("{!?}")
|
||||||
return test{
|
return test{
|
||||||
|
@ -516,7 +516,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/validate": func(t *testing.T) test {
|
"fail/validate": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
Reference: strings.Repeat("A", 257),
|
Reference: strings.Repeat("A", 257),
|
||||||
|
@ -538,7 +538,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
Reference: "aRef",
|
Reference: "aRef",
|
||||||
|
@ -560,7 +560,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/acmeDB.GetExternalAccountKeyByReference": func(t *testing.T) test {
|
"fail/acmeDB.GetExternalAccountKeyByReference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -591,7 +591,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/reference-conflict-409": func(t *testing.T) test {
|
"fail/reference-conflict-409": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -629,7 +629,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/acmeDB.CreateExternalAccountKey-no-reference": func(t *testing.T) test {
|
"fail/acmeDB.CreateExternalAccountKey-no-reference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -659,7 +659,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/acmeDB.CreateExternalAccountKey-with-reference": func(t *testing.T) test {
|
"fail/acmeDB.CreateExternalAccountKey-with-reference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -694,7 +694,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/no-reference": func(t *testing.T) test {
|
"ok/no-reference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -731,7 +731,7 @@ func TestHandler_CreateExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/with-reference": func(t *testing.T) test {
|
"ok/with-reference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
req := CreateExternalAccountKeyRequest{
|
req := CreateExternalAccountKeyRequest{
|
||||||
|
@ -831,7 +831,7 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
return test{
|
return test{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -846,7 +846,7 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/acmeDB.DeleteExternalAccountKey": func(t *testing.T) test {
|
"fail/acmeDB.DeleteExternalAccountKey": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
chiCtx.URLParams.Add("id", "keyID")
|
chiCtx.URLParams.Add("id", "keyID")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -871,7 +871,7 @@ func TestHandler_DeleteExternalAccountKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok": func(t *testing.T) test {
|
"ok": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
chiCtx.URLParams.Add("id", "keyID")
|
chiCtx.URLParams.Add("id", "keyID")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -948,7 +948,7 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
"fail/no-provisioner-in-context": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
return test{
|
return test{
|
||||||
|
@ -963,10 +963,28 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fail/parse-cursor": func(t *testing.T) test {
|
||||||
|
chiCtx := chi.NewRouteContext()
|
||||||
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
|
req := httptest.NewRequest("GET", "/foo?limit=A", nil)
|
||||||
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
return test{
|
||||||
|
ctx: ctx,
|
||||||
|
statusCode: 400,
|
||||||
|
req: req,
|
||||||
|
err: &admin.Error{
|
||||||
|
Status: 400,
|
||||||
|
Type: admin.ErrorBadRequestType.String(),
|
||||||
|
Detail: "bad request",
|
||||||
|
Message: "error parsing cursor and limit from query params: limit 'A' is not an integer: strconv.Atoi: parsing \"A\": invalid syntax",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
"fail/acmeDB.GetExternalAccountKeyByReference": func(t *testing.T) test {
|
"fail/acmeDB.GetExternalAccountKeyByReference": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
chiCtx.URLParams.Add("ref", "an-external-key-reference")
|
chiCtx.URLParams.Add("reference", "an-external-key-reference")
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -992,14 +1010,16 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail/acmeDB.GetExternalAccountKeys": func(t *testing.T) test {
|
"fail/acmeDB.GetExternalAccountKeys": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
db := &acme.MockDB{
|
db := &acme.MockDB{
|
||||||
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
|
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID, cursor string, limit int) ([]*acme.ExternalAccountKey, string, error) {
|
||||||
assert.Equals(t, "provID", provisionerID)
|
assert.Equals(t, "provID", provisionerID)
|
||||||
return nil, errors.New("force")
|
assert.Equals(t, "", cursor)
|
||||||
|
assert.Equals(t, 0, limit)
|
||||||
|
return nil, "", errors.New("force")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return test{
|
return test{
|
||||||
|
@ -1017,8 +1037,8 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/reference-not-found": func(t *testing.T) test {
|
"ok/reference-not-found": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
chiCtx.URLParams.Add("ref", "an-external-key-reference")
|
chiCtx.URLParams.Add("reference", "an-external-key-reference")
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -1042,8 +1062,8 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/reference-found": func(t *testing.T) test {
|
"ok/reference-found": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
chiCtx.URLParams.Add("ref", "an-external-key-reference")
|
chiCtx.URLParams.Add("reference", "an-external-key-reference")
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -1082,7 +1102,7 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
"ok/multiple-keys": func(t *testing.T) test {
|
"ok/multiple-keys": func(t *testing.T) test {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("prov", "provName")
|
chiCtx.URLParams.Add("provisionerName", "provName")
|
||||||
req := httptest.NewRequest("GET", "/foo", nil)
|
req := httptest.NewRequest("GET", "/foo", nil)
|
||||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -1090,8 +1110,10 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
var boundAt time.Time
|
var boundAt time.Time
|
||||||
boundAtSet := time.Now().Add(-12 * time.Hour)
|
boundAtSet := time.Now().Add(-12 * time.Hour)
|
||||||
db := &acme.MockDB{
|
db := &acme.MockDB{
|
||||||
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID string) ([]*acme.ExternalAccountKey, error) {
|
MockGetExternalAccountKeys: func(ctx context.Context, provisionerID, cursor string, limit int) ([]*acme.ExternalAccountKey, string, error) {
|
||||||
assert.Equals(t, "provID", provisionerID)
|
assert.Equals(t, "provID", provisionerID)
|
||||||
|
assert.Equals(t, "", cursor)
|
||||||
|
assert.Equals(t, 0, limit)
|
||||||
return []*acme.ExternalAccountKey{
|
return []*acme.ExternalAccountKey{
|
||||||
{
|
{
|
||||||
ID: "eakID1",
|
ID: "eakID1",
|
||||||
|
@ -1116,7 +1138,7 @@ func TestHandler_GetExternalAccountKeys(t *testing.T) {
|
||||||
BoundAt: boundAtSet,
|
BoundAt: boundAtSet,
|
||||||
AccountID: "accountID",
|
AccountID: "accountID",
|
||||||
},
|
},
|
||||||
}, nil
|
}, "", nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return test{
|
return test{
|
||||||
|
|
|
@ -47,8 +47,8 @@ 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(requireEABEnabled(h.GetExternalAccountKeys)))
|
r.MethodFunc("GET", "/acme/eab/{provisionerName}/{reference}", authnz(requireEABEnabled(h.GetExternalAccountKeys)))
|
||||||
r.MethodFunc("GET", "/acme/eab/{prov}", authnz(requireEABEnabled(h.GetExternalAccountKeys)))
|
r.MethodFunc("GET", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.GetExternalAccountKeys)))
|
||||||
r.MethodFunc("POST", "/acme/eab/{prov}", authnz(requireEABEnabled(h.CreateExternalAccountKey)))
|
r.MethodFunc("POST", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.CreateExternalAccountKey)))
|
||||||
r.MethodFunc("DELETE", "/acme/eab/{prov}/{id}", authnz(requireEABEnabled(h.DeleteExternalAccountKey)))
|
r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", authnz(requireEABEnabled(h.DeleteExternalAccountKey)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -667,49 +667,6 @@ retry:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys returns all ACME EAB Keys from the GET /admin/acme/eab request to the CA.
|
|
||||||
func (c *AdminClient) GetExternalAccountKeys(provisionerName, reference string, opts ...AdminOption) ([]*linkedca.EABKey, error) {
|
|
||||||
var retried bool
|
|
||||||
o := new(adminOptions)
|
|
||||||
if err := o.apply(opts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := path.Join(adminURLPrefix, "acme/eab", provisionerName)
|
|
||||||
if reference != "" {
|
|
||||||
p = path.Join(p, "/", reference)
|
|
||||||
}
|
|
||||||
u := c.endpoint.ResolveReference(&url.URL{
|
|
||||||
Path: p,
|
|
||||||
RawQuery: o.rawQuery(),
|
|
||||||
})
|
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", u.String(), http.NoBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "create GET %s request failed", u)
|
|
||||||
}
|
|
||||||
req.Header.Add("Authorization", tok)
|
|
||||||
retry:
|
|
||||||
resp, err := c.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
|
||||||
}
|
|
||||||
if resp.StatusCode >= 400 {
|
|
||||||
if !retried && c.retryOnError(resp) {
|
|
||||||
retried = true
|
|
||||||
goto retry
|
|
||||||
}
|
|
||||||
return nil, readAdminError(resp.Body)
|
|
||||||
}
|
|
||||||
var body = new(adminAPI.GetExternalAccountKeysResponse)
|
|
||||||
if err := readJSON(resp.Body, body); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
|
||||||
}
|
|
||||||
return body.EAKs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAdminError(r io.ReadCloser) error {
|
func readAdminError(r io.ReadCloser) error {
|
||||||
// TODO: not all errors can be read (i.e. 404); seems to be a bigger issue
|
// TODO: not all errors can be read (i.e. 404); seems to be a bigger issue
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
Loading…
Reference in a new issue