Add autocert

This commit is contained in:
Torjus Håkestad 2021-04-10 11:24:10 +02:00
parent f356858f02
commit 7ce2b2aa2b
7 changed files with 97 additions and 11 deletions

View File

@ -8,7 +8,8 @@ Type = "memory"
[Store.Postgres] [Store.Postgres]
# Connection string for postgres # Connection string for postgres
# Must be set if using Store.Type = "postgres" # Must be set if using Store.Type = "postgres"
DSN = "postgresql://film:film@10.69.10.130:5432/film?sslmode=disable" DSN = "postgresql://user:password@example.org:5432/apiary"
[Honeypot] [Honeypot]
# Path to SSH host key # Path to SSH host key
# If empty, a new one will be generated each time the service starts # If empty, a new one will be generated each time the service starts
@ -36,12 +37,18 @@ ListenAddr = ":8080"
[Frontend.Autocert] [Frontend.Autocert]
# Enable using letsencrypt for automatic certificates # Enable using letsencrypt for automatic certificates
# When enabled Frontend.ListenAddr will be ignored, and the server
# will listen to 443
# Default: false # Default: false
Enable = false Enable = false
# Email address for certificate owner
Email = ""
# Domains to use for certificates. Required when using autocert. # Domains to use for certificates. Required when using autocert.
# Default: "" # Default: ""
Domains = "example.org" Domains = ["example.org"]
# Dir where certificates are cached. # Dir where certificates are cached.
# Default: "/tmp" # Default: "/tmp"
CacheDir = "/var/apiary/certs" CacheDir = "/var/apiary/certs"
# Redirect HTTP to HTTPS
# Default: true
RedirectHTTP = true

View File

@ -17,6 +17,7 @@ import (
"github.uio.no/torjus/apiary/web" "github.uio.no/torjus/apiary/web"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"golang.org/x/crypto/acme/autocert"
) )
func main() { func main() {
@ -50,8 +51,10 @@ func ActionServe(c *cli.Context) error {
return err return err
} }
// Setup logging
loggers := setupLoggers(cfg) loggers := setupLoggers(cfg)
// Setup store
var s store.LoginAttemptStore var s store.LoginAttemptStore
switch cfg.Store.Type { switch cfg.Store.Type {
case "MEMORY", "memory": case "MEMORY", "memory":
@ -69,16 +72,32 @@ func ActionServe(c *cli.Context) error {
return fmt.Errorf("Invalid store configured") return fmt.Errorf("Invalid store configured")
} }
// Setup honeypot
hs, err := honeypot.NewHoneypotServer(cfg.Honeypot, s) hs, err := honeypot.NewHoneypotServer(cfg.Honeypot, s)
if err != nil { if err != nil {
return err return err
} }
hs.Logger = loggers.honeypotLogger hs.Logger = loggers.honeypotLogger
// Setup webserver
web := web.NewServer(cfg.Frontend, hs, s) web := web.NewServer(cfg.Frontend, hs, s)
web.AccessLogger = loggers.webAccessLogger web.AccessLogger = loggers.webAccessLogger
web.ServerLogger = loggers.webServerLogger web.ServerLogger = loggers.webServerLogger
if cfg.Frontend.Autocert.Enable {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(cfg.Frontend.Autocert.Domains...),
Email: cfg.Frontend.Autocert.Email,
}
if cfg.Frontend.Autocert.CacheDir != "" {
certManager.Cache = autocert.DirCache(cfg.Frontend.Autocert.CacheDir)
}
tlsConfig := certManager.TLSConfig()
web.TLSConfig = tlsConfig
}
// Setup interrupt handling
interruptChan := make(chan os.Signal, 1) interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt) signal.Notify(interruptChan, os.Interrupt)
@ -103,7 +122,7 @@ func ActionServe(c *cli.Context) error {
// Start web server // Start web server
go func() { go func() {
loggers.rootLogger.Info("Starting web server") loggers.rootLogger.Info("Starting web server")
if err := web.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := web.StartServe(); err != nil && err != http.ErrServerClosed {
loggers.rootLogger.Warnw("Web server returned error", "error", err) loggers.rootLogger.Warnw("Web server returned error", "error", err)
} }
}() }()

View File

