From 35c8250d80973c7623706c80e78d2a519256e242 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Wed, 18 Dec 2019 21:54:32 -0300 Subject: [PATCH] add security options Add options to: * Change the process UID to 1001 / username `haproxy` * Performs a chroot() in an empty and non writable directory Other notable changes: * Change the permissions in the file system to allow start the container as UID 1001 * Change the permission of the admin stats socket to `600` --- docs/content/en/docs/configuration/keys.md | 35 +++++++++++++++++++ pkg/common/ingress/controller/launch.go | 6 ++-- pkg/converters/ingress/annotations/updater.go | 2 ++ pkg/converters/ingress/types/global.go | 2 ++ pkg/haproxy/instance_test.go | 6 ++-- pkg/haproxy/types/types.go | 2 ++ rootfs/Dockerfile | 9 +++-- rootfs/etc/haproxy/template/haproxy.tmpl | 9 ++++- 8 files changed, 62 insertions(+), 9 deletions(-) diff --git a/docs/content/en/docs/configuration/keys.md b/docs/content/en/docs/configuration/keys.md index d8b7e065e..6e0e2478d 100644 --- a/docs/content/en/docs/configuration/keys.md +++ b/docs/content/en/docs/configuration/keys.md @@ -227,6 +227,8 @@ The table below describes all supported configuration keys. | [`timeout-stop`](#timeout) | time with suffix | Global | no timeout | | [`timeout-tunnel`](#timeout) | time with suffix | Backend | `1h` | | [`tls-alpn`](#tls-alpn) | TLS ALPN advertisement | Global | `h2,http/1.1` | +| [`use-chroot`](#security) | [true\|false] | Global | `false` | +| [`use-haproxy-user`](#security) | [true\|false] | Global | `false` | | [`use-htx`](#use-htx) | [true\|false] | Global | `false` | | [`use-proxy-protocol`](#proxy-protocol) | [true\|false] | Global | `false` | | [`use-resolver`](#dns-resolvers) | resolver name | Backend | | @@ -1251,6 +1253,39 @@ See also: --- +## Security + +| Configuration key | Scope | Default | Since | +|--------------------|----------|---------|-------| +| `use-chroot` | `Global` | `false` | v0.9 | +| `use-haproxy-user` | `Global` | `false` | v0.9 | + +Change security options. + +* `use-chroot`: If `true`, configures haproxy to perform a `chroot()` in the empty and non-writable directory `/var/empty` during the startup process, just before it drops its own privileges. Only root can perform a `chroot()`, so HAProxy Ingress container should start as UID `0` if this option is configured as `true`. +* `use-haproxy-user`: Defines if the haproxy's process should be changed to `haproxy`, UID `1001`. The default value `false` leaves haproxy running as root. Note that even running as root, haproxy always drops its own privileges before start its event loop. + +In the default configuration, HAProxy Ingress container starts as root. It is also possible to configure the container to start as `haproxy` user, UID `1001`. Read the [Security considerations](http://cbonte.github.io/haproxy-dconv/1.9/management.html#13) from HAProxy doc before change the starting user. The starting user can be changed in the deployment or daemonset's pod template using the following configuration: + +```yaml +... + template: + spec: + securityContext: + runAsUser: 1001 +``` + +Note that ports below 1024 cannot be bound if the container starts as non-root. + +See also: + +* http://cbonte.github.io/haproxy-dconv/1.9/management.html#13 +* http://cbonte.github.io/haproxy-dconv/1.9/configuration.html#3.1-chroot +* http://cbonte.github.io/haproxy-dconv/1.9/configuration.html#3.1-uid +* http://cbonte.github.io/haproxy-dconv/1.9/configuration.html#3.1-gid + +--- + ## Server alias | Configuration key | Scope | Default | Since | diff --git a/pkg/common/ingress/controller/launch.go b/pkg/common/ingress/controller/launch.go index 9a158c4e2..de27190b1 100644 --- a/pkg/common/ingress/controller/launch.go +++ b/pkg/common/ingress/controller/launch.go @@ -261,15 +261,15 @@ func NewIngressController(backend ingress.Controller) *GenericController { glog.Fatalf("resync period (%vs) is too low", resyncPeriod.Seconds()) } - err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655) + err = os.MkdirAll(ingress.DefaultSSLDirectory, 0755) if err != nil { glog.Fatalf("Failed to mkdir SSL directory: %v", err) } - err = os.MkdirAll(ingress.DefaultCACertsDirectory, 0655) + err = os.MkdirAll(ingress.DefaultCACertsDirectory, 0755) if err != nil { glog.Fatalf("Failed to mkdir cacerts directory: %v", err) } - err = os.MkdirAll(ingress.DefaultCrlDirectory, 0655) + err = os.MkdirAll(ingress.DefaultCrlDirectory, 0755) if err != nil { glog.Fatalf("Failed to mkdir crl directory: %v", err) } diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index 42a4020ea..e13229a1e 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -112,6 +112,8 @@ func (c *updater) UpdateGlobalConfig(haproxyConfig haproxy.Config, mapper *Mappe d.global.LoadServerState = mapper.Get(ingtypes.GlobalLoadServerState).Bool() d.global.SSL.ALPN = mapper.Get(ingtypes.GlobalTLSALPN).Value d.global.StrictHost = mapper.Get(ingtypes.GlobalStrictHost).Bool() + d.global.UseChroot = mapper.Get(ingtypes.GlobalUseChroot).Bool() + d.global.UseHAProxyUser = mapper.Get(ingtypes.GlobalUseHAProxyUser).Bool() d.global.UseHTX = mapper.Get(ingtypes.GlobalUseHTX).Bool() c.buildGlobalAcme(d) c.buildGlobalBind(d) diff --git a/pkg/converters/ingress/types/global.go b/pkg/converters/ingress/types/global.go index 80ab91a27..b5c65aa64 100644 --- a/pkg/converters/ingress/types/global.go +++ b/pkg/converters/ingress/types/global.go @@ -80,6 +80,8 @@ const ( GlobalTCPLogFormat = "tcp-log-format" GlobalTimeoutStop = "timeout-stop" GlobalTLSALPN = "tls-alpn" + GlobalUseChroot = "use-chroot" + GlobalUseHAProxyUser = "use-haproxy-user" GlobalUseHTX = "use-htx" GlobalUseProxyProtocol = "use-proxy-protocol" ) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 07909a162..732376d50 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -652,7 +652,7 @@ func TestInstanceEmpty(t *testing.T) { c.checkConfig(` global daemon - stats socket /var/run/haproxy.sock level admin expose-fd listeners + stats socket /var/run/haproxy.sock level admin expose-fd listeners mode 600 maxconn 2000 hard-stop-after 15m lua-load /usr/local/etc/haproxy/lua/auth-request.lua @@ -2065,7 +2065,7 @@ func TestInstanceSyslog(t *testing.T) { c.checkConfig(` global daemon - stats socket /var/run/haproxy.sock level admin expose-fd listeners + stats socket /var/run/haproxy.sock level admin expose-fd listeners mode 600 maxconn 2000 hard-stop-after 15m log 127.0.0.1:1514 len 2048 format rfc3164 local0 @@ -2957,7 +2957,7 @@ func (c *testConfig) checkConfig(expected string) { replace := map[string]string{ "<>": `global daemon - stats socket /var/run/haproxy.sock level admin expose-fd listeners + stats socket /var/run/haproxy.sock level admin expose-fd listeners mode 600 maxconn 2000 hard-stop-after 15m lua-load /usr/local/etc/haproxy/lua/auth-request.lua diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 1ed9cef74..9b8e17473 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -55,6 +55,8 @@ type Global struct { Healthz HealthzConfig Stats StatsConfig StrictHost bool + UseChroot bool + UseHAProxyUser bool UseHTX bool CustomConfig []string CustomDefaults []string diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile index 16a6f2511..3ec0fed97 100644 --- a/rootfs/Dockerfile +++ b/rootfs/Dockerfile @@ -13,10 +13,15 @@ # limitations under the License. FROM haproxy:1.9.13-alpine -RUN apk --no-cache add socat openssl lua5.3 lua-socket dumb-init \ - && mkdir -p /ingress-controller /etc/haproxy/maps +RUN apk --no-cache add socat openssl lua5.3 lua-socket dumb-init COPY . / +RUN addgroup -g 1001 haproxy\ + && adduser -u 1001 -G haproxy -D -s /bin/false haproxy\ + && mkdir -p /var/empty /var/lib/haproxy /etc/haproxy/maps /ingress-controller\ + && chown -R haproxy:haproxy /run /var/lib/haproxy /etc/haproxy /ingress-controller\ + && chmod 0 /var/empty + STOPSIGNAL SIGTERM ENTRYPOINT ["/usr/bin/dumb-init", "--", "/start.sh"] diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 4a23ab3d3..4106e1697 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -10,6 +10,13 @@ {{- $global := $cfg.Global }} global daemon +{{- if $global.UseHAProxyUser }} + user haproxy + group haproxy +{{- end }} +{{- if $global.UseChroot }} + chroot /var/empty +{{- end }} {{- if gt $global.Procs.Nbproc 1 }} nbproc {{ $global.Procs.Nbproc }} {{- end }} @@ -19,7 +26,7 @@ global {{- if $global.Procs.CPUMap }} cpu-map {{ $global.Procs.CPUMap }} {{- end }} - stats socket {{ default "--" $global.AdminSocket }} level admin expose-fd listeners + stats socket {{ default "--" $global.AdminSocket }} level admin expose-fd listeners mode 600 {{- if gt $global.Procs.Nbproc 1 }} process 1{{ end }} {{- if $global.LoadServerState }} server-state-file state-global