2018-12-11 22:01:50 +01:00
|
|
|
package tls
|
|
|
|
|
|
|
|
import (
|
2019-03-22 19:41:12 +01:00
|
|
|
"context"
|
2018-12-11 22:01:50 +01:00
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
2019-03-22 19:41:12 +01:00
|
|
|
"time"
|
|
|
|
|
2018-12-11 22:01:50 +01:00
|
|
|
"github.com/pkg/errors"
|
2019-03-22 19:41:12 +01:00
|
|
|
|
2024-10-18 19:21:17 +02:00
|
|
|
"github.com/zrepl/zrepl/internal/config"
|
|
|
|
"github.com/zrepl/zrepl/internal/tlsconf"
|
|
|
|
"github.com/zrepl/zrepl/internal/transport"
|
|
|
|
"github.com/zrepl/zrepl/internal/util/tcpsock"
|
2018-12-11 22:01:50 +01:00
|
|
|
)
|
|
|
|
|
2019-03-22 20:45:27 +01:00
|
|
|
type TLSListenerFactory struct{}
|
2018-12-11 22:01:50 +01:00
|
|
|
|
2022-03-30 04:39:10 +02:00
|
|
|
func TLSListenerFactoryFromConfig(c *config.Global, in *config.TLSServe, parseFlags config.ParseFlags) (transport.AuthenticatedListenerFactory, error) {
|
2018-12-11 22:01:50 +01:00
|
|
|
|
|
|
|
address := in.Listen
|
|
|
|
handshakeTimeout := in.HandshakeTimeout
|
|
|
|
|
|
|
|
if in.Ca == "" || in.Cert == "" || in.Key == "" {
|
|
|
|
return nil, errors.New("fields 'ca', 'cert' and 'key'must be specified")
|
|
|
|
}
|
|
|
|
|
2022-03-30 04:39:10 +02:00
|
|
|
if parseFlags&config.ParseFlagsNoCertCheck != 0 {
|
2020-07-19 23:59:33 +02:00
|
|
|
return func() (transport.AuthenticatedListener, error) { return nil, nil }, nil
|
|
|
|
}
|
|
|
|
|
2018-12-11 22:01:50 +01:00
|
|
|
clientCA, err := tlsconf.ParseCAFile(in.Ca)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "cannot parse ca file")
|
|
|
|
}
|
|
|
|
|
|
|
|
serverCert, err := tls.LoadX509KeyPair(in.Cert, in.Key)
|
|
|
|
if err != nil {
|
2020-02-17 17:56:15 +01:00
|
|
|
return nil, errors.Wrap(err, "cannot parse cert/key pair")
|
2018-12-11 22:01:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
clientCNs := make(map[string]struct{}, len(in.ClientCNs))
|
|
|
|
for i, cn := range in.ClientCNs {
|
|
|
|
if err := transport.ValidateClientIdentity(cn); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "unsuitable client_cn #%d %q", i, cn)
|
|
|
|
}
|
|
|
|
// dupes are ok fr now
|
|
|
|
clientCNs[cn] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
lf := func() (transport.AuthenticatedListener, error) {
|
2019-12-30 19:42:17 +01:00
|
|
|
l, err := tcpsock.Listen(address, in.ListenFreeBind)
|
2018-12-11 22:01:50 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-30 19:42:17 +01:00
|
|
|
tl := tlsconf.NewClientAuthListener(l, clientCA, serverCert, handshakeTimeout)
|
2018-12-11 22:01:50 +01:00
|
|
|
return &tlsAuthListener{tl, clientCNs}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return lf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type tlsAuthListener struct {
|
|
|
|
*tlsconf.ClientAuthListener
|
|
|
|
clientCNs map[string]struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l tlsAuthListener) Accept(ctx context.Context) (*transport.AuthConn, error) {
|
|
|
|
tcpConn, tlsConn, cn, err := l.ClientAuthListener.Accept()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if _, ok := l.clientCNs[cn]; !ok {
|
2019-03-22 20:45:27 +01:00
|
|
|
log := transport.GetLogger(ctx)
|
2018-12-11 22:01:50 +01:00
|
|
|
if dl, ok := ctx.Deadline(); ok {
|
2019-03-22 20:45:27 +01:00
|
|
|
defer func() {
|
|
|
|
err := tlsConn.SetDeadline(time.Time{})
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("cannot clear connection deadline")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
err := tlsConn.SetDeadline(dl)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).WithField("deadline", dl).Error("cannot set connection deadline inherited from context")
|
|
|
|
}
|
2018-12-11 22:01:50 +01:00
|
|
|
}
|
|
|
|
if err := tlsConn.Close(); err != nil {
|
2019-03-22 20:45:27 +01:00
|
|
|
log.WithError(err).Error("error closing connection with unauthorized common name")
|
2018-12-11 22:01:50 +01:00
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("unauthorized client common name %q from %s", cn, tlsConn.RemoteAddr())
|
|
|
|
}
|
|
|
|
adaptor := newWireAdaptor(tlsConn, tcpConn)
|
|
|
|
return transport.NewAuthConn(adaptor, cn), nil
|
|
|
|
}
|