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:
Daniel McCarney 2018-03-22 16:12:43 -04:00 committed by GitHub
parent 866627ee29
commit 17922a6d2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 144 additions and 5 deletions

View File

@ -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))

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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])

View File

@ -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")

View File

@ -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) {

View File

@ -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")

View File

@ -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) {