Skip to content

Commit

Permalink
Add systemd socket listener activation (#95)
Browse files Browse the repository at this point in the history
* add systemd socket listener activation

Signed-off-by: Perry Naseck <git@perrynaseck.com>
  • Loading branch information
DaAwesomeP committed Oct 17, 2022
1 parent 9980373 commit bca43f1
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 31 deletions.
2 changes: 2 additions & 0 deletions go.mod
Expand Up @@ -3,9 +3,11 @@ module github.com/prometheus/exporter-toolkit
go 1.17

require (
github.com/coreos/go-systemd/v22 v22.3.2
github.com/go-kit/log v0.2.1
github.com/prometheus/common v0.37.0
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v2 v2.4.0
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Expand Up @@ -89,6 +89,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -118,6 +120,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -417,6 +420,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
24 changes: 18 additions & 6 deletions web/handler_test.go
Expand Up @@ -24,7 +24,6 @@ import (
// protected endpoint multiple times.
func TestBasicAuthCache(t *testing.T) {
server := &http.Server{
Addr: port,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}),
Expand All @@ -39,7 +38,12 @@ func TestBasicAuthCache(t *testing.T) {
})

go func() {
ListenAndServe(server, "testdata/web_config_users_noTLS.good.yml", testlogger)
flags := FlagConfig{
WebListenAddresses: &([]string{port}),
WebSystemdSocket: OfBool(false),
WebConfigFile: OfString("testdata/web_config_users_noTLS.good.yml"),
}
ListenAndServe(server, &flags, testlogger)
close(done)
}()

Expand Down Expand Up @@ -88,7 +92,6 @@ func TestBasicAuthCache(t *testing.T) {
// to prevent user enumeration.
func TestBasicAuthWithFakepassword(t *testing.T) {
server := &http.Server{
Addr: port,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}),
Expand All @@ -103,7 +106,12 @@ func TestBasicAuthWithFakepassword(t *testing.T) {
})

go func() {
ListenAndServe(server, "testdata/web_config_users_noTLS.good.yml", testlogger)
flags := FlagConfig{
WebListenAddresses: &([]string{port}),
WebSystemdSocket: OfBool(false),
WebConfigFile: OfString("testdata/web_config_users_noTLS.good.yml"),
}
ListenAndServe(server, &flags, testlogger)
close(done)
}()

Expand Down Expand Up @@ -132,7 +140,6 @@ func TestBasicAuthWithFakepassword(t *testing.T) {
// TestHTTPHeaders validates that HTTP headers are added correctly.
func TestHTTPHeaders(t *testing.T) {
server := &http.Server{
Addr: port,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}),
Expand All @@ -147,7 +154,12 @@ func TestHTTPHeaders(t *testing.T) {
})

go func() {
ListenAndServe(server, "testdata/web_config_headers.good.yml", testlogger)
flags := FlagConfig{
WebListenAddresses: &([]string{port}),
WebSystemdSocket: OfBool(false),
WebConfigFile: OfString("testdata/web_config_headers.good.yml"),
}
ListenAndServe(server, &flags, testlogger)
close(done)
}()

Expand Down
32 changes: 26 additions & 6 deletions web/kingpinflag/flag.go
Expand Up @@ -13,14 +13,34 @@
package kingpinflag

import (
"runtime"

"gopkg.in/alecthomas/kingpin.v2"

"github.com/prometheus/exporter-toolkit/web"
)

// AddFlags adds the flags used by this package to the Kingpin application.
// To use the default Kingpin application, call AddFlags(kingpin.CommandLine)
func AddFlags(a *kingpin.Application) *string {
return a.Flag(
"web.config.file",
"[EXPERIMENTAL] Path to configuration file that can enable TLS or authentication.",
).Default("").String()
// To use the default Kingpin application, call
// AddFlags(kingpin.CommandLine, ":portNum") where portNum is the default port.
func AddFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig {
systemdSocket := func() *bool { b := false; return &b }() // Socket activation only available on Linux
if runtime.GOOS == "linux" {
systemdSocket = kingpin.Flag(
"web.systemd-socket",
"Use systemd socket activation listeners instead of port listeners (Linux only).",
).Bool()
}
flags := web.FlagConfig{
WebListenAddresses: a.Flag(
"web.listen-address",
"Addresses on which to expose metrics and web interface. Repeatable for multiple addresses.",
).Default(defaultAddress).Strings(),
WebSystemdSocket: systemdSocket,
WebConfigFile: a.Flag(
"web.config.file",
"[EXPERIMENTAL] Path to configuration file that can enable TLS or authentication.",
).Default("").String(),
}
return &flags
}
72 changes: 56 additions & 16 deletions web/tls_config.go
Expand Up @@ -23,9 +23,11 @@ import (
"os"
"path/filepath"

"github.com/coreos/go-systemd/v22/activation"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
config_util "github.com/prometheus/common/config"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v2"
)

Expand All @@ -51,6 +53,12 @@ type TLSConfig struct {
PreferServerCipherSuites bool `yaml:"prefer_server_cipher_suites"`
}

type FlagConfig struct {
WebListenAddresses *[]string
WebSystemdSocket *bool
WebConfigFile *string
}

// SetDirectory joins any relative file paths with dir.
func (t *TLSConfig) SetDirectory(dir string) {
t.TLSCertPath = config_util.JoinDir(dir, t.TLSCertPath)
Expand Down Expand Up @@ -177,22 +185,54 @@ func ConfigToTLSConfig(c *TLSConfig) (*tls.Config, error) {
return cfg, nil
}

// ListenAndServe starts the server on the given address. Based on the file
// tlsConfigPath, TLS or basic auth could be enabled.
func ListenAndServe(server *http.Server, tlsConfigPath string, logger log.Logger) error {
listener, err := net.Listen("tcp", server.Addr)
if err != nil {
return err
// ServeMultiple starts the server on the given listeners. The FlagConfig is
// also passed on to Serve.
func ServeMultiple(listeners []net.Listener, server *http.Server, flags *FlagConfig, logger log.Logger) error {
errs := new(errgroup.Group)
for _, l := range listeners {
l := l
errs.Go(func() error {
return Serve(l, server, flags, logger)
})
}
return errs.Wait()
}

// ListenAndServe starts the server on addresses given in WebListenAddresses in
// the FlagConfig or instead uses systemd socket activated listeners if
// WebSystemdSocket in the FlagConfig is true. The FlagConfig is also passed on
// to ServeMultiple.
func ListenAndServe(server *http.Server, flags *FlagConfig, logger log.Logger) error {
if *flags.WebSystemdSocket {
level.Info(logger).Log("msg", "Listening on systemd activated listeners instead of port listeners.")
listeners, err := activation.Listeners()
if err != nil {
return err
}
if len(listeners) < 1 {
return errors.New("no socket activation file descriptors found")
}
return ServeMultiple(listeners, server, flags, logger)
}
defer listener.Close()
return Serve(listener, server, tlsConfigPath, logger)
listeners := make([]net.Listener, 0, len(*flags.WebListenAddresses))
for _, address := range *flags.WebListenAddresses {
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
defer listener.Close()
listeners = append(listeners, listener)
}
return ServeMultiple(listeners, server, flags, logger)
}

// Server starts the server on the given listener. Based on the file
// tlsConfigPath, TLS or basic auth could be enabled.
func Serve(l net.Listener, server *http.Server, tlsConfigPath string, logger log.Logger) error {
// Server starts the server on the given listener. Based on the file path
// WebConfigFile in the FlagConfig, TLS or basic auth could be enabled.
func Serve(l net.Listener, server *http.Server, flags *FlagConfig, logger log.Logger) error {
level.Info(logger).Log("msg", "Listening on", "address", l.Addr().String())
tlsConfigPath := *flags.WebConfigFile
if tlsConfigPath == "" {
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false)
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false, "address", l.Addr().String())
return server.Serve(l)
}

Expand Down Expand Up @@ -225,10 +265,10 @@ func Serve(l net.Listener, server *http.Server, tlsConfigPath string, logger log
server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
// Valid TLS config.
level.Info(logger).Log("msg", "TLS is enabled.", "http2", c.HTTPConfig.HTTP2)
level.Info(logger).Log("msg", "TLS is enabled.", "http2", c.HTTPConfig.HTTP2, "address", l.Addr().String())
case errNoTLSConfig:
// No TLS config, back to plain HTTP.
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false)
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false, "address", l.Addr().String())
return server.Serve(l)
default:
// Invalid TLS config.
Expand Down Expand Up @@ -356,6 +396,6 @@ func (tv *TLSVersion) MarshalYAML() (interface{}, error) {
// tlsConfigPath, TLS or basic auth could be enabled.
//
// Deprecated: Use ListenAndServe instead.
func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error {
return ListenAndServe(server, tlsConfigPath, logger)
func Listen(server *http.Server, flags *FlagConfig, logger log.Logger) error {
return ListenAndServe(server, flags, logger)
}
23 changes: 20 additions & 3 deletions web/tls_config_test.go
Expand Up @@ -31,6 +31,14 @@ import (
"time"
)

// Helpers for literal FlagConfig
func OfBool(i bool) *bool {
return &i
}
func OfString(i string) *string {
return &i
}

var (
port = getPort()
testlogger = &testLogger{}
Expand Down Expand Up @@ -378,7 +386,12 @@ func TestConfigReloading(t *testing.T) {
recordConnectionError(errors.New("Panic starting server"))
}
}()
err := Listen(server, badYAMLPath, testlogger)
flagsBadYAMLPath := FlagConfig{
WebListenAddresses: &([]string{port}),
WebSystemdSocket: OfBool(false),
WebConfigFile: OfString(badYAMLPath),
}
err := Listen(server, &flagsBadYAMLPath, testlogger)
recordConnectionError(err)
}()

Expand Down Expand Up @@ -436,7 +449,6 @@ func (test *TestInputs) Test(t *testing.T) {
}()

server := &http.Server{
Addr: port,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}),
Expand All @@ -448,7 +460,12 @@ func (test *TestInputs) Test(t *testing.T) {
recordConnectionError(errors.New("Panic starting server"))
}
}()
err := ListenAndServe(server, test.YAMLConfigPath, testlogger)
flags := FlagConfig{
WebListenAddresses: &([]string{port}),
WebSystemdSocket: OfBool(false),
WebConfigFile: &test.YAMLConfigPath,
}
err := ListenAndServe(server, &flags, testlogger)
recordConnectionError(err)
}()

Expand Down

0 comments on commit bca43f1

Please sign in to comment.