mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-27 10:19:34 +00:00
219 lines
7.8 KiB
Diff
219 lines
7.8 KiB
Diff
diff --git a/policy/pa.go b/policy/pa.go
|
|
index ab17bd89d..0a71a962d 100644
|
|
--- a/policy/pa.go
|
|
+++ b/policy/pa.go
|
|
@@ -32,6 +32,9 @@ type AuthorityImpl struct {
|
|
domainBlocklist map[string]bool
|
|
fqdnBlocklist map[string]bool
|
|
wildcardFqdnBlocklist map[string]bool
|
|
+ whitelist map[string]bool
|
|
+ lockdown map[string]bool
|
|
+ ldPublicContacts bool
|
|
ipPrefixBlocklist []netip.Prefix
|
|
blocklistMu sync.RWMutex
|
|
|
|
@@ -73,6 +76,10 @@ type blockedIdentsPolicy struct {
|
|
// AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
|
|
// contained within the prefix are blocked.
|
|
AdminBlockedPrefixes []string `yaml:"AdminBlockedPrefixes"`
|
|
+
|
|
+ Whitelist []string `yaml:"Whitelist"`
|
|
+ Lockdown []string `yaml:"Lockdown"`
|
|
+ LockdownAllowPublicContacts bool `yaml:"LockdownAllowPublicContacts"`
|
|
}
|
|
|
|
// LoadIdentPolicyFile will load the given policy file, returning an error if it
|
|
@@ -144,11 +151,23 @@ func (pa *AuthorityImpl) processIdentPolicy(policy blockedIdentsPolicy) error {
|
|
prefixes = append(prefixes, prefix)
|
|
}
|
|
|
|
+ whiteMap := make(map[string]bool)
|
|
+ for _, v := range policy.Whitelist {
|
|
+ whiteMap[v] = true
|
|
+ }
|
|
+ lockMap := make(map[string]bool)
|
|
+ for _, v := range policy.Lockdown {
|
|
+ lockMap[v] = true
|
|
+ }
|
|
+
|
|
pa.blocklistMu.Lock()
|
|
pa.domainBlocklist = nameMap
|
|
pa.fqdnBlocklist = exactNameMap
|
|
pa.wildcardFqdnBlocklist = wildcardNameMap
|
|
pa.ipPrefixBlocklist = prefixes
|
|
+ pa.whitelist = whiteMap
|
|
+ pa.lockdown = lockMap
|
|
+ pa.ldPublicContacts = policy.LockdownAllowPublicContacts
|
|
pa.blocklistMu.Unlock()
|
|
return nil
|
|
}
|
|
@@ -219,7 +238,7 @@ var (
|
|
// - exactly equal to an IANA registered TLD
|
|
//
|
|
// It does NOT ensure that the domain is absent from any PA blocked lists.
|
|
-func validNonWildcardDomain(domain string) error {
|
|
+func (pa *AuthorityImpl) ValidNonWildcardDomain(domain string, isContact bool) error {
|
|
if domain == "" {
|
|
return errEmptyIdentifier
|
|
}
|
|
@@ -252,7 +271,9 @@ func validNonWildcardDomain(domain string) error {
|
|
return errTooManyLabels
|
|
}
|
|
if len(labels) < 2 {
|
|
- return errTooFewLabels
|
|
+ if (len(pa.lockdown) > 0 || len(pa.whitelist) > 0) && !pa.lockdown[domain] && !pa.whitelist[domain] {
|
|
+ return errTooFewLabels
|
|
+ }
|
|
}
|
|
for _, label := range labels {
|
|
// Check that this is a valid LDH Label: "A string consisting of ASCII
|
|
@@ -296,12 +317,17 @@ func validNonWildcardDomain(domain string) error {
|
|
}
|
|
}
|
|
|
|
- // Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
|
|
- icannTLD, err := iana.ExtractSuffix(domain)
|
|
+ ok, err := pa.checkWhitelist(domain, isContact)
|
|
if err != nil {
|
|
- return errNonPublic
|
|
+ return err
|
|
+ }
|
|
+ if ok {
|
|
+ return nil
|
|
}
|
|
- if icannTLD == domain {
|
|
+
|
|
+ // Names must not be equal to an ICANN TLD.
|
|
+ icannTLD, err := iana.ExtractSuffix(domain)
|
|
+ if err == nil && icannTLD == domain {
|
|
return errICANNTLD
|
|
}
|
|
|
|
@@ -311,9 +337,9 @@ func validNonWildcardDomain(domain string) error {
|
|
// ValidDomain checks that a domain is valid and that it doesn't contain any
|
|
// invalid wildcard characters. It does NOT ensure that the domain is absent
|
|
// from any PA blocked lists.
|
|
-func ValidDomain(domain string) error {
|
|
+func (pa *AuthorityImpl) ValidDomain(domain string) error {
|
|
if strings.Count(domain, "*") <= 0 {
|
|
- return validNonWildcardDomain(domain)
|
|
+ return pa.ValidNonWildcardDomain(domain, false)
|
|
}
|
|
|
|
// Names containing more than one wildcard are invalid.
|
|
@@ -332,7 +358,7 @@ func ValidDomain(domain string) error {
|
|
|
|
// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
|
|
icannTLD, err := iana.ExtractSuffix(baseDomain)
|
|
- if err != nil {
|
|
+ if err != nil && !pa.lockdown[baseDomain] && !pa.whitelist[baseDomain] {
|
|
return errNonPublic
|
|
}
|
|
// Names must have a non-wildcard label immediately adjacent to the ICANN
|
|
@@ -340,7 +366,7 @@ func ValidDomain(domain string) error {
|
|
if baseDomain == icannTLD {
|
|
return errICANNTLDWildcard
|
|
}
|
|
- return validNonWildcardDomain(baseDomain)
|
|
+ return pa.ValidNonWildcardDomain(baseDomain, false)
|
|
}
|
|
|
|
// ValidIP checks that an IP address:
|
|
@@ -383,14 +409,14 @@ var forbiddenMailDomains = map[string]bool{
|
|
// ValidEmail returns an error if the input doesn't parse as an email address,
|
|
// the domain isn't a valid hostname in Preferred Name Syntax, or its on the
|
|
// list of domains forbidden for mail (because they are often used in examples).
|
|
-func ValidEmail(address string) error {
|
|
+func (pa *AuthorityImpl) ValidEmail(address string) error {
|
|
email, err := mail.ParseAddress(address)
|
|
if err != nil {
|
|
return berrors.InvalidEmailError("unable to parse email address")
|
|
}
|
|
splitEmail := strings.SplitN(email.Address, "@", -1)
|
|
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
|
|
- err = validNonWildcardDomain(domain)
|
|
+ err = pa.ValidNonWildcardDomain(domain, true)
|
|
if err != nil {
|
|
return berrors.InvalidEmailError("contact email has invalid domain: %s", err)
|
|
}
|
|
@@ -432,7 +458,7 @@ func subError(ident identifier.ACMEIdentifier, err error) berrors.SubBoulderErro
|
|
//
|
|
// Precondition: all input identifier values must be in lowercase.
|
|
func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error {
|
|
- err := WellFormedIdentifiers(idents)
|
|
+ err := pa.WellFormedIdentifiers(idents)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
@@ -449,6 +475,10 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|
// The base domain is the wildcard request with the `*.` prefix removed
|
|
baseDomain := strings.TrimPrefix(ident.Value, "*.")
|
|
|
|
+ if ok, _ := pa.checkWhitelist(ident.Value, false); ok {
|
|
+ return nil
|
|
+ }
|
|
+
|
|
// The base domain can't be in the wildcard exact blocklist
|
|
err = pa.checkWildcardBlocklist(baseDomain)
|
|
if err != nil {
|
|
@@ -497,12 +527,12 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|
//
|
|
// If multiple identifiers are invalid, the error will contain suberrors
|
|
// specific to each identifier.
|
|
-func WellFormedIdentifiers(idents identifier.ACMEIdentifiers) error {
|
|
+func (pa *AuthorityImpl) WellFormedIdentifiers(idents identifier.ACMEIdentifiers) error {
|
|
var subErrors []berrors.SubBoulderError
|
|
for _, ident := range idents {
|
|
switch ident.Type {
|
|
case identifier.TypeDNS:
|
|
- err := ValidDomain(ident.Value)
|
|
+ err := pa.ValidDomain(ident.Value)
|
|
if err != nil {
|
|
subErrors = append(subErrors, subError(ident, err))
|
|
}
|
|
@@ -544,6 +574,34 @@ func combineSubErrors(subErrors []berrors.SubBoulderError) error {
|
|
return nil
|
|
}
|
|
|
|
+func (pa *AuthorityImpl) checkWhitelist(domain string, isContact bool) (bool, error) {
|
|
+ pa.blocklistMu.RLock()
|
|
+ defer pa.blocklistMu.RUnlock()
|
|
+
|
|
+ if (pa.whitelist == nil) || (pa.lockdown == nil) {
|
|
+ return false, fmt.Errorf("Hostname policy not yet loaded.")
|
|
+ }
|
|
+
|
|
+ labels := strings.Split(domain, ".")
|
|
+ for i := range labels {
|
|
+ joined := strings.Join(labels[i:], ".")
|
|
+ if pa.whitelist[joined] || pa.lockdown[joined] {
|
|
+ return true, nil
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if len(pa.lockdown) > 0 {
|
|
+ if isContact && pa.ldPublicContacts {
|
|
+ return false, nil
|
|
+ }
|
|
+ // In Lockdown mode, the domain MUST be in the list, so return an error if not found
|
|
+ return false, errPolicyForbidden
|
|
+ } else {
|
|
+ // In Whitelist mode, if the domain is not in the list, continue with the other checks
|
|
+ return false, nil
|
|
+ }
|
|
+}
|
|
+
|
|
// checkWildcardBlocklist checks the wildcardExactBlocklist for a given domain.
|
|
// If the domain is not present on the list nil is returned, otherwise
|
|
// errPolicyForbidden is returned.
|
|
@@ -575,6 +633,9 @@ func (pa *AuthorityImpl) checkBlocklists(ident identifier.ACMEIdentifier) error
|
|
labels := strings.Split(ident.Value, ".")
|
|
for i := range labels {
|
|
joined := strings.Join(labels[i:], ".")
|
|
+ if pa.lockdown[joined] || pa.whitelist[joined] {
|
|
+ return nil
|
|
+ }
|
|
if pa.domainBlocklist[joined] {
|
|
return errPolicyForbidden
|
|
}
|