Skip to content

Commit

Permalink
Merge branch 'main' of github.com:letsencrypt/boulder into updatereg-2
Browse files Browse the repository at this point in the history
  • Loading branch information
jprenken committed Nov 22, 2024
2 parents db90139 + 7791262 commit 024b9f9
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 71 deletions.
13 changes: 12 additions & 1 deletion ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -2425,7 +2425,7 @@ func (ra *RegistrationAuthorityImpl) DeactivateRegistration(ctx context.Context,

// DeactivateAuthorization deactivates a currently valid authorization
func (ra *RegistrationAuthorityImpl) DeactivateAuthorization(ctx context.Context, req *corepb.Authorization) (*emptypb.Empty, error) {
if req == nil || req.Id == "" || req.Status == "" {
if core.IsAnyNilOrZero(req, req.Id, req.Status, req.RegistrationID) {
return nil, errIncompleteGRPCRequest
}
authzID, err := strconv.ParseInt(req.Id, 10, 64)
Expand All @@ -2435,6 +2435,17 @@ func (ra *RegistrationAuthorityImpl) DeactivateAuthorization(ctx context.Context
if _, err := ra.SA.DeactivateAuthorization2(ctx, &sapb.AuthorizationID2{Id: authzID}); err != nil {
return nil, err
}
if req.Status == string(core.StatusPending) {
// Some clients deactivate pending authorizations without attempting them.
// We're not sure exactly when this happens but it's most likely due to
// internal errors in the client. From our perspective this uses storage
// resources similar to how failed authorizations do, so we increment the
// failed authorizations limit.
err = ra.countFailedValidations(ctx, req.RegistrationID, identifier.NewDNS(req.DnsName))
if err != nil {
return nil, fmt.Errorf("failed to update rate limits: %w", err)
}
}
return &emptypb.Empty{}, nil
}

Expand Down
84 changes: 76 additions & 8 deletions ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,13 +865,13 @@ func (msa *mockSAPaused) FinalizeAuthorization2(ctx context.Context, req *sapb.F
}

func TestPerformValidation_FailedValidationsTriggerPauseIdentifiersRatelimit(t *testing.T) {
if !strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
t.Skip()
}

va, sa, ra, redisSrc, fc, cleanUp := initAuthorities(t)
defer cleanUp()

if ra.limiter == nil {
t.Skip("no redis limiter configured")
}

features.Set(features.Config{AutomaticallyPauseZombieClients: true})
defer features.Reset()

Expand Down Expand Up @@ -988,13 +988,13 @@ func TestPerformValidation_FailedValidationsTriggerPauseIdentifiersRatelimit(t *
}

func TestPerformValidation_FailedThenSuccessfulValidationResetsPauseIdentifiersRatelimit(t *testing.T) {
if !strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
t.Skip()
}

va, sa, ra, redisSrc, fc, cleanUp := initAuthorities(t)
defer cleanUp()

if ra.limiter == nil {
t.Skip("no redis limiter configured")
}

features.Set(features.Config{AutomaticallyPauseZombieClients: true})
defer features.Reset()

Expand Down Expand Up @@ -1778,6 +1778,74 @@ func TestDeactivateAuthorization(t *testing.T) {
test.AssertEquals(t, deact.Status, string(core.StatusDeactivated))
}

type mockSARecordingPauses struct {
sapb.StorageAuthorityClient
recv *sapb.PauseRequest
}

func (sa *mockSARecordingPauses) PauseIdentifiers(ctx context.Context, req *sapb.PauseRequest, _ ...grpc.CallOption) (*sapb.PauseIdentifiersResponse, error) {
sa.recv = req
return &sapb.PauseIdentifiersResponse{Paused: int64(len(req.Identifiers))}, nil
}

func (sa *mockSARecordingPauses) DeactivateAuthorization2(_ context.Context, _ *sapb.AuthorizationID2, _ ...grpc.CallOption) (*emptypb.Empty, error) {
return nil, nil
}

func TestDeactivateAuthorization_Pausing(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()

if ra.limiter == nil {
t.Skip("no redis limiter configured")
}

msa := mockSARecordingPauses{}
ra.SA = &msa

features.Set(features.Config{AutomaticallyPauseZombieClients: true})
defer features.Reset()

// Override the default ratelimits to only allow one failed validation.
txnBuilder, err := ratelimits.NewTransactionBuilder("testdata/one-failed-validation-before-pausing.yml", "")
test.AssertNotError(t, err, "making transaction composer")
ra.txnBuilder = txnBuilder

// The first deactivation of a pending authz should work and nothing should
// get paused.
_, err = ra.DeactivateAuthorization(ctx, &corepb.Authorization{
Id: "1",
RegistrationID: 1,
DnsName: "example.com",
Status: string(core.StatusPending),
})
test.AssertNotError(t, err, "mock deactivation should work")
test.AssertBoxedNil(t, msa.recv, "shouldn't be a pause request yet")

// Deactivating a valid authz shouldn't increment any limits or pause anything.
_, err = ra.DeactivateAuthorization(ctx, &corepb.Authorization{
Id: "2",
RegistrationID: 1,
DnsName: "example.com",
Status: string(core.StatusValid),
})
test.AssertNotError(t, err, "mock deactivation should work")
test.AssertBoxedNil(t, msa.recv, "deactivating valid authz should never pause")

// Deactivating a second pending authz should surpass the limit and result
// in a pause request.
_, err = ra.DeactivateAuthorization(ctx, &corepb.Authorization{
Id: "3",
RegistrationID: 1,
DnsName: "example.com",
Status: string(core.StatusPending),
})
test.AssertNotError(t, err, "mock deactivation should work")
test.AssertNotNil(t, msa.recv, "should have recorded a pause request")
test.AssertEquals(t, msa.recv.RegistrationID, int64(1))
test.AssertEquals(t, msa.recv.Identifiers[0].Value, "example.com")
}

func TestDeactivateRegistration(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
Expand Down
65 changes: 34 additions & 31 deletions wfe2/wfe.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,22 @@ const (
newAcctPath = "/acme/new-acct"
acctPath = "/acme/acct/"
// When we moved to authzv2, we used a "-v3" suffix to avoid confusion
// regarding ACMEv2.
authzPath = "/acme/authz-v3/"
authzPathWithAcct = "/acme/authz/"
challengePath = "/acme/chall-v3/"
challengePathWithAcct = "/acme/chall/"
certPath = "/acme/cert/"
revokeCertPath = "/acme/revoke-cert"
buildIDPath = "/build"
rolloverPath = "/acme/key-change"
newNoncePath = "/acme/new-nonce"
newOrderPath = "/acme/new-order"
orderPath = "/acme/order/"
finalizeOrderPath = "/acme/finalize/"
// regarding ACMEv2. More recently we moved back to using plain `/acme/authz/`
// and `/acme/chall/`, so the `-v3` paths are deprecated.
// TODO(#7683): Remove authz-v3 and chall-v3 once the new paths have been
// the default in prod for 30 days.
deprecatedAuthzPath = "/acme/authz-v3/"
authzPathWithAcct = "/acme/authz/"
deprecatedChallengePath = "/acme/chall-v3/"
challengePathWithAcct = "/acme/chall/"
certPath = "/acme/cert/"
revokeCertPath = "/acme/revoke-cert"
buildIDPath = "/build"
rolloverPath = "/acme/key-change"
newNoncePath = "/acme/new-nonce"
newOrderPath = "/acme/new-order"
orderPath = "/acme/order/"
finalizeOrderPath = "/acme/finalize/"

getAPIPrefix = "/get/"
getOrderPath = getAPIPrefix + "order/"
Expand Down Expand Up @@ -434,15 +437,15 @@ func (wfe *WebFrontEndImpl) Handler(stats prometheus.Registerer, oTelHTTPOptions
// TODO(@cpu): After November 1st, 2020 support for "GET" to the following
// endpoints will be removed, leaving only POST-as-GET support.
wfe.HandleFunc(m, orderPath, wfe.GetOrder, "GET", "POST")
wfe.HandleFunc(m, authzPath, wfe.AuthorizationHandler, "GET", "POST")
wfe.HandleFunc(m, authzPathWithAcct, wfe.AuthorizationHandlerWithAccount, "GET", "POST")
wfe.HandleFunc(m, challengePath, wfe.ChallengeHandler, "GET", "POST")
wfe.HandleFunc(m, challengePathWithAcct, wfe.ChallengeHandlerWithAccount, "GET", "POST")
wfe.HandleFunc(m, deprecatedAuthzPath, wfe.DeprecatedAuthorizationHandler, "GET", "POST")
wfe.HandleFunc(m, authzPathWithAcct, wfe.AuthorizationHandler, "GET", "POST")
wfe.HandleFunc(m, deprecatedChallengePath, wfe.DeprecatedChallengeHandler, "GET", "POST")
wfe.HandleFunc(m, challengePathWithAcct, wfe.ChallengeHandler, "GET", "POST")
wfe.HandleFunc(m, certPath, wfe.Certificate, "GET", "POST")
// Boulder-specific GET-able resource endpoints
wfe.HandleFunc(m, getOrderPath, wfe.GetOrder, "GET")
wfe.HandleFunc(m, getAuthzPath, wfe.AuthorizationHandler, "GET")
wfe.HandleFunc(m, getChallengePath, wfe.ChallengeHandler, "GET")
wfe.HandleFunc(m, getAuthzPath, wfe.DeprecatedAuthorizationHandler, "GET")
wfe.HandleFunc(m, getChallengePath, wfe.DeprecatedChallengeHandler, "GET")
wfe.HandleFunc(m, getCertPath, wfe.Certificate, "GET")

// Endpoint for draft-ietf-acme-ari
Expand Down Expand Up @@ -1087,9 +1090,9 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(
response.WriteHeader(http.StatusOK)
}

// ChallengeHandler handles POST requests to challenge URLs of the form /acme/chall-v3/<authorizationID>/<challengeID>.
// DeprecatedChallengeHandler handles POST requests to challenge URLs of the form /acme/chall-v3/<authorizationID>/<challengeID>.
// Such requests are clients' responses to the server's challenges.
func (wfe *WebFrontEndImpl) ChallengeHandler(
func (wfe *WebFrontEndImpl) DeprecatedChallengeHandler(
ctx context.Context,
logEvent *web.RequestEvent,
response http.ResponseWriter,
Expand All @@ -1100,11 +1103,11 @@ func (wfe *WebFrontEndImpl) ChallengeHandler(
return
}

wfe.Challenge(ctx, logEvent, challengePath, response, request, slug[0], slug[1])
wfe.Challenge(ctx, logEvent, deprecatedChallengePath, response, request, slug[0], slug[1])
}

// ChallengeHandlerWithAccount handles POST requests to challenge URLs of the form /acme/chall/{regID}/{authzID}/{challID}.
func (wfe *WebFrontEndImpl) ChallengeHandlerWithAccount(
// ChallengeHandler handles POST requests to challenge URLs of the form /acme/chall/{regID}/{authzID}/{challID}.
func (wfe *WebFrontEndImpl) ChallengeHandler(
ctx context.Context,
logEvent *web.RequestEvent,
response http.ResponseWriter,
Expand Down Expand Up @@ -1216,7 +1219,7 @@ func (wfe *WebFrontEndImpl) prepChallengeForDisplay(
challenge *core.Challenge,
) {
// Update the challenge URL to be relative to the HTTP request Host
challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%s/%s", challengePath, authz.ID, challenge.StringID()))
challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%s/%s", deprecatedChallengePath, authz.ID, challenge.StringID()))
if handlerPath == challengePathWithAcct || handlerPath == authzPathWithAcct {
challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s/%s", challengePathWithAcct, authz.RegistrationID, authz.ID, challenge.StringID()))
}
Expand Down Expand Up @@ -1531,17 +1534,17 @@ func (wfe *WebFrontEndImpl) deactivateAuthorization(
return true
}

// AuthorizationHandler handles requests to authorization URLs of the form /acme/authz/{authzID}.
func (wfe *WebFrontEndImpl) AuthorizationHandler(
// DeprecatedAuthorizationHandler handles requests to authorization URLs of the form /acme/authz/{authzID}.
func (wfe *WebFrontEndImpl) DeprecatedAuthorizationHandler(
ctx context.Context,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
wfe.Authorization(ctx, authzPath, logEvent, response, request, request.URL.Path)
wfe.Authorization(ctx, deprecatedAuthzPath, logEvent, response, request, request.URL.Path)
}

// AuthorizationHandlerWithAccount handles requests to authorization URLs of the form /acme/authz/{regID}/{authzID}.
func (wfe *WebFrontEndImpl) AuthorizationHandlerWithAccount(
// AuthorizationHandler handles requests to authorization URLs of the form /acme/authz/{regID}/{authzID}.
func (wfe *WebFrontEndImpl) AuthorizationHandler(
ctx context.Context,
logEvent *web.RequestEvent,
response http.ResponseWriter,
Expand Down Expand Up @@ -2762,5 +2765,5 @@ func urlForAuthz(handlerPath string, authz core.Authorization, request *http.Req
return web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s", authzPathWithAcct, authz.RegistrationID, authz.ID))
}

return web.RelativeEndpoint(request, authzPath+authz.ID)
return web.RelativeEndpoint(request, deprecatedAuthzPath+authz.ID)
}
Loading

0 comments on commit 024b9f9

Please sign in to comment.