Add CAAIdentities and Website to /directory "meta". (#3588)
This commit updates the WFE and WFE2 to have configuration support for setting a value for the `/directory` object's "meta" field's optional "caaIdentities" and "website" fields. The config-next wfe/wfe2 configuration are updated with values for these fields. Unit tests are updated to check that they are sent when expected and not otherwise. Bonus content: The `test.AssertUnmarshaledEquals` function had a bug where it would consider two inputs equal when the # of keys differed. This commit also fixes that bug.
This commit is contained in:
parent
866627ee29
commit
17922a6d2d
|
@ -43,6 +43,14 @@ type config struct {
|
|||
SAService *cmd.GRPCClientConfig
|
||||
|
||||
Features map[string]bool
|
||||
|
||||
// DirectoryCAAIdentity is used for the /directory response's "meta"
|
||||
// element's "caaIdentities" field. It should match the VA's "issuerDomain"
|
||||
// configuration value (this value is the one used to enforce CAA)
|
||||
DirectoryCAAIdentity string
|
||||
// DirectoryWebsite is used for the /directory response's "meta" element's
|
||||
// "website" field.
|
||||
DirectoryWebsite string
|
||||
}
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
|
@ -99,6 +107,8 @@ func main() {
|
|||
wfe.AllowOrigins = c.WFE.AllowOrigins
|
||||
wfe.AcceptRevocationReason = c.WFE.AcceptRevocationReason
|
||||
wfe.AllowAuthzDeactivation = c.WFE.AllowAuthzDeactivation
|
||||
wfe.DirectoryCAAIdentity = c.WFE.DirectoryCAAIdentity
|
||||
wfe.DirectoryWebsite = c.WFE.DirectoryWebsite
|
||||
|
||||
wfe.IssuerCert, err = cmd.LoadCert(c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
|
||||
|
|
|
@ -52,6 +52,14 @@ type config struct {
|
|||
CertificateChains map[string][]string
|
||||
|
||||
Features map[string]bool
|
||||
|
||||
// DirectoryCAAIdentity is used for the /directory response's "meta"
|
||||
// element's "caaIdentities" field. It should match the VA's "issuerDomain"
|
||||
// configuration value (this value is the one used to enforce CAA)
|
||||
DirectoryCAAIdentity string
|
||||
// DirectoryWebsite is used for the /directory response's "meta" element's
|
||||
// "website" field.
|
||||
DirectoryWebsite string
|
||||
}
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"acceptRevocationReason": true,
|
||||
"allowAuthzDeactivation": true,
|
||||
"debugAddr": ":8000",
|
||||
"directoryCAAIdentity": "happy-hacker-ca.invalid",
|
||||
"directoryWebsite": "https://github.com/letsencrypt/boulder",
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/wfe.boulder/cert.pem",
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"acceptRevocationReason": true,
|
||||
"allowAuthzDeactivation": true,
|
||||
"debugAddr": ":8013",
|
||||
"directoryCAAIdentity": "happy-hacker-ca.invalid",
|
||||
"directoryWebsite": "https://github.com/letsencrypt/boulder",
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/wfe.boulder/cert.pem",
|
||||
|
|
|
@ -100,6 +100,9 @@ func AssertUnmarshaledEquals(t *testing.T, got, expected string) {
|
|||
AssertNotError(t, err, "Could not unmarshal 'got'")
|
||||
err = json.Unmarshal([]byte(expected), &expectedMap)
|
||||
AssertNotError(t, err, "Could not unmarshal 'expected'")
|
||||
if len(gotMap) != len(expectedMap) {
|
||||
t.Errorf("Expected had %d keys, got had %d", len(gotMap), len(expectedMap))
|
||||
}
|
||||
for k, v := range expectedMap {
|
||||
if !reflect.DeepEqual(v, gotMap[k]) {
|
||||
t.Errorf("Field %q: Expected \"%v\", got \"%v\"", k, v, gotMap[k])
|
||||
|
|
29
wfe/wfe.go
29
wfe/wfe.go
|
@ -75,6 +75,15 @@ type WebFrontEndImpl struct {
|
|||
// URL to the current subscriber agreement (should contain some version identifier)
|
||||
SubscriberAgreementURL string
|
||||
|
||||
// DirectoryCAAIdentity is used for the /directory response's "meta"
|
||||
// element's "caaIdentities" field. It should match the VA's issuerDomain
|
||||
// field value.
|
||||
DirectoryCAAIdentity string
|
||||
|
||||
// DirectoryWebsite is used for the /directory response's "meta" element's
|
||||
// "website" field.
|
||||
DirectoryWebsite string
|
||||
|
||||
// Register of anti-replay nonces
|
||||
nonceService *nonce.NonceService
|
||||
|
||||
|
@ -351,14 +360,28 @@ func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *web.Request
|
|||
// expected set of keys. This ensures that we can properly extend the directory when we
|
||||
// need to add a new endpoint or meta element.
|
||||
directoryEndpoints[core.RandomString(8)] = randomDirKeyExplanationLink
|
||||
}
|
||||
if !clientDirChangeIntolerant {
|
||||
|
||||
// ACME since draft-02 describes an optional "meta" directory entry. The
|
||||
// meta entry may optionally contain a "terms-of-service" URI for the
|
||||
// current ToS.
|
||||
directoryEndpoints["meta"] = map[string]string{
|
||||
metaMap := map[string]interface{}{
|
||||
"terms-of-service": wfe.SubscriberAgreementURL,
|
||||
}
|
||||
// The "meta" directory entry may also include a []string of CAA identities
|
||||
if wfe.DirectoryCAAIdentity != "" {
|
||||
// The specification says caaIdentities is an array of strings. In
|
||||
// practice Boulder's VA only allows configuring ONE CAA identity. Given
|
||||
// that constraint it doesn't make sense to allow multiple directory CAA
|
||||
// identities so we use just the `wfe.DirectoryCAAIdentity` alone.
|
||||
metaMap["caaIdentities"] = []string{
|
||||
wfe.DirectoryCAAIdentity,
|
||||
}
|
||||
}
|
||||
// The "meta" directory entry may also include a string with a website URL
|
||||
if wfe.DirectoryWebsite != "" {
|
||||
metaMap["website"] = wfe.DirectoryWebsite
|
||||
}
|
||||
directoryEndpoints["meta"] = metaMap
|
||||
}
|
||||
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
|
|
|
@ -730,7 +730,39 @@ func TestDirectory(t *testing.T) {
|
|||
body = replaceRandomKey(responseWriter.Body.Bytes())
|
||||
assertJSONEquals(t, string(body), fmt.Sprintf(`{"key-change":"http://localhost:4300/acme/key-change","meta":{"terms-of-service":"http://example.invalid/terms"},"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","%s":"%s","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`, randomKey, randomDirKeyExplanationLink))
|
||||
|
||||
// if the UA is LetsEncryptPythonClient we expect to *not* see the meta entry.
|
||||
// Configure a caaIdentity and website for the /directory meta
|
||||
wfe.DirectoryCAAIdentity = "Radiant Lock"
|
||||
wfe.DirectoryWebsite = "zombo.com"
|
||||
responseWriter = httptest.NewRecorder()
|
||||
url, _ = url.Parse("/directory")
|
||||
mux.ServeHTTP(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
Host: "localhost:4300",
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
// The directory response should include the CAAIdentities and Website meta
|
||||
// elements as expected
|
||||
body = replaceRandomKey(responseWriter.Body.Bytes())
|
||||
assertJSONEquals(t, string(body), fmt.Sprintf(`{
|
||||
"key-change": "http://localhost:4300/acme/key-change",
|
||||
"meta": {
|
||||
"caaIdentities": [
|
||||
"Radiant Lock"
|
||||
],
|
||||
"terms-of-service": "http://example.invalid/terms",
|
||||
"website": "zombo.com"
|
||||
},
|
||||
"%s": "%s",
|
||||
"new-authz": "http://localhost:4300/acme/new-authz",
|
||||
"new-cert": "http://localhost:4300/acme/new-cert",
|
||||
"new-reg": "http://localhost:4300/acme/new-reg",
|
||||
"revoke-cert": "http://localhost:4300/acme/revoke-cert"
|
||||
}`, randomKey, randomDirKeyExplanationLink))
|
||||
|
||||
// if the UA is LetsEncryptPythonClient we expect to *not* see the meta entry,
|
||||
// even with the DirectoryCAAIdentity and DirectoryWebsite configured.
|
||||
responseWriter.Body.Reset()
|
||||
url, _ = url.Parse("/directory")
|
||||
headers := map[string][]string{
|
||||
|
@ -745,6 +777,7 @@ func TestDirectory(t *testing.T) {
|
|||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
assertJSONEquals(t, responseWriter.Body.String(), `{"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`)
|
||||
|
||||
}
|
||||
|
||||
func TestRandomDirectoryKey(t *testing.T) {
|
||||
|
|
26
wfe2/wfe.go
26
wfe2/wfe.go
|
@ -82,6 +82,15 @@ type WebFrontEndImpl struct {
|
|||
// URL to the current subscriber agreement (should contain some version identifier)
|
||||
SubscriberAgreementURL string
|
||||
|
||||
// DirectoryCAAIdentity is used for the /directory response's "meta"
|
||||
// element's "caaIdentities" field. It should match the VA's issuerDomain
|
||||
// field value.
|
||||
DirectoryCAAIdentity string
|
||||
|
||||
// DirectoryWebsite is used for the /directory response's "meta" element's
|
||||
// "website" field.
|
||||
DirectoryWebsite string
|
||||
|
||||
// Register of anti-replay nonces
|
||||
nonceService *nonce.NonceService
|
||||
|
||||
|
@ -371,9 +380,24 @@ func (wfe *WebFrontEndImpl) Directory(
|
|||
// ACME since draft-02 describes an optional "meta" directory entry. The
|
||||
// meta entry may optionally contain a "termsOfService" URI for the
|
||||
// current ToS.
|
||||
directoryEndpoints["meta"] = map[string]string{
|
||||
metaMap := map[string]interface{}{
|
||||
"termsOfService": wfe.SubscriberAgreementURL,
|
||||
}
|
||||
// The "meta" directory entry may also include a []string of CAA identities
|
||||
if wfe.DirectoryCAAIdentity != "" {
|
||||
// The specification says caaIdentities is an array of strings. In
|
||||
// practice Boulder's VA only allows configuring ONE CAA identity. Given
|
||||
// that constraint it doesn't make sense to allow multiple directory CAA
|
||||
// identities so we use just the `wfe.DirectoryCAAIdentity` alone.
|
||||
metaMap["caaIdentities"] = []string{
|
||||
wfe.DirectoryCAAIdentity,
|
||||
}
|
||||
}
|
||||
// The "meta" directory entry may also include a string with a website URL
|
||||
if wfe.DirectoryWebsite != "" {
|
||||
metaMap["website"] = wfe.DirectoryWebsite
|
||||
}
|
||||
directoryEndpoints["meta"] = metaMap
|
||||
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
|
||||
|
|
|
@ -721,6 +721,40 @@ func TestDirectory(t *testing.T) {
|
|||
test.AssertEquals(t,
|
||||
randomDirectoryKeyPresent(t, responseWriter.Body.Bytes()),
|
||||
true)
|
||||
|
||||
// Configure a caaIdentity and website for the /directory meta
|
||||
wfe.DirectoryCAAIdentity = "Radiant Lock"
|
||||
wfe.DirectoryWebsite = "zombo.com"
|
||||
|
||||
// Expect directory with a key change endpoint and a meta entry that has both
|
||||
// a website and a caaIdentity
|
||||
metaJSON = `{
|
||||
"AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
|
||||
"keyChange": "http://localhost:4300/acme/key-change",
|
||||
"meta": {
|
||||
"caaIdentities": [
|
||||
"Radiant Lock"
|
||||
],
|
||||
"termsOfService": "http://example.invalid/terms",
|
||||
"website": "zombo.com"
|
||||
},
|
||||
"newAccount": "http://localhost:4300/acme/new-acct",
|
||||
"newNonce": "http://localhost:4300/acme/new-nonce",
|
||||
"newOrder": "http://localhost:4300/acme/new-order",
|
||||
"revokeCert": "http://localhost:4300/acme/revoke-cert"
|
||||
}`
|
||||
// Serve the /directory response for this request into a recorder
|
||||
responseWriter = httptest.NewRecorder()
|
||||
mux.ServeHTTP(responseWriter, req)
|
||||
// We expect all directory requests to return a json object with a good HTTP status
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), metaJSON)
|
||||
// Check if there is a random directory key present and if so, that it is
|
||||
// expected to be present
|
||||
test.AssertEquals(t,
|
||||
randomDirectoryKeyPresent(t, responseWriter.Body.Bytes()),
|
||||
true)
|
||||
}
|
||||
|
||||
func TestRelativeDirectory(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue