vault/builtin/logical/pki/path_acme_challenges.go
Steven Clark cbf6dc2c4f
PKI refactoring to start breaking apart monolith into sub-packages (#24406)
* PKI refactoring to start breaking apart monolith into sub-packages

 - This was broken down by commit within enterprise for ease of review
   but would be too difficult to bring back individual commits back
   to the CE repository. (they would be squashed anyways)
 - This change was created by exporting a patch of the enterprise PR
   and applying it to CE repository

* Fix TestBackend_OID_SANs to not be rely on map ordering
2023-12-07 09:22:53 -05:00

115 lines
3.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathAcmeChallenge(b *backend, baseUrl string, opts acmeWrapperOpts) *framework.Path {
return patternAcmeChallenge(b, baseUrl+
"/challenge/"+framework.MatchAllRegex("auth_id")+"/"+framework.MatchAllRegex("challenge_type"), opts)
}
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, opts acmeWrapperOpts) *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.acmeAccountRequiredWrapper(opts, b.acmeChallengeHandler),
ForwardPerformanceSecondary: false,
ForwardPerformanceStandby: true,
},
},
HelpSynopsis: pathAcmeHelpSync,
HelpDescription: pathAcmeHelpDesc,
}
}
func (b *backend) acmeChallengeHandler(acmeCtx *acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *jwsCtx, data map[string]interface{}, _ *acmeAccount) (*logical.Response, error) {
authId := fields.Get("auth_id").(string)
challengeType := fields.Get("challenge_type").(string)
authz, err := b.GetAcmeState().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)
}
// If data was nil, we got a POST-as-GET request, just return current challenge without an accept,
// otherwise we most likely got a "{}" payload which we should now accept the challenge.
if data != nil {
thumbprint, err := userCtx.GetKeyThumbprint()
if err != nil {
return nil, fmt.Errorf("failed to get thumbprint for key: %w", err)
}
if err := b.GetAcmeState().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
}