Skip to content

Commit a78a980

Browse files
authored
[Heartbeat] Setuid to regular user / lower capabilities when possible (elastic#27878)
partial fix for elastic#27648 , this PR: Detects if the user is running as root then: Checks to see if an environment variable BEAT_SETUID_AS (set in our Docker.tmpl) is present Attempts to Setuid , Setgid and Setgroups to that user / groups Invokes setcap to drop all privileges except NET_RAW+ep This PR also fixes the broken syscall filtering in heartbeat, some non-syscall strings were breaking that. With the changes here elastic-agent can still run as root, but the subprocesses can lower their privileges ASAP. This should also make it possible for heartbeat to safely run ICMP pings and synthetics. Synthetics must run as non-root, but ICMP requires NET_RAW. This lets us be consistent in our docs with the recommendation that elastic-agent run as root.
1 parent 058c405 commit a78a980

16 files changed

Lines changed: 1095 additions & 103 deletions

File tree

CHANGELOG.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ for a few releases. Please use other tools provided by Elastic to fetch data fro
325325

326326
- Fixed excessive memory usage introduced in 7.5 due to over-allocating memory for HTTP checks. {pull}15639[15639]
327327
- Fixed TCP TLS checks to properly validate hostnames, this broke in 7.x and only worked for IP SANs. {pull}17549[17549]
328+
- Fix broken seccomp filtering and improve security via `setcap` and `setuid` when running as root on linux in containers. {pull}27878[27878]
328329

329330
*Journalbeat*
330331

NOTICE.txt

Lines changed: 812 additions & 0 deletions
Large diffs are not rendered by default.

dev-tools/notice/overrides.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@
1111
{"name": "github.com/munnerz/goautoneg", "licenceType": "BSD-3-Clause"}
1212
{"name": "github.com/pelletier/go-buffruneio", "licenceType": "MIT"}
1313
{"name": "github.com/urso/magetools", "licenceType": "Apache-2.0"}
14+
{"name": "kernel.org/pub/linux/libs/security/libcap/cap", "licenceType": "BSD-3-Clause", "note": "dual licensed as GPL-v2 and BSD"}
15+
{"name": "kernel.org/pub/linux/libs/security/libcap/psx", "licenceType": "BSD-3-Clause", "note": "dual licensed as GPL-v2 and BSD"}

dev-tools/packaging/packages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ shared:
432432
dockerfile: 'Dockerfile.elastic-agent.tmpl'
433433
docker_entrypoint: 'docker-entrypoint.elastic-agent.tmpl'
434434
user: '{{ .BeatName }}'
435-
linux_capabilities: ''
435+
linux_capabilities: 'cap_net_raw+eip'
436436
image_name: ''
437437
files:
438438
'elastic-agent.yml':

dev-tools/packaging/templates/docker/Dockerfile.elastic-agent.tmpl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ RUN mkdir -p {{ $beatHome }}/data {{ $beatHome }}/data/elastic-agent-{{ commit_s
1717
rm {{ $beatBinary }} && \
1818
ln -s {{ $beatHome }}/data/elastic-agent-{{ commit_short }}/elastic-agent {{ $beatBinary }} && \
1919
chmod 0755 {{ $beatHome }}/data/elastic-agent-*/elastic-agent && \
20-
{{- if .linux_capabilities }}
21-
setcap {{ .linux_capabilities }} {{ $beatBinary }} && \
22-
{{- end }}
2320
{{- range $i, $modulesd := .ModulesDirs }}
2421
chmod 0775 {{ $beatHome}}/{{ $modulesd }} && \
2522
{{- end }}
@@ -30,11 +27,20 @@ RUN mkdir -p {{ $beatHome }}/data {{ $beatHome }}/data/elastic-agent-{{ commit_s
3027
{{- end }}
3128
true
3229

30+
{{- if .linux_capabilities }}
31+
# Since the beat is stored at the other end of a symlink we must follow the symlink first
32+
# For security reasons setcap does not support symlinks. This is smart in the general case
33+
# but in our specific case since we're building a trusted image from trusted binaries this is
34+
# fine. Thus, we use readlink to follow the link and setcap on the actual binary
35+
RUN readlink -f {{ $beatBinary }} | xargs setcap {{ .linux_capabilities }}
36+
{{- end }}
37+
3338
FROM {{ .from }}
3439

3540
# Contains the elastic agent image variant, an empty string for the standard variant
3641
# or "complete" for the bigger one.
3742
ENV ELASTIC_AGENT_IMAGE_VARIANT={{.Variant}}
43+
ENV BEAT_SETUID_AS={{ .user }}
3844

3945
{{- if contains .from "ubi-minimal" }}
4046
RUN for iter in {1..10}; do microdnf update -y && microdnf install -y shadow-utils jq && microdnf clean all && exit_code=0 && break || exit_code=$? && echo "microdnf error: retry $iter in 10s" && sleep 10; done; (exit $exit_code)

dev-tools/packaging/templates/docker/Dockerfile.tmpl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@ RUN mkdir -p {{ $beatHome }}/data {{ $beatHome }}/logs && \
1313
find {{ $beatHome }} -type d -exec chmod 0755 {} \; && \
1414
find {{ $beatHome }} -type f -exec chmod 0644 {} \; && \
1515
chmod 0755 {{ $beatBinary }} && \
16-
{{- if .linux_capabilities }}
17-
setcap {{ .linux_capabilities }} {{ $beatBinary }} && \
18-
{{- end }}
1916
{{- range $i, $modulesd := .ModulesDirs }}
2017
chmod 0775 {{ $beatHome}}/{{ $modulesd }} && \
2118
{{- end }}
2219
chmod 0775 {{ $beatHome }}/data {{ $beatHome }}/logs
2320

21+
{{- if .linux_capabilities }}
22+
# Since the beat is stored at the other end of a symlink we must follow the symlink first
23+
# For security reasons setcap does not support symlinks. This is smart in the general case
24+
# but in our specific case since we're building a trusted image from trusted binaries this is
25+
# fine. Thus, we use readlink to follow the link and setcap on the actual binary
26+
RUN readlink -f {{ $beatBinary }} | xargs setcap {{ .linux_capabilities }}
27+
{{- end }}
28+
2429
FROM {{ .from }}
2530

2631
{{- if contains .from "ubi-minimal" }}
@@ -127,6 +132,7 @@ USER {{ .user }}
127132
{{- if (and (eq .BeatName "heartbeat") (not (contains .from "ubi-minimal"))) }}
128133
# Setup synthetics env vars
129134
ENV ELASTIC_SYNTHETICS_CAPABLE=true
135+
ENV BEAT_SETUID_AS={{ .user }}
130136
ENV SUITES_DIR={{ $beatHome }}/suites
131137
ENV NODE_VERSION=14.17.5
132138
ENV PATH="$NODE_PATH/node/bin:$PATH"

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ require (
192192
k8s.io/api v0.21.1
193193
k8s.io/apimachinery v0.21.1
194194
k8s.io/client-go v0.21.1
195+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.57
195196
)
196197

197198
require (
@@ -283,6 +284,7 @@ require (
283284
k8s.io/klog/v2 v2.8.0 // indirect
284285
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect
285286
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
287+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 // indirect
286288
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
287289
sigs.k8s.io/yaml v1.2.0 // indirect
288290
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,10 @@ k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
13441344
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0=
13451345
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
13461346
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
1347+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 h1:2nmqI+aw7EQZuelYktkQHBE4jESD2tOR+lOJEnv/Apo=
1348+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.57/go.mod h1:uI99C3r4SXvJeuqoEtx/eWt7UbmfqqZ80H8q+9t/A7I=
1349+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 h1:NOFATXSf5z/cMR3HIwQ3Xrd3nwnWl5xThmNr5U/F0pI=
1350+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.57/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
13471351
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
13481352
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
13491353
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

heartbeat/beater/heartbeat.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package beater
2020
import (
2121
"errors"
2222
"fmt"
23+
"syscall"
2324
"time"
2425

2526
"github.com/elastic/beats/v7/heartbeat/config"
@@ -81,6 +82,9 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) {
8182
func (bt *Heartbeat) Run(b *beat.Beat) error {
8283
logp.Info("heartbeat is running! Hit CTRL-C to stop it.")
8384

85+
groups, _ := syscall.Getgroups()
86+
logp.Info("Effective user/group ids: %d/%d, with groups: %v", syscall.Geteuid(), syscall.Getegid(), groups)
87+
8488
stopStaticMonitors, err := bt.RunStaticMonitors(b)
8589
if err != nil {
8690
return err

heartbeat/scripts/mage/package.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func CustomizePackaging() {
4848
pkgType := args.Types[0]
4949
switch pkgType {
5050
case devtools.Docker:
51-
args.Spec.ExtraVar("linux_capabilities", "cap_net_raw=eip")
51+
args.Spec.ExtraVar("linux_capabilities", "cap_net_raw+eip")
5252
args.Spec.Files[monitorsDTarget] = monitorsD
5353
case devtools.TarGz, devtools.Zip:
5454
args.Spec.Files[monitorsDTarget] = monitorsD

0 commit comments

Comments
 (0)