005d989230
* Allow creating storageContext with timeout Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add challenge validation engine to ACME Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Initialize the ACME challenge validation engine Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Trigger challenge validation on endpoint submission Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Fix GetKeyThumbprint to use raw base64 Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Point at localhost for testing Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add cleanup of validation engine Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
111 lines
3.6 KiB
Go
111 lines
3.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package pki
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
func pathAcmeChallenge(b *backend) []*framework.Path {
|
|
return buildAcmeFrameworkPaths(b, patternAcmeChallenge,
|
|
"/challenge/"+framework.MatchAllRegex("auth_id")+"/"+framework.MatchAllRegex("challenge_type"))
|
|
}
|
|
|
|
func addFieldsForACMEChallenge(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["auth_id"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "ACME authorization identifier value",
|
|
Required: true,
|
|
}
|
|
|
|
fields["challenge_type"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "ACME challenge type",
|
|
Required: true,
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
func patternAcmeChallenge(b *backend, pattern string) *framework.Path {
|
|
fields := map[string]*framework.FieldSchema{}
|
|
addFieldsForACMEPath(fields, pattern)
|
|
addFieldsForACMERequest(fields)
|
|
addFieldsForACMEChallenge(fields)
|
|
|
|
return &framework.Path{
|
|
Pattern: pattern,
|
|
Fields: fields,
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
Callback: b.acmeParsedWrapper(b.acmeChallengeHandler),
|
|
ForwardPerformanceSecondary: false,
|
|
ForwardPerformanceStandby: true,
|
|
},
|
|
},
|
|
|
|
HelpSynopsis: "",
|
|
HelpDescription: "",
|
|
}
|
|
}
|
|
|
|
func (b *backend) acmeChallengeHandler(acmeCtx *acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *jwsCtx, data map[string]interface{}) (*logical.Response, error) {
|
|
authId := fields.Get("auth_id").(string)
|
|
challengeType := fields.Get("challenge_type").(string)
|
|
|
|
authz, err := b.acmeState.LoadAuthorization(acmeCtx, userCtx, authId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load authorization: %w", err)
|
|
}
|
|
|
|
return b.acmeChallengeFetchHandler(acmeCtx, r, fields, userCtx, data, authz, challengeType)
|
|
}
|
|
|
|
func (b *backend) acmeChallengeFetchHandler(acmeCtx *acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *jwsCtx, data map[string]interface{}, authz *ACMEAuthorization, challengeType string) (*logical.Response, error) {
|
|
var challenge *ACMEChallenge
|
|
for _, c := range authz.Challenges {
|
|
if string(c.Type) == challengeType {
|
|
challenge = c
|
|
break
|
|
}
|
|
}
|
|
|
|
if challenge == nil {
|
|
return nil, fmt.Errorf("unknown challenge of type '%v' in authorization: %w", challengeType, ErrMalformed)
|
|
}
|
|
|
|
// Per RFC 8555 Section 7.5.1. Responding to Challenges:
|
|
//
|
|
// > The client indicates to the server that it is ready for the challenge
|
|
// > validation by sending an empty JSON body ("{}") carried in a POST
|
|
// > request to the challenge URL (not the authorization URL).
|
|
if len(data) > 0 {
|
|
return nil, fmt.Errorf("unexpected request parameters: %w", ErrMalformed)
|
|
}
|
|
|
|
thumbprint, err := userCtx.GetKeyThumbprint()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get thumbprint for key: %w", err)
|
|
}
|
|
|
|
if err := b.acmeState.validator.AcceptChallenge(acmeCtx.sc, userCtx.Kid, authz, challenge, thumbprint); err != nil {
|
|
return nil, fmt.Errorf("error submitting challenge for validation: %w", err)
|
|
}
|
|
|
|
return &logical.Response{
|
|
Data: challenge.NetworkMarshal(acmeCtx, authz.Id),
|
|
|
|
// Per RFC 8555 Section 7.1. Resources:
|
|
//
|
|
// > The "up" link relation is used with challenge resources to indicate
|
|
// > the authorization resource to which a challenge belongs.
|
|
Headers: map[string][]string{
|
|
"Link": {fmt.Sprintf("<%s>;rel=\"up\"", buildAuthorizationUrl(acmeCtx, authz.Id))},
|
|
},
|
|
}, nil
|
|
}
|