Skip to content

Commit

Permalink
Add tests for fetching ACME authorizations and challenges (hashicorp#…
Browse files Browse the repository at this point in the history
…20205)

- Add tests to validate that we can load authorizations and
   challenges from the server
  • Loading branch information
stevendpclark authored Apr 17, 2023
1 parent ccf9492 commit cc749d6
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 17 deletions.
13 changes: 8 additions & 5 deletions builtin/logical/pki/acme_authorizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,16 @@ const (

type ACMEChallenge struct {
Type ACMEChallengeType `json:"type"`
URL string `json:"url"`
Status ACMEChallengeStatusType `json:"status"`
Validated string `json:"validated,optional"`
Error map[string]interface{} `json:"error,optional"`
ChallengeFields map[string]interface{} `json:"challenge_fields"`
}

func (ac *ACMEChallenge) NetworkMarshal() map[string]interface{} {
func (ac *ACMEChallenge) NetworkMarshal(acmeCtx *acmeContext, authId string) map[string]interface{} {
resp := map[string]interface{}{
"type": ac.Type,
"url": ac.URL,
"url": buildChallengeUrl(acmeCtx, authId, string(ac.Type)),
"status": ac.Status,
}

Expand All @@ -87,6 +86,10 @@ func (ac *ACMEChallenge) NetworkMarshal() map[string]interface{} {
return resp
}

func buildChallengeUrl(acmeCtx *acmeContext, authId, challengeType string) string {
return acmeCtx.baseUrl.JoinPath("/challenge/", authId, challengeType).String()
}

type ACMEAuthorization struct {
Id string `json:"id"`
AccountId string `json:"account_id"`
Expand All @@ -112,7 +115,7 @@ func (aa *ACMEAuthorization) GetExpires() (time.Time, error) {
return time.Parse(time.RFC3339, aa.Expires)
}

func (aa *ACMEAuthorization) NetworkMarshal() map[string]interface{} {
func (aa *ACMEAuthorization) NetworkMarshal(acmeCtx *acmeContext) map[string]interface{} {
resp := map[string]interface{}{
"identifier": aa.Identifier,
"status": aa.Status,
Expand All @@ -126,7 +129,7 @@ func (aa *ACMEAuthorization) NetworkMarshal() map[string]interface{} {
if len(aa.Challenges) > 0 {
challenges := []map[string]interface{}{}
for _, challenge := range aa.Challenges {
challenges = append(challenges, challenge.NetworkMarshal())
challenges = append(challenges, challenge.NetworkMarshal(acmeCtx, aa.Id))
}
resp["challenges"] = challenges
}
Expand Down
5 changes: 3 additions & 2 deletions builtin/logical/pki/acme_wrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

type acmeContext struct {
// baseUrl is the combination of the configured cluster local URL and the acmePath up to /acme/
baseUrl *url.URL
sc *storageContext
}
Expand Down Expand Up @@ -46,13 +47,13 @@ func (b *backend) acmeWrapper(op acmeOperation) framework.OperationFunc {
return nil, fmt.Errorf("ACME is disabled in configuration: %w", ErrServerInternal)
}

baseUrl, err := getAcmeBaseUrl(sc, r.Path)
acmeBaseUrl, err := getAcmeBaseUrl(sc, r.Path)
if err != nil {
return nil, err
}

acmeCtx := &acmeContext{
baseUrl: baseUrl,
baseUrl: acmeBaseUrl,
sc: sc,
}

Expand Down
4 changes: 2 additions & 2 deletions builtin/logical/pki/path_acme_authorizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (b *backend) acmeAuthorizationHandler(acmeCtx *acmeContext, r *logical.Requ

func (b *backend) acmeAuthorizationFetchHandler(acmeCtx *acmeContext, r *logical.Request, fields *framework.FieldData, userCtx *jwsCtx, data map[string]interface{}, authz *ACMEAuthorization) (*logical.Response, error) {
return &logical.Response{
Data: authz.NetworkMarshal(),
Data: authz.NetworkMarshal(acmeCtx),
}, nil
}

Expand All @@ -95,6 +95,6 @@ func (b *backend) acmeAuthorizationDeactivateHandler(acmeCtx *acmeContext, r *lo
}

return &logical.Response{
Data: authz.NetworkMarshal(),
Data: authz.NetworkMarshal(acmeCtx),
}, nil
}
2 changes: 1 addition & 1 deletion builtin/logical/pki/path_acme_challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,6 @@ func (b *backend) acmeChallengeFetchHandler(acmeCtx *acmeContext, r *logical.Req
// XXX: Prompt for challenge to be tried by the server.

return &logical.Response{
Data: challenge.NetworkMarshal(),
Data: challenge.NetworkMarshal(acmeCtx, authz.Id),
}, nil
}
17 changes: 11 additions & 6 deletions builtin/logical/pki/path_acme_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (b *backend) acmeNewOrderHandler(ac *acmeContext, r *logical.Request, _ *fr
var authorizations []*ACMEAuthorization
var authorizationIds []string
for _, identifier := range identifiers {
authz := generateAuthorization(account, identifier)
authz := generateAuthorization(ac, account, identifier)
authorizations = append(authorizations, authz)

err = b.acmeState.SaveAuthorization(ac, authz)
Expand Down Expand Up @@ -263,7 +263,7 @@ func formatOrderResponse(acmeCtx *acmeContext, order *acmeOrder) *logical.Respon

var authorizationUrls []string
for _, authId := range order.AuthorizationIds {
authorizationUrls = append(authorizationUrls, acmeCtx.baseUrl.String()+"authz/"+authId)
authorizationUrls = append(authorizationUrls, buildAuthorizationUrl(acmeCtx, authId))
}

resp := &logical.Response{
Expand All @@ -287,22 +287,27 @@ func formatOrderResponse(acmeCtx *acmeContext, order *acmeOrder) *logical.Respon
return resp
}

func buildAuthorizationUrl(acmeCtx *acmeContext, authId string) string {
return acmeCtx.baseUrl.JoinPath("authorization", authId).String()
}

func buildOrderUrl(acmeCtx *acmeContext, orderId string) string {
return acmeCtx.baseUrl.String() + "order/" + orderId
return acmeCtx.baseUrl.JoinPath("order", orderId).String()
}

func generateAuthorization(acct *acmeAccount, identifier *ACMEIdentifier) *ACMEAuthorization {
func generateAuthorization(acmeCtx *acmeContext, acct *acmeAccount, identifier *ACMEIdentifier) *ACMEAuthorization {
authId := genUuid()

challenges := []*ACMEChallenge{
{
Type: ACMEHTTPChallenge,
URL: genUuid(),
Status: ACMEChallengePending,
ChallengeFields: map[string]interface{}{}, // TODO fill this in properly
},
}

return &ACMEAuthorization{
Id: genUuid(),
Id: authId,
AccountId: acct.KeyId,
Identifier: identifier,
Status: ACMEAuthorizationPending,
Expand Down
32 changes: 31 additions & 1 deletion builtin/logical/pki/path_acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ func TestAcmeBasicWorkflow(t *testing.T) {
acme.WithOrderNotAfter(time.Now().Add(7*24*time.Hour)))
require.NoError(t, err, "failed creating order")
require.Equal(t, acme.StatusPending, createOrder.Status)
require.Empty(t, createOrder.CertURL)
require.Equal(t, createOrder.URI+"/finalize", createOrder.FinalizeURL)
require.Len(t, createOrder.AuthzURLs, 1, "expected one authzurls")

// Get orders
// Get order
t.Logf("Testing GetOrder on %s", baseAcmeURL)
getOrder, err := acmeClient.GetOrder(testCtx, createOrder.URI)
require.NoError(t, err, "failed fetching order")
Expand All @@ -117,6 +120,33 @@ func TestAcmeBasicWorkflow(t *testing.T) {
t.Fatalf("Differences exist between create and get order: \n%v", strings.Join(diffs, "\n"))
}

// Load authorization
auth, err := acmeClient.GetAuthorization(testCtx, getOrder.AuthzURLs[0])
require.NoError(t, err, "failed fetching authorization")
require.Equal(t, acme.StatusPending, auth.Status)
require.Equal(t, "dns", auth.Identifier.Type)
require.Equal(t, "www.test.com", auth.Identifier.Value)
require.False(t, auth.Wildcard, "should not be a wildcard")
require.True(t, auth.Expires.IsZero(), "authorization should only have expiry set on valid status")

require.Len(t, auth.Challenges, 1, "expected one challenge")
require.Equal(t, acme.StatusPending, auth.Challenges[0].Status)
require.True(t, auth.Challenges[0].Validated.IsZero(), "validated time should be 0 on challenge")
require.Equal(t, "http-01", auth.Challenges[0].Type)

// TODO: This currently does fail
// require.NotEmpty(t, auth.Challenges[0].Token, "missing challenge token")

// Load a challenge directly
challenge, err := acmeClient.GetChallenge(testCtx, auth.Challenges[0].URI)
require.NoError(t, err, "failed to load challenge")
require.Equal(t, acme.StatusPending, challenge.Status)
require.True(t, challenge.Validated.IsZero(), "validated time should be 0 on challenge")
require.Equal(t, "http-01", challenge.Type)

// TODO: This currently does fail
// require.NotEmpty(t, challenge.Token, "missing challenge token")

// Deactivate account
t.Logf("Testing deactivate account on %s", baseAcmeURL)
err = acmeClient.DeactivateReg(testCtx)
Expand Down

0 comments on commit cc749d6

Please sign in to comment.