Upgrading to GitLab 12.5.4.

Commit 23deaf1f authored by Alberto Bertogli's avatar Alberto Bertogli

Reinstate the MTA-STS (Strict Transport Security) implementation

This commit brings back the experimental MTA-STS (Strict Transport
Security) implementation, removed in commit
7f5bedf4.

We will continue development in the "sts" branch, subject to rebase,
until it is ready to be integrated into "next" again.
parent a94253ba
......@@ -7,6 +7,7 @@
package main
import (
"context"
"expvar"
"flag"
"fmt"
......@@ -25,6 +26,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/normalize"
"blitiri.com.ar/go/chasquid/internal/smtpsrv"
"blitiri.com.ar/go/chasquid/internal/sts"
"blitiri.com.ar/go/chasquid/internal/userdb"
"blitiri.com.ar/go/log"
"blitiri.com.ar/go/systemd"
......@@ -146,12 +148,18 @@ func main() {
dinfo := s.InitDomainInfo(conf.DataDir + "/domaininfo")
stsCache, err := sts.NewCache(conf.DataDir + "/sts-cache")
if err != nil {
log.Fatalf("Failed to initialize STS cache: %v", err)
}
go stsCache.PeriodicallyRefresh(context.Background())
localC := &courier.Procmail{
Binary: conf.MailDeliveryAgentBin,
Args: conf.MailDeliveryAgentArgs,
Timeout: 30 * time.Second,
}
remoteC := &courier.SMTP{Dinfo: dinfo}
remoteC := &courier.SMTP{Dinfo: dinfo, STSCache: stsCache}
s.InitQueue(conf.DataDir+"/queue", localC, remoteC)
// Load the addresses and listeners.
......
......@@ -5,12 +5,15 @@
package main
import (
"context"
"crypto/tls"
"flag"
"log"
"net"
"net/smtp"
"time"
"blitiri.com.ar/go/chasquid/internal/sts"
"blitiri.com.ar/go/chasquid/internal/tlsconst"
"blitiri.com.ar/go/spf"
......@@ -37,6 +40,21 @@ func main() {
log.Fatalf("IDNA conversion failed: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
log.Printf("=== STS policy")
policy, err := sts.UncheckedFetch(ctx, domain)
if err != nil {
log.Printf("Not available (%s)", err)
} else {
log.Printf("Parsed contents: [%+v]\n", *policy)
if err := policy.Check(); err != nil {
log.Fatalf("Invalid: %v", err)
}
log.Printf("OK")
}
mxs, err := net.LookupMX(domain)
if err != nil {
log.Fatalf("MX lookup: %v", err)
......@@ -86,6 +104,13 @@ func main() {
c.Close()
}
if policy != nil {
if !policy.MXIsAllowed(mx.Host) {
log.Fatalf("NOT allowed by STS policy")
}
log.Printf("Allowed by policy")
}
log.Printf("")
}
......
package courier
import (
"context"
"crypto/tls"
"expvar"
"flag"
......@@ -13,6 +14,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/domaininfo"
"blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/smtp"
"blitiri.com.ar/go/chasquid/internal/sts"
"blitiri.com.ar/go/chasquid/internal/trace"
)
......@@ -30,17 +32,26 @@ var (
// TODO: replace this with proper lookup interception once it is supported
// by Go.
netLookupMX = net.LookupMX
// Enable STS policy checking; this is an experimental flag and will be
// removed in the future, once this is made the default.
enableSTS = flag.Bool("experimental__enable_sts", false,
"enable STS policy checking; EXPERIMENTAL")
)
// Exported variables.
var (
tlsCount = expvar.NewMap("chasquid/smtpOut/tlsCount")
slcResults = expvar.NewMap("chasquid/smtpOut/securityLevelChecks")
stsSecurityModes = expvar.NewMap("chasquid/smtpOut/sts/mode")
stsSecurityResults = expvar.NewMap("chasquid/smtpOut/sts/security")
)
// SMTP delivers remote mail via outgoing SMTP.
type SMTP struct {
Dinfo *domaininfo.DB
Dinfo *domaininfo.DB
STSCache *sts.PolicyCache
}
// Deliver an email. On failures, returns an error, and whether or not it is
......@@ -62,7 +73,9 @@ func (s *SMTP) Deliver(from string, to string, data []byte) (error, bool) {
a.from = ""
}
mxs, err := lookupMXs(a.tr, a.toDomain)
a.stsPolicy = s.fetchSTSPolicy(a.tr, a.toDomain)
mxs, err := lookupMXs(a.tr, a.toDomain, a.stsPolicy)
if err != nil || len(mxs) == 0 {
// Note this is considered a permanent error.
// This is in line with what other servers (Exim) do. However, the
......@@ -108,6 +121,8 @@ type attempt struct {
toDomain string
helloDomain string
stsPolicy *sts.Policy
tr *trace.Trace
}
......@@ -175,6 +190,18 @@ retry:
}
slcResults.Add("pass", 1)
if a.stsPolicy != nil && a.stsPolicy.Mode == sts.Enforce {
// The connection MUST be validated TLS.
// https://tools.ietf.org/html/draft-ietf-uta-mta-sts-03#section-4.2
if secLevel != domaininfo.SecLevel_TLS_SECURE {
stsSecurityResults.Add("fail", 1)
return a.tr.Errorf("invalid security level (%v) for STS policy",
secLevel), false
}
stsSecurityResults.Add("pass", 1)
a.tr.Debugf("STS policy: connection is using valid TLS")
}
if err = c.MailAndRcpt(a.from, a.to); err != nil {
return a.tr.Errorf("MAIL+RCPT %v", err), smtp.IsPermanent(err)
}
......@@ -199,7 +226,29 @@ retry:
return nil, false
}
func lookupMXs(tr *trace.Trace, domain string) ([]string, error) {
func (s *SMTP) fetchSTSPolicy(tr *trace.Trace, domain string) *sts.Policy {
if !*enableSTS {
return nil
}
if s.STSCache == nil {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
policy, err := s.STSCache.Fetch(ctx, domain)
if err != nil {
return nil
}
tr.Debugf("got STS policy")
stsSecurityModes.Add(string(policy.Mode), 1)
return policy
}
func lookupMXs(tr *trace.Trace, domain string, policy *sts.Policy) ([]string, error) {
domain, err := idna.ToASCII(domain)
if err != nil {
return nil, err
......@@ -239,12 +288,39 @@ func lookupMXs(tr *trace.Trace, domain string) ([]string, error) {
// This case is explicitly covered by the SMTP RFC.
// https://tools.ietf.org/html/rfc5321#section-5.1
// Cap the list of MXs to 5 hosts, to keep delivery attempt times sane
// and prevent abuse.
if len(mxs) > 5 {
mxs = filterMXs(tr, policy, mxs)
if len(mxs) == 0 {
tr.Errorf("domain %q has no valid MX/A record", domain)
} else if len(mxs) > 5 {
// Cap the list of MXs to 5 hosts, to keep delivery attempt times
// sane and prevent abuse.
mxs = mxs[:5]
}
tr.Debugf("MXs: %v", mxs)
return mxs, nil
}
func filterMXs(tr *trace.Trace, p *sts.Policy, mxs []string) []string {
if p == nil {
return mxs
}
filtered := []string{}
for _, mx := range mxs {
if p.MXIsAllowed(mx) {
filtered = append(filtered, mx)
} else {
tr.Printf("MX %q not allowed by policy, skipping", mx)
}
}
// We don't want to return an empty set if the mode is not enforce.
// This prevents failures for policies in reporting mode.
// https://tools.ietf.org/html/draft-ietf-uta-mta-sts-03#section-5.2
if len(filtered) == 0 && p.Mode != sts.Enforce {
filtered = mxs
}
return filtered
}
......@@ -35,7 +35,7 @@ func newSMTP(t *testing.T) (*SMTP, string) {
t.Fatal(err)
}
return &SMTP{dinfo}, dir
return &SMTP{dinfo, nil}, dir
}
// Fake server, to test SMTP out.
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment