WFE: Use RA.GetAuthorization to filter out disabled challenges (#7659)

Have the WFE ask the RA for authorizations, rather than asking the SA
directly. This extra layer of indirection allows us to filter out
challenges which have been disabled, so that clients don't think they
can attempt challenges that we have disabled.

Also shuffle the order of challenges within the authz objects rendered
by the API. We used to have code which does this at authz creation time,
but of course that was completely ineffectual once we stored the
challenges as just a bitmap in the database.

Update the WFE unit tests to mock RA.GetAuthorization instead of
SA.GetAuthorization2. This includes making the mock more accurate, so
that (e.g.) valid authorizations contain valid challenges, and the
challenges have their correct types (e.g. "http-01" instead of just
"http"). Also update the OTel tracing test to account for the new RPC.

Part of https://github.com/letsencrypt/boulder/issues/5913
This commit is contained in:
Aaron Gable 2024-08-22 13:42:58 -07:00 committed by GitHub
parent c9be034c00
commit cac431c661
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 243 additions and 160 deletions

View File

@ -5,7 +5,6 @@ import (
"context"
"crypto/x509"
"errors"
"fmt"
"math/rand/v2"
"net"
"os"
@ -501,57 +500,9 @@ func (sa *StorageAuthorityReadOnly) GetAuthorizations2(ctx context.Context, req
return &sapb.Authorizations{}, nil
}
var (
authzIdValid = int64(1)
authzIdPending = int64(2)
authzIdExpired = int64(3)
authzIdErrorResult = int64(4)
authzIdDiffAccount = int64(5)
)
// GetAuthorization2 is a mock
func (sa *StorageAuthorityReadOnly) GetAuthorization2(ctx context.Context, id *sapb.AuthorizationID2, _ ...grpc.CallOption) (*corepb.Authorization, error) {
authz := core.Authorization{
Status: core.StatusValid,
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("not-an-example.com"),
Challenges: []core.Challenge{
{
Status: "pending",
Token: "token",
Type: "dns",
},
},
}
switch id.Id {
case authzIdValid:
exp := sa.clk.Now().AddDate(100, 0, 0)
authz.Expires = &exp
authz.ID = fmt.Sprintf("%d", authzIdValid)
return bgrpc.AuthzToPB(authz)
case authzIdPending:
exp := sa.clk.Now().AddDate(100, 0, 0)
authz.Expires = &exp
authz.ID = fmt.Sprintf("%d", authzIdPending)
authz.Status = core.StatusPending
return bgrpc.AuthzToPB(authz)
case authzIdExpired:
exp := sa.clk.Now().AddDate(0, -1, 0)
authz.Expires = &exp
authz.ID = fmt.Sprintf("%d", authzIdExpired)
return bgrpc.AuthzToPB(authz)
case authzIdErrorResult:
return nil, fmt.Errorf("unspecified database error")
case authzIdDiffAccount:
exp := sa.clk.Now().AddDate(100, 0, 0)
authz.RegistrationID = 2
authz.Expires = &exp
authz.ID = fmt.Sprintf("%d", authzIdDiffAccount)
return bgrpc.AuthzToPB(authz)
}
return nil, berrors.NotFoundError("no authorization found with id %q", id)
return &corepb.Authorization{}, nil
}
// GetSerialsByKey is a mock

View File

@ -233,9 +233,11 @@ func TestTraces(t *testing.T) {
rpcSpan("sa.StorageAuthority/GetOrderForNames", ra, sa),
rpcSpan("sa.StorageAuthority/NewOrderAndAuthzs", ra, sa))),
httpSpan("/acme/authz-v3/",
rpcSpan("sa.StorageAuthorityReadOnly/GetAuthorization2", wfe, sa)),
rpcSpan("ra.RegistrationAuthority/GetAuthorization", wfe, ra,
rpcSpan("sa.StorageAuthority/GetAuthorization2", ra, sa))),
httpSpan("/acme/chall-v3/",
rpcSpan("sa.StorageAuthorityReadOnly/GetAuthorization2", wfe, sa),
rpcSpan("ra.RegistrationAuthority/GetAuthorization", wfe, ra,
rpcSpan("sa.StorageAuthority/GetAuthorization2", ra, sa)),
rpcSpan("ra.RegistrationAuthority/PerformValidation", wfe, ra,
rpcSpan("sa.StorageAuthority/GetRegistration", ra, sa))),
httpSpan("/acme/finalize/",
@ -251,8 +253,10 @@ func TestTraces(t *testing.T) {
rpcSpan("ca.CertificateAuthority/IssueCertificateForPrecertificate", ra, ca),
redisPipelineSpan("set", ra),
rpcSpan("sa.StorageAuthority/FinalizeOrder", ra, sa))),
httpSpan("/acme/order/", rpcSpan("sa.StorageAuthorityReadOnly/GetOrder", wfe, sa)),
httpSpan("/acme/cert/", rpcSpan("sa.StorageAuthorityReadOnly/GetCertificate", wfe, sa)),
httpSpan("/acme/order/",
rpcSpan("sa.StorageAuthorityReadOnly/GetOrder", wfe, sa)),
httpSpan("/acme/cert/",
rpcSpan("sa.StorageAuthorityReadOnly/GetCertificate", wfe, sa)),
},
}

