Merge pull request #491 from letsencrypt/cache-headers
WFE cache headers
This commit is contained in:
commit
7fce01b7ce
|
|
@ -97,6 +97,15 @@ func main() {
|
|||
wfe.Stats = stats
|
||||
wfe.SubscriberAgreementURL = c.SubscriberAgreementURL
|
||||
|
||||
wfe.CertCacheDuration, err = time.ParseDuration(c.WFE.CertCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate caching duration")
|
||||
wfe.CertNoCacheExpirationWindow, err = time.ParseDuration(c.WFE.CertNoCacheExpirationWindow)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate expiration no-cache window")
|
||||
wfe.IndexCacheDuration, err = time.ParseDuration(c.WFE.IndexCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse index caching duration")
|
||||
wfe.IssuerCacheDuration, err = time.ParseDuration(c.WFE.IssuerCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
|
||||
|
||||
wfe.IssuerCert, err = cmd.LoadCert(c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@ func main() {
|
|||
cmd.FailOnError(err, "Unable to create SA")
|
||||
sa.SetSQLDebug(c.SQL.SQLDebug)
|
||||
|
||||
wfei.CertCacheDuration, err = time.ParseDuration(c.WFE.CertCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate caching duration")
|
||||
wfei.CertNoCacheExpirationWindow, err = time.ParseDuration(c.WFE.CertNoCacheExpirationWindow)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate expiration no-cache window")
|
||||
wfei.IndexCacheDuration, err = time.ParseDuration(c.WFE.IndexCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse index caching duration")
|
||||
wfei.IssuerCacheDuration, err = time.ParseDuration(c.WFE.IssuerCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
|
||||
|
||||
ra := ra.NewRegistrationAuthorityImpl()
|
||||
|
||||
va := va.NewValidationAuthorityImpl(c.CA.TestMode)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ type Config struct {
|
|||
BaseURL string
|
||||
ListenAddress string
|
||||
|
||||
CertCacheDuration string
|
||||
CertNoCacheExpirationWindow string
|
||||
IndexCacheDuration string
|
||||
IssuerCacheDuration string
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4000",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4000",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4300",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "8765h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ type WebFrontEndImpl struct {
|
|||
|
||||
// Register of anti-replay nonces
|
||||
nonceService core.NonceService
|
||||
|
||||
// Cache settings
|
||||
CertCacheDuration time.Duration
|
||||
CertNoCacheExpirationWindow time.Duration
|
||||
IndexCacheDuration time.Duration
|
||||
IssuerCacheDuration time.Duration
|
||||
}
|
||||
|
||||
func statusCodeFromError(err interface{}) int {
|
||||
|
|
@ -231,6 +237,15 @@ func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Re
|
|||
`))
|
||||
tmpl.Execute(response, wfe)
|
||||
response.Header().Set("Content-Type", "text/html")
|
||||
addCacheHeader(response, wfe.IndexCacheDuration.Seconds())
|
||||
}
|
||||
|
||||
func addNoCacheHeader(w http.ResponseWriter) {
|
||||
w.Header().Add("Cache-Control", "public, max-age=0, no-cache")
|
||||
}
|
||||
|
||||
func addCacheHeader(w http.ResponseWriter, age float64) {
|
||||
w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age))
|
||||
}
|
||||
|
||||
// The ID is always the last slash-separated token in the path
|
||||
|
|
@ -933,12 +948,14 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
if !strings.HasPrefix(path, CertPath) {
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
serial := path[len(CertPath):]
|
||||
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
|
||||
|
|
@ -950,11 +967,14 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
|
||||
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
|
||||
} else {
|
||||
wfe.sendError(response, "Not found", err, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
wfe.sendError(response, "Certificate not found", err, http.StatusNotFound)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
addCacheHeader(response, wfe.CertCacheDuration.Seconds())
|
||||
|
||||
// TODO Content negotiation
|
||||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.Header().Add("Link", link(IssuerPath, "up"))
|
||||
|
|
@ -985,6 +1005,8 @@ func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.R
|
|||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
addCacheHeader(response, wfe.IssuerCacheDuration.Seconds())
|
||||
|
||||
// TODO Content negotiation
|
||||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
package wfe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
|
|
@ -193,13 +194,12 @@ func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
|
|||
RegistrationID: 1,
|
||||
DER: certBlock.Bytes,
|
||||
}, nil
|
||||
} else {
|
||||
return core.Certificate{}, errors.New("No cert")
|
||||
}
|
||||
return core.Certificate{}, errors.New("No cert")
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetCertificateByShortSerial(string) (core.Certificate, error) {
|
||||
return core.Certificate{}, nil
|
||||
func (sa *MockSA) GetCertificateByShortSerial(serial string) (core.Certificate, error) {
|
||||
return sa.GetCertificate("0000000000000000" + serial)
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, error) {
|
||||
|
|
@ -378,7 +378,7 @@ func TestHandleFunc(t *testing.T) {
|
|||
{[]string{"GET", "POST"}, "POST", true},
|
||||
{[]string{"GET"}, "", false},
|
||||
{[]string{"GET"}, "POST", false},
|
||||
{[]string{"GET"}, "OPTIONS", false}, // TODO, #469
|
||||
{[]string{"GET"}, "OPTIONS", false}, // TODO, #469
|
||||
{[]string{"GET"}, "MAKE-COFFEE", false}, // 405, or 418?
|
||||
} {
|
||||
runWrappedHandler(&http.Request{Method: c.reqMethod}, c.allowed...)
|
||||
|
|
@ -446,6 +446,7 @@ func TestStandardHeaders(t *testing.T) {
|
|||
|
||||
func TestIndex(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.IndexCacheDuration = time.Second * 10
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
|
|
@ -458,14 +459,17 @@ func TestIndex(t *testing.T) {
|
|||
test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
||||
test.Assert(t, strings.Contains(responseWriter.Body.String(), wfe.NewReg),
|
||||
"new-reg not found")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
responseWriter.Header().Del("Cache-Control")
|
||||
url, _ = url.Parse("/foo")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
URL: url,
|
||||
})
|
||||
//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "")
|
||||
}
|
||||
|
||||
// TODO: Write additional test cases for:
|
||||
|
|
@ -1110,3 +1114,75 @@ func TestTermsRedirect(t *testing.T) {
|
|||
agreementURL)
|
||||
test.AssertEquals(t, responseWriter.Code, 302)
|
||||
}
|
||||
|
||||
func TestIssuer(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.IssuerCacheDuration = time.Second * 10
|
||||
wfe.IssuerCert = []byte{0, 0, 1}
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
wfe.Issuer(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), wfe.IssuerCert) == 0, "Incorrect bytes returned")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
}
|
||||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.CertCacheDuration = time.Second * 10
|
||||
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
|
||||
wfe.SA = &MockSA{}
|
||||
|
||||
certPemBytes, _ := ioutil.ReadFile("test/178.crt")
|
||||
certBlock, _ := pem.Decode(certPemBytes)
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
// Valid short serial, cached
|
||||
path, _ := url.Parse("/acme/cert/00000000000000b2")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, 200)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert")
|
||||
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match")
|
||||
|
||||
// Unused short serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/00000000000000ff")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
// Invalid short serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/nothex")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
// Invalid short serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/00000000000000")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue