Skip to content

Commit 98cf5d0

Browse files
authored
Add support for systemd socket activation (#704)
* 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.
1 parent 9cd78fc commit 98cf5d0

File tree

13 files changed

+529
-5
lines changed

13 files changed

+529
-5
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ Note that when running in this mode the [`ip-whitelist`](docs/Hook-Rules.md#matc
117117
## CORS Headers
118118
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.
119119

120+
## Running under `systemd`
121+
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.
122+
120123
## Interested in running webhook inside of a Docker container?
121124
You can use one of the following Docker images, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)):
122125
- [almir/webhook](https://github.com/almir/docker-webhook)

docs/Systemd-Activation.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Using systemd socket activation
2+
3+
_New in v2.9.0_
4+
5+
On platforms that use [systemd](https://systemd.io), [webhook][w]
6+
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:
7+
8+
- [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
9+
- 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
10+
11+
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.
12+
13+
## Configuration
14+
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:
15+
16+
**webhook.socket**
17+
```
18+
[Unit]
19+
Description=Webhook server socket
20+
21+
[Socket]
22+
# Listen on all network interfaces, port 9000
23+
ListenStream=9000
24+
25+
# Alternatives:
26+
27+
## Listen on one specific interface only
28+
# ListenStream=10.0.0.1:9000
29+
# FreeBind=true
30+
31+
## Listen on a Unix domain socket
32+
# ListenStream=/tmp/webhook.sock
33+
34+
[Install]
35+
WantedBy=multi-user.target
36+
```
37+
38+
**webhook.service**
39+
```
40+
[Unit]
41+
Description=Webhook server
42+
43+
[Service]
44+
Type=exec
45+
ExecStart=webhook -nopanic -hooks /etc/webhook/hooks.yml
46+
47+
# Which user should the webhooks run as?
48+
User=nobody
49+
Group=nogroup
50+
```
51+
52+
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.
53+
54+
```sh
55+
sudo systemctl enable webhook.socket
56+
sudo systemctl start webhook.socket
57+
```
58+
59+
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.
60+
61+
[w]: https://github.com/adnanh/webhook

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ toolchain go1.22.0
77
require (
88
github.com/Microsoft/go-winio v0.6.2
99
github.com/clbanning/mxj/v2 v2.7.0
10+
github.com/coreos/go-systemd/v22 v22.5.0
1011
github.com/dustin/go-humanize v1.0.1
1112
github.com/fsnotify/fsnotify v1.7.0
1213
github.com/ghodss/yaml v1.0.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
22
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
33
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
44
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
5+
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
6+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
57
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
68
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
79
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -10,6 +12,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
1012
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
1113
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
1214
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
15+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
1316
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
1417
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
1518
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=

platform_unix.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,40 @@ package main
66
import (
77
"flag"
88
"fmt"
9+
"github.com/coreos/go-systemd/v22/activation"
910
"net"
1011
)
1112

1213
func platformFlags() {
1314
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")
15+
flag.IntVar(&setGID, "setgid", 0, "set group ID after opening listening port; must be used with setuid, not permitted with -socket")
16+
flag.IntVar(&setUID, "setuid", 0, "set user ID after opening listening port; must be used with setgid, not permitted with -socket")
1417
}
1518

1619
func trySocketListener() (net.Listener, error) {
20+
// first check whether we have any sockets from systemd
21+
listeners, err := activation.Listeners()
22+
if err != nil {
23+
return nil, fmt.Errorf("failed to retrieve sockets from systemd: %w", err)
24+
}
25+
numListeners := len(listeners)
26+
if numListeners > 1 {
27+
return nil, fmt.Errorf("received %d sockets from systemd, but only 1 is supported", numListeners)
28+
}
29+
if numListeners == 1 {
30+
sockAddr := listeners[0].Addr()
31+
if sockAddr.Network() == "tcp" {
32+
addr = sockAddr.String()
33+
} else {
34+
addr = fmt.Sprintf("{%s:%s}", sockAddr.Network(), sockAddr.String())
35+
}
36+
return listeners[0], nil
37+
}
38+
// if we get to here, we got no sockets from systemd, so check -socket flag
1739
if socket != "" {
40+
if setGID != 0 || setUID != 0 {
41+
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")
42+
}
1843
addr = fmt.Sprintf("{unix:%s}", socket)
1944
return net.Listen("unix", socket)
2045
}

vendor/github.com/coreos/go-systemd/v22/LICENSE

Lines changed: 191 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/coreos/go-systemd/v22/NOTICE

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)