Skip to content

Commit

Permalink
feat: add support for systemd socket activation
Browse files Browse the repository at this point in the history
  • Loading branch information
rgl committed Jan 26, 2024
1 parent 9def35f commit a21de08
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 26 deletions.
15 changes: 15 additions & 0 deletions examples/systemd-socket-activation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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
```
16 changes: 16 additions & 0 deletions examples/systemd-socket-activation/zot.service
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions examples/systemd-socket-activation/zot.socket
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=OCI Distribution Registry

[Socket]
ListenStream=80
FileDescriptorName=http
Service=zot.service

[Install]
WantedBy=sockets.target
97 changes: 71 additions & 26 deletions pkg/api/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"syscall"
"time"

"github.com/coreos/go-systemd/activation"
"github.com/gorilla/mux"
"github.com/zitadel/oidc/pkg/client/rp"

Expand Down Expand Up @@ -94,6 +95,71 @@ 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.ParseInt(port, 10, 64)
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, _ := strconv.ParseInt(c.Config.HTTP.Port, 10, 64)

c.chosenPort = int(chosenPort)
}

return listener, addr, nil
}

func (c *Controller) Run() error {
if err := c.initCookieStore(); err != nil {
return err
Expand Down Expand Up @@ -133,7 +199,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,
Expand All @@ -142,31 +212,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{
Expand Down

0 comments on commit a21de08

Please sign in to comment.