Add ACME new account creation handlers (#19820)
* Identify whether JWKs existed or were created, set KIDs Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Reclassify ErrAccountDoesNotExist as 400 per spec Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add additional stub methods for ACME accounts Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Start adding ACME newAccount handlers This handler supports two pieces of functionality: 1. Searching for whether an existing account already exists. 2. Creating a new account. One side effect of our JWS parsing logic is that we needed a way to differentiate between whether a JWK existed on disk from an account or if it was specified in the request. This technically means we're potentially responding to certain requests with positive results (e.g., key search based on kid) versus erring earlier like other implementations do. No account storage has been done as part of this commit. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Unify path fields handling, fix newAccount method Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
parent
853e0e0fc1
commit
73c468787b
|
@ -73,7 +73,7 @@ var errIdMappings = map[error]string{
|
||||||
|
|
||||||
// Mapping of err->status codes; see table in RFC 8555 Section 6.7. Errors.
|
// Mapping of err->status codes; see table in RFC 8555 Section 6.7. Errors.
|
||||||
var errCodeMappings = map[error]int{
|
var errCodeMappings = map[error]int{
|
||||||
ErrAccountDoesNotExist: http.StatusNotFound,
|
ErrAccountDoesNotExist: http.StatusBadRequest, // See RFC 8555 Section 7.3.1. Finding an Account URL Given a Key.
|
||||||
ErrAlreadyRevoked: http.StatusBadRequest,
|
ErrAlreadyRevoked: http.StatusBadRequest,
|
||||||
ErrBadCSR: http.StatusBadRequest,
|
ErrBadCSR: http.StatusBadRequest,
|
||||||
ErrBadNonce: http.StatusBadRequest,
|
ErrBadNonce: http.StatusBadRequest,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -22,12 +24,13 @@ var AllowedOuterJWSTypes = map[string]interface{}{
|
||||||
|
|
||||||
// This wraps a JWS message structure.
|
// This wraps a JWS message structure.
|
||||||
type JWSCtx struct {
|
type JWSCtx struct {
|
||||||
Algo string `json:"alg"`
|
Algo string `json:"alg"`
|
||||||
Kid string `json:"kid"`
|
Kid string `json:"kid"`
|
||||||
jwk json.RawMessage `json:"jwk"`
|
jwk json.RawMessage `json:"jwk"`
|
||||||
Nonce string `json:"nonce"`
|
Nonce string `json:"nonce"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
key jose.JSONWebKey `json:"-"`
|
key jose.JSONWebKey `json:"-"`
|
||||||
|
Existing bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *JWSCtx) UnmarshalJSON(a *ACMEState, jws []byte) error {
|
func (c *JWSCtx) UnmarshalJSON(a *ACMEState, jws []byte) error {
|
||||||
|
@ -71,6 +74,7 @@ func (c *JWSCtx) UnmarshalJSON(a *ACMEState, jws []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.Existing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.key.UnmarshalJSON(c.jwk); err != nil {
|
if err = c.key.UnmarshalJSON(c.jwk); err != nil {
|
||||||
|
@ -81,6 +85,17 @@ func (c *JWSCtx) UnmarshalJSON(a *ACMEState, jws []byte) error {
|
||||||
return fmt.Errorf("received invalid jwk")
|
return fmt.Errorf("received invalid jwk")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Kid != "" {
|
||||||
|
// Create a key ID
|
||||||
|
kid, err := c.key.Thumbprint(crypto.SHA256)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating thumbprint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Kid = base64.URLEncoding.EncodeToString(kid)
|
||||||
|
c.Existing = false
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,13 +99,23 @@ func (a *ACMEState) TidyNonces() {
|
||||||
a.nextExpiry.Store(nextRun.Unix())
|
a.nextExpiry.Store(nextRun.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACMEState) LoadKey(keyID string) (map[string]interface{}, error) {
|
func (a *ACMEState) CreateAccount(c *JWSCtx, contact []string, termsOfServiceAgreed bool) (map[string]interface{}, error) {
|
||||||
// TODO
|
// TODO
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ACMEState) LoadAccount(keyID string) (map[string]interface{}, error) {
|
||||||
|
// TODO
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACMEState) DoesAccountExist(keyId string) bool {
|
||||||
|
account, err := a.LoadAccount(keyId)
|
||||||
|
return err == nil && len(account) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ACMEState) LoadJWK(keyID string) ([]byte, error) {
|
func (a *ACMEState) LoadJWK(keyID string) ([]byte, error) {
|
||||||
key, err := a.LoadKey(keyID)
|
key, err := a.LoadAccount(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,20 +12,17 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/builtin/logical/pki/acme"
|
|
||||||
|
|
||||||
atomic2 "go.uber.org/atomic"
|
atomic2 "go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/constants"
|
"github.com/hashicorp/vault/builtin/logical/pki/acme"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/vault/helper/constants"
|
||||||
"github.com/hashicorp/vault/helper/metricsutil"
|
"github.com/hashicorp/vault/helper/metricsutil"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -220,11 +217,14 @@ func Backend(conf *logical.BackendConfig) *backend {
|
||||||
pathAcmeRoleDirectory(&b),
|
pathAcmeRoleDirectory(&b),
|
||||||
pathAcmeIssuerDirectory(&b),
|
pathAcmeIssuerDirectory(&b),
|
||||||
pathAcmeIssuerAndRoleDirectory(&b),
|
pathAcmeIssuerAndRoleDirectory(&b),
|
||||||
|
|
||||||
pathAcmeRootNonce(&b),
|
pathAcmeRootNonce(&b),
|
||||||
pathAcmeRoleNonce(&b),
|
pathAcmeRoleNonce(&b),
|
||||||
pathAcmeIssuerNonce(&b),
|
pathAcmeIssuerNonce(&b),
|
||||||
pathAcmeIssuerAndRoleNonce(&b),
|
pathAcmeIssuerAndRoleNonce(&b),
|
||||||
|
pathAcmeRootNewAccount(&b),
|
||||||
|
pathAcmeRoleNewAccount(&b),
|
||||||
|
pathAcmeIssuerNewAccount(&b),
|
||||||
|
pathAcmeIssuerAndRoleNewAccount(&b),
|
||||||
},
|
},
|
||||||
|
|
||||||
Secrets: []*framework.Secret{
|
Secrets: []*framework.Secret{
|
||||||
|
@ -241,6 +241,7 @@ func Backend(conf *logical.BackendConfig) *backend {
|
||||||
for _, acmePrefix := range []string{"", "issuer/+/", "roles/+/", "issuer/+/roles/+/"} {
|
for _, acmePrefix := range []string{"", "issuer/+/", "roles/+/", "issuer/+/roles/+/"} {
|
||||||
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/directory")
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/directory")
|
||||||
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/new-nonce")
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/new-nonce")
|
||||||
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/new-account")
|
||||||
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/new-order")
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/new-order")
|
||||||
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/revoke-cert")
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/revoke-cert")
|
||||||
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/key-change")
|
b.PathsSpecial.Unauthenticated = append(b.PathsSpecial.Unauthenticated, acmePrefix+"acme/key-change")
|
||||||
|
@ -322,7 +323,9 @@ type backend struct {
|
||||||
|
|
||||||
// Write lock around issuers and keys.
|
// Write lock around issuers and keys.
|
||||||
issuersLock sync.RWMutex
|
issuersLock sync.RWMutex
|
||||||
acmeState *acme.ACMEState
|
|
||||||
|
// Context around ACME operations
|
||||||
|
acmeState *acme.ACMEState
|
||||||
}
|
}
|
||||||
|
|
||||||
type roleOperation func(ctx context.Context, req *logical.Request, data *framework.FieldData, role *roleEntry) (*logical.Response, error)
|
type roleOperation func(ctx context.Context, req *logical.Request, data *framework.FieldData, role *roleEntry) (*logical.Response, error)
|
||||||
|
@ -410,6 +413,7 @@ func (b *backend) initialize(ctx context.Context, _ *logical.InitializationReque
|
||||||
b.Logger().Error("Could not initialize stored certificate counts", err)
|
b.Logger().Error("Could not initialize stored certificate counts", err)
|
||||||
b.certCountError = err.Error()
|
b.certCountError = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6812,6 +6812,7 @@ func TestProperAuthing(t *testing.T) {
|
||||||
for _, acmePrefix := range []string{"", "issuer/default/", "roles/test/", "issuer/default/roles/test/"} {
|
for _, acmePrefix := range []string{"", "issuer/default/", "roles/test/", "issuer/default/roles/test/"} {
|
||||||
paths[acmePrefix+"acme/directory"] = shouldBeUnauthedReadList
|
paths[acmePrefix+"acme/directory"] = shouldBeUnauthedReadList
|
||||||
paths[acmePrefix+"acme/new-nonce"] = shouldBeUnauthedReadList
|
paths[acmePrefix+"acme/new-nonce"] = shouldBeUnauthedReadList
|
||||||
|
paths[acmePrefix+"acme/new-account"] = shouldBeUnauthedWriteOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
for path, checkerType := range paths {
|
for path, checkerType := range paths {
|
||||||
|
|
|
@ -19,42 +19,27 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func pathAcmeRootDirectory(b *backend) *framework.Path {
|
func pathAcmeRootDirectory(b *backend) *framework.Path {
|
||||||
return patternAcmeDirectory(b, "acme/directory", false /* requireRole */, false /* requireIssuer */)
|
return patternAcmeDirectory(b, "acme/directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeRoleDirectory(b *backend) *framework.Path {
|
func pathAcmeRoleDirectory(b *backend) *framework.Path {
|
||||||
return patternAcmeDirectory(b, "roles/"+framework.GenericNameRegex("role")+"/acme/directory",
|
return patternAcmeDirectory(b, "roles/"+framework.GenericNameRegex("role")+"/acme/directory")
|
||||||
true /* requireRole */, false /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeIssuerDirectory(b *backend) *framework.Path {
|
func pathAcmeIssuerDirectory(b *backend) *framework.Path {
|
||||||
return patternAcmeDirectory(b, "issuer/"+framework.GenericNameRegex(issuerRefParam)+"/acme/directory",
|
return patternAcmeDirectory(b, "issuer/"+framework.GenericNameRegex(issuerRefParam)+"/acme/directory")
|
||||||
false /* requireRole */, true /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeIssuerAndRoleDirectory(b *backend) *framework.Path {
|
func pathAcmeIssuerAndRoleDirectory(b *backend) *framework.Path {
|
||||||
return patternAcmeDirectory(b,
|
return patternAcmeDirectory(b,
|
||||||
"issuer/"+framework.GenericNameRegex(issuerRefParam)+"/roles/"+framework.GenericNameRegex(
|
"issuer/"+framework.GenericNameRegex(issuerRefParam)+
|
||||||
"role")+"/acme/directory",
|
"/roles/"+framework.GenericNameRegex("role")+"/acme/directory")
|
||||||
true /* requireRole */, true /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patternAcmeDirectory(b *backend, pattern string, requireRole, requireIssuer bool) *framework.Path {
|
func patternAcmeDirectory(b *backend, pattern string) *framework.Path {
|
||||||
fields := map[string]*framework.FieldSchema{}
|
fields := map[string]*framework.FieldSchema{}
|
||||||
if requireRole {
|
addFieldsForACMEPath(fields, pattern)
|
||||||
fields["role"] = &framework.FieldSchema{
|
|
||||||
Type: framework.TypeString,
|
|
||||||
Description: `The desired role for the acme request`,
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if requireIssuer {
|
|
||||||
fields[issuerRefParam] = &framework.FieldSchema{
|
|
||||||
Type: framework.TypeString,
|
|
||||||
Description: `Reference to an existing issuer name or issuer id`,
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &framework.Path{
|
return &framework.Path{
|
||||||
Pattern: pattern,
|
Pattern: pattern,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
package pki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/builtin/logical/pki/acme"
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pathAcmeRootNewAccount(b *backend) *framework.Path {
|
||||||
|
return patternAcmeNewAccount(b, "acme/new-account")
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathAcmeRoleNewAccount(b *backend) *framework.Path {
|
||||||
|
return patternAcmeNewAccount(b, "roles/"+framework.GenericNameRegex("role")+"/acme/new-account")
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathAcmeIssuerNewAccount(b *backend) *framework.Path {
|
||||||
|
return patternAcmeNewAccount(b, "issuer/"+framework.GenericNameRegex(issuerRefParam)+"/acme/new-account")
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathAcmeIssuerAndRoleNewAccount(b *backend) *framework.Path {
|
||||||
|
return patternAcmeNewAccount(b,
|
||||||
|
"issuer/"+framework.GenericNameRegex(issuerRefParam)+
|
||||||
|
"/roles/"+framework.GenericNameRegex("role")+"/acme/new-account")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFieldsForACMEPath(fields map[string]*framework.FieldSchema, pattern string) map[string]*framework.FieldSchema {
|
||||||
|
if strings.Contains(pattern, framework.GenericNameRegex("role")) {
|
||||||
|
fields["role"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: `The desired role for the acme request`,
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(pattern, framework.GenericNameRegex(issuerRefParam)) {
|
||||||
|
fields[issuerRefParam] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: `Reference to an existing issuer name or issuer id`,
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFieldsForACMERequest(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
||||||
|
fields["protected"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "ACME request 'protected' value",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
fields["payload"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "ACME request 'payload' value",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
fields["signature"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "ACME request 'signature' value",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func patternAcmeNewAccount(b *backend, pattern string) *framework.Path {
|
||||||
|
fields := map[string]*framework.FieldSchema{}
|
||||||
|
addFieldsForACMEPath(fields, pattern)
|
||||||
|
addFieldsForACMERequest(fields)
|
||||||
|
|
||||||
|
return &framework.Path{
|
||||||
|
Pattern: pattern,
|
||||||
|
Fields: fields,
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.acmeParsedWrapper(b.acmeNewAccountHandler),
|
||||||
|
ForwardPerformanceSecondary: false,
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
HelpSynopsis: pathOcspHelpSyn,
|
||||||
|
HelpDescription: pathOcspHelpDesc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type acmeParsedOperation func(acmeCtx acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *acme.JWSCtx, data map[string]interface{}) (*logical.Response, error)
|
||||||
|
|
||||||
|
func (b *backend) acmeParsedWrapper(op acmeParsedOperation) framework.OperationFunc {
|
||||||
|
return b.acmeWrapper(func(acmeCtx acmeContext, r *logical.Request, fields *framework.FieldData) (*logical.Response, error) {
|
||||||
|
user, data, err := b.acmeState.ParseRequestParams(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return op(acmeCtx, r, fields, user, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) acmeNewAccountHandler(acmeCtx acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *acme.JWSCtx, data map[string]interface{}) (*logical.Response, error) {
|
||||||
|
// Parameters
|
||||||
|
var ok bool
|
||||||
|
var onlyReturnExisting bool
|
||||||
|
var contact []string
|
||||||
|
var termsOfServiceAgreed bool
|
||||||
|
|
||||||
|
rawContact, present := data["contact"]
|
||||||
|
if present {
|
||||||
|
contact, ok = rawContact.([]string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid type for field 'contact': %w", acme.ErrMalformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTermsOfServiceAgreed, present := data["termsOfServiceAgreed"]
|
||||||
|
if present {
|
||||||
|
termsOfServiceAgreed, ok = rawTermsOfServiceAgreed.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid type for field 'termsOfServiceAgreed': %w", acme.ErrMalformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawOnlyReturnExisting, present := data["onlyReturnExisting"]
|
||||||
|
if present {
|
||||||
|
onlyReturnExisting, ok = rawOnlyReturnExisting.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid type for field 'onlyReturnExisting': %w", acme.ErrMalformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ignore the EAB parameter as it is currently not supported.
|
||||||
|
|
||||||
|
// We have two paths here: search or create.
|
||||||
|
if onlyReturnExisting {
|
||||||
|
return b.acmeNewAccountSearchHandler(acmeCtx, r, fields, userCtx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.acmeNewAccountCreateHandler(acmeCtx, r, fields, userCtx, data, contact, termsOfServiceAgreed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAccountResponse(location string, status string, contact []string) *logical.Response {
|
||||||
|
resp := &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"orders": location + "/orders",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contact) > 0 {
|
||||||
|
resp.Data["contact"] = contact
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Headers["Location"] = []string{location}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) acmeNewAccountSearchHandler(acmeCtx acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *acme.JWSCtx, data map[string]interface{}) (*logical.Response, error) {
|
||||||
|
if userCtx.Existing || b.acmeState.DoesAccountExist(userCtx.Kid) {
|
||||||
|
// This account exists; return its details. It would be slightly
|
||||||
|
// weird to specify a kid in the request (and not use an explicit
|
||||||
|
// jwk here), but we might as well support it too.
|
||||||
|
account, err := b.acmeState.LoadAccount(userCtx.Kid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error loading account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
location := acmeCtx.baseUrl.String() + "/acme/account/" + userCtx.Kid
|
||||||
|
return formatAccountResponse(location, account["status"].(string), account["contact"].([]string)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per RFC 8555 Section 7.3.1. Finding an Account URL Given a Key:
|
||||||
|
//
|
||||||
|
// > If a client sends such a request and an account does not exist,
|
||||||
|
// > then the server MUST return an error response with status code
|
||||||
|
// > 400 (Bad Request) and type "urn:ietf:params:acme:error:accountDoesNotExist".
|
||||||
|
return nil, fmt.Errorf("An account with this key does not exist: %w", acme.ErrAccountDoesNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) acmeNewAccountCreateHandler(acmeCtx acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *acme.JWSCtx, data map[string]interface{}, contact []string, termsOfServiceAgreed bool) (*logical.Response, error) {
|
||||||
|
if userCtx.Existing {
|
||||||
|
return nil, fmt.Errorf("cannot submit to newAccount with 'kid': %w", acme.ErrMalformed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the account already exists, return the existing one.
|
||||||
|
if b.acmeState.DoesAccountExist(userCtx.Kid) {
|
||||||
|
return b.acmeNewAccountSearchHandler(acmeCtx, r, fields, userCtx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Limit this only when ToS are required by the operator.
|
||||||
|
if !termsOfServiceAgreed {
|
||||||
|
return nil, fmt.Errorf("terms of service not agreed to: %w", acme.ErrUserActionRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := b.acmeState.CreateAccount(userCtx, contact, termsOfServiceAgreed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
location := acmeCtx.baseUrl.String() + "/acme/account/" + userCtx.Kid
|
||||||
|
return formatAccountResponse(location, account["status"].(string), account["contact"].([]string)), nil
|
||||||
|
}
|
|
@ -9,42 +9,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func pathAcmeRootNonce(b *backend) *framework.Path {
|
func pathAcmeRootNonce(b *backend) *framework.Path {
|
||||||
return patternAcmeNonce(b, "acme/new-nonce", false /* requireRole */, false /* requireIssuer */)
|
return patternAcmeNonce(b, "acme/new-nonce")
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeRoleNonce(b *backend) *framework.Path {
|
func pathAcmeRoleNonce(b *backend) *framework.Path {
|
||||||
return patternAcmeNonce(b, "roles/"+framework.GenericNameRegex("role")+"/acme/new-nonce",
|
return patternAcmeNonce(b, "roles/"+framework.GenericNameRegex("role")+"/acme/new-nonce")
|
||||||
true /* requireRole */, false /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeIssuerNonce(b *backend) *framework.Path {
|
func pathAcmeIssuerNonce(b *backend) *framework.Path {
|
||||||
return patternAcmeNonce(b, "issuer/"+framework.GenericNameRegex(issuerRefParam)+"/acme/new-nonce",
|
return patternAcmeNonce(b, "issuer/"+framework.GenericNameRegex(issuerRefParam)+"/acme/new-nonce")
|
||||||
false /* requireRole */, true /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathAcmeIssuerAndRoleNonce(b *backend) *framework.Path {
|
func pathAcmeIssuerAndRoleNonce(b *backend) *framework.Path {
|
||||||
return patternAcmeNonce(b,
|
return patternAcmeNonce(b,
|
||||||
"issuer/"+framework.GenericNameRegex(issuerRefParam)+"/roles/"+framework.GenericNameRegex(
|
"issuer/"+framework.GenericNameRegex(issuerRefParam)+
|
||||||
"role")+"/acme/new-nonce",
|
"/roles/"+framework.GenericNameRegex("role")+"/acme/new-nonce")
|
||||||
true /* requireRole */, true /* requireIssuer */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patternAcmeNonce(b *backend, pattern string, requireRole, requireIssuer bool) *framework.Path {
|
func patternAcmeNonce(b *backend, pattern string) *framework.Path {
|
||||||
fields := map[string]*framework.FieldSchema{}
|
fields := map[string]*framework.FieldSchema{}
|
||||||
if requireRole {
|
addFieldsForACMEPath(fields, pattern)
|
||||||
fields["role"] = &framework.FieldSchema{
|
|
||||||
Type: framework.TypeString,
|
|
||||||
Description: `The desired role for the acme request`,
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if requireIssuer {
|
|
||||||
fields[issuerRefParam] = &framework.FieldSchema{
|
|
||||||
Type: framework.TypeString,
|
|
||||||
Description: `Reference to an existing issuer name or issuer id`,
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &framework.Path{
|
return &framework.Path{
|
||||||
Pattern: pattern,
|
Pattern: pattern,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
|
|
Loading…
Reference in New Issue