Add tests for fetching ACME authorizations and challenges (#20205)

- Add tests to validate that we can load authorizations and
   challenges from the server
This commit is contained in:
Steven Clark 2023-04-17 13:52:54 -04:00 committed by GitHub
parent adbfffc47b
commit 7361ce1e57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 18 deletions

View File

@ -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,
}
@ -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"`
@ -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,
@ -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
}

View File

@ -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
}
@ -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,
}

View File

@ -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
}
@ -95,6 +95,6 @@ func (b *backend) acmeAuthorizationDeactivateHandler(acmeCtx *acmeContext, r *lo
}
return &logical.Response{
Data: authz.NetworkMarshal(),
Data: authz.NetworkMarshal(acmeCtx),
}, nil
}

View File

@ -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
}

View File

@ -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)
@ -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{
@ -287,22 +287,27 @@ func formatOrderResponse(acmeCtx *acmeContext, order *acmeOrder) *logical.Respon
return resp
}
func buildOrderUrl(acmeCtx *acmeContext, orderId string) string {
return acmeCtx.baseUrl.String() + "order/" + orderId
func buildAuthorizationUrl(acmeCtx *acmeContext, authId string) string {
return acmeCtx.baseUrl.JoinPath("authorization", authId).String()
}
func generateAuthorization(acct *acmeAccount, identifier *ACMEIdentifier) *ACMEAuthorization {
func buildOrderUrl(acmeCtx *acmeContext, orderId string) string {
return acmeCtx.baseUrl.JoinPath("order", orderId).String()
}
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,

View File

@ -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")
@ -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)