mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-10-12 02:12:14 +00:00
chore: Add upper bounds to supplied emails
This prevents users from abusing `--allowed-mail` policies by limiting the nr. of rules to 100 and defining a max per rule of 320 bytes. Validation errors now identify only the rule position and category, preventing recipient values from appearing in logs. Parsing also stops once limits are exceeded while preserving normalisation and deduplication.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package quicktunnelauth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"strings"
|
||||
@@ -8,33 +9,48 @@ import (
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
quickTunnelAuthMaxAllowedMailRules = 100
|
||||
quickTunnelAuthMaxAllowedMailRuleBytes = 320
|
||||
)
|
||||
|
||||
// validateQuickTunnelAllowedMail validates and normalizes exact email addresses and
|
||||
// wildcard domains from one or more comma-separated values.
|
||||
func validateQuickTunnelAllowedMail(values []string) (emails, wildcardDomains map[string]struct{}, err error) {
|
||||
if len(values) == 0 {
|
||||
return nil, nil, errors.New("allowed mail rule 1 is empty")
|
||||
}
|
||||
|
||||
emails, wildcardDomains = make(map[string]struct{}), make(map[string]struct{})
|
||||
for i, rawEntry := range strings.Split(strings.Join(values, ","), ",") {
|
||||
for ruleIndex, rawEntry := range strings.Split(strings.Join(values, ","), ",") {
|
||||
rulePosition := ruleIndex + 1
|
||||
if rulePosition > quickTunnelAuthMaxAllowedMailRules {
|
||||
return nil, nil, fmt.Errorf("allowed mail rules exceed the %d-entry limit", quickTunnelAuthMaxAllowedMailRules)
|
||||
}
|
||||
if len(rawEntry) > quickTunnelAuthMaxAllowedMailRuleBytes {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"allowed mail rule %d exceeds the %d-byte limit",
|
||||
rulePosition,
|
||||
quickTunnelAuthMaxAllowedMailRuleBytes,
|
||||
)
|
||||
}
|
||||
|
||||
entry := normalizeQuickTunnelEmail(rawEntry)
|
||||
domain, isWildcard := strings.CutPrefix(entry, "*@")
|
||||
|
||||
switch {
|
||||
case entry == "":
|
||||
return nil, nil, fmt.Errorf("allowed mail rule %d is empty", i+1)
|
||||
return nil, nil, fmt.Errorf("allowed mail rule %d is empty", rulePosition)
|
||||
|
||||
case isWildcard:
|
||||
if !isValidQuickTunnelEmailDomain(domain) {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"allowed mail rule %q has an invalid wildcard domain",
|
||||
rawEntry,
|
||||
)
|
||||
return nil, nil, fmt.Errorf("allowed mail rule %d has an invalid wildcard domain", rulePosition)
|
||||
}
|
||||
wildcardDomains[domain] = struct{}{}
|
||||
|
||||
default:
|
||||
if !isValidQuickTunnelEmail(entry) {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"allowed mail rule %q is not a valid email address",
|
||||
rawEntry,
|
||||
)
|
||||
return nil, nil, fmt.Errorf("allowed mail rule %d is not a valid email address", rulePosition)
|
||||
}
|
||||
emails[entry] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package quicktunnelauth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -96,10 +97,45 @@ func TestValidateQuickTunnelAllowedMailReportsInvalidRule(t *testing.T) {
|
||||
values []string
|
||||
expectedError string
|
||||
}{
|
||||
{name: "email", values: []string{"[email protected]", "[email protected]/path"}, expectedError: `allowed mail rule "[email protected]/path" is not a valid email address`},
|
||||
{name: "wildcard", values: []string{"[email protected]", "*@example.com/path"}, expectedError: `allowed mail rule "*@example.com/path" has an invalid wildcard domain`},
|
||||
{name: "email", values: []string{"[email protected]", "[email protected]/path"}, expectedError: "allowed mail rule 2 is not a valid email address"},
|
||||
{name: "wildcard", values: []string{"[email protected]", "*@example.com/path"}, expectedError: "allowed mail rule 2 has an invalid wildcard domain"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, _, err := validateQuickTunnelAllowedMail(test.values)
|
||||
require.EqualError(t, err, test.expectedError)
|
||||
for _, value := range test.values {
|
||||
assert.NotContains(t, err.Error(), value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateQuickTunnelAllowedMailBoundsInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tooManyRules := make([]string, quickTunnelAuthMaxAllowedMailRules+1)
|
||||
for index := range tooManyRules {
|
||||
tooManyRules[index] = "[email protected]"
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
values []string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "too many rules",
|
||||
values: tooManyRules,
|
||||
expectedError: fmt.Sprintf("allowed mail rules exceed the %d-entry limit", quickTunnelAuthMaxAllowedMailRules),
|
||||
},
|
||||
{
|
||||
name: "rule too long before trimming",
|
||||
values: []string{strings.Repeat(" ", quickTunnelAuthMaxAllowedMailRuleBytes) + "a"},
|
||||
expectedError: fmt.Sprintf("allowed mail rule 1 exceeds the %d-byte limit", quickTunnelAuthMaxAllowedMailRuleBytes),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Reference in New Issue
Block a user