0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00

feat: add support for systemd socket activation

This commit is contained in:
Rui Lopes 2024-02-07 19:41:43 +00:00
parent 0aa6bf0fff
commit 43ca3c7a98
No known key found for this signature in database
6 changed files with 126 additions and 26 deletions

View file

@ -0,0 +1,25 @@
This allows Zot to indirectly listen at a privileged socket port (e.g. `443`) without granting it the `CAP_NET_BIND_SERVICE` capability.
This uses the [systemd Socket Activation](https://0pointer.de/blog/projects/socket-activated-containers.html) feature to create the listening socket at the privileged port. The port is defined by the `ListenStream` variable declared in the [`zot.socket` file](zot.socket).
At the first socket client connection, systemd will start the `zot` service, and will pass it the listening socket in the file descriptor defined by the `LISTEN_FDS` environment variable.
To install the `zot` service as described, review the example [`zot.service`](zot.service) and [`zot.socket`](zot.socket) files, and then execute the following commands as the `root` user:
```bash
install zot.service /etc/systemd/system/zot.service
install zot.socket /etc/systemd/system/zot.socket
systemctl daemon-reload
systemctl enable zot.service zot.socket
systemctl restart zot.service zot.socket
```
At development time, you can test the systemd Socket Activation using something like:
```bash
systemd-socket-activate \
--listen=127.0.0.1:9999 \
./bin/zot-linux-amd64 \
serve \
examples/config-minimal.json
```

View file

@ -0,0 +1,16 @@
[Unit]
Description=OCI Distribution Registry
Documentation=https://github.com/project-zot/zot
After=network.target auditd.service local-fs.target
Requires=zot.socket
[Service]
Type=simple
ExecStart=/usr/bin/zot serve /etc/zot/config.json
Restart=on-failure
User=zot
Group=zot
LimitNOFILE=500000
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,10 @@
[Unit]
Description=OCI Distribution Registry
[Socket]
ListenStream=80
FileDescriptorName=http
Service=zot.service
[Install]
WantedBy=sockets.target

1
go.mod
View file

@ -51,6 +51,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2
github.com/aws/aws-secretsmanager-caching-go v1.1.3
github.com/containers/image/v5 v5.29.1
github.com/coreos/go-systemd/v22 v22.5.0
github.com/google/go-github/v52 v52.0.0
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.2.2

1
go.sum
View file

@ -581,6 +581,7 @@ github.com/containers/storage v1.51.0 h1:AowbcpiWXzAjHosKz7MKvPEqpyX+ryZA/ZurytR
github.com/containers/storage v1.51.0/go.mod h1:ybl8a3j1PPtpyaEi/5A6TOFs+5TrEyObeKJzVtkUlfc=
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=

View file

@ -14,6 +14,7 @@ import (
"syscall"
"time"
"github.com/coreos/go-systemd/v22/activation"
"github.com/gorilla/mux"
"github.com/zitadel/oidc/pkg/client/rp"
@ -94,6 +95,73 @@ func (c *Controller) GetPort() int {
return c.chosenPort
}
func (c *Controller) createListener() (net.Listener, string, error) {
// try to create the listener from the ambient systemd socket activation
// environment variables. otherwise, create the listener from the address
// defined in the configuration.
listeners, err := activation.Listeners()
if err != nil {
return nil, "", fmt.Errorf("systemd socket activation listeners failed to initialize: %w", err)
}
if len(listeners) == 1 {
listener := listeners[0]
c.Log.Info().Stringer("addr", listener.Addr()).Msg("using systemd socket activation")
_, port, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
return nil, "", err
}
chosenPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return nil, "", err
}
c.chosenPort = int(chosenPort)
addr := fmt.Sprintf("%s:%d", c.Config.HTTP.Address, c.chosenPort)
return listener, addr, nil
}
if len(listeners) != 0 {
return nil, "", fmt.Errorf("systemd socket activation has an unexpected number of listeners: %w", err)
}
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, "", err
}
if c.Config.HTTP.Port == "0" || c.Config.HTTP.Port == "" {
chosenAddr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
c.Log.Error().Str("port", c.Config.HTTP.Port).Msg("invalid addr type")
return nil, "", errors.ErrBadType
}
c.chosenPort = chosenAddr.Port
c.Log.Info().Int("port", chosenAddr.Port).IPAddr("address", chosenAddr.IP).Msg(
"port is unspecified, listening on kernel chosen port",
)
} else {
chosenPort, err := strconv.ParseUint(c.Config.HTTP.Port, 10, 16)
if err != nil {
return nil, "", err
}
c.chosenPort = int(chosenPort)
}
return listener, addr, nil
}
func (c *Controller) Run() error {
if err := c.initCookieStore(); err != nil {
return err
@ -133,7 +201,11 @@ func (c *Controller) Run() error {
//nolint: contextcheck
_ = NewRouteHandler(c)
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
listener, addr, err := c.createListener()
if err != nil {
return err
}
server := &http.Server{
Addr: addr,
Handler: c.Router,
@ -142,31 +214,6 @@ func (c *Controller) Run() error {
}
c.Server = server
// Create the listener
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
if c.Config.HTTP.Port == "0" || c.Config.HTTP.Port == "" {
chosenAddr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
c.Log.Error().Str("port", c.Config.HTTP.Port).Msg("invalid addr type")
return errors.ErrBadType
}
c.chosenPort = chosenAddr.Port
c.Log.Info().Int("port", chosenAddr.Port).IPAddr("address", chosenAddr.IP).Msg(
"port is unspecified, listening on kernel chosen port",
)
} else {
chosenPort, _ := strconv.ParseInt(c.Config.HTTP.Port, 10, 64)
c.chosenPort = int(chosenPort)
}
if c.Config.HTTP.TLS != nil && c.Config.HTTP.TLS.Key != "" && c.Config.HTTP.TLS.Cert != "" {
server.TLSConfig = &tls.Config{
CipherSuites: []uint16{