@ -30,9 +30,18 @@ type HoneypotConfig struct {
} }
type FrontendConfig struct { type FrontendConfig struct {
ListenAddr string `toml:"ListenAddr"` ListenAddr string `toml:"ListenAddr"`
LogLevel string `toml:"LogLevel"` LogLevel string `toml:"LogLevel"`
AccessLogEnable bool `toml:"AccessLogEnable"` AccessLogEnable bool `toml:"AccessLogEnable"`
Autocert FrontendAutocertConfig `toml:"Autocert"`
}
type FrontendAutocertConfig struct {
Enable bool `toml:"Enable"`
Email string `toml:"Email"`
Domains []string `toml:"Domains"`
CacheDir string `toml:"CacheDir"`
RedirectHTTP bool `toml:"RedirectHTTP"`
} }
func FromReader(r io.Reader) (Config, error) { func FromReader(r io.Reader) (Config, error) {

1
go.mod
View File

@ -16,5 +16,6 @@ require (
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
go.uber.org/zap v1.13.0 go.uber.org/zap v1.13.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 // indirect
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
) )

6
go.sum
View File

@ -400,6 +400,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -430,14 +432,16 @@ golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=

View File

@ -55,7 +55,6 @@ func NewHoneypotServer(cfg config.HoneypotConfig, store store.LoginAttemptStore)
} }
hs.sshServer.AddHostKey(signer) hs.sshServer.AddHostKey(signer)
} }
return &hs, nil return &hs, nil

View File

@ -18,11 +18,14 @@ import (
"github.uio.no/torjus/apiary/honeypot/store" "github.uio.no/torjus/apiary/honeypot/store"
"github.uio.no/torjus/apiary/models" "github.uio.no/torjus/apiary/models"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/acme/autocert"
) )
type Server struct { type Server struct {
http.Server http.Server
cfg config.FrontendConfig
store store.LoginAttemptStore store store.LoginAttemptStore
ServerLogger *zap.SugaredLogger ServerLogger *zap.SugaredLogger
@ -33,7 +36,8 @@ type Server struct {
attemptListenersLock sync.RWMutex attemptListenersLock sync.RWMutex
attemptListeners map[string]chan models.LoginAttempt attemptListeners map[string]chan models.LoginAttempt
streamContext context.Context streamContext context.Context
httpRedirectServer http.Server
} }
func NewServer(cfg config.FrontendConfig, hs *honeypot.HoneypotServer, store store.LoginAttemptStore) *Server { func NewServer(cfg config.FrontendConfig, hs *honeypot.HoneypotServer, store store.LoginAttemptStore) *Server {
@ -41,8 +45,35 @@ func NewServer(cfg config.FrontendConfig, hs *honeypot.HoneypotServer, store sto
ServerLogger: zap.NewNop().Sugar(), ServerLogger: zap.NewNop().Sugar(),
AccessLogger: zap.NewNop().Sugar(), AccessLogger: zap.NewNop().Sugar(),
store: store, store: store,
cfg: cfg,
}
if cfg.Autocert.Enable {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(cfg.Autocert.Domains...),
Email: cfg.Autocert.Email,
}
if cfg.Autocert.CacheDir != "" {
certManager.Cache = autocert.DirCache(cfg.Autocert.CacheDir)
}
tlsConfig := certManager.TLSConfig()
s.TLSConfig = tlsConfig
s.RegisterOnShutdown(func() {
timeoutCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
s.httpRedirectServer.Shutdown(timeoutCtx)
})
s.Addr = ":443"
if cfg.Autocert.RedirectHTTP {
s.httpRedirectServer.Addr = ":80"
s.httpRedirectServer.Handler = certManager.HTTPHandler(nil)
}
} else {
s.Addr = cfg.ListenAddr
} }
s.Addr = cfg.ListenAddr
r := chi.NewRouter() r := chi.NewRouter()
@ -79,6 +110,22 @@ func NewServer(cfg config.FrontendConfig, hs *honeypot.HoneypotServer, store sto
return s return s
} }
func (s *Server) StartServe() error {
if s.cfg.Autocert.Enable {
if s.cfg.Autocert.RedirectHTTP {
s.ServerLogger.Debug("Starting HTTP redirect server")
go func() {
if err := s.httpRedirectServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
s.ServerLogger.Warnw("HTTP redirect server returned error", "error", err)
}
}()
}
return s.ListenAndServeTLS("", "")
} else {
return s.ListenAndServe()
}
}
func (s *Server) addAttemptListener() (string, chan models.LoginAttempt) { func (s *Server) addAttemptListener() (string, chan models.LoginAttempt) {
ch := make(chan models.LoginAttempt) ch := make(chan models.LoginAttempt)
s.attemptListenersLock.Lock() s.attemptListenersLock.Lock()