View File

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"math/big"
"math/rand/v2"
"net"
"net/http"
"slices"
@ -1111,7 +1112,7 @@ func (wfe *WebFrontEndImpl) Challenge(
return
}
challengeID := slug[1]
authzPB, err := wfe.sa.GetAuthorization2(ctx, &sapb.AuthorizationID2{Id: authorizationID})
authzPB, err := wfe.ra.GetAuthorization(ctx, &rapb.GetAuthorizationRequest{Id: authorizationID})
if err != nil {
if errors.Is(err, berrors.NotFound) {
notFound()
@ -1218,6 +1219,12 @@ func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(request *http.Request, a
for i := range authz.Challenges {
wfe.prepChallengeForDisplay(request, *authz, &authz.Challenges[i])
}
// Shuffle the challenges so no one relies on their order.
rand.Shuffle(len(authz.Challenges), func(i, j int) {
authz.Challenges[i], authz.Challenges[j] = authz.Challenges[j], authz.Challenges[i]
})
authz.ID = ""
authz.RegistrationID = 0
@ -1552,7 +1559,7 @@ func (wfe *WebFrontEndImpl) Authorization(
return
}
authzPB, err := wfe.sa.GetAuthorization2(ctx, &sapb.AuthorizationID2{Id: authzID})
authzPB, err := wfe.ra.GetAuthorization(ctx, &rapb.GetAuthorizationRequest{Id: authzID})
if errors.Is(err, berrors.NotFound) {
wfe.sendError(response, logEvent, probs.NotFound("No such authorization"), nil)
return

View File

@ -41,7 +41,6 @@ import (
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/goodkey"
bgrpc "github.com/letsencrypt/boulder/grpc"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
@ -185,6 +184,7 @@ EeMZ9nWyIM6bktLrE11HnFOnKhAYsM5fZA==
type MockRegistrationAuthority struct {
rapb.RegistrationAuthorityClient
clk clock.Clock
lastRevocationReason revocation.Reason
}
@ -216,6 +216,67 @@ func (ra *MockRegistrationAuthority) RevokeCertByKey(ctx context.Context, in *ra
return &emptypb.Empty{}, nil
}
// GetAuthorization returns a different authorization depending on the requested
// ID. All authorizations are associated with RegID 1, except for the one that isn't.
func (ra *MockRegistrationAuthority) GetAuthorization(_ context.Context, in *rapb.GetAuthorizationRequest, _ ...grpc.CallOption) (*corepb.Authorization, error) {
switch in.Id {
case 1: // Return a valid authorization with a single valid challenge.
return &corepb.Authorization{
Id: "1",
RegistrationID: 1,
DnsName: "not-an-example.com",
Status: string(core.StatusValid),
Expires: timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
Challenges: []*corepb.Challenge{
{Id: 1, Type: "http-01", Status: string(core.StatusValid), Token: "token"},
},
}, nil
case 2: // Return a pending authorization with three pending challenges.
return &corepb.Authorization{
Id: "2",
RegistrationID: 1,
DnsName: "not-an-example.com",
Status: string(core.StatusPending),
Expires: timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
Challenges: []*corepb.Challenge{
{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
},
}, nil
case 3: // Return an expired authorization with three pending (but expired) challenges.
return &corepb.Authorization{
Id: "3",
RegistrationID: 1,
DnsName: "not-an-example.com",
Status: string(core.StatusPending),
Expires: timestamppb.New(ra.clk.Now().AddDate(-1, 0, 0)),
Challenges: []*corepb.Challenge{
{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
},
}, nil
case 4: // Return an internal server error.
return nil, fmt.Errorf("unspecified error")
case 5: // Return a pending authorization as above, but associated with RegID 2.
return &corepb.Authorization{
Id: "5",
RegistrationID: 2,
DnsName: "not-an-example.com",
Status: string(core.StatusPending),
Expires: timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
Challenges: []*corepb.Challenge{
{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
},
}, nil
}
return nil, berrors.NotFoundError("no authorization found with id %q", in.Id)
}
func (ra *MockRegistrationAuthority) DeactivateAuthorization(context.Context, *corepb.Authorization, ...grpc.CallOption) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
@ -394,7 +455,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
10*time.Second,
30*24*time.Hour,
7*24*time.Hour,
&MockRegistrationAuthority{},
&MockRegistrationAuthority{clk: fc},
mockSA,
gnc,
rnc,
@ -1107,35 +1168,33 @@ func TestHTTPMethods(t *testing.T) {
func TestGetChallenge(t *testing.T) {
wfe, _, _ := setupWFE(t)
challengeURL := "http://localhost/acme/chall-v3/1/-ZfxEw"
// The slug "7TyhFQ" is the StringID of a challenge with type "http-01" and
// token "token".
challSlug := "7TyhFQ"
for _, method := range []string{"GET", "HEAD"} {
resp := httptest.NewRecorder()
// We set req.URL.Path separately to emulate the path-stripping that
// Boulder's request handler does.
challengeURL := fmt.Sprintf("http://localhost/acme/chall-v3/1/%s", challSlug)
req, err := http.NewRequest(method, challengeURL, nil)
req.URL.Path = "1/-ZfxEw"
test.AssertNotError(t, err, "Could not make NewRequest")
req.URL.Path = fmt.Sprintf("1/%s", challSlug)
wfe.Challenge(ctx, newRequestEvent(), resp, req)
test.AssertEquals(t,
resp.Code,
http.StatusOK)
test.AssertEquals(t,
resp.Header().Get("Location"),
challengeURL)
test.AssertEquals(t,
resp.Header().Get("Content-Type"),
"application/json")
test.AssertEquals(t,
resp.Header().Get("Link"),
`<http://localhost/acme/authz-v3/1>;rel="up"`)
test.AssertEquals(t, resp.Code, http.StatusOK)
test.AssertEquals(t, resp.Header().Get("Location"), challengeURL)
test.AssertEquals(t, resp.Header().Get("Content-Type"), "application/json")
test.AssertEquals(t, resp.Header().Get("Link"), `<http://localhost/acme/authz-v3/1>;rel="up"`)
// Body is only relevant for GET. For HEAD, body will
// be discarded by HandleFunc() anyway, so it doesn't
// matter what Challenge() writes to it.
if method == "GET" {
test.AssertUnmarshaledEquals(
t, resp.Body.String(),
`{"status": "pending", "type":"dns","token":"token","url":"http://localhost/acme/chall-v3/1/-ZfxEw"}`)
`{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall-v3/1/7TyhFQ"}`)
}
}
}
@ -1162,17 +1221,18 @@ func TestChallenge(t *testing.T) {
}{
{
Name: "Valid challenge",
Request: post("1/-ZfxEw"),
Request: post("1/7TyhFQ"),
ExpectedStatus: http.StatusOK,
ExpectedHeaders: map[string]string{
"Location": "http://localhost/acme/chall-v3/1/-ZfxEw",
"Link": `<http://localhost/acme/authz-v3/1>;rel="up"`,
"Content-Type": "application/json",
"Location": "http://localhost/acme/chall-v3/1/7TyhFQ",
"Link": `<http://localhost/acme/authz-v3/1>;rel="up"`,
},
ExpectedBody: `{"status": "pending", "type":"dns","token":"token","url":"http://localhost/acme/chall-v3/1/-ZfxEw"}`,
ExpectedBody: `{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall-v3/1/7TyhFQ"}`,
},
{
Name: "Expired challenge",
Request: post("3/-ZfxEw"),
Request: post("3/7TyhFQ"),
ExpectedStatus: http.StatusNotFound,
ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Expired authorization","status":404}`,
},
@ -1184,21 +1244,21 @@ func TestChallenge(t *testing.T) {
},
{
Name: "Unspecified database error",
Request: post("4/-ZfxEw"),
Request: post("4/7TyhFQ"),
ExpectedStatus: http.StatusInternalServerError,
ExpectedBody: `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Problem getting authorization","status":500}`,
},
{
Name: "POST-as-GET, wrong owner",
Request: postAsGet(1, "5/-ZfxEw", ""),
Request: postAsGet(1, "5/7TyhFQ", ""),
ExpectedStatus: http.StatusForbidden,
ExpectedBody: `{"type":"` + probs.ErrorNS + `unauthorized","detail":"User account ID doesn't match account ID in authorization","status":403}`,
},
{
Name: "Valid POST-as-GET",
Request: postAsGet(1, "1/-ZfxEw", ""),
Request: postAsGet(1, "1/7TyhFQ", ""),
ExpectedStatus: http.StatusOK,
ExpectedBody: `{"status": "pending", "type":"dns", "token":"token", "url": "http://localhost/acme/chall-v3/1/-ZfxEw"}`,
ExpectedBody: `{"status": "valid", "type":"http-01", "token":"token", "url": "http://localhost/acme/chall-v3/1/7TyhFQ"}`,
},
}
@ -1232,21 +1292,21 @@ func (ra *MockRAPerformValidationError) PerformValidation(context.Context, *rapb
// with an already valid authorization just returns the challenge without calling
// the RA.
func TestUpdateChallengeFinalizedAuthz(t *testing.T) {
wfe, _, signer := setupWFE(t)
wfe.ra = &MockRAPerformValidationError{}
wfe, fc, signer := setupWFE(t)
wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}}
responseWriter := httptest.NewRecorder()
signedURL := "http://localhost/1/-ZfxEw"
signedURL := "http://localhost/1/7TyhFQ"
_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
request := makePostRequestWithPath("1/-ZfxEw", jwsBody)
request := makePostRequestWithPath("1/7TyhFQ", jwsBody)
wfe.Challenge(ctx, newRequestEvent(), responseWriter, request)
body := responseWriter.Body.String()
test.AssertUnmarshaledEquals(t, body, `{
"status": "pending",
"type": "dns",
"token":"token",
"url": "http://localhost/acme/chall-v3/1/-ZfxEw"
"status": "valid",
"type": "http-01",
"token": "token",
"url": "http://localhost/acme/chall-v3/1/7TyhFQ"
}`)
}
@ -1254,15 +1314,15 @@ func TestUpdateChallengeFinalizedAuthz(t *testing.T) {
// PerformValidation that the WFE returns an internal server error as expected
// and does not panic or otherwise bug out.
func TestUpdateChallengeRAError(t *testing.T) {
wfe, _, signer := setupWFE(t)
wfe, fc, signer := setupWFE(t)
// Mock the RA to always fail PerformValidation
wfe.ra = &MockRAPerformValidationError{}
wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}}
// Update a pending challenge
signedURL := "http://localhost/2/-ZfxEw"
signedURL := "http://localhost/2/7TyhFQ"
_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
responseWriter := httptest.NewRecorder()
request := makePostRequestWithPath("2/-ZfxEw", jwsBody)
request := makePostRequestWithPath("2/7TyhFQ", jwsBody)
wfe.Challenge(ctx, newRequestEvent(), responseWriter, request)
@ -1622,10 +1682,10 @@ func TestGetAuthorization(t *testing.T) {
"expires": "2070-01-01T00:00:00Z",
"challenges": [
{
"status": "pending",
"type": "dns",
"status": "valid",
"type": "http-01",
"token":"token",
"url": "http://localhost/acme/chall-v3/1/-ZfxEw"
"url": "http://localhost/acme/chall-v3/1/7TyhFQ"
}
]
}`)
@ -1649,49 +1709,46 @@ func TestAuthorization500(t *testing.T) {
test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), expected)
}
// SAWithFailedChallenges is a mocks.StorageAuthority that has
// a `GetAuthorization` implementation that can return authorizations with
// failed challenges.
type SAWithFailedChallenges struct {
sapb.StorageAuthorityReadOnlyClient
Clk clock.FakeClock
// RAWithFailedChallenges is a fake RA whose GetAuthorization method returns
// an authz with a failed challenge.
type RAWithFailedChallenge struct {
rapb.RegistrationAuthorityClient
clk clock.Clock
}
func (sa *SAWithFailedChallenges) GetAuthorization2(ctx context.Context, id *sapb.AuthorizationID2, _ ...grpc.CallOption) (*corepb.Authorization, error) {
authz := core.Authorization{
ID: "55",
Status: core.StatusValid,
func (ra *RAWithFailedChallenge) GetAuthorization(ctx context.Context, id *rapb.GetAuthorizationRequest, _ ...grpc.CallOption) (*corepb.Authorization, error) {
return &corepb.Authorization{
Id: "6",
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("not-an-example.com"),
Challenges: []core.Challenge{
DnsName: "not-an-example.com",
Status: string(core.StatusInvalid),
Expires: timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
Challenges: []*corepb.Challenge{
{
Status: core.StatusInvalid,
Type: "dns",
Token: "exampleToken",
Error: &probs.ProblemDetails{
Type: "things:are:whack",
Detail: "whack attack",
HTTPStatus: 555,
Id: 1,
Type: "http-01",
Status: string(core.StatusInvalid),
Token: "token",
Error: &corepb.ProblemDetails{
ProblemType: "things:are:whack",
Detail: "whack attack",
HttpStatus: 555,
},
},
},
}
exp := sa.Clk.Now().AddDate(100, 0, 0)
authz.Expires = &exp
return bgrpc.AuthzToPB(authz)
}, nil
}
// TestAuthorizationChallengeNamespace tests that the runtime prefixing of
// Challenge Problem Types works as expected
func TestAuthorizationChallengeNamespace(t *testing.T) {
wfe, clk, _ := setupWFE(t)
wfe.sa = &SAWithFailedChallenges{Clk: clk}
wfe.ra = &RAWithFailedChallenge{clk: clk}
responseWriter := httptest.NewRecorder()
wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{
Method: "GET",
URL: mustParseURL("55"),
URL: mustParseURL("6"),
})
var authz core.Authorization
@ -2368,10 +2425,10 @@ func TestDeactivateAuthorization(t *testing.T) {
"expires": "2070-01-01T00:00:00Z",
"challenges": [
{
"status": "pending",
"type": "dns",
"token":"token",
"url": "http://localhost/acme/chall-v3/1/-ZfxEw"
"status": "valid",
"type": "http-01",
"token": "token",
"url": "http://localhost/acme/chall-v3/1/7TyhFQ"
}
]
}`)
@ -3327,34 +3384,117 @@ func TestNewAccountWhenGetRegByKeyNotFound(t *testing.T) {
}
func TestPrepAuthzForDisplay(t *testing.T) {
t.Parallel()
wfe, _, _ := setupWFE(t)
authz := &core.Authorization{
ID: "12345",
Status: core.StatusPending,
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("example.com"),
Challenges: []core.Challenge{
{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
},
}
// This modifies the authz in-place.
wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
// The ID and RegID should be empty, since they're not part of the ACME API object.
test.AssertEquals(t, authz.ID, "")
test.AssertEquals(t, authz.RegistrationID, int64(0))
}
func TestPrepRevokedAuthzForDisplay(t *testing.T) {
t.Parallel()
wfe, _, _ := setupWFE(t)
authz := &core.Authorization{
ID: "12345",
Status: core.StatusInvalid,
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("example.com"),
Challenges: []core.Challenge{
{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
},
}
// This modifies the authz in-place.
wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
// All of the challenges should be revoked as well.
for _, chall := range authz.Challenges {
test.AssertEquals(t, chall.Status, core.StatusInvalid)
}
}
func TestPrepWildcardAuthzForDisplay(t *testing.T) {
t.Parallel()
wfe, _, _ := setupWFE(t)
// Make an authz for a wildcard identifier
authz := &core.Authorization{
ID: "12345",
Status: core.StatusPending,
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("*.example.com"),
Challenges: []core.Challenge{
{
Type: "dns",
},
{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
},
}
// Prep the wildcard authz for display
// This modifies the authz in-place.
wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
// The authz should not have a wildcard prefix in the identifier value
// The identifier should not start with a star, but the authz should be marked
// as a wildcard.
test.AssertEquals(t, strings.HasPrefix(authz.Identifier.Value, "*."), false)
// The authz should be marked as corresponding to a wildcard name
test.AssertEquals(t, authz.Wildcard, true)
}
// We expect the authz challenge has its URL set and the URI emptied.
authz.ID = "12345"
wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
chal := authz.Challenges[0]
test.AssertEquals(t, chal.URL, "http://localhost/acme/chall-v3/12345/po1V2w")
func TestPrepAuthzForDisplayShuffle(t *testing.T) {
t.Parallel()
wfe, _, _ := setupWFE(t)
authz := &core.Authorization{
ID: "12345",
Status: core.StatusPending,
RegistrationID: 1,
Identifier: identifier.DNSIdentifier("example.com"),
Challenges: []core.Challenge{
{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
},
}
// The challenges should be presented in an unpredictable order.
// Create a structure to count how many times each challenge type ends up in
// each position in the output authz.Challenges list.
counts := make(map[core.AcmeChallenge]map[int]int)
counts[core.ChallengeTypeDNS01] = map[int]int{0: 0, 1: 0, 2: 0}
counts[core.ChallengeTypeHTTP01] = map[int]int{0: 0, 1: 0, 2: 0}
counts[core.ChallengeTypeTLSALPN01] = map[int]int{0: 0, 1: 0, 2: 0}
// Prep the authz 100 times, and count where each challenge ended up each time.
for range 100 {
// This modifies the authz in place
wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
for i, chall := range authz.Challenges {
counts[chall.Type][i] += 1
}
}
// Ensure that at least some amount of randomization is happening.
for challType, indices := range counts {
for index, count := range indices {
test.Assert(t, count > 10, fmt.Sprintf("challenge type %s did not appear in position %d as often as expected", challType, index))
}
}
}
// noSCTMockRA is a mock RA that always returns a `berrors.MissingSCTsError` from `FinalizeOrder`
@ -3411,25 +3551,6 @@ func TestOrderToOrderJSONV2Authorizations(t *testing.T) {
})
}
func TestGetChallengeUpRel(t *testing.T) {
wfe, _, _ := setupWFE(t)
challengeURL := "http://localhost/acme/chall-v3/1/-ZfxEw"
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", challengeURL, nil)
test.AssertNotError(t, err, "Could not make NewRequest")
req.URL.Path = "1/-ZfxEw"
wfe.Challenge(ctx, newRequestEvent(), resp, req)
test.AssertEquals(t,
resp.Code,
http.StatusOK)
test.AssertEquals(t,
resp.Header().Get("Link"),
`<http://localhost/acme/authz-v3/1>;rel="up"`)
}
func TestPrepAccountForDisplay(t *testing.T) {
acct := &core.Registration{
ID: 1987,
@ -3498,12 +3619,12 @@ func TestGETAPIChallenge(t *testing.T) {
}{
{
name: "fresh authz challenge",
path: "1/-ZfxEw",
path: "1/7TyhFQ",
expectTooFreshErr: true,
},
{
name: "old authz challenge",
path: "2/-ZfxEw",
path: "2/7TyhFQ",
expectTooFreshErr: false,
},
}