Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds an option to make DKIM validation optional #472

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions helpers/inbound/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,37 @@ func (email *ParsedEmail) parseHeaders(headers string) {
}
}

// ValidateConfig adds configuration options for validating a parsed email. Specifically turning DKIM as optional. DKIM validates emails in transit from tampering. However many valid email clients
// do not have it configured
// SPF could potentially be added but it poses a more significant security risk
type ValidateConfig struct {
dkimOptional bool
}

// NewValidateConfig returns a configuration struct for validating the parsed email. Specifically turning DKIM as optional. DKIM validates emails in transit from tampering. However many valid email clients
// do not have it configured
func NewValidateConfig() ValidateConfig {
return ValidateConfig{}
}

// WithDKIMOptional validates DKIM values only if they exist. This allows the inbound client to support more email clients that might not support this configuration
func (vc ValidateConfig) WithDKIMOptional() ValidateConfig {
vc.dkimOptional = true
return vc
}

// Validate validates the DKIM and SPF scores to ensure that the email client and address was not spoofed
func (email *ParsedEmail) Validate() error {
if len(email.rawValues["dkim"]) == 0 || len(email.rawValues["SPF"]) == 0 {
return fmt.Errorf("missing DKIM and SPF score")
func (email *ParsedEmail) Validate(config ...ValidateConfig) error {
var dkimOptional bool
if len(config) > 0 {
dkimOptional = config[0].dkimOptional
}
if len(email.rawValues["SPF"]) == 0 {
return fmt.Errorf("missing SPF score")
}

if !dkimOptional && len(email.rawValues["dkim"]) == 0 {
return fmt.Errorf("missing DKIM score")
}

for _, val := range email.rawValues["dkim"] {
Expand Down
27 changes: 24 additions & 3 deletions helpers/inbound/inbound_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,18 @@ func TestValidate(t *testing.T) {
name string
values map[string][]string
expectedError error
config ValidateConfig
}{
{
name: "MissingHeaders",
name: "MissingSPFHeaders",
values: map[string][]string{},
expectedError: fmt.Errorf("missing DKIM and SPF score"),
expectedError: fmt.Errorf("missing SPF score"),
},
{
name: "MissingDKIMHeaders",
values: map[string][]string{"SPF": {"pass"}},
expectedError: fmt.Errorf("missing DKIM score"),
}, {
name: "FailedDkim",
values: map[string][]string{"dkim": {"pass", "fail", "pass"}, "SPF": {"pass"}},
expectedError: fmt.Errorf("DKIM validation failed"),
Expand All @@ -161,13 +166,29 @@ func TestValidate(t *testing.T) {
name: "success",
values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}},
},
{
name: "dkimOptionalAndPresent",
values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}},
config: NewValidateConfig().WithDKIMOptional(),
},
{
name: "dkimOptionalWithFailure",
values: map[string][]string{"dkim": {"fail", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}},
config: NewValidateConfig().WithDKIMOptional(),
expectedError: fmt.Errorf("DKIM validation failed"),
},
{
name: "dkimOptionalAndAbsent",
values: map[string][]string{"SPF": {"pass", "pass", "pass"}},
config: NewValidateConfig().WithDKIMOptional(),
},
}

for _, test := range tests {
t.Run(test.name, func(subTest *testing.T) {
//Load POST body
email := ParsedEmail{rawValues: test.values}
err := email.Validate()
err := email.Validate(test.config)

if test.expectedError != nil {
assert.EqualError(subTest, test.expectedError, err.Error())
Expand Down
Loading