open-vault/builtin/logical/pki/path_acme_challenges.go
Alexander Scheel 005d989230
Add acme challenge validation engine (#20221)
* 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>
2023-04-19 12:31:19 -04:00

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
}