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:
parent
0aa6bf0fff
commit
43ca3c7a98
6 changed files with 126 additions and 26 deletions
25
examples/systemd-socket-activation/README.md
Normal file
25
examples/systemd-socket-activation/README.md
Normal 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
|
||||
```
|
16
examples/systemd-socket-activation/zot.service
Normal file
16
examples/systemd-socket-activation/zot.service
Normal 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
|
10
examples/systemd-socket-activation/zot.socket
Normal file
10
examples/systemd-socket-activation/zot.socket
Normal 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
1
go.mod
|
@ -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
1
go.sum
|
@ -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=
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in a new issue