Commit eb4feb3c authored by ale's avatar ale

Add the auth-server-proxy (separate Debian pkg)

The proxy allows us to reduce SSL connection overhead to a remote
server by multiplexing proxied requests from the local UNIX socket.
parent bee58ad7
Pipeline #8531 passed with stages
in 1 minute and 40 seconds
......@@ -24,14 +24,14 @@ type Client interface {
type socketClient struct {
socketPath string
codec auth.Codec
pool *pool
pool *Pool
}
func New(socketPath string) Client {
return &socketClient{
socketPath: socketPath,
codec: auth.DefaultCodec,
pool: newPool(func() (*lineproto.Conn, error) {
pool: NewPool(func() (*lineproto.Conn, error) {
c, err := net.Dial("unix", socketPath)
if err != nil {
return nil, err
......@@ -74,7 +74,7 @@ func (c *socketClient) Authenticate(ctx context.Context, req *auth.Request) (*au
func (c *socketClient) doAuthenticate(ctx context.Context, req *auth.Request) (*auth.Response, error) {
var resp auth.Response
err := c.pool.withConn(func(conn *lineproto.Conn) error {
err := c.pool.WithConn(func(conn *lineproto.Conn) error {
// Make space in the channel for at least one element, or we
// will leak a goroutine whenever the authentication request
// times out.
......
......@@ -4,21 +4,21 @@ import (
"git.autistici.org/id/auth/lineproto"
)
type poolDialer func() (*lineproto.Conn, error)
type PoolDialer func() (*lineproto.Conn, error)
type pool struct {
type Pool struct {
ch chan *lineproto.Conn
dialer poolDialer
dialer PoolDialer
}
func newPool(dialer poolDialer, size int) *pool {
return &pool{
func NewPool(dialer PoolDialer, size int) *Pool {
return &Pool{
ch: make(chan *lineproto.Conn, size),
dialer: dialer,
}
}
func (p *pool) withConn(f func(*lineproto.Conn) error) error {
func (p *Pool) WithConn(f func(*lineproto.Conn) error) error {
// Acquire a connection.
var conn *lineproto.Conn
select {
......
package main
import (
"bytes"
"context"
"crypto/tls"
"errors"
"flag"
"log"
"net"
"os"
"os/signal"
"syscall"
common "git.autistici.org/ai3/go-common"
"git.autistici.org/id/auth"
"git.autistici.org/id/auth/client"
"git.autistici.org/id/auth/lineproto"
"github.com/coreos/go-systemd/activation"
"github.com/coreos/go-systemd/daemon"
"golang.org/x/sync/errgroup"
)
var (
socketPath = flag.String("socket", "", "`path` to the UNIX socket to listen on")
systemdSocketActivation = flag.Bool("systemd-socket", false, "use SystemD socket activation")
upstreamAddr = flag.String("upstream", "", "upstream address (host:port)")
sslCert = flag.String("ssl-cert", "", "SSL certificate `file`")
sslKey = flag.String("ssl-key", "", "SSL private key `file`")
sslCA = flag.String("ssl-ca", "", "SSL CA `file` (enables client TLS)")
)
func buildTLSConfig() (*tls.Config, error) {
if *sslCA == "" {
return nil, nil
}
cas, err := common.LoadCA(*sslCA)
if err != nil {
return nil, err
}
tlsConf := &tls.Config{
RootCAs: cas,
}
if *sslCert != "" && *sslKey != "" {
cert, err := tls.LoadX509KeyPair(*sslCert, *sslKey)
if err != nil {
return nil, err
}
tlsConf.Certificates = []tls.Certificate{cert}
}
return tlsConf, nil
}
type proxyServer struct {
codec auth.Codec
pool *client.Pool
}
var (
authCmd = []byte("auth ")
quitCmd = []byte("quit")
)
func newProxyServer(upstream string, tlsConfig *tls.Config) *proxyServer {
var dialer func() (*lineproto.Conn, error)
if tlsConfig != nil {
dialer = func() (*lineproto.Conn, error) {
c, err := tls.Dial("tcp", upstream, tlsConfig)
if err != nil {
return nil, err
}
return lineproto.NewConn(c, ""), nil
}
} else {
dialer = func() (*lineproto.Conn, error) {
c, err := net.Dial("tcp", upstream)
if err != nil {
return nil, err
}
return lineproto.NewConn(c, ""), nil
}
}
return &proxyServer{
codec: auth.DefaultCodec,
pool: client.NewPool(dialer, client.DefaultPoolSize),
}
}
func (s *proxyServer) ServeLine(ctx context.Context, lw lineproto.LineResponseWriter, line []byte) error {
if bytes.HasPrefix(line, quitCmd) {
return lineproto.ErrCloseConnection
}
if bytes.HasPrefix(line, authCmd) {
var resp []byte
err := s.pool.WithConn(func(conn *lineproto.Conn) (err error) {
if err = conn.WriteLine(line); err != nil {
return
}
resp, err = conn.ReadLine()
return
})
if err != nil {
return err
}
return lw.WriteLine(resp)
}
return errors.New("syntax error")
}
type genericServer interface {
Serve() error
Close()
}
func runServer(ctx context.Context, srv genericServer) error {
go func() {
<-ctx.Done()
if ctx.Err() == context.Canceled {
srv.Close()
}
}()
daemon.SdNotify(false, "READY=1") // nolint: errcheck
return srv.Serve()
}
func main() {
log.SetFlags(0)
flag.Parse()
syscall.Umask(007)
tlsConfig, err := buildTLSConfig()
if err != nil {
log.Fatal(err)
}
proxySrv := newProxyServer(*upstreamAddr, tlsConfig)
srv := lineproto.NewLineServer(proxySrv)
outerCtx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(outerCtx)
var servers []genericServer
if *systemdSocketActivation {
ll, err := activation.Listeners()
if err != nil {
log.Fatal(err)
}
for _, l := range ll {
servers = append(servers, lineproto.NewServer("systemd", l, srv))
}
}
if *socketPath != "" {
l, err := lineproto.NewUNIXSocketListener(*socketPath)
if err != nil {
log.Fatal(err)
}
servers = append(servers, lineproto.NewServer("unix", l, srv))
}
if len(servers) == 0 {
log.Fatal("no sockets available for listening")
}
sigCh := make(chan os.Signal, 1)
go func() {
<-sigCh
log.Printf("terminating")
cancel()
}()
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
for _, s := range servers {
func(s genericServer) {
g.Go(func() error {
return runServer(ctx, s)
})
}(s)
}
err = g.Wait()
if err != nil && err != context.Canceled {
log.Fatal(err)
}
}
usr/bin/auth-server-proxy
#!/bin/sh
# postinstall script for auth-server-proxy.
case "$1" in
configure)
adduser --quiet --system --home /nonexistent --no-create-home \
--disabled-password --group auth-server
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
[Unit]
Description=Authentication server proxy
Requires=auth-server-proxy.socket
[Service]
Type=notify
EnvironmentFile=-/etc/default/auth-server-proxy
ExecStart=/usr/bin/auth-server-proxy --systemd-socket $OPTIONS
Restart=on-failure
User=auth-server
[Install]
WantedBy=multi-user.target
[Unit]
Description=Authentication socket (proxied)
[Socket]
ListenStream=/run/auth/socket
SocketMode=660
DirectoryMode=755
SocketUser=auth-server
SocketGroup=auth-server
Accept=false
usr/bin/auth-server
auth-server (0.3) unstable; urgency=medium
* TCP support and proxying.
-- Autistici/Inventati <debian@autistici.org> Thu, 12 Nov 2020 08:43:55 +0000
auth-server (0.2) unstable; urgency=medium
* New release (UNIX socket only).
......
......@@ -8,6 +8,13 @@ Standards-Version: 3.9.4
Package: auth-server
Architecture: all
Depends: ${misc:Depends}
Conflicts: auth-server-proxy
Description: Auth server package.
Centralized authentication server with OTP support.
Package: auth-server-proxy
Architecture: all
Depends: ${misc:Depends}
Conflicts: auth-server
Description: Auth server proxy.
Shim to forward local auth-server connections to a remote server.
#!/usr/bin/make -f
# -*- makefile -*-
# This has to be exported to make some magic below work.
export DH_OPTIONS
export DH_GOPKG = git.autistici.org/id/auth
export DH_GOLANG_EXCLUDES = vendor
......@@ -11,6 +8,5 @@ export DH_GOLANG_EXCLUDES = vendor
dh $@ --with golang --with systemd --buildsystem golang
override_dh_auto_install:
dh_auto_install
$(RM) -rv debian/auth-server/usr/share/gocode
dh_auto_install -- --no-source
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