Skip to content

Commit

Permalink
Add support for systemd socket activation (#704)
Browse files Browse the repository at this point in the history
* feat: add support for systemd socket activation

If webhook has been launched via systemd socket activation, simply use the systemd-provided socket rather than opening our own.

* docs: documentation for the systemd socket activation mode

* refactor: moved setuid and setgid flags into platform-specific section

The setuid and setgid flags do not work on Windows, so moved them to platform_unix so they are only added to the flag set on compatible platforms.

Also disallow the use of setuid and setgid in combination with -socket, since a setuid webhook process would not be able to clean up a socket that was created while running as root.  If you _need_ to have the socket owned by root but the webhook process running as a normal user, you can achieve the same effect with systemd socket activation.
  • Loading branch information
ianroberts authored Oct 25, 2024
1 parent 9cd78fc commit 98cf5d0
Show file tree
Hide file tree
Showing 13 changed files with 529 additions and 5 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ Note that when running in this mode the [`ip-whitelist`](docs/Hook-Rules.md#matc
## CORS Headers
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.

## Running under `systemd`
On platforms that use [systemd](https://systemd.io), [webhook][w] supports the _socket activation_ mechanism. If [webhook][w] detects that it has been launched from a systemd-managed socket it will automatically use that instead of opening its own listening port. See [the systemd page](docs/Systemd-Activation.md) for full details.

## Interested in running webhook inside of a Docker container?
You can use one of the following Docker images, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)):
- [almir/webhook](https://github.com/almir/docker-webhook)
Expand Down
61 changes: 61 additions & 0 deletions docs/Systemd-Activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Using systemd socket activation

_New in v2.9.0_

On platforms that use [systemd](https://systemd.io), [webhook][w]
supports the _socket activation_ mechanism. In this mode, systemd itself is responsible for managing the listening socket, and it launches [webhook][w] the first time it receives a request on the socket. This has a number of advantages over the standard mode:

- [webhook][w] can run as a normal user while still being able to use a port number like 80 or 443 that would normally require root privilege
- if the [webhook][w] process dies and is restarted, pending connections are not dropped - they just keep waiting until the restarted [webhook][w] is ready

No special configuration is necessary to tell [webhook][w] that socket activation is being used - socket activation sets specific environment variables when launching the activated service, if [webhook][w] detects these variables it will ignore the `-port` and `-socket` options and simply use the systemd-provided socket instead of opening its own.

## Configuration
To run [webhook][w] with socket activation you need to create _two_ separate unit files in your systemd configuration directory (typically `/etc/systemd/system`), one for the socket and one for the service. They must have matching names; in this example we use `webhook.socket` and `webhook.service`. At their simplest, these files should look like:

**webhook.socket**
```
[Unit]
Description=Webhook server socket
[Socket]
# Listen on all network interfaces, port 9000
ListenStream=9000
# Alternatives:
## Listen on one specific interface only
# ListenStream=10.0.0.1:9000
# FreeBind=true
## Listen on a Unix domain socket
# ListenStream=/tmp/webhook.sock
[Install]
WantedBy=multi-user.target
```

**webhook.service**
```
[Unit]
Description=Webhook server
[Service]
Type=exec
ExecStart=webhook -nopanic -hooks /etc/webhook/hooks.yml
# Which user should the webhooks run as?
User=nobody
Group=nogroup
```

You should enable and start the _socket_, but it is not necessary to enable the _service_ - this will be started automatically when the socket receives its first request.

```sh
sudo systemctl enable webhook.socket
sudo systemctl start webhook.socket
```

Systemd unit files support many other options, see the [systemd.socket](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html) and [systemd.service](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html) manual pages for full details.

[w]: https://github.com/adnanh/webhook
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.22.0
require (
github.com/Microsoft/go-winio v0.6.2
github.com/clbanning/mxj/v2 v2.7.0
github.com/coreos/go-systemd/v22 v22.5.0
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.7.0
github.com/ghodss/yaml v1.0.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
Expand All @@ -10,6 +12,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand Down
25 changes: 25 additions & 0 deletions platform_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,40 @@ package main
import (
"flag"
"fmt"
"github.com/coreos/go-systemd/v22/activation"
"net"
)

func platformFlags() {
flag.StringVar(&socket, "socket", "", "path to a Unix socket (e.g. /tmp/webhook.sock) to use instead of listening on an ip and port; if specified, the ip and port options are ignored")
flag.IntVar(&setGID, "setgid", 0, "set group ID after opening listening port; must be used with setuid, not permitted with -socket")
flag.IntVar(&setUID, "setuid", 0, "set user ID after opening listening port; must be used with setgid, not permitted with -socket")
}

func trySocketListener() (net.Listener, error) {
// first check whether we have any sockets from systemd
listeners, err := activation.Listeners()
if err != nil {
return nil, fmt.Errorf("failed to retrieve sockets from systemd: %w", err)
}
numListeners := len(listeners)
if numListeners > 1 {
return nil, fmt.Errorf("received %d sockets from systemd, but only 1 is supported", numListeners)
}
if numListeners == 1 {
sockAddr := listeners[0].Addr()
if sockAddr.Network() == "tcp" {
addr = sockAddr.String()
} else {
addr = fmt.Sprintf("{%s:%s}", sockAddr.Network(), sockAddr.String())
}
return listeners[0], nil
}
// if we get to here, we got no sockets from systemd, so check -socket flag
if socket != "" {
if setGID != 0 || setUID != 0 {
return nil, fmt.Errorf("-setuid and -setgid options are not compatible with -socket. If you need to bind a socket as root but run webhook as a different user, consider using systemd activation")
}
addr = fmt.Sprintf("{unix:%s}", socket)
return net.Listen("unix", socket)
}
Expand Down
191 changes: 191 additions & 0 deletions vendor/github.com/coreos/go-systemd/v22/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vendor/github.com/coreos/go-systemd/v22/NOTICE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 98cf5d0

Please sign in to comment.