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:
parent
e49ffaf94c
commit
748f315b1a
173
policy/pa.go
173
policy/pa.go
|
@ -20,16 +20,17 @@ import (
|
|||
"github.com/letsencrypt/boulder/iana"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/reloader"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// AuthorityImpl enforces CA policy decisions.
|
||||
type AuthorityImpl struct {
|
||||
log blog.Logger
|
||||
|
||||
blacklist map[string]bool
|
||||
exactBlacklist map[string]bool
|
||||
wildcardExactBlacklist map[string]bool
|
||||
blacklistMu sync.RWMutex
|
||||
blocklist map[string]bool
|
||||
exactBlocklist map[string]bool
|
||||
wildcardExactBlocklist map[string]bool
|
||||
blocklistMu sync.RWMutex
|
||||
|
||||
enabledChallenges map[string]bool
|
||||
pseudoRNG *rand.Rand
|
||||
|
@ -49,46 +50,106 @@ func New(challengeTypes map[string]bool) (*AuthorityImpl, error) {
|
|||
return &pa, nil
|
||||
}
|
||||
|
||||
type blacklistJSON struct {
|
||||
Blacklist []string
|
||||
ExactBlacklist []string
|
||||
// blockedNamesPolicy is a struct holding lists of blocked domain names. One for
|
||||
// exact blocks and one for blocks including all subdomains.
|
||||
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
|
||||
// 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 {
|
||||
_, err := reloader.New(f, pa.loadHostnamePolicy, pa.hostnamePolicyLoadError)
|
||||
return err
|
||||
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 nil
|
||||
}
|
||||
|
||||
func (pa *AuthorityImpl) hostnamePolicyLoadError(err error) {
|
||||
pa.log.AuditErrf("error loading hostname policy: %s", err)
|
||||
}
|
||||
|
||||
func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error {
|
||||
hash := sha256.Sum256(b)
|
||||
pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:]))
|
||||
var bl blacklistJSON
|
||||
err := json.Unmarshal(b, &bl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bl.Blacklist) == 0 {
|
||||
return fmt.Errorf("No entries in blacklist.")
|
||||
// unmarshalHandler is a function type that abstracts away a choice between
|
||||
// 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[:]))
|
||||
var policy blockedNamesPolicy
|
||||
err := unmarshal(contents, &policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(policy.HighRiskBlockedNames) == 0 {
|
||||
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)
|
||||
for _, v := range bl.Blacklist {
|
||||
for _, v := range policy.HighRiskBlockedNames {
|
||||
nameMap[v] = true
|
||||
}
|
||||
for _, v := range policy.AdminBlockedNames {
|
||||
nameMap[v] = true
|
||||
}
|
||||
exactNameMap := make(map[string]bool)
|
||||
wildcardNameMap := make(map[string]bool)
|
||||
for _, v := range bl.ExactBlacklist {
|
||||
for _, v := range policy.ExactBlockedNames {
|
||||
exactNameMap[v] = true
|
||||
// Remove the leftmost label of the exact blacklist entry to make an exact
|
||||
// wildcard blacklist entry that will prevent issuing a wildcard that would
|
||||
// include the exact blacklist entry. e.g. if "highvalue.example.com" is on
|
||||
// the exact blacklist we want "example.com" to be on the
|
||||
// wildcardExactBlacklist so that "*.example.com" cannot be issued.
|
||||
// Remove the leftmost label of the exact blocked names entry to make an exact
|
||||
// wildcard block list entry that will prevent issuing a wildcard that would
|
||||
// include the exact blocklist entry. e.g. if "highvalue.example.com" is on
|
||||
// the exact blocklist we want "example.com" to be in the
|
||||
// wildcardExactBlocklist so that "*.example.com" cannot be issued.
|
||||
//
|
||||
// First, split the domain into two parts: the first label and the rest of the domain.
|
||||
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"
|
||||
if len(parts) < 2 {
|
||||
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
|
||||
// wildcardNameMap to block issuance for `*.`+parts[1]
|
||||
wildcardNameMap[parts[1]] = true
|
||||
}
|
||||
pa.blacklistMu.Lock()
|
||||
pa.blacklist = nameMap
|
||||
pa.exactBlacklist = exactNameMap
|
||||
pa.wildcardExactBlacklist = wildcardNameMap
|
||||
pa.blacklistMu.Unlock()
|
||||
pa.blocklistMu.Lock()
|
||||
pa.blocklist = nameMap
|
||||
pa.exactBlocklist = exactNameMap
|
||||
pa.wildcardExactBlocklist = wildcardNameMap
|
||||
pa.blocklistMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -141,7 +202,7 @@ var (
|
|||
errInvalidIdentifier = berrors.MalformedError("Invalid identifier type")
|
||||
errNonPublic = berrors.MalformedError("Name does not end in a public suffix")
|
||||
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")
|
||||
errNameTooLong = berrors.MalformedError("DNS name too long")
|
||||
errIPAddress = berrors.MalformedError("Issuance for IP addresses not supported")
|
||||
|
@ -174,7 +235,7 @@ var (
|
|||
// * MUST NOT match the syntax of an IP address
|
||||
// * MUST end in a 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)
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// Require no match against blacklist
|
||||
// Require no match against hostname block lists
|
||||
if err := pa.checkHostLists(domain); err != nil {
|
||||
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 label is not immediately adjacent to a top level ICANN
|
||||
// TLD
|
||||
// * That the wildcard wouldn't cover an exact blacklist entry (e.g. an exact
|
||||
// blacklist entry for "foo.example.com" should prevent issuance for
|
||||
// * That the wildcard wouldn't cover an exact blocklist entry (e.g. an exact
|
||||
// blocklist entry for "foo.example.com" should prevent issuance for
|
||||
// "*.example.com")
|
||||
//
|
||||
// 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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
// Check that the PA is willing to issue for the base domain
|
||||
// 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
|
||||
// exact hostname check.
|
||||
//
|
||||
|
@ -336,42 +397,42 @@ func (pa *AuthorityImpl) WillingToIssueWildcard(ident core.AcmeIdentifier) error
|
|||
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
|
||||
// errBlacklisted is returned.
|
||||
// errPolicyForbidden is returned.
|
||||
func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
|
||||
pa.blacklistMu.RLock()
|
||||
defer pa.blacklistMu.RUnlock()
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
|
||||
if pa.blacklist == nil {
|
||||
if pa.blocklist == nil {
|
||||
return fmt.Errorf("Hostname policy not yet loaded.")
|
||||
}
|
||||
|
||||
if pa.wildcardExactBlacklist[domain] {
|
||||
return errBlacklisted
|
||||
if pa.wildcardExactBlocklist[domain] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pa *AuthorityImpl) checkHostLists(domain string) error {
|
||||
pa.blacklistMu.RLock()
|
||||
defer pa.blacklistMu.RUnlock()
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
|
||||
if pa.blacklist == nil {
|
||||
if pa.blocklist == nil {
|
||||
return fmt.Errorf("Hostname policy not yet loaded.")
|
||||
}
|
||||
|
||||
labels := strings.Split(domain, ".")
|
||||
for i := range labels {
|
||||
joined := strings.Join(labels[i:], ".")
|
||||
if pa.blacklist[joined] {
|
||||
return errBlacklisted
|
||||
if pa.blocklist[joined] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
}
|
||||
|
||||
if pa.exactBlacklist[domain] {
|
||||
return errBlacklisted
|
||||
if pa.exactBlocklist[domain] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -430,7 +491,7 @@ func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) ([]core.C
|
|||
|
||||
// ChallengeTypeEnabled returns whether the specified challenge type is enabled
|
||||
func (pa *AuthorityImpl) ChallengeTypeEnabled(t string) bool {
|
||||
pa.blacklistMu.RLock()
|
||||
defer pa.blacklistMu.RUnlock()
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
return pa.enabledChallenges[t]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package policy
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/features"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var log = blog.UseMock()
|
||||
|
@ -106,24 +108,29 @@ func TestWillingToIssue(t *testing.T) {
|
|||
`foo.bd`,
|
||||
}
|
||||
|
||||
shouldBeBlacklisted := []string{
|
||||
shouldBeBlocked := []string{
|
||||
`highvalue.website1.org`,
|
||||
`website2.co.uk`,
|
||||
`www.website3.com`,
|
||||
`lots.of.labels.website4.com`,
|
||||
`banned.in.dc.com`,
|
||||
`bad.brains.banned.in.dc.com`,
|
||||
}
|
||||
blacklistContents := []string{
|
||||
blocklistContents := []string{
|
||||
`website2.com`,
|
||||
`website2.org`,
|
||||
`website2.co.uk`,
|
||||
`website3.com`,
|
||||
`website4.com`,
|
||||
}
|
||||
exactBlacklistContents := []string{
|
||||
exactBlocklistContents := []string{
|
||||
`www.website1.org`,
|
||||
`highvalue.website1.org`,
|
||||
`dl.website1.org`,
|
||||
}
|
||||
adminBlockedContents := []string{
|
||||
`banned.in.dc.com`,
|
||||
}
|
||||
|
||||
shouldBeAccepted := []string{
|
||||
`lowvalue.website1.org`,
|
||||
|
@ -136,71 +143,93 @@ func TestWillingToIssue(t *testing.T) {
|
|||
"www.web-site2.com",
|
||||
}
|
||||
|
||||
pa := paImpl(t)
|
||||
|
||||
blacklistBytes, err := json.Marshal(blacklistJSON{
|
||||
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 for invalid identifier type
|
||||
identifier := core.AcmeIdentifier{Type: "ip", Value: "example.com"}
|
||||
err = pa.WillingToIssue(identifier)
|
||||
if err != errInvalidIdentifier {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier)
|
||||
policy := blockedNamesPolicy{
|
||||
HighRiskBlockedNames: blocklistContents,
|
||||
ExactBlockedNames: exactBlocklistContents,
|
||||
AdminBlockedNames: adminBlockedContents,
|
||||
}
|
||||
|
||||
// Test syntax errors
|
||||
for _, tc := range testCases {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: tc.domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != tc.err {
|
||||
t.Errorf("WillingToIssue(%q) = %q, expected %q", tc.domain, err, tc.err)
|
||||
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)
|
||||
|
||||
err = pa.SetHostnamePolicyFile(f)
|
||||
test.AssertNotError(t, err, "Couldn't load rules")
|
||||
|
||||
// Test for invalid identifier type
|
||||
identifier := core.AcmeIdentifier{Type: "ip", Value: "example.com"}
|
||||
err = pa.WillingToIssue(identifier)
|
||||
if err != errInvalidIdentifier {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier)
|
||||
}
|
||||
|
||||
// Test syntax errors
|
||||
for _, tc := range testCases {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: tc.domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != tc.err {
|
||||
t.Errorf("WillingToIssue(%q) = %q, expected %q", tc.domain, err, tc.err)
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid encoding
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "www.xn--m.com"})
|
||||
test.AssertError(t, err, "WillingToIssue didn't fail on a malformed IDN")
|
||||
// Valid encoding
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "www.xn--mnich-kva.com"})
|
||||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed IDN")
|
||||
// IDN TLD
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "xn--example--3bhk5a.xn--p1ai"})
|
||||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed domain with IDN TLD")
|
||||
features.Reset()
|
||||
|
||||
// Test domains that are equal to public suffixes
|
||||
for _, domain := range shouldBeTLDError {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != errICANNTLD {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test expected blocked domains
|
||||
for _, domain := range shouldBeBlocked {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != errPolicyForbidden {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test acceptance of good names
|
||||
for _, domain := range shouldBeAccepted {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
if err := pa.WillingToIssue(identifier); err != nil {
|
||||
t.Error("Identifier was incorrectly forbidden: ", identifier, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid encoding
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "www.xn--m.com"})
|
||||
test.AssertError(t, err, "WillingToIssue didn't fail on a malformed IDN")
|
||||
// Valid encoding
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "www.xn--mnich-kva.com"})
|
||||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed IDN")
|
||||
// IDN TLD
|
||||
err = pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "xn--example--3bhk5a.xn--p1ai"})
|
||||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed domain with IDN TLD")
|
||||
features.Reset()
|
||||
|
||||
// Test domains that are equal to public suffixes
|
||||
for _, domain := range shouldBeTLDError {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != errICANNTLD {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test blacklisting
|
||||
for _, domain := range shouldBeBlacklisted {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
err := pa.WillingToIssue(identifier)
|
||||
if err != errBlacklisted {
|
||||
t.Error("Identifier was not correctly forbidden: ", identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test acceptance of good names
|
||||
for _, domain := range shouldBeAccepted {
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
if err := pa.WillingToIssue(identifier); err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,12 +242,12 @@ func TestWillingToIssueWildcard(t *testing.T) {
|
|||
}
|
||||
pa := paImpl(t)
|
||||
|
||||
bannedBytes, err := json.Marshal(blacklistJSON{
|
||||
Blacklist: bannedDomains,
|
||||
ExactBlacklist: exactBannedDomains,
|
||||
bannedBytes, err := json.Marshal(blockedNamesPolicy{
|
||||
HighRiskBlockedNames: bannedDomains,
|
||||
ExactBlockedNames: exactBannedDomains,
|
||||
})
|
||||
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())
|
||||
err = ioutil.WriteFile(f.Name(), bannedBytes, 0640)
|
||||
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",
|
||||
Ident: makeDNSIdent("*.zombo.gov.us"),
|
||||
ExpectedErr: errBlacklisted,
|
||||
ExpectedErr: errPolicyForbidden,
|
||||
},
|
||||
// We should not allow getting a wildcard for that would cover an exact
|
||||
// blocklist domain
|
||||
{
|
||||
Name: "Wildcard for ExactBlacklist base domain",
|
||||
Name: "Wildcard for ExactBlocklist base domain",
|
||||
Ident: makeDNSIdent("*.letsdecrypt.org"),
|
||||
ExpectedErr: errBlacklisted,
|
||||
ExpectedErr: errPolicyForbidden,
|
||||
},
|
||||
// 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"),
|
||||
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.
|
||||
{
|
||||
Name: "Wildcard for ExactBlacklist domain",
|
||||
Name: "Wildcard for ExactBlocklist domain",
|
||||
Ident: makeDNSIdent("*.highvalue.letsdecrypt.org"),
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
|
@ -365,9 +394,9 @@ func TestChallengesForWildcard(t *testing.T) {
|
|||
test.AssertEquals(t, challenges[0].Type, core.ChallengeTypeDNS01)
|
||||
}
|
||||
|
||||
// TestMalformedExactBlacklist tests that loading a JSON policy file with an
|
||||
// invalid exact blacklist entry will fail as expected.
|
||||
func TestMalformedExactBlacklist(t *testing.T) {
|
||||
// TestMalformedExactBlocklist tests that loading a JSON policy file with an
|
||||
// invalid exact blocklist entry will fail as expected.
|
||||
func TestMalformedExactBlocklist(t *testing.T) {
|
||||
pa := paImpl(t)
|
||||
|
||||
exactBannedDomains := []string{
|
||||
|
@ -379,22 +408,36 @@ func TestMalformedExactBlacklist(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create JSON for the exactBannedDomains
|
||||
bannedBytes, err := json.Marshal(blacklistJSON{
|
||||
Blacklist: bannedDomains,
|
||||
ExactBlacklist: exactBannedDomains,
|
||||
bannedBytes, err := json.Marshal(blockedNamesPolicy{
|
||||
HighRiskBlockedNames: bannedDomains,
|
||||
ExactBlockedNames: exactBannedDomains,
|
||||
})
|
||||
test.AssertNotError(t, err, "Couldn't serialize banned list")
|
||||
|
||||
// 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())
|
||||
// Write the JSON to the temp file
|
||||
err = ioutil.WriteFile(f.Name(), bannedBytes, 0640)
|
||||
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
|
||||
// error since the exact blacklist contents are malformed.
|
||||
// error since the exact blocklist contents are malformed.
|
||||
err = pa.SetHostnamePolicyFile(f.Name())
|
||||
test.AssertError(t, err, "Loaded invalid exact blacklist content without error")
|
||||
test.AssertEquals(t, err.Error(), "Malformed exact blacklist entry, only one label: \"com\"")
|
||||
test.AssertError(t, err, "Loaded invalid exact blocklist content without error")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
"lifespanOCSP": "96h",
|
||||
"maxNames": 100,
|
||||
"enableMustStaple": true,
|
||||
"hostnamePolicyFile": "test/hostname-policy.json",
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"cfssl": {
|
||||
"signing": {
|
||||
"profiles": {
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
"lifespanOCSP": "96h",
|
||||
"maxNames": 100,
|
||||
"enableMustStaple": true,
|
||||
"hostnamePolicyFile": "test/hostname-policy.json",
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"cfssl": {
|
||||
"signing": {
|
||||
"profiles": {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"certChecker": {
|
||||
"dbConnectFile": "test/secrets/cert_checker_dburl",
|
||||
"maxDBConns": 10,
|
||||
"hostnamePolicyFile": "test/hostname-policy.json"
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml"
|
||||
},
|
||||
|
||||
"pa": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"maxConcurrentRPCServerRequests": 100000,
|
||||
"maxContactsPerRegistration": 100,
|
||||
"debugAddr": ":8002",
|
||||
"hostnamePolicyFile": "test/hostname-policy.json",
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"maxNames": 100,
|
||||
"reuseValidAuthz": true,
|
||||
"authorizationLifetimeDays": 30,
|
||||
|
|
|
@ -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"
|
Loading…
Reference in New Issue