mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 13:37:41 +03:00
Migrate TLS certificate loading to use modules for sources
This commit is contained in:
parent
6c0b947464
commit
cee8bbdce7
14 changed files with 387 additions and 128 deletions
|
@ -4,17 +4,47 @@ maddy-tls(5) "maddy mail server" "maddy reference documentation"
|
|||
|
||||
# TLS server configuration
|
||||
|
||||
You can specify other TLS-related options in a configuration block for 'tls'
|
||||
directive:
|
||||
TLS certificates are obtained by modules called "certificate loaders". 'tls' directive
|
||||
arguments specify name of loader to use and arguments. Due to syntax limitations
|
||||
advanced configuration for loader should be specified using 'loader' directive, see
|
||||
below.
|
||||
|
||||
```
|
||||
tls cert.pem cert.pem {
|
||||
protocols tls1.2 tls1.3
|
||||
curve X25519
|
||||
ciphers ...
|
||||
tls file cert.pem key.pem {
|
||||
protocols tls1.2 tls1.3
|
||||
curve X25519
|
||||
ciphers ...
|
||||
}
|
||||
|
||||
tls {
|
||||
loader file cert.pem key.pem {
|
||||
# Options for loader go here.
|
||||
}
|
||||
protocols tls1.2 tls1.3
|
||||
curve X25519
|
||||
ciphers ...
|
||||
}
|
||||
```
|
||||
|
||||
## Available certificate loaders
|
||||
|
||||
- file
|
||||
|
||||
Accepts argument pairs specifying certificate and then key.
|
||||
E.g. 'tls file certA.pem keyA.pem certB.pem keyB.pem'
|
||||
|
||||
If multiple certificates are listed, SNI will be used.
|
||||
|
||||
- off
|
||||
|
||||
Not really a loader but a special value for tls directive, explicitly disables TLS for
|
||||
endpoint(s).
|
||||
|
||||
## Advanced TLS configuration
|
||||
|
||||
*Note: maddy uses secure defaults and TLS handshake is resistant to active downgrade attacks.*
|
||||
*There is no need to change anything in most cases.*
|
||||
|
||||
*Syntax*: ++
|
||||
protocols _min_version_ _max_version_ ++
|
||||
protocols _version_ ++
|
||||
|
|
|
@ -119,8 +119,8 @@ Domain that is used in From field for auto-generated messages (such as Delivery
|
|||
Status Notifications).
|
||||
|
||||
*Syntax*: ++
|
||||
tls _cert_file_ _pkey_file_ ++
|
||||
tls self_signed ++
|
||||
tls file _cert_file_ _pkey_file_ ++
|
||||
tls _module reference_ ++
|
||||
tls off ++
|
||||
*Default*: not specified
|
||||
|
||||
|
@ -129,11 +129,8 @@ Default TLS certificate to use for all endpoints.
|
|||
Must be present in either all endpoint modules configuration blocks or as
|
||||
global directive.
|
||||
|
||||
Use of 'self_signed' generates temporary self-signed certificate, this useful
|
||||
for testing but should be used only for it.
|
||||
|
||||
You can also specify other configuration options such as cipher suites and TLS
|
||||
version. See TLS server configuration for details. maddy uses reasonable
|
||||
version. See maddy-tls(5) for details. maddy uses reasonable
|
||||
cipher suites and TLS versions by default so you generally don't have to worry
|
||||
about it.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package config
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
@ -6,13 +6,14 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
)
|
||||
|
||||
func TLSClientBlock(m *Map, node Node) (interface{}, error) {
|
||||
func TLSClientBlock(m *config.Map, node config.Node) (interface{}, error) {
|
||||
cfg := tls.Config{}
|
||||
|
||||
childM := NewMap(nil, node)
|
||||
childM := config.NewMap(nil, node)
|
||||
var (
|
||||
tlsVersions [2]uint16
|
||||
rootCAPaths []string
|
|
@ -1,8 +1,9 @@
|
|||
package config
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
)
|
||||
|
||||
|
@ -51,26 +52,26 @@ var strCurvesMap = map[string]tls.CurveID{
|
|||
// minimum and maximum supported TLS versions.
|
||||
//
|
||||
// It returns [2]uint16 value for use in corresponding fields from tls.Config.
|
||||
func TLSVersionsDirective(m *Map, node Node) (interface{}, error) {
|
||||
func TLSVersionsDirective(m *config.Map, node config.Node) (interface{}, error) {
|
||||
switch len(node.Args) {
|
||||
case 1:
|
||||
value, ok := strVersionsMap[node.Args[0]]
|
||||
if !ok {
|
||||
return nil, NodeErr(node, "invalid TLS version value: %s", node.Args[0])
|
||||
return nil, config.NodeErr(node, "invalid TLS version value: %s", node.Args[0])
|
||||
}
|
||||
return [2]uint16{value, value}, nil
|
||||
case 2:
|
||||
minValue, ok := strVersionsMap[node.Args[0]]
|
||||
if !ok {
|
||||
return nil, NodeErr(node, "invalid TLS version value: %s", node.Args[0])
|
||||
return nil, config.NodeErr(node, "invalid TLS version value: %s", node.Args[0])
|
||||
}
|
||||
maxValue, ok := strVersionsMap[node.Args[1]]
|
||||
if !ok {
|
||||
return nil, NodeErr(node, "invalid TLS version value: %s", node.Args[1])
|
||||
return nil, config.NodeErr(node, "invalid TLS version value: %s", node.Args[1])
|
||||
}
|
||||
return [2]uint16{minValue, maxValue}, nil
|
||||
default:
|
||||
return nil, NodeErr(node, "expected 1 or 2 arguments")
|
||||
return nil, config.NodeErr(node, "expected 1 or 2 arguments")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,16 +79,16 @@ func TLSVersionsDirective(m *Map, node Node) (interface{}, error) {
|
|||
// list of ciphers to offer to clients (or to use for outgoing connections).
|
||||
//
|
||||
// It returns list of []uint16 with corresponding cipher IDs.
|
||||
func TLSCiphersDirective(m *Map, node Node) (interface{}, error) {
|
||||
func TLSCiphersDirective(m *config.Map, node config.Node) (interface{}, error) {
|
||||
if len(node.Args) == 0 {
|
||||
return nil, NodeErr(node, "expected at least 1 argument, got 0")
|
||||
return nil, config.NodeErr(node, "expected at least 1 argument, got 0")
|
||||
}
|
||||
|
||||
res := make([]uint16, 0, len(node.Args))
|
||||
for _, arg := range node.Args {
|
||||
cipherId, ok := strCiphersMap[arg]
|
||||
if !ok {
|
||||
return nil, NodeErr(node, "unknown cipher: %s", arg)
|
||||
return nil, config.NodeErr(node, "unknown cipher: %s", arg)
|
||||
}
|
||||
res = append(res, cipherId)
|
||||
}
|
||||
|
@ -99,16 +100,16 @@ func TLSCiphersDirective(m *Map, node Node) (interface{}, error) {
|
|||
// elliptic curves to use during TLS key exchange.
|
||||
//
|
||||
// It returns []tls.CurveID.
|
||||
func TLSCurvesDirective(m *Map, node Node) (interface{}, error) {
|
||||
func TLSCurvesDirective(m *config.Map, node config.Node) (interface{}, error) {
|
||||
if len(node.Args) == 0 {
|
||||
return nil, NodeErr(node, "expected at least 1 argument, got 0")
|
||||
return nil, config.NodeErr(node, "expected at least 1 argument, got 0")
|
||||
}
|
||||
|
||||
res := make([]tls.CurveID, 0, len(node.Args))
|
||||
for _, arg := range node.Args {
|
||||
curveId, ok := strCurvesMap[arg]
|
||||
if !ok {
|
||||
return nil, NodeErr(node, "unknown curve: %s", arg)
|
||||
return nil, config.NodeErr(node, "unknown curve: %s", arg)
|
||||
}
|
||||
res = append(res, curveId)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package config
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
@ -10,68 +10,33 @@ import (
|
|||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/hooks"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type TLSConfig struct {
|
||||
initCfg Node
|
||||
|
||||
l sync.Mutex
|
||||
cfg *tls.Config
|
||||
loader module.TLSLoader
|
||||
baseCfg tls.Config
|
||||
}
|
||||
|
||||
func (cfg *TLSConfig) Get() *tls.Config {
|
||||
cfg.l.Lock()
|
||||
defer cfg.l.Unlock()
|
||||
if cfg.cfg == nil {
|
||||
return nil
|
||||
func (cfg *TLSConfig) Get() (*tls.Config, error) {
|
||||
if cfg.loader == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return cfg.cfg.Clone()
|
||||
}
|
||||
tlsCfg := cfg.baseCfg.Clone()
|
||||
|
||||
func (cfg *TLSConfig) read(m *Map, node Node, generateSelfSig bool) error {
|
||||
cfg.l.Lock()
|
||||
defer cfg.l.Unlock()
|
||||
|
||||
switch len(node.Args) {
|
||||
case 1:
|
||||
switch node.Args[0] {
|
||||
case "off":
|
||||
cfg.cfg = nil
|
||||
return nil
|
||||
case "self_signed":
|
||||
if !generateSelfSig {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsCfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS10,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
if err := makeSelfSignedCert(tlsCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("tls: using self-signed certificate, this is not secure!")
|
||||
cfg.cfg = tlsCfg
|
||||
return nil
|
||||
default:
|
||||
log.Println(node.Name, node.Args)
|
||||
return NodeErr(node, "unexpected argument (%s), want 'off' or 'self_signed'", node.Args[0])
|
||||
}
|
||||
case 2:
|
||||
tlsCfg, err := readTLSBlock(m, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.cfg = tlsCfg
|
||||
return nil
|
||||
default:
|
||||
return NodeErr(node, "expected 1 or 2 arguments")
|
||||
certs, err := cfg.loader.LoadCerts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsCfg.Certificates = certs
|
||||
|
||||
return tlsCfg, nil
|
||||
}
|
||||
|
||||
// TLSDirective reads the TLS configuration and adds the reload handler to
|
||||
|
@ -79,55 +44,54 @@ func (cfg *TLSConfig) read(m *Map, node Node, generateSelfSig bool) error {
|
|||
//
|
||||
// The returned value is *tls.TLSConfig with GetConfigForClient set.
|
||||
// If the 'tls off' is used, returned value is nil.
|
||||
func TLSDirective(m *Map, node Node) (interface{}, error) {
|
||||
cfg := TLSConfig{
|
||||
initCfg: node,
|
||||
}
|
||||
if err := cfg.read(m, node, true); err != nil {
|
||||
func TLSDirective(m *config.Map, node config.Node) (interface{}, error) {
|
||||
cfg, err := readTLSBlock(m.Globals, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hooks.AddHook(hooks.EventReload, func() {
|
||||
log.Debugln("tls: reloading certificates")
|
||||
if err := cfg.read(NewMap(nil, cfg.initCfg), cfg.initCfg, false); err != nil {
|
||||
log.DefaultLogger.Error("tls: failed to load new certs", err)
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
t := time.NewTicker(1 * time.Minute)
|
||||
for range t.C {
|
||||
log.Debugln("tls: reloading certificates")
|
||||
if err := cfg.read(NewMap(nil, cfg.initCfg), cfg.initCfg, false); err != nil {
|
||||
log.DefaultLogger.Error("tls: failed to load new certs", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Return nil so callers can check whether TLS is enabled easier.
|
||||
if cfg.cfg == nil {
|
||||
if cfg.loader == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
return cfg.Get(), nil
|
||||
return cfg.Get()
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readTLSBlock(m *Map, blockNode Node) (*tls.Config, error) {
|
||||
cfg := tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
func readTLSBlock(globals map[string]interface{}, blockNode config.Node) (*TLSConfig, error) {
|
||||
baseCfg := tls.Config{}
|
||||
|
||||
var loader module.TLSLoader
|
||||
if len(blockNode.Args) > 0 {
|
||||
if blockNode.Args[0] == "off" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(blockNode.Args[0]); err == nil || strings.Contains(blockNode.Args[0], "/") {
|
||||
log.Println("'tls cert_path key_path' syntax is deprecated, use 'tls file cert_path key_path'")
|
||||
blockNode.Args = append([]string{"file"}, blockNode.Args...)
|
||||
}
|
||||
|
||||
err := modconfig.ModuleFromNode("tls.loader", blockNode.Args, config.Node{}, globals, &loader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
childM := NewMap(nil, blockNode)
|
||||
childM := config.NewMap(globals, blockNode)
|
||||
var tlsVersions [2]uint16
|
||||
|
||||
if len(blockNode.Args) != 2 {
|
||||
return nil, NodeErr(blockNode, "two arguments required")
|
||||
}
|
||||
certPath := blockNode.Args[0]
|
||||
keyPath := blockNode.Args[1]
|
||||
childM.Custom("loader", false, false, func() (interface{}, error) {
|
||||
return loader, nil
|
||||
}, func(m *config.Map, node config.Node) (interface{}, error) {
|
||||
var l module.TLSLoader
|
||||
err := modconfig.ModuleFromNode("tls.loader", blockNode.Args, config.Node{}, globals, &l)
|
||||
return l, err
|
||||
}, &loader)
|
||||
|
||||
childM.Custom("protocols", false, false, func() (interface{}, error) {
|
||||
return [2]uint16{0, 0}, nil
|
||||
|
@ -135,29 +99,28 @@ func readTLSBlock(m *Map, blockNode Node) (*tls.Config, error) {
|
|||
|
||||
childM.Custom("ciphers", false, false, func() (interface{}, error) {
|
||||
return nil, nil
|
||||
}, TLSCiphersDirective, &cfg.CipherSuites)
|
||||
}, TLSCiphersDirective, &baseCfg.CipherSuites)
|
||||
|
||||
childM.Custom("curves", false, false, func() (interface{}, error) {
|
||||
return nil, nil
|
||||
}, TLSCurvesDirective, &cfg.CurvePreferences)
|
||||
}, TLSCurvesDirective, &baseCfg.CurvePreferences)
|
||||
|
||||
if _, err := childM.Process(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(baseCfg.CipherSuites) != 0 {
|
||||
baseCfg.PreferServerCipherSuites = true
|
||||
}
|
||||
|
||||
log.Debugf("tls: using %s : %s", certPath, keyPath)
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
|
||||
cfg.MinVersion = tlsVersions[0]
|
||||
cfg.MaxVersion = tlsVersions[1]
|
||||
baseCfg.MinVersion = tlsVersions[0]
|
||||
baseCfg.MaxVersion = tlsVersions[1]
|
||||
log.Debugf("tls: min version: %x, max version: %x", tlsVersions[0], tlsVersions[1])
|
||||
|
||||
return &cfg, nil
|
||||
return &TLSConfig{
|
||||
loader: loader,
|
||||
baseCfg: baseCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeSelfSignedCert(config *tls.Config) error {
|
21
framework/module/tls_loader.go
Normal file
21
framework/module/tls_loader.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package module
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// TLSLoader interface is module interface that can be used to supply TLS
|
||||
// certificates to TLS-enabled endpoints.
|
||||
//
|
||||
// The interface is intentionally kept simple, all configuration and parameters
|
||||
// necessary are to be provided using conventional module configuration.
|
||||
//
|
||||
// If loader returns multiple certificate chains - endpoint will serve them
|
||||
// based on SNI matching.
|
||||
//
|
||||
// Note that loading function will be called for each connections - it is
|
||||
// highly recommended to cache parsed form.
|
||||
//
|
||||
// Modules implementing this interface should be registered with prefix
|
||||
// "tls.loader." in name.
|
||||
type TLSLoader interface {
|
||||
LoadCerts() ([]tls.Certificate, error)
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
|
@ -74,7 +75,7 @@ func (c *Check) Init(cfg *config.Map) error {
|
|||
|
||||
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
|
||||
return tls.Config{}, nil
|
||||
}, config.TLSClientBlock, &tlsConfig)
|
||||
}, tls2.TLSClientBlock, &tlsConfig)
|
||||
cfg.String("api_path", false, false, c.apiPath, &c.apiPath)
|
||||
cfg.String("settings_id", false, false, "", &c.settingsID)
|
||||
cfg.String("tag", false, false, "maddy", &c.tag)
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/foxcpp/go-imap-sql/children"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/auth"
|
||||
|
@ -68,7 +69,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error {
|
|||
return endp.saslAuth.AddProvider(m, node)
|
||||
})
|
||||
cfg.Custom("storage", false, true, nil, modconfig.StorageDirective, &endp.Store)
|
||||
cfg.Custom("tls", true, true, nil, config.TLSDirective, &endp.tlsConfig)
|
||||
cfg.Custom("tls", true, true, nil, tls2.TLSDirective, &endp.tlsConfig)
|
||||
cfg.Bool("insecure_auth", false, false, &insecureAuth)
|
||||
cfg.Bool("io_debug", false, false, &ioDebug)
|
||||
cfg.Bool("io_errors", false, false, &ioErrors)
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/dns"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/future"
|
||||
|
@ -224,7 +225,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, config.TLSDirective, &endp.serv.TLSConfig)
|
||||
cfg.Custom("tls", true, endp.name != "lmtp", nil, tls2.TLSDirective, &endp.serv.TLSConfig)
|
||||
cfg.Bool("insecure_auth", endp.name == "lmtp", false, &endp.serv.AllowInsecureAuth)
|
||||
cfg.Bool("io_debug", false, false, &ioDebug)
|
||||
cfg.Bool("debug", true, false, &endp.Log.Debug)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/dns"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
|
@ -86,7 +87,7 @@ func (rt *Target) Init(cfg *config.Map) error {
|
|||
cfg.Bool("debug", true, false, &rt.Log.Debug)
|
||||
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
|
||||
return &tls.Config{}, nil
|
||||
}, config.TLSClientBlock, &rt.tlsConfig)
|
||||
}, tls2.TLSClientBlock, &rt.tlsConfig)
|
||||
cfg.Custom("mx_auth", false, false, func() (interface{}, error) {
|
||||
// Default is "no policies" to follow the principles of explicit
|
||||
// configuration (if it is not requested - it is not done).
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/emersion/go-smtp"
|
||||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
|
@ -82,7 +83,7 @@ func (u *Downstream) Init(cfg *config.Map) error {
|
|||
}, saslAuthDirective, &u.saslFactory)
|
||||
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
|
||||
return tls.Config{}, nil
|
||||
}, config.TLSClientBlock, &u.tlsConfig)
|
||||
}, tls2.TLSClientBlock, &u.tlsConfig)
|
||||
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
|
|
147
internal/tls/file.go
Normal file
147
internal/tls/file.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/hooks"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type FileLoader struct {
|
||||
instName string
|
||||
inlineArgs []string
|
||||
certPaths []string
|
||||
keyPaths []string
|
||||
log log.Logger
|
||||
|
||||
certs []tls.Certificate
|
||||
certsLock sync.RWMutex
|
||||
|
||||
reloadTick *time.Ticker
|
||||
stopTick chan struct{}
|
||||
}
|
||||
|
||||
func NewFileLoader(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
return &FileLoader{
|
||||
instName: instName,
|
||||
inlineArgs: inlineArgs,
|
||||
log: log.Logger{Name: "tls.loader.file", Debug: log.DefaultLogger.Debug},
|
||||
stopTick: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *FileLoader) Init(cfg *config.Map) error {
|
||||
cfg.StringList("certs", false, false, nil, &f.certPaths)
|
||||
cfg.StringList("keys", false, false, nil, &f.keyPaths)
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(f.certPaths) != len(f.keyPaths) {
|
||||
return errors.New("tls.loader.file: mismatch in certs and keys count")
|
||||
}
|
||||
|
||||
if len(f.inlineArgs)%2 != 0 {
|
||||
return errors.New("tls.loader.file: odd amount of arguments")
|
||||
}
|
||||
for i := 0; i < len(f.inlineArgs); i += 2 {
|
||||
f.certPaths = append(f.certPaths, f.inlineArgs[i])
|
||||
f.keyPaths = append(f.keyPaths, f.inlineArgs[i+1])
|
||||
}
|
||||
|
||||
for _, certPath := range f.certPaths {
|
||||
if !filepath.IsAbs(certPath) {
|
||||
return fmt.Errorf("tls.loader.file: only absolute paths allowed in certificate paths: sorry :(")
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.loadCerts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hooks.AddHook(hooks.EventReload, func() {
|
||||
f.log.Println("reloading certificates")
|
||||
if err := f.loadCerts(); err != nil {
|
||||
f.log.Error("reload failed", err)
|
||||
}
|
||||
})
|
||||
|
||||
f.reloadTick = time.NewTicker(time.Minute)
|
||||
go f.reloadTicker()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLoader) Close() error {
|
||||
f.reloadTick.Stop()
|
||||
f.stopTick <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLoader) Name() string {
|
||||
return "tls.loader.file"
|
||||
}
|
||||
|
||||
func (f *FileLoader) InstanceName() string {
|
||||
return f.instName
|
||||
}
|
||||
|
||||
func (f *FileLoader) reloadTicker() {
|
||||
for {
|
||||
select {
|
||||
case <-f.reloadTick.C:
|
||||
f.log.Debugln("reloading certs")
|
||||
if err := f.loadCerts(); err != nil {
|
||||
f.log.Error("reload failed", err)
|
||||
}
|
||||
case <-f.stopTick:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileLoader) loadCerts() error {
|
||||
if len(f.certPaths) != len(f.keyPaths) {
|
||||
return errors.New("mismatch in certs and keys count")
|
||||
}
|
||||
|
||||
if len(f.certPaths) == 0 {
|
||||
return errors.New("tls.loader.file: at least one certificate required")
|
||||
}
|
||||
|
||||
certs := make([]tls.Certificate, 0, len(f.certPaths))
|
||||
|
||||
for i := range f.certPaths {
|
||||
certPath := f.certPaths[i]
|
||||
keyPath := f.keyPaths[i]
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load %s and %s: %v", certPath, keyPath, err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
f.certsLock.Lock()
|
||||
defer f.certsLock.Unlock()
|
||||
f.certs = certs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLoader) LoadCerts() ([]tls.Certificate, error) {
|
||||
// Loader function replaces only the whole slice.
|
||||
f.certsLock.RLock()
|
||||
defer f.certsLock.RUnlock()
|
||||
return f.certs, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register("tls.loader.file", NewFileLoader)
|
||||
}
|
92
internal/tls/self_signed.go
Normal file
92
internal/tls/self_signed.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type SelfSignedLoader struct {
|
||||
instName string
|
||||
serverNames []string
|
||||
|
||||
cert tls.Certificate
|
||||
}
|
||||
|
||||
func NewSelfSignedLoader(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
return &SelfSignedLoader{
|
||||
instName: instName,
|
||||
serverNames: inlineArgs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *SelfSignedLoader) Init(cfg *config.Map) error {
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(24 * time.Hour * 7)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{Organization: []string{"Maddy Self-Signed"}},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
|
||||
for _, name := range f.serverNames {
|
||||
if ip := net.ParseIP(name); ip != nil {
|
||||
cert.IPAddresses = append(cert.IPAddresses, ip)
|
||||
} else {
|
||||
cert.DNSNames = append(cert.DNSNames, name)
|
||||
}
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privKey.PublicKey, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.cert = tls.Certificate{
|
||||
Certificate: [][]byte{derBytes},
|
||||
PrivateKey: privKey,
|
||||
Leaf: cert,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *SelfSignedLoader) Name() string {
|
||||
return "tls.loader.self_signed"
|
||||
}
|
||||
|
||||
func (f *SelfSignedLoader) InstanceName() string {
|
||||
return f.instName
|
||||
}
|
||||
|
||||
func (f *SelfSignedLoader) LoadCerts() ([]tls.Certificate, error) {
|
||||
return []tls.Certificate{f.cert}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register("tls.loader.self_signed", NewSelfSignedLoader)
|
||||
}
|
4
maddy.go
4
maddy.go
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
parser "github.com/foxcpp/maddy/framework/cfgparser"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/hooks"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
|
@ -45,6 +46,7 @@ import (
|
|||
_ "github.com/foxcpp/maddy/internal/target/queue"
|
||||
_ "github.com/foxcpp/maddy/internal/target/remote"
|
||||
_ "github.com/foxcpp/maddy/internal/target/smtp"
|
||||
_ "github.com/foxcpp/maddy/internal/tls"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -285,7 +287,7 @@ func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, erro
|
|||
globals.String("prometheus_endpoint", false, false, "", &prometheusEndpoint)
|
||||
globals.String("hostname", false, false, "", nil)
|
||||
globals.String("autogenerated_msg_domain", false, false, "", nil)
|
||||
globals.Custom("tls", false, false, nil, config.TLSDirective, nil)
|
||||
globals.Custom("tls", false, false, nil, tls.TLSDirective, nil)
|
||||
globals.Bool("storage_perdomain", false, false, nil)
|
||||
globals.Bool("auth_perdomain", false, false, nil)
|
||||
globals.StringList("auth_domains", false, false, nil, nil)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue