mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-03 05:07:38 +03:00
Proxy protocol support for SMTP and IMAP
This commit is contained in:
parent
f5def9cb04
commit
3c4fe105cd
8 changed files with 238 additions and 12 deletions
|
@ -40,6 +40,25 @@ tls cert.crt key.key {
|
|||
|
||||
See [TLS configuration / Server](/reference/tls/#server-side) for details.
|
||||
|
||||
**Syntax**: proxy_protocol _trusted ips..._ { ... } <br>
|
||||
**Default**: not enabled
|
||||
|
||||
Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols.
|
||||
If a list of trusted IP addresses or subnets is provided, only connections
|
||||
from those will be trusted.
|
||||
|
||||
TLS for the channel between the proxies and maddy can be configured
|
||||
using a 'tls' directive:
|
||||
```
|
||||
proxy_protocol {
|
||||
trust 127.0.0.1 ::1 192.168.0.1/24
|
||||
tls &proxy_tls
|
||||
}
|
||||
```
|
||||
Note that the top-level 'tls' directive is not inherited here. If you
|
||||
need TLS on top of the PROXY protocol, securing the protocol header,
|
||||
you must declare TLS explicitly.
|
||||
|
||||
**Syntax**: io\_debug _boolean_ <br>
|
||||
**Default**: no
|
||||
|
||||
|
|
|
@ -58,6 +58,21 @@ tls cert.crt key.key {
|
|||
|
||||
See [TLS configuration / Server](/reference/tls/#server-side) for details.
|
||||
|
||||
**Syntax**: proxy_protocol _trusted ips..._ { ... } <br>
|
||||
**Default**: not enabled
|
||||
|
||||
Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols.
|
||||
If a list of trusted IP addresses or subnets is provided, only connections
|
||||
from those will be trusted.
|
||||
|
||||
TLS for the channel between the proxies and maddy can be configured
|
||||
using a 'tls' directive:
|
||||
```
|
||||
proxy_protocol {
|
||||
trust 127.0.0.1 ::1 192.168.0.1/24
|
||||
tls &proxy_tls
|
||||
}
|
||||
```
|
||||
|
||||
**Syntax**: io\_debug _boolean_ <br>
|
||||
**Default**: no
|
||||
|
|
1
go.mod
1
go.mod
|
@ -73,6 +73,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/digitalocean/godo v1.96.0 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -236,6 +236,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
|
|||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34=
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320=
|
||||
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
|
||||
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
|
|
@ -44,14 +44,16 @@ import (
|
|||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/auth"
|
||||
"github.com/foxcpp/maddy/internal/authz"
|
||||
"github.com/foxcpp/maddy/internal/proxy_protocol"
|
||||
"github.com/foxcpp/maddy/internal/updatepipe"
|
||||
)
|
||||
|
||||
type Endpoint struct {
|
||||
addrs []string
|
||||
serv *imapserver.Server
|
||||
listeners []net.Listener
|
||||
Store module.Storage
|
||||
addrs []string
|
||||
serv *imapserver.Server
|
||||
listeners []net.Listener
|
||||
proxyProtocol *proxy_protocol.ProxyProtocol
|
||||
Store module.Storage
|
||||
|
||||
tlsConfig *tls.Config
|
||||
listenersWg sync.WaitGroup
|
||||
|
@ -90,6 +92,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error {
|
|||
})
|
||||
cfg.Custom("storage", false, true, nil, modconfig.StorageDirective, &endp.Store)
|
||||
cfg.Custom("tls", true, true, nil, tls2.TLSDirective, &endp.tlsConfig)
|
||||
cfg.Custom("proxy_protocol", false, false, nil, proxy_protocol.ProxyProtocolDirective, &endp.proxyProtocol)
|
||||
cfg.Bool("insecure_auth", false, false, &insecureAuth)
|
||||
cfg.Bool("io_debug", false, false, &ioDebug)
|
||||
cfg.Bool("io_errors", false, false, &ioErrors)
|
||||
|
@ -167,6 +170,10 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error {
|
|||
l = tls.NewListener(l, endp.tlsConfig)
|
||||
}
|
||||
|
||||
if endp.proxyProtocol != nil {
|
||||
l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log)
|
||||
}
|
||||
|
||||
endp.listeners = append(endp.listeners, l)
|
||||
|
||||
endp.listenersWg.Add(1)
|
||||
|
|
|
@ -46,18 +46,20 @@ import (
|
|||
"github.com/foxcpp/maddy/internal/authz"
|
||||
"github.com/foxcpp/maddy/internal/limits"
|
||||
"github.com/foxcpp/maddy/internal/msgpipeline"
|
||||
"github.com/foxcpp/maddy/internal/proxy_protocol"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
type Endpoint struct {
|
||||
saslAuth auth.SASLAuth
|
||||
serv *smtp.Server
|
||||
name string
|
||||
addrs []string
|
||||
listeners []net.Listener
|
||||
pipeline *msgpipeline.MsgPipeline
|
||||
resolver dns.Resolver
|
||||
limits *limits.Group
|
||||
saslAuth auth.SASLAuth
|
||||
serv *smtp.Server
|
||||
name string
|
||||
addrs []string
|
||||
listeners []net.Listener
|
||||
proxyProtocol *proxy_protocol.ProxyProtocol
|
||||
pipeline *msgpipeline.MsgPipeline
|
||||
resolver dns.Resolver
|
||||
limits *limits.Group
|
||||
|
||||
buffer func(r io.Reader) (buffer.Buffer, error)
|
||||
|
||||
|
@ -263,6 +265,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error {
|
|||
return autoBufferMode(1*1024*1024 /* 1 MiB */, path), nil
|
||||
}, bufferModeDirective, &endp.buffer)
|
||||
cfg.Custom("tls", true, endp.name != "lmtp", nil, tls2.TLSDirective, &endp.serv.TLSConfig)
|
||||
cfg.Custom("proxy_protocol", false, false, nil, proxy_protocol.ProxyProtocolDirective, &endp.proxyProtocol)
|
||||
cfg.Bool("insecure_auth", endp.name == "lmtp", false, &endp.serv.AllowInsecureAuth)
|
||||
cfg.Int("smtp_max_line_length", false, false, 4000, &endp.serv.MaxLineLength)
|
||||
cfg.Bool("io_debug", false, false, &ioDebug)
|
||||
|
@ -350,6 +353,10 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error {
|
|||
l = tls.NewListener(l, endp.serv.TLSConfig)
|
||||
}
|
||||
|
||||
if endp.proxyProtocol != nil {
|
||||
l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log)
|
||||
}
|
||||
|
||||
endp.listeners = append(endp.listeners, l)
|
||||
|
||||
endp.listenersWg.Add(1)
|
||||
|
|
86
internal/proxy_protocol/proxy_protocol.go
Normal file
86
internal/proxy_protocol/proxy_protocol.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package proxy_protocol
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/c0va23/go-proxyprotocol"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
)
|
||||
|
||||
type ProxyProtocol struct {
|
||||
trust []net.IPNet
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func ProxyProtocolDirective(_ *config.Map, node config.Node) (interface{}, error) {
|
||||
p := ProxyProtocol{}
|
||||
|
||||
childM := config.NewMap(nil, node)
|
||||
var trustList []string
|
||||
|
||||
childM.StringList("trust", false, false, nil, &trustList)
|
||||
childM.Custom("tls", true, false, nil, tls2.TLSDirective, &p.tlsConfig)
|
||||
|
||||
if _, err := childM.Process(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(node.Args) > 0 {
|
||||
if trustList == nil {
|
||||
trustList = make([]string, 0)
|
||||
}
|
||||
trustList = append(trustList, node.Args...)
|
||||
}
|
||||
|
||||
for _, trust := range trustList {
|
||||
if !strings.Contains(trust, "/") {
|
||||
trust += "/32"
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(trust)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.trust = append(p.trust, *ipNet)
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func NewListener(inner net.Listener, p *ProxyProtocol, logger log.Logger) net.Listener {
|
||||
var listener net.Listener
|
||||
|
||||
sourceChecker := func(upstream net.Addr) (bool, error) {
|
||||
if tcpAddr, ok := upstream.(*net.TCPAddr); ok {
|
||||
if len(p.trust) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
for _, trusted := range p.trust {
|
||||
if trusted.Contains(tcpAddr.IP) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
} else if _, ok := upstream.(*net.UnixAddr); ok {
|
||||
// UNIX local socket connection, always trusted
|
||||
return true, nil
|
||||
}
|
||||
|
||||
logger.Printf("proxy_protocol: connection from untrusted source %s", upstream)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
listener = proxyprotocol.NewDefaultListener(inner).
|
||||
WithLogger(proxyprotocol.LoggerFunc(func(format string, v ...interface{}) {
|
||||
logger.Debugf("proxy_protocol: "+format, v...)
|
||||
})).
|
||||
WithSourceChecker(sourceChecker)
|
||||
|
||||
if p.tlsConfig != nil {
|
||||
listener = tls.NewListener(listener, p.tlsConfig)
|
||||
}
|
||||
|
||||
return listener
|
||||
}
|
|
@ -23,6 +23,7 @@ package tests_test
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -68,6 +69,94 @@ func TestCheckRequireTLS(tt *testing.T) {
|
|||
conn.ExpectPattern("221 *")
|
||||
}
|
||||
|
||||
func TestProxyProtocolTrustedSource(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
t := tests.NewT(tt)
|
||||
t.DNS(map[string]mockdns.Zone{
|
||||
"one.maddy.test.": {
|
||||
TXT: []string{"v=spf1 ip4:127.0.0.17 -all"},
|
||||
},
|
||||
})
|
||||
t.Port("smtp")
|
||||
t.Config(`
|
||||
smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
|
||||
hostname mx.maddy.test
|
||||
tls off
|
||||
|
||||
proxy_protocol {
|
||||
trust ` + tests.DefaultSourceIP.String() + ` ::1/128
|
||||
tls off
|
||||
}
|
||||
|
||||
defer_sender_reject no
|
||||
|
||||
check {
|
||||
spf {
|
||||
enforce_early yes
|
||||
fail_action reject
|
||||
}
|
||||
}
|
||||
|
||||
deliver_to dummy
|
||||
}
|
||||
`)
|
||||
t.Run(1)
|
||||
defer t.Close()
|
||||
|
||||
conn := t.Conn("smtp")
|
||||
defer conn.Close()
|
||||
conn.Writeln(fmt.Sprintf("PROXY TCP4 127.0.0.17 %s 12345 %d", tests.DefaultSourceIP.String(), t.Port("smtp")))
|
||||
conn.SMTPNegotation("localhost", nil, nil)
|
||||
conn.Writeln("MAIL FROM:<testing@one.maddy.test>")
|
||||
conn.ExpectPattern("250 *")
|
||||
conn.Writeln("QUIT")
|
||||
conn.ExpectPattern("221 *")
|
||||
}
|
||||
|
||||
func TestProxyProtocolUntrustedSource(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
t := tests.NewT(tt)
|
||||
t.DNS(map[string]mockdns.Zone{
|
||||
"one.maddy.test.": {
|
||||
TXT: []string{"v=spf1 ip4:127.0.0.17 -all"},
|
||||
},
|
||||
})
|
||||
t.Port("smtp")
|
||||
t.Config(`
|
||||
smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
|
||||
hostname mx.maddy.test
|
||||
tls off
|
||||
|
||||
proxy_protocol {
|
||||
trust fe80::bad/128
|
||||
tls off
|
||||
}
|
||||
|
||||
defer_sender_reject no
|
||||
|
||||
check {
|
||||
spf {
|
||||
enforce_early yes
|
||||
fail_action reject
|
||||
}
|
||||
}
|
||||
|
||||
deliver_to dummy
|
||||
}
|
||||
`)
|
||||
t.Run(1)
|
||||
defer t.Close()
|
||||
|
||||
conn := t.Conn("smtp")
|
||||
defer conn.Close()
|
||||
conn.Writeln(fmt.Sprintf("PROXY TCP4 127.0.0.17 %s 12345 %d", tests.DefaultSourceIP.String(), t.Port("smtp")))
|
||||
conn.SMTPNegotation("localhost", nil, nil)
|
||||
conn.Writeln("MAIL FROM:<testing@one.maddy.test>")
|
||||
conn.ExpectPattern("550 *")
|
||||
conn.Writeln("QUIT")
|
||||
conn.ExpectPattern("221 *")
|
||||
}
|
||||
|
||||
func TestCheckSPF(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
t := tests.NewT(tt)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue