mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-28 02:19:31 +00:00
Option to allow public contact email addresses in lockdown mode
When in lockdown mode, only those domains can be used to request certificates for, but it also only accepts email addresses in those domains. With this option in the GUI it is now possible to still allow all public domains in contact addresses.
This commit is contained in:
@@ -86,6 +86,15 @@ if [ "$PKI_DOMAIN_MODE" == "lockdown" ] && [ "$PKI_LOCKDOWN_DOMAINS" != "" ]; th
|
||||
for d in $(echo $PKI_LOCKDOWN_DOMAINS | sed -e "s/\\\r/ /g" | sed -e "s/\\\n/ /g" | tr '\r' ' '); do
|
||||
echo " - \"$d\"" >> hostname-policy.yaml
|
||||
done
|
||||
|
||||
allow_public="false"
|
||||
ld_public_contacts=$(grep ld_public_contacts $dataDir/config.json | grep true || echo "")
|
||||
if [ "$ld_public_contacts" != "" ]; then
|
||||
allow_public="true"
|
||||
fi
|
||||
|
||||
echo >> hostname-policy.yaml
|
||||
echo "LockdownAllowPublicContacts: $allow_public" >> hostname-policy.yaml
|
||||
fi
|
||||
if [ "$PKI_DOMAIN_MODE" == "whitelist" ] && [ "$PKI_WHITELIST_DOMAINS" != "" ]; then
|
||||
echo >> hostname-policy.yaml
|
||||
|
||||
11
gui/main.go
11
gui/main.go
@@ -176,6 +176,7 @@ type SetupConfig struct {
|
||||
DomainMode string
|
||||
LockdownDomains string
|
||||
WhitelistDomains string
|
||||
LDPublicContacts bool
|
||||
ExtendedTimeout bool
|
||||
RequestBase string
|
||||
Errors map[string]string
|
||||
@@ -666,6 +667,7 @@ func _configUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
DomainMode: r.Form.Get("domain_mode"),
|
||||
LockdownDomains: r.Form.Get("lockdown_domains"),
|
||||
WhitelistDomains: r.Form.Get("whitelist_domains"),
|
||||
LDPublicContacts: (r.Form.Get("ld_public_contacts") == "true"),
|
||||
ExtendedTimeout: (r.Form.Get("extended_timeout") == "true"),
|
||||
}
|
||||
|
||||
@@ -715,6 +717,11 @@ func _configUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
delta = true
|
||||
viper.Set("labca.lockdown", cfg.LockdownDomains)
|
||||
}
|
||||
|
||||
if cfg.LDPublicContacts != viper.GetBool("labca.ld_public_contacts") {
|
||||
delta = true
|
||||
viper.Set("labca.ld_public_contacts", cfg.LDPublicContacts)
|
||||
}
|
||||
}
|
||||
if domainMode == "whitelist" {
|
||||
if cfg.WhitelistDomains != viper.GetString("labca.whitelist") {
|
||||
@@ -1585,6 +1592,7 @@ func _manageGet(w http.ResponseWriter, r *http.Request) {
|
||||
manageData["DomainMode"] = domainMode
|
||||
if domainMode == "lockdown" {
|
||||
manageData["LockdownDomains"] = viper.GetString("labca.lockdown")
|
||||
manageData["LDPublicContacts"] = viper.GetBool("labca.ld_public_contacts")
|
||||
}
|
||||
if domainMode == "whitelist" {
|
||||
manageData["WhitelistDomains"] = viper.GetString("labca.whitelist")
|
||||
@@ -2536,6 +2544,7 @@ func _setupBaseConfig(w http.ResponseWriter, r *http.Request) bool {
|
||||
DomainMode: "lockdown",
|
||||
LockdownDomains: domain,
|
||||
WhitelistDomains: domain,
|
||||
LDPublicContacts: true,
|
||||
RequestBase: r.Header.Get("X-Request-Base"),
|
||||
}
|
||||
|
||||
@@ -2553,6 +2562,7 @@ func _setupBaseConfig(w http.ResponseWriter, r *http.Request) bool {
|
||||
DomainMode: r.Form.Get("domain_mode"),
|
||||
LockdownDomains: r.Form.Get("lockdown_domains"),
|
||||
WhitelistDomains: r.Form.Get("whitelist_domains"),
|
||||
LDPublicContacts: (r.Form.Get("ld_public_contacts") == "true"),
|
||||
RequestBase: r.Header.Get("X-Request-Base"),
|
||||
}
|
||||
|
||||
@@ -2571,6 +2581,7 @@ func _setupBaseConfig(w http.ResponseWriter, r *http.Request) bool {
|
||||
viper.Set("labca.domain_mode", cfg.DomainMode)
|
||||
if cfg.DomainMode == "lockdown" {
|
||||
viper.Set("labca.lockdown", cfg.LockdownDomains)
|
||||
viper.Set("labca.ld_public_contacts", cfg.LDPublicContacts)
|
||||
}
|
||||
if cfg.DomainMode == "whitelist" {
|
||||
viper.Set("labca.whitelist", cfg.WhitelistDomains)
|
||||
|
||||
@@ -165,8 +165,10 @@ $(function() {
|
||||
|
||||
if ($("input[type=radio]#whitelist").prop('checked') || $("input[type=radio]#standard").prop('checked') ) {
|
||||
$("#domain_mode_warning").show();
|
||||
$("#ld_options").hide();
|
||||
} else {
|
||||
$("#domain_mode_warning").hide();
|
||||
$("#ld_options").show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +177,7 @@ $(function() {
|
||||
});
|
||||
|
||||
$("#domain_mode_warning").hide();
|
||||
$("#ld_options").show();
|
||||
radioDisable();
|
||||
|
||||
|
||||
|
||||
@@ -295,17 +295,26 @@
|
||||
<span class="error config-error hidden" id="whitelistdomains-error"></span>
|
||||
<br/>
|
||||
|
||||
<input type="radio" id="standard" name="domain_mode" value="standard" {{ if eq .DomainMode "standard"}}checked{{ end }}/> Standard - any official domains<br/>
|
||||
<input type="radio" id="standard" name="domain_mode" value="standard" {{ if eq .DomainMode "standard"}}checked{{ end }}/> Standard - any official domains
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="ld_options">
|
||||
<label>Lockdown options:</label><br/>
|
||||
<input type="checkbox" id="ld_public_contacts" name="ld_public_contacts" value="true" {{ if .LDPublicContacts }}checked{{ end }}></input>
|
||||
Still allow all public domain names in contact email addresses when creating new ACME accounts
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Extended timeout:</label><br/>
|
||||
<input type="checkbox" class="extended_timeout" id="extended_timeout" value="extend" {{ if .ExtendedTimeout }}checked{{ end }}></input>
|
||||
If you see timeout related errors on the Dashboard / Audit Log, try checking this box.
|
||||
</div>
|
||||
<div class="form-group" id="domain_mode_warning">
|
||||
<img src="static/img/warning.png"> Are you sure? This facilitates man-in-the-middle attacks!
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="hidden" id="update-config-result"></span>
|
||||
<button class="btn btn-default" type="button" id="update-config" title="Update the system configuration">Update</button>
|
||||
<span id="domain_mode_warning"> <img src="static/img/warning.png"> Are you sure? This facilitates man-in-the-middle attacks!<br/></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1001,6 +1010,7 @@
|
||||
domain_mode: ($("#standard").prop('checked') ? 'standard' : ($("#whitelist").prop('checked') ? 'whitelist' : 'lockdown')),
|
||||
lockdown_domains: $("#lockdown_domains").val(),
|
||||
whitelist_domains: $("#whitelist_domains").val(),
|
||||
ld_public_contacts: $("#ld_public_contacts").prop("checked"),
|
||||
extended_timeout: $("#extended_timeout").prop("checked"),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -37,11 +37,18 @@
|
||||
<span class="error">{{ . }}</span><br/>
|
||||
{{ end }}
|
||||
|
||||
<input type="radio" id="standard" name="domain_mode" value="standard" {{ if eq .DomainMode "standard"}}checked{{ end }}/> Standard - any official domains<br/><br/>
|
||||
<input type="radio" id="standard" name="domain_mode" value="standard" {{ if eq .DomainMode "standard"}}checked{{ end }}/> Standard - any official domains<br/>
|
||||
</div>
|
||||
<div class="form-group" id="ld_options">
|
||||
<label>Lockdown options:</label><br/>
|
||||
<input type="checkbox" id="ld_public_contacts" name="ld_public_contacts" value="true" {{ if .LDPublicContacts }}checked{{ end }}></input>
|
||||
Still allow all public domain names in contact email addresses when creating new ACME accounts
|
||||
</div>
|
||||
<div class="form-group" id="domain_mode_warning">
|
||||
<img src="static/img/warning.png"> Are you sure? This facilitates man-in-the-middle attacks!
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input class="btn btn-default" type="submit" value="Create">
|
||||
<span id="domain_mode_warning"> <img src="static/img/warning.png"> Are you sure? This facilitates man-in-the-middle attacks!</span><br/>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
diff --git a/policy/pa.go b/policy/pa.go
|
||||
index d872d5cbe..49daeb7d4 100644
|
||||
index d872d5cbe..faa052d6c 100644
|
||||
--- a/policy/pa.go
|
||||
+++ b/policy/pa.go
|
||||
@@ -32,6 +32,8 @@ type AuthorityImpl struct {
|
||||
@@ -32,6 +32,9 @@ type AuthorityImpl struct {
|
||||
blocklist map[string]bool
|
||||
exactBlocklist map[string]bool
|
||||
wildcardExactBlocklist map[string]bool
|
||||
+ whitelist map[string]bool
|
||||
+ lockdown map[string]bool
|
||||
+ ldPublicContacts bool
|
||||
blocklistMu sync.RWMutex
|
||||
|
||||
enabledChallenges map[core.AcmeChallenge]bool
|
||||
@@ -72,6 +74,9 @@ type blockedNamesPolicy struct {
|
||||
@@ -72,6 +75,10 @@ type blockedNamesPolicy struct {
|
||||
// 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"`
|
||||
+
|
||||
+ Whitelist []string `yaml:"Whitelist"`
|
||||
+ Lockdown []string `yaml:"Lockdown"`
|
||||
+ Whitelist []string `yaml:"Whitelist"`
|
||||
+ Lockdown []string `yaml:"Lockdown"`
|
||||
+ LockdownAllowPublicContacts bool `yaml:"LockdownAllowPublicContacts"`
|
||||
}
|
||||
|
||||
// LoadHostnamePolicyFile will load the given policy file, returning an error if
|
||||
@@ -131,10 +136,20 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
|
||||
@@ -131,10 +138,21 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
|
||||
// wildcardNameMap to block issuance for `*.`+parts[1]
|
||||
wildcardNameMap[parts[1]] = true
|
||||
}
|
||||
@@ -39,23 +41,35 @@ index d872d5cbe..49daeb7d4 100644
|
||||
pa.wildcardExactBlocklist = wildcardNameMap
|
||||
+ pa.whitelist = whiteMap
|
||||
+ pa.lockdown = lockMap
|
||||
+ pa.ldPublicContacts = policy.LockdownAllowPublicContacts
|
||||
pa.blocklistMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -203,7 +218,7 @@ var (
|
||||
@@ -203,7 +221,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) error {
|
||||
+func (pa *AuthorityImpl) ValidNonWildcardDomain(domain string, isContact bool) error {
|
||||
if domain == "" {
|
||||
return errEmptyName
|
||||
}
|
||||
@@ -279,6 +294,14 @@ func ValidNonWildcardDomain(domain string) error {
|
||||
@@ -235,7 +253,9 @@ func ValidNonWildcardDomain(domain string) error {
|
||||
return errTooManyLabels
|
||||
}
|
||||
if len(labels) < 2 {
|
||||
- return errTooFewLabels
|
||||
+ if !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
|
||||
@@ -279,6 +299,14 @@ func ValidNonWildcardDomain(domain string) error {
|
||||
}
|
||||
}
|
||||
|
||||
+ ok, err := pa.checkWhitelist(domain)
|
||||
+ ok, err := pa.checkWhitelist(domain, isContact)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
@@ -66,7 +80,7 @@ index d872d5cbe..49daeb7d4 100644
|
||||
// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
|
||||
icannTLD, err := iana.ExtractSuffix(domain)
|
||||
if err != nil {
|
||||
@@ -294,9 +317,9 @@ func ValidNonWildcardDomain(domain string) error {
|
||||
@@ -294,9 +322,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.
|
||||
@@ -74,20 +88,29 @@ index d872d5cbe..49daeb7d4 100644
|
||||
+func (pa *AuthorityImpl) ValidDomain(domain string) error {
|
||||
if strings.Count(domain, "*") <= 0 {
|
||||
- return ValidNonWildcardDomain(domain)
|
||||
+ return pa.ValidNonWildcardDomain(domain)
|
||||
+ return pa.ValidNonWildcardDomain(domain, false)
|
||||
}
|
||||
|
||||
// Names containing more than one wildcard are invalid.
|
||||
@@ -323,7 +346,7 @@ func ValidDomain(domain string) error {
|
||||
@@ -315,7 +343,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
|
||||
@@ -323,7 +351,7 @@ func ValidDomain(domain string) error {
|
||||
if baseDomain == icannTLD {
|
||||
return errICANNTLDWildcard
|
||||
}
|
||||
- return ValidNonWildcardDomain(baseDomain)
|
||||
+ return pa.ValidNonWildcardDomain(baseDomain)
|
||||
+ return pa.ValidNonWildcardDomain(baseDomain, false)
|
||||
}
|
||||
|
||||
// forbiddenMailDomains is a map of domain names we do not allow after the
|
||||
@@ -341,7 +364,7 @@ var forbiddenMailDomains = map[string]bool{
|
||||
@@ -341,7 +369,7 @@ 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).
|
||||
@@ -96,16 +119,16 @@ index d872d5cbe..49daeb7d4 100644
|
||||
email, err := mail.ParseAddress(address)
|
||||
if err != nil {
|
||||
if len(address) > 254 {
|
||||
@@ -351,7 +374,7 @@ func ValidEmail(address string) error {
|
||||
@@ -351,7 +379,7 @@ func ValidEmail(address string) error {
|
||||
}
|
||||
splitEmail := strings.SplitN(email.Address, "@", -1)
|
||||
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
|
||||
- err = ValidNonWildcardDomain(domain)
|
||||
+ err = pa.ValidNonWildcardDomain(domain)
|
||||
+ err = pa.ValidNonWildcardDomain(domain, true)
|
||||
if err != nil {
|
||||
return berrors.InvalidEmailError(
|
||||
"contact email %q has invalid domain : %s",
|
||||
@@ -416,7 +439,7 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
@@ -416,7 +444,7 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
for _, domain := range domains {
|
||||
if strings.Count(domain, "*") > 0 {
|
||||
// Domain contains a wildcard, check that it is valid.
|
||||
@@ -114,28 +137,28 @@ index d872d5cbe..49daeb7d4 100644
|
||||
if err != nil {
|
||||
appendSubError(domain, err)
|
||||
continue
|
||||
@@ -433,12 +456,15 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
@@ -433,12 +461,15 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
}
|
||||
} else {
|
||||
// Validate that the domain is well-formed.
|
||||
- err := ValidNonWildcardDomain(domain)
|
||||
+ err := pa.ValidNonWildcardDomain(domain)
|
||||
+ err := pa.ValidNonWildcardDomain(domain, false)
|
||||
if err != nil {
|
||||
appendSubError(domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
+ if ok, _ := pa.checkWhitelist(domain); ok {
|
||||
+ if ok, _ := pa.checkWhitelist(domain, false); ok {
|
||||
+ return nil
|
||||
+ }
|
||||
// Require no match against hostname block lists
|
||||
err := pa.checkHostLists(domain)
|
||||
if err != nil {
|
||||
@@ -471,6 +497,31 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
@@ -471,6 +502,34 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+func (pa *AuthorityImpl) checkWhitelist(domain string) (bool, error) {
|
||||
+func (pa *AuthorityImpl) checkWhitelist(domain string, isContact bool) (bool, error) {
|
||||
+ pa.blocklistMu.RLock()
|
||||
+ defer pa.blocklistMu.RUnlock()
|
||||
+
|
||||
@@ -152,6 +175,9 @@ index d872d5cbe..49daeb7d4 100644
|
||||
+ }
|
||||
+
|
||||
+ 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 {
|
||||
@@ -163,3 +189,13 @@ index d872d5cbe..49daeb7d4 100644
|
||||
// checkWildcardHostList checks the wildcardExactBlocklist for a given domain.
|
||||
// If the domain is not present on the list nil is returned, otherwise
|
||||
// errPolicyForbidden is returned.
|
||||
@@ -500,6 +559,9 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error {
|
||||
labels := strings.Split(domain, ".")
|
||||
for i := range labels {
|
||||
joined := strings.Join(labels[i:], ".")
|
||||
+ if pa.lockdown[domain] {
|
||||
+ continue
|
||||
+ }
|
||||
if pa.blocklist[joined] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user