Remove MandatoryPOSTasGET flag (#6672)
Remove the `MandatoryPOSTasGET` flag from the WFE2. Update the ACMEv2 divergence doc to note that neither staging nor production use MandatoryPOSTasGET. Fixes #6582.
This commit is contained in:
parent
e3a383208a
commit
6c84a69043
|
@ -7,10 +7,8 @@ Presently, Boulder diverges from the [RFC 8555] ACME spec in the following ways:
|
|||
|
||||
## [Section 6.3](https://tools.ietf.org/html/rfc8555#section-6.3)
|
||||
|
||||
Boulder supports POST-as-GET but does not mandate it by default for requests
|
||||
Boulder supports POST-as-GET but does not mandate it for requests
|
||||
that simply fetch a resource (certificate, order, authorization, or challenge).
|
||||
This behavior is configurable with a flag: Let's Encrypt's Staging environment
|
||||
does mandate POST-as-GET, while the Production environment does not.
|
||||
|
||||
## [Section 6.6](https://tools.ietf.org/html/rfc8555#section-6.6)
|
||||
|
||||
|
|
|
@ -14,20 +14,19 @@ func _() {
|
|||
_ = x[CAAAccountURI-3]
|
||||
_ = x[EnforceMultiVA-4]
|
||||
_ = x[MultiVAFullResults-5]
|
||||
_ = x[MandatoryPOSTAsGET-6]
|
||||
_ = x[ECDSAForAll-7]
|
||||
_ = x[ServeRenewalInfo-8]
|
||||
_ = x[AllowUnrecognizedFeatures-9]
|
||||
_ = x[ROCSPStage6-10]
|
||||
_ = x[ROCSPStage7-11]
|
||||
_ = x[ExpirationMailerUsesJoin-12]
|
||||
_ = x[CertCheckerChecksValidations-13]
|
||||
_ = x[CertCheckerRequiresValidations-14]
|
||||
_ = x[ECDSAForAll-6]
|
||||
_ = x[ServeRenewalInfo-7]
|
||||
_ = x[AllowUnrecognizedFeatures-8]
|
||||
_ = x[ROCSPStage6-9]
|
||||
_ = x[ROCSPStage7-10]
|
||||
_ = x[ExpirationMailerUsesJoin-11]
|
||||
_ = x[CertCheckerChecksValidations-12]
|
||||
_ = x[CertCheckerRequiresValidations-13]
|
||||
}
|
||||
|
||||
const _FeatureFlag_name = "unusedStoreRevokerInfoCAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsMandatoryPOSTAsGETECDSAForAllServeRenewalInfoAllowUnrecognizedFeaturesROCSPStage6ROCSPStage7ExpirationMailerUsesJoinCertCheckerChecksValidationsCertCheckerRequiresValidations"
|
||||
const _FeatureFlag_name = "unusedStoreRevokerInfoCAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsECDSAForAllServeRenewalInfoAllowUnrecognizedFeaturesROCSPStage6ROCSPStage7ExpirationMailerUsesJoinCertCheckerChecksValidationsCertCheckerRequiresValidations"
|
||||
|
||||
var _FeatureFlag_index = [...]uint16{0, 6, 22, 42, 55, 69, 87, 105, 116, 132, 157, 168, 179, 203, 231, 261}
|
||||
var _FeatureFlag_index = [...]uint8{0, 6, 22, 42, 55, 69, 87, 98, 114, 139, 150, 161, 185, 213, 243}
|
||||
|
||||
func (i FeatureFlag) String() string {
|
||||
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {
|
||||
|
|
|
@ -26,9 +26,6 @@ const (
|
|||
// MultiVAFullResults will cause the main VA to wait for all of the remote VA
|
||||
// results, not just the threshold required to make a decision.
|
||||
MultiVAFullResults
|
||||
// MandatoryPOSTAsGET forbids legacy unauthenticated GET requests for ACME
|
||||
// resources.
|
||||
MandatoryPOSTAsGET
|
||||
// ECDSAForAll enables all accounts, regardless of their presence in the CA's
|
||||
// ecdsaAllowedAccounts config value, to get issuance from ECDSA issuers.
|
||||
ECDSAForAll
|
||||
|
@ -72,7 +69,6 @@ var features = map[FeatureFlag]bool{
|
|||
CAAAccountURI: false,
|
||||
EnforceMultiVA: false,
|
||||
MultiVAFullResults: false,
|
||||
MandatoryPOSTAsGET: false,
|
||||
StoreRevokerInfo: false,
|
||||
ECDSAForAll: false,
|
||||
ServeRenewalInfo: false,
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
"authorizationLifetimeDays": 30,
|
||||
"pendingAuthorizationLifetimeDays": 7,
|
||||
"features": {
|
||||
"MandatoryPOSTAsGET": true,
|
||||
"ServeRenewalInfo": true
|
||||
}
|
||||
},
|
||||
|
|
21
wfe2/wfe.go
21
wfe2/wfe.go
|
@ -1054,11 +1054,6 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
return
|
||||
}
|
||||
|
||||
if features.Enabled(features.MandatoryPOSTAsGET) && request.Method != http.MethodPost && !requiredStale(request, logEvent) {
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
|
||||
return
|
||||
|
@ -1454,12 +1449,6 @@ func (wfe *WebFrontEndImpl) Authorization(
|
|||
logEvent *web.RequestEvent,
|
||||
response http.ResponseWriter,
|
||||
request *http.Request) {
|
||||
|
||||
if features.Enabled(features.MandatoryPOSTAsGET) && request.Method != http.MethodPost && !requiredStale(request, logEvent) {
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var requestAccount *core.Registration
|
||||
var requestBody []byte
|
||||
// If the request is a POST it is either:
|
||||
|
@ -1560,11 +1549,6 @@ func (wfe *WebFrontEndImpl) Authorization(
|
|||
// Certificate is used by clients to request a copy of their current certificate, or to
|
||||
// request a reissuance of the certificate.
|
||||
func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
if features.Enabled(features.MandatoryPOSTAsGET) && request.Method != http.MethodPost && !requiredStale(request, logEvent) {
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var requesterAccount *core.Registration
|
||||
// Any POSTs to the Certificate endpoint should be POST-as-GET requests. There are
|
||||
// no POSTs with a body allowed for this endpoint.
|
||||
|
@ -2065,11 +2049,6 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
|||
|
||||
// GetOrder is used to retrieve a existing order object
|
||||
func (wfe *WebFrontEndImpl) GetOrder(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
if features.Enabled(features.MandatoryPOSTAsGET) && request.Method != http.MethodPost && !requiredStale(request, logEvent) {
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var requesterAccount *core.Registration
|
||||
// Any POSTs to the Order endpoint should be POST-as-GET requests. There are
|
||||
// no POSTs with a body allowed for this endpoint.
|
||||
|
|
108
wfe2/wfe_test.go
108
wfe2/wfe_test.go
|
@ -1756,13 +1756,8 @@ type mockSAWithCert struct {
|
|||
status core.OCSPStatus
|
||||
}
|
||||
|
||||
func newMockSAWithCert(t *testing.T, sa sapb.StorageAuthorityReadOnlyClient, zeroNotBefore bool) *mockSAWithCert {
|
||||
func newMockSAWithCert(t *testing.T, sa sapb.StorageAuthorityReadOnlyClient) *mockSAWithCert {
|
||||
cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
|
||||
if zeroNotBefore {
|
||||
// Just for the sake of TestGetAPIAndMandatoryPOSTAsGET, we set the
|
||||
// Issued timestamp of this certificate to be very old (the year 0).
|
||||
cert.NotBefore = time.Time{}
|
||||
}
|
||||
test.AssertNotError(t, err, "Failed to load test cert")
|
||||
return &mockSAWithCert{sa, cert, core.OCSPStatusGood}
|
||||
}
|
||||
|
@ -1831,7 +1826,7 @@ func (sa *mockSAWithIncident) IncidentsForSerial(_ context.Context, req *sapb.Se
|
|||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
mux := wfe.Handler(metrics.NoopRegisterer)
|
||||
|
||||
makeGet := func(path string) *http.Request {
|
||||
|
@ -2180,7 +2175,7 @@ func TestGetCertificateNew(t *testing.T) {
|
|||
// body from being sent like the net/http Server's actually do.
|
||||
func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
|
||||
wfe, _, _ := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
certPemBytes, _ := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
|
||||
cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
|
||||
|
@ -2949,7 +2944,7 @@ func makeRevokeRequestJSONForCert(der []byte, reason *revocation.Reason) ([]byte
|
|||
// issuing account key.
|
||||
func TestRevokeCertificateByApplicantValid(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
mockLog := wfe.log.(*blog.Mock)
|
||||
mockLog.Clear()
|
||||
|
@ -2973,7 +2968,7 @@ func TestRevokeCertificateByApplicantValid(t *testing.T) {
|
|||
// certificate private key.
|
||||
func TestRevokeCertificateByKeyValid(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
mockLog := wfe.log.(*blog.Mock)
|
||||
mockLog.Clear()
|
||||
|
@ -3002,7 +2997,7 @@ func TestRevokeCertificateByKeyValid(t *testing.T) {
|
|||
// wasn't issued by any issuer the Boulder is aware of.
|
||||
func TestRevokeCertificateNotIssued(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
// Make a self-signed junk certificate
|
||||
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
@ -3037,7 +3032,7 @@ func TestRevokeCertificateNotIssued(t *testing.T) {
|
|||
|
||||
func TestRevokeCertificateExpired(t *testing.T) {
|
||||
wfe, fc, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
|
||||
test.AssertNotError(t, err, "Failed to load key")
|
||||
|
@ -3062,7 +3057,7 @@ func TestRevokeCertificateExpired(t *testing.T) {
|
|||
|
||||
func TestRevokeCertificateReasons(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
ra := wfe.ra.(*MockRegistrationAuthority)
|
||||
|
||||
reason0 := revocation.Reason(ocsp.Unspecified)
|
||||
|
@ -3128,7 +3123,7 @@ func TestRevokeCertificateReasons(t *testing.T) {
|
|||
// A revocation request signed by an incorrect certificate private key.
|
||||
func TestRevokeCertificateWrongCertificateKey(t *testing.T) {
|
||||
wfe, _, signer := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, false)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa)
|
||||
|
||||
keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-e1.key.pem")
|
||||
test.AssertNotError(t, err, "Failed to load key")
|
||||
|
@ -3309,66 +3304,6 @@ func TestOrderToOrderJSONV2Authorizations(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// TestMandatoryPOSTAsGET tests that the MandatoryPOSTAsGET feature flag
|
||||
// correctly causes unauthenticated GET requests to ACME resources to be
|
||||
// forbidden.
|
||||
func TestMandatoryPOSTAsGET(t *testing.T) {
|
||||
wfe, _, _ := setupWFE(t)
|
||||
|
||||
_ = features.Set(map[string]bool{"MandatoryPOSTAsGET": true})
|
||||
defer features.Reset()
|
||||
|
||||
// CheckProblem matches a HTTP response body to a Method Not Allowed problem.
|
||||
checkProblem := func(actual []byte) {
|
||||
var prob probs.ProblemDetails
|
||||
err := json.Unmarshal(actual, &prob)
|
||||
test.AssertNotError(t, err, "error unmarshaling HTTP response body as problem")
|
||||
test.AssertEquals(t, string(prob.Type), "urn:ietf:params:acme:error:malformed")
|
||||
test.AssertEquals(t, prob.Detail, "Method not allowed")
|
||||
test.AssertEquals(t, prob.HTTPStatus, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
handler web.WFEHandlerFunc
|
||||
}{
|
||||
{
|
||||
// GET requests to a mocked order path should return an error
|
||||
name: "GET Order",
|
||||
path: "1/1",
|
||||
handler: wfe.GetOrder,
|
||||
},
|
||||
{
|
||||
// GET requests to a mocked authorization path should return an error
|
||||
name: "GET Authz",
|
||||
path: "1",
|
||||
handler: wfe.Authorization,
|
||||
},
|
||||
{
|
||||
// GET requests to a mocked challenge path should return an error
|
||||
name: "GET Chall",
|
||||
path: "1/-ZfxEw",
|
||||
handler: wfe.Challenge,
|
||||
},
|
||||
{
|
||||
// GET requests to a mocked certificate serial path should return an error
|
||||
name: "GET Cert",
|
||||
path: "acme/cert/0000000000000000000000000000000000b2",
|
||||
handler: wfe.Certificate,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
responseWriter := httptest.NewRecorder()
|
||||
req := &http.Request{URL: &url.URL{Path: tc.path}, Method: "GET"}
|
||||
tc.handler(ctx, newRequestEvent(), responseWriter, req)
|
||||
checkProblem(responseWriter.Body.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChallengeUpRel(t *testing.T) {
|
||||
wfe, _, _ := setupWFE(t)
|
||||
|
||||
|
@ -3504,34 +3439,11 @@ func TestIndexGet404(t *testing.T) {
|
|||
test.AssertEquals(t, logEvent.Slug, path[1:])
|
||||
}
|
||||
|
||||
// TestGetAPIAndMandatoryPOSTAsGet that, even when MandatoryPOSTAsGet is on,
|
||||
// we are still willing to allow a GET request for a certificate that is old
|
||||
// enough that we're no longer worried about stale info being cached.
|
||||
func TestGetAPIAndMandatoryPOSTAsGET(t *testing.T) {
|
||||
wfe, _, _ := setupWFE(t)
|
||||
wfe.sa = newMockSAWithCert(t, wfe.sa, true)
|
||||
|
||||
makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
|
||||
return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
|
||||
&web.RequestEvent{Endpoint: endpoint, Extra: map[string]interface{}{}}
|
||||
}
|
||||
_ = features.Set(map[string]bool{"MandatoryPOSTAsGET": true})
|
||||
defer features.Reset()
|
||||
|
||||
cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
|
||||
test.AssertNotError(t, err, "failed to load test certificate")
|
||||
|
||||
req, event := makeGet(core.SerialToString(cert.SerialNumber), getCertPath)
|
||||
resp := httptest.NewRecorder()
|
||||
wfe.Certificate(context.Background(), event, resp, req)
|
||||
test.AssertEquals(t, resp.Code, 200)
|
||||
}
|
||||
|
||||
// TestARI tests that requests for real certs result in renewal info, while
|
||||
// requests for certs that don't exist result in errors.
|
||||
func TestARI(t *testing.T) {
|
||||
wfe, _, _ := setupWFE(t)
|
||||
msa := newMockSAWithCert(t, wfe.sa, false)
|
||||
msa := newMockSAWithCert(t, wfe.sa)
|
||||
wfe.sa = msa
|
||||
|
||||
makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
|
||||
|
|
Loading…
Reference in New Issue