PA: Support YAML for hostname policy. (#4180)

Our existing hostname policy configs use JSON. We would like to switch to YAML
to match the rate limit policy configs and to support commenting/tagging
entries in the hostname policy.

The PA is updated to support both JSON and YAML while we migrate the existing
policy data to the new format. To verify there are no changes in functionality
the existing unit tests were updated to test the same policy expressed in YAML
and JSON form.

In integration tests the `config` tree continues to use a JSON hostname policy
file to test the legacy support. In `config-next` a YAML hostname policy file
is used instead.

The new YAML format allows separating out `HighRiskBlockedNames` (primarily
used to meet the requirement of managing issuance for "high risk" domains per
CABF BRs, mostly static) and `AdminBlockedNames` (used after administrative
action is taken to block a domain. Additions are made with some frequency).
Since the rate at which we change entries in these lists differs it is helpful
to separate them in the policy content.
This commit is contained in:
Daniel McCarney 2019-04-26 14:35:28 -04:00 committed by GitHub
parent e49ffaf94c
commit 748f315b1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 281 additions and 144 deletions

View File

@ -20,16 +20,17 @@ import (
"github.com/letsencrypt/boulder/iana" "github.com/letsencrypt/boulder/iana"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/reloader" "github.com/letsencrypt/boulder/reloader"
"gopkg.in/yaml.v2"
) )
// AuthorityImpl enforces CA policy decisions. // AuthorityImpl enforces CA policy decisions.
type AuthorityImpl struct { type AuthorityImpl struct {
log blog.Logger log blog.Logger
blacklist map[string]bool blocklist map[string]bool
exactBlacklist map[string]bool exactBlocklist map[string]bool
wildcardExactBlacklist map[string]bool wildcardExactBlocklist map[string]bool
blacklistMu sync.RWMutex blocklistMu sync.RWMutex
enabledChallenges map[string]bool enabledChallenges map[string]bool
pseudoRNG *rand.Rand pseudoRNG *rand.Rand
@ -49,46 +50,106 @@ func New(challengeTypes map[string]bool) (*AuthorityImpl, error) {
return &pa, nil return &pa, nil
} }
type blacklistJSON struct { // blockedNamesPolicy is a struct holding lists of blocked domain names. One for
Blacklist []string // exact blocks and one for blocks including all subdomains.
ExactBlacklist []string type blockedNamesPolicy struct {
// ExactBlockedNames is a list of domain names. Issuance for names exactly
// matching an entry in the list will be forbidden. (e.g. `ExactBlockedNames`
// containing `www.example.com` will not block `example.com` or
// `mail.example.com`).
//
// TODO(@cpu): Remove the JSON tag when data is updated to use the new field names
ExactBlockedNames []string `json:"exactBlacklist" yaml:"ExactBlockedNames"`
// HighRiskBlockedNames is like ExactBlockedNames except that issuance is
// blocked for subdomains as well. (e.g. BlockedNames containing `example.com`
// will block `www.example.com`).
//
// This list typically doesn't change with much regularity.
//
// TODO(@cpu): Remove the JSON tag when data is updated to use the new field names
HighRiskBlockedNames []string `json:"blacklist" yaml:"HighRiskBlockedNames"`
// AdminBlockedNames operates the same as BlockedNames but is changed with more
// frequency based on administrative blocks/revocations that are added over
// time above and beyond the high-risk domains. Managing these entries separately
// from HighRiskBlockedNames makes it easier to vet changes accurately.
AdminBlockedNames []string `yaml:"AdminBlockedNames"`
} }
// SetHostnamePolicyFile will load the given policy file, returning error if it // SetHostnamePolicyFile will load the given policy file, returning error if it
// fails. It will also start a reloader in case the file changes. // fails. It will also start a reloader in case the file changes. It supports
// YAML and JSON serialization formats and chooses the correct unserialization
// method based on the file extension which must be ".yaml", ".yml", or ".json".
func (pa *AuthorityImpl) SetHostnamePolicyFile(f string) error { func (pa *AuthorityImpl) SetHostnamePolicyFile(f string) error {
_, err := reloader.New(f, pa.loadHostnamePolicy, pa.hostnamePolicyLoadError) var loadHandler func([]byte) error
if strings.HasSuffix(f, ".json") {
loadHandler = pa.loadHostnamePolicy(json.Unmarshal)
} else if strings.HasSuffix(f, ".yml") || strings.HasSuffix(f, ".yaml") {
loadHandler = pa.loadHostnamePolicy(yaml.Unmarshal)
} else {
return fmt.Errorf(
"Hostname policy file %q has unknown extension. Supported: .yml,.yaml,.json",
f)
}
if _, err := reloader.New(f, loadHandler, pa.hostnamePolicyLoadError); err != nil {
return err return err
}
return nil
} }
func (pa *AuthorityImpl) hostnamePolicyLoadError(err error) { func (pa *AuthorityImpl) hostnamePolicyLoadError(err error) {
pa.log.AuditErrf("error loading hostname policy: %s", err) pa.log.AuditErrf("error loading hostname policy: %s", err)
} }
func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error { // unmarshalHandler is a function type that abstracts away a choice between
hash := sha256.Sum256(b) // json.Unmarshal and yaml.Unmarshal, both of which take a byte slice, an
// interface to unmarshal to, and return an error.
type unmarshalHandler func([]byte, interface{}) error
// loadHostnamePolicy returns a reloader dataCallback function that uses the
// unmarshalHandler to load a hostname policy. The returned callback is suitable
// for use with reloader.New()
func (pa *AuthorityImpl) loadHostnamePolicy(unmarshal unmarshalHandler) func([]byte) error {
// Return a reloader dataCallback that uses the provided unmarshalHandler.
return func(contents []byte) error {
hash := sha256.Sum256(contents)
pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:])) pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:]))
var bl blacklistJSON var policy blockedNamesPolicy
err := json.Unmarshal(b, &bl) err := unmarshal(contents, &policy)
if err != nil { if err != nil {
return err return err
} }
if len(bl.Blacklist) == 0 { if len(policy.HighRiskBlockedNames) == 0 {
return fmt.Errorf("No entries in blacklist.") return fmt.Errorf("No entries in HighRiskBlockedNames.")
} }
if len(policy.ExactBlockedNames) == 0 {
return fmt.Errorf("No entries in ExactBlockedNames.")
}
return pa.processHostnamePolicy(policy)
}
}
// processHostnamePolicy handles loading a new blockedNamesPolicy into the PA.
// All of the policy.ExactBlockedNames will be added to the
// wildcardExactBlocklist by processHostnamePolicy to ensure that wildcards for
// exact blocked names entries are forbidden.
func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error {
nameMap := make(map[string]bool) nameMap := make(map[string]bool)
for _, v := range bl.Blacklist { for _, v := range policy.HighRiskBlockedNames {
nameMap[v] = true
}
for _, v := range policy.AdminBlockedNames {
nameMap[v] = true nameMap[v] = true
} }
exactNameMap := make(map[string]bool) exactNameMap := make(map[string]bool)
wildcardNameMap := make(map[string]bool) wildcardNameMap := make(map[string]bool)
for _, v := range bl.ExactBlacklist { for _, v := range policy.ExactBlockedNames {
exactNameMap[v] = true exactNameMap[v] = true
// Remove the leftmost label of the exact blacklist entry to make an exact // Remove the leftmost label of the exact blocked names entry to make an exact
// wildcard blacklist entry that will prevent issuing a wildcard that would // wildcard block list entry that will prevent issuing a wildcard that would
// include the exact blacklist entry. e.g. if "highvalue.example.com" is on // include the exact blocklist entry. e.g. if "highvalue.example.com" is on
// the exact blacklist we want "example.com" to be on the // the exact blocklist we want "example.com" to be in the
// wildcardExactBlacklist so that "*.example.com" cannot be issued. // wildcardExactBlocklist so that "*.example.com" cannot be issued.
// //
// First, split the domain into two parts: the first label and the rest of the domain. // First, split the domain into two parts: the first label and the rest of the domain.
parts := strings.SplitN(v, ".", 2) parts := strings.SplitN(v, ".", 2)
@ -96,17 +157,17 @@ func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error {
// at least be a "something." and a TLD like "com" // at least be a "something." and a TLD like "com"
if len(parts) < 2 { if len(parts) < 2 {
return fmt.Errorf( return fmt.Errorf(
"Malformed exact blacklist entry, only one label: %q", v) "Malformed ExactBlockedNames entry, only one label: %q", v)
} }
// Add the second part, the domain minus the first label, to the // Add the second part, the domain minus the first label, to the
// wildcardNameMap to block issuance for `*.`+parts[1] // wildcardNameMap to block issuance for `*.`+parts[1]
wildcardNameMap[parts[1]] = true wildcardNameMap[parts[1]] = true
} }
pa.blacklistMu.Lock() pa.blocklistMu.Lock()
pa.blacklist = nameMap pa.blocklist = nameMap
pa.exactBlacklist = exactNameMap pa.exactBlocklist = exactNameMap
pa.wildcardExactBlacklist = wildcardNameMap pa.wildcardExactBlocklist = wildcardNameMap
pa.blacklistMu.Unlock() pa.blocklistMu.Unlock()
return nil return nil
} }
@ -141,7 +202,7 @@ var (
errInvalidIdentifier = berrors.MalformedError("Invalid identifier type") errInvalidIdentifier = berrors.MalformedError("Invalid identifier type")
errNonPublic = berrors.MalformedError("Name does not end in a public suffix") errNonPublic = berrors.MalformedError("Name does not end in a public suffix")
errICANNTLD = berrors.MalformedError("Name is an ICANN TLD") errICANNTLD = berrors.MalformedError("Name is an ICANN TLD")
errBlacklisted = berrors.RejectedIdentifierError("Policy forbids issuing for name") errPolicyForbidden = berrors.RejectedIdentifierError("Policy forbids issuing for name")
errInvalidDNSCharacter = berrors.MalformedError("Invalid character in DNS name") errInvalidDNSCharacter = berrors.MalformedError("Invalid character in DNS name")
errNameTooLong = berrors.MalformedError("DNS name too long") errNameTooLong = berrors.MalformedError("DNS name too long")
errIPAddress = berrors.MalformedError("Issuance for IP addresses not supported") errIPAddress = berrors.MalformedError("Issuance for IP addresses not supported")
@ -174,7 +235,7 @@ var (
// * MUST NOT match the syntax of an IP address // * MUST NOT match the syntax of an IP address
// * MUST end in a public suffix // * MUST end in a public suffix
// * MUST have at least one label in addition to the public suffix // * MUST have at least one label in addition to the public suffix
// * MUST NOT be a label-wise suffix match for a name on the black list, // * MUST NOT be a label-wise suffix match for a name on the block list,
// where comparison is case-independent (normalized to lower case) // where comparison is case-independent (normalized to lower case)
// //
// If WillingToIssue returns an error, it will be of type MalformedRequestError // If WillingToIssue returns an error, it will be of type MalformedRequestError
@ -260,7 +321,7 @@ func (pa *AuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error {
return errICANNTLD return errICANNTLD
} }
// Require no match against blacklist // Require no match against hostname block lists
if err := pa.checkHostLists(domain); err != nil { if err := pa.checkHostLists(domain); err != nil {
return err return err
} }
@ -275,8 +336,8 @@ func (pa *AuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error {
// * That the wildcard character is the leftmost label // * That the wildcard character is the leftmost label
// * That the wildcard label is not immediately adjacent to a top level ICANN // * That the wildcard label is not immediately adjacent to a top level ICANN
// TLD // TLD
// * That the wildcard wouldn't cover an exact blacklist entry (e.g. an exact // * That the wildcard wouldn't cover an exact blocklist entry (e.g. an exact
// blacklist entry for "foo.example.com" should prevent issuance for // blocklist entry for "foo.example.com" should prevent issuance for
// "*.example.com") // "*.example.com")
// //
// If all of the above is true then the base domain (e.g. without the *.) is run // If all of the above is true then the base domain (e.g. without the *.) is run
@ -314,13 +375,13 @@ func (pa *AuthorityImpl) WillingToIssueWildcard(ident core.AcmeIdentifier) error
if baseDomain == icannTLD { if baseDomain == icannTLD {
return errICANNTLDWildcard return errICANNTLDWildcard
} }
// The base domain can't be in the wildcard exact blacklist // The base domain can't be in the wildcard exact blocklist
if err := pa.checkWildcardHostList(baseDomain); err != nil { if err := pa.checkWildcardHostList(baseDomain); err != nil {
return err return err
} }
// Check that the PA is willing to issue for the base domain // Check that the PA is willing to issue for the base domain
// Since the base domain without the "*." may trip the exact hostname policy // Since the base domain without the "*." may trip the exact hostname policy
// blacklist when the "*." is removed we replace it with a single "x" // blocklist when the "*." is removed we replace it with a single "x"
// character to differentiate "*.example.com" from "example.com" for the // character to differentiate "*.example.com" from "example.com" for the
// exact hostname check. // exact hostname check.
// //
@ -336,42 +397,42 @@ func (pa *AuthorityImpl) WillingToIssueWildcard(ident core.AcmeIdentifier) error
return pa.WillingToIssue(ident) return pa.WillingToIssue(ident)
} }
// checkWildcardHostList checks the wildcardExactBlacklist for a given domain. // checkWildcardHostList checks the wildcardExactBlocklist for a given domain.
// If the domain is not present on the list nil is returned, otherwise // If the domain is not present on the list nil is returned, otherwise
// errBlacklisted is returned. // errPolicyForbidden is returned.
func (pa *AuthorityImpl) checkWildcardHostList(domain string) error { func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
pa.blacklistMu.RLock() pa.blocklistMu.RLock()
defer pa.blacklistMu.RUnlock() defer pa.blocklistMu.RUnlock()
if pa.blacklist == nil { if pa.blocklist == nil {
return fmt.Errorf("Hostname policy not yet loaded.") return fmt.Errorf("Hostname policy not yet loaded.")
} }
if pa.wildcardExactBlacklist[domain] { if pa.wildcardExactBlocklist[domain] {
return errBlacklisted return errPolicyForbidden
} }
return nil return nil
} }
func (pa *AuthorityImpl) checkHostLists(domain string) error { func (pa *AuthorityImpl) checkHostLists(domain string) error {
pa.blacklistMu.RLock() pa.blocklistMu.RLock()
defer pa.blacklistMu.RUnlock() defer pa.blocklistMu.RUnlock()
if pa.blacklist == nil { if pa.blocklist == nil {
return fmt.Errorf("Hostname policy not yet loaded.") return fmt.Errorf("Hostname policy not yet loaded.")
} }
labels := strings.Split(domain, ".") labels := strings.Split(domain, ".")
for i := range labels { for i := range labels {
joined := strings.Join(labels[i:], ".") joined := strings.Join(labels[i:], ".")
if pa.blacklist[joined] { if pa.blocklist[joined] {
return errBlacklisted return errPolicyForbidden
} }
} }
if pa.exactBlacklist[domain] { if pa.exactBlocklist[domain] {
return errBlacklisted return errPolicyForbidden
} }
return nil return nil
} }
@ -430,7 +491,7 @@ func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) ([]core.C
// ChallengeTypeEnabled returns whether the specified challenge type is enabled // ChallengeTypeEnabled returns whether the specified challenge type is enabled
func (pa *AuthorityImpl) ChallengeTypeEnabled(t string) bool { func (pa *AuthorityImpl) ChallengeTypeEnabled(t string) bool {
pa.blacklistMu.RLock() pa.blocklistMu.RLock()
defer pa.blacklistMu.RUnlock() defer pa.blocklistMu.RUnlock()
return pa.enabledChallenges[t] return pa.enabledChallenges[t]
} }

View File

@ -2,6 +2,7 @@ package policy
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@ -10,6 +11,7 @@ import (
"github.com/letsencrypt/boulder/features" "github.com/letsencrypt/boulder/features"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/test" "github.com/letsencrypt/boulder/test"
"gopkg.in/yaml.v2"
) )
var log = blog.UseMock() var log = blog.UseMock()
@ -106,24 +108,29 @@ func TestWillingToIssue(t *testing.T) {
`foo.bd`, `foo.bd`,
} }
shouldBeBlacklisted := []string{ shouldBeBlocked := []string{
`highvalue.website1.org`, `highvalue.website1.org`,
`website2.co.uk`, `website2.co.uk`,
`www.website3.com`, `www.website3.com`,
`lots.of.labels.website4.com`, `lots.of.labels.website4.com`,
`banned.in.dc.com`,
`bad.brains.banned.in.dc.com`,
} }
blacklistContents := []string{ blocklistContents := []string{
`website2.com`, `website2.com`,
`website2.org`, `website2.org`,
`website2.co.uk`, `website2.co.uk`,
`website3.com`, `website3.com`,
`website4.com`, `website4.com`,
} }
exactBlacklistContents := []string{ exactBlocklistContents := []string{
`www.website1.org`, `www.website1.org`,
`highvalue.website1.org`, `highvalue.website1.org`,
`dl.website1.org`, `dl.website1.org`,
} }
adminBlockedContents := []string{
`banned.in.dc.com`,
}
shouldBeAccepted := []string{ shouldBeAccepted := []string{
`lowvalue.website1.org`, `lowvalue.website1.org`,
@ -136,18 +143,30 @@ func TestWillingToIssue(t *testing.T) {
"www.web-site2.com", "www.web-site2.com",
} }
policy := blockedNamesPolicy{
HighRiskBlockedNames: blocklistContents,
ExactBlockedNames: exactBlocklistContents,
AdminBlockedNames: adminBlockedContents,
}
jsonPolicyBytes, err := json.Marshal(policy)
test.AssertNotError(t, err, "Couldn't JSON serialize blocklist")
jsonPolicyFile, _ := ioutil.TempFile("", "test-blocklist.*.json")
defer os.Remove(jsonPolicyFile.Name())
err = ioutil.WriteFile(jsonPolicyFile.Name(), jsonPolicyBytes, 0640)
test.AssertNotError(t, err, "Couldn't write JSON blocklist")
yamlPolicyBytes, err := yaml.Marshal(policy)
test.AssertNotError(t, err, "Couldn't YAML serialize blocklist")
yamlPolicyFile, _ := ioutil.TempFile("", "test-blocklist.*.yaml")
defer os.Remove(yamlPolicyFile.Name())
err = ioutil.WriteFile(yamlPolicyFile.Name(), yamlPolicyBytes, 0640)
test.AssertNotError(t, err, "Couldn't write YAML blocklist")
testPolicyFile := func(f string) {
pa := paImpl(t) pa := paImpl(t)
blacklistBytes, err := json.Marshal(blacklistJSON{ err = pa.SetHostnamePolicyFile(f)
Blacklist: blacklistContents,
ExactBlacklist: exactBlacklistContents,
})
test.AssertNotError(t, err, "Couldn't serialize blacklist")
f, _ := ioutil.TempFile("", "test-blacklist.txt")
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), blacklistBytes, 0640)
test.AssertNotError(t, err, "Couldn't write blacklist")
err = pa.SetHostnamePolicyFile(f.Name())
test.AssertNotError(t, err, "Couldn't load rules") test.AssertNotError(t, err, "Couldn't load rules")
// Test for invalid identifier type // Test for invalid identifier type
@ -186,11 +205,11 @@ func TestWillingToIssue(t *testing.T) {
} }
} }
// Test blacklisting // Test expected blocked domains
for _, domain := range shouldBeBlacklisted { for _, domain := range shouldBeBlocked {
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain} identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
err := pa.WillingToIssue(identifier) err := pa.WillingToIssue(identifier)
if err != errBlacklisted { if err != errPolicyForbidden {
t.Error("Identifier was not correctly forbidden: ", identifier, err) t.Error("Identifier was not correctly forbidden: ", identifier, err)
} }
} }
@ -202,6 +221,16 @@ func TestWillingToIssue(t *testing.T) {
t.Error("Identifier was incorrectly forbidden: ", identifier, err) t.Error("Identifier was incorrectly forbidden: ", identifier, err)
} }
} }
}
// Both the JSON and the YAML policy files should behave the exact same way
// when tested.
for _, f := range []string{
jsonPolicyFile.Name(),
yamlPolicyFile.Name(),
} {
testPolicyFile(f)
}
} }
func TestWillingToIssueWildcard(t *testing.T) { func TestWillingToIssueWildcard(t *testing.T) {
@ -213,12 +242,12 @@ func TestWillingToIssueWildcard(t *testing.T) {
} }
pa := paImpl(t) pa := paImpl(t)
bannedBytes, err := json.Marshal(blacklistJSON{ bannedBytes, err := json.Marshal(blockedNamesPolicy{
Blacklist: bannedDomains, HighRiskBlockedNames: bannedDomains,
ExactBlacklist: exactBannedDomains, ExactBlockedNames: exactBannedDomains,
}) })
test.AssertNotError(t, err, "Couldn't serialize banned list") test.AssertNotError(t, err, "Couldn't serialize banned list")
f, _ := ioutil.TempFile("", "test-wildcard-banlist.txt") f, _ := ioutil.TempFile("", "test-wildcard-banlist.*.json")
defer os.Remove(f.Name()) defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), bannedBytes, 0640) err = ioutil.WriteFile(f.Name(), bannedBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized banned list to file") test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
@ -265,26 +294,26 @@ func TestWillingToIssueWildcard(t *testing.T) {
{ {
Name: "Forbidden base domain", Name: "Forbidden base domain",
Ident: makeDNSIdent("*.zombo.gov.us"), Ident: makeDNSIdent("*.zombo.gov.us"),
ExpectedErr: errBlacklisted, ExpectedErr: errPolicyForbidden,
}, },
// We should not allow getting a wildcard for that would cover an exact // We should not allow getting a wildcard for that would cover an exact
// blocklist domain // blocklist domain
{ {
Name: "Wildcard for ExactBlacklist base domain", Name: "Wildcard for ExactBlocklist base domain",
Ident: makeDNSIdent("*.letsdecrypt.org"), Ident: makeDNSIdent("*.letsdecrypt.org"),
ExpectedErr: errBlacklisted, ExpectedErr: errPolicyForbidden,
}, },
// We should allow a wildcard for a domain that doesn't match the exact // We should allow a wildcard for a domain that doesn't match the exact
// blacklist domain // blocklist domain
{ {
Name: "Wildcard for non-matching subdomain of ExactBlacklist domain", Name: "Wildcard for non-matching subdomain of ExactBlocklist domain",
Ident: makeDNSIdent("*.lowvalue.letsdecrypt.org"), Ident: makeDNSIdent("*.lowvalue.letsdecrypt.org"),
ExpectedErr: nil, ExpectedErr: nil,
}, },
// We should allow getting a wildcard for an exact blacklist domain since it // We should allow getting a wildcard for an exact blocklist domain since it
// only covers subdomains, not the exact name. // only covers subdomains, not the exact name.
{ {
Name: "Wildcard for ExactBlacklist domain", Name: "Wildcard for ExactBlocklist domain",
Ident: makeDNSIdent("*.highvalue.letsdecrypt.org"), Ident: makeDNSIdent("*.highvalue.letsdecrypt.org"),
ExpectedErr: nil, ExpectedErr: nil,
}, },
@ -365,9 +394,9 @@ func TestChallengesForWildcard(t *testing.T) {
test.AssertEquals(t, challenges[0].Type, core.ChallengeTypeDNS01) test.AssertEquals(t, challenges[0].Type, core.ChallengeTypeDNS01)
} }
// TestMalformedExactBlacklist tests that loading a JSON policy file with an // TestMalformedExactBlocklist tests that loading a JSON policy file with an
// invalid exact blacklist entry will fail as expected. // invalid exact blocklist entry will fail as expected.
func TestMalformedExactBlacklist(t *testing.T) { func TestMalformedExactBlocklist(t *testing.T) {
pa := paImpl(t) pa := paImpl(t)
exactBannedDomains := []string{ exactBannedDomains := []string{
@ -379,22 +408,36 @@ func TestMalformedExactBlacklist(t *testing.T) {
} }
// Create JSON for the exactBannedDomains // Create JSON for the exactBannedDomains
bannedBytes, err := json.Marshal(blacklistJSON{ bannedBytes, err := json.Marshal(blockedNamesPolicy{
Blacklist: bannedDomains, HighRiskBlockedNames: bannedDomains,
ExactBlacklist: exactBannedDomains, ExactBlockedNames: exactBannedDomains,
}) })
test.AssertNotError(t, err, "Couldn't serialize banned list") test.AssertNotError(t, err, "Couldn't serialize banned list")
// Create a temp file for the JSON contents // Create a temp file for the JSON contents
f, _ := ioutil.TempFile("", "test-invalid-exactblacklist.json") f, _ := ioutil.TempFile("", "test-invalid-exactblocklist.*.json")
defer os.Remove(f.Name()) defer os.Remove(f.Name())
// Write the JSON to the temp file // Write the JSON to the temp file
err = ioutil.WriteFile(f.Name(), bannedBytes, 0640) err = ioutil.WriteFile(f.Name(), bannedBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized banned list to file") test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
// Try to use the JSON tempfile as the hostname policy. It should produce an // Try to use the JSON tempfile as the hostname policy. It should produce an
// error since the exact blacklist contents are malformed. // error since the exact blocklist contents are malformed.
err = pa.SetHostnamePolicyFile(f.Name()) err = pa.SetHostnamePolicyFile(f.Name())
test.AssertError(t, err, "Loaded invalid exact blacklist content without error") test.AssertError(t, err, "Loaded invalid exact blocklist content without error")
test.AssertEquals(t, err.Error(), "Malformed exact blacklist entry, only one label: \"com\"") test.AssertEquals(t, err.Error(), "Malformed ExactBlockedNames entry, only one label: \"com\"")
}
func TestSetHostnamePolicyFileExtension(t *testing.T) {
filename := "hostname.policy.json.j2"
expectedErrMsg := fmt.Sprintf(
`Hostname policy file %q has unknown extension. Supported: .yml,.yaml,.json`,
filename)
pa := paImpl(t)
if err := pa.SetHostnamePolicyFile(filename); err != nil && err.Error() != expectedErrMsg {
t.Errorf("expected SetHostnamePolicyFile error %q got %q", expectedErrMsg, err.Error())
} else if err == nil {
t.Errorf("expected SetHostnamePolicyFile error %q got nil", expectedErrMsg)
}
} }

View File

@ -41,7 +41,7 @@
"lifespanOCSP": "96h", "lifespanOCSP": "96h",
"maxNames": 100, "maxNames": 100,
"enableMustStaple": true, "enableMustStaple": true,
"hostnamePolicyFile": "test/hostname-policy.json", "hostnamePolicyFile": "test/hostname-policy.yaml",
"cfssl": { "cfssl": {
"signing": { "signing": {
"profiles": { "profiles": {

View File

@ -41,7 +41,7 @@
"lifespanOCSP": "96h", "lifespanOCSP": "96h",
"maxNames": 100, "maxNames": 100,
"enableMustStaple": true, "enableMustStaple": true,
"hostnamePolicyFile": "test/hostname-policy.json", "hostnamePolicyFile": "test/hostname-policy.yaml",
"cfssl": { "cfssl": {
"signing": { "signing": {
"profiles": { "profiles": {

View File

@ -2,7 +2,7 @@
"certChecker": { "certChecker": {
"dbConnectFile": "test/secrets/cert_checker_dburl", "dbConnectFile": "test/secrets/cert_checker_dburl",
"maxDBConns": 10, "maxDBConns": 10,
"hostnamePolicyFile": "test/hostname-policy.json" "hostnamePolicyFile": "test/hostname-policy.yaml"
}, },
"pa": { "pa": {

View File

@ -4,7 +4,7 @@
"maxConcurrentRPCServerRequests": 100000, "maxConcurrentRPCServerRequests": 100000,
"maxContactsPerRegistration": 100, "maxContactsPerRegistration": 100,
"debugAddr": ":8002", "debugAddr": ":8002",
"hostnamePolicyFile": "test/hostname-policy.json", "hostnamePolicyFile": "test/hostname-policy.yaml",
"maxNames": 100, "maxNames": 100,
"reuseValidAuthz": true, "reuseValidAuthz": true,
"authorizationLifetimeDays": 30, "authorizationLifetimeDays": 30,

33
test/hostname-policy.yaml Normal file
View File

@ -0,0 +1,33 @@
#
# Example YAML Boulder hostname policy
#
# This is *not* a production ready policy file and not reflective of Let's
# Encrypt's policies! It is just an example.
# ExactBlockedNames prevent issuance for the exact names listed, as well as
# their wildcard form.
ExactBlockedNames:
- "highrisk.le-test.hoffman-andrews.com"
- "exactblacklist.letsencrypt.org"
# HighRiskBlockedNames prevent issuance for the exact names listed as well as
# all subdomains/wildcards.
HighRiskBlockedNames:
# See RFC 3152
- "ipv6.arpa"
# See RFC 2317
- "in-addr.arpa"
# Etc etc etc
- "example"
- "example.net"
- "example.org"
- "invalid"
- "local"
- "localhost"
- "test"
# AdminBlockedNames are treated the same as HighRiskBlockedNames by Boulder but
# since they change more frequently based on administrative action over time
# they are separated into their own list.
AdminBlockedNames:
- "sealand"