DevSecOps
Wie man Sicherheitslücken verhindert, bevor sie entstehen
Am 01.06. wurde in der Kubernetes Security Announce group auf Google eine Sicherheitslücke unter dem Titel „IPv4 only clusters susceptible to MitM attacks via IPv6 rogue router advertisements“ veröffentlicht. Die CVE Nummer ist CVE-2020-10749. Diese Sicherheitslücke nutzt aus, dass viele aktuelle Programme und Bibliotheken zuerst versuchen, sich mittels IPv6 zu verbinden, bevor sie IPv4 verwenden. Indem IPv6 router advertisments verwendet werden, ist es möglich, den Datenverkehr des Hosts zu einem Pod umzuleiten und sich als Man-In-The-Middle zu positionieren, um zum Beispiel Passwörter und Schlüssel abzufangen. Die Sicherheitslücke ist nicht direkt in Kubernetes zu finden, sondern in der Bibliothek containernetworking-plugins.
Im folgenden gehen wir darauf ein, wie man diese Sicherheitslücke ausschalten kann und warum unsere Kubernetes Cluster garnicht erst anfällig waren.
Gegenmaßnahmen
1. Docker CAP_NET_RAW verbieten
Um seinen Cluster vor der Sicherheitslücke zu schützen, gibt es eine Reihe von möglichen Gegenmaßnahmen. Da die Sicherheitslücke besondere Privilegien in Docker ausnutzt, wäre eine mögliche Gegenmaßnahme, die CAP_NET_RAW Eigenschaft von Docker Containern zu verbieten. Diese gehört zum Standardumfang eines Docker Containers kann jedoch abgeschaltet werden. Hierfür kann man zum Beispiel den Kubernetes eigenen SecurityContext verwenden.
2. Kubernetes upgraden
Weiterhin gibt es mittlerweile gepatchte Versionen des Container Networking Interface Plugins, die mit neuen Kubernetes Versionen ausgerollt werden können:
- kubelet v1.18.4
- kubelet v1.17.7
- kubelet v1.16.11
Diese verhindern die Sicherheitslücke, in dem sie router advertisements auf den Interfaces verbieten.
3. router advertisements auf den Hosts verhindern
Die letzte Vermeidung der Schwachstelle ist das generelle Verbieten von router advertisements mittels Kernel Parameter net.ipv6.conf.all.accept_ra = 0. Dies ist die gleiche Vermeidung, wie sie letztlich auch im containernetworking-plugins angewendet wird, nur generell für den Host und nicht nur für das virtuelle Interface.
Konstanter Fokus auf Sicherheit: xKube
Unsere managed Kubernetes Lösung xKube war nie anfällig für diese Sicherheitslücke. Der Grund hierfür liegt im erfolgreichen Anwenden von DevSecOps Tools in unserer Delivery Pipeline und der Umsetzung von IT-Security Best Practices. Falls der Begriff DevSecOps #neuland ist, hier eine kleine Erklärung:
DevSecOps
Unter DevSecOps versteht man im allgemeinen eine Erweiterung des DevOps Ansatz um Best Practices aus der IT-Security. Hier geht es darum, die IT-Security im gesamten Lebenszyklus einer Anwendung oder eines IT-Systems zu verankern und die Agilität und Geschwindigkeit von DevOps zu nutzen, um schnell auf Sicherheitslücken und -vorfälle reagieren zu können.
DevSecOps in der Praxis
Bei der x-ion ist DevSecOps in die Deploy Pipeline unserer xKube Lösung integriert. Wir benutzen hier einen standardisierten Prozess, der es uns erlaubt, jede unserer Softwarekomponenten mit der gleichen Betriebssystembasis zu versehen.
Der Schritt Basis-Image erzeugen ist bei uns in gitlab als eigene Pipeline realisiert, die regelmäßig und automatisch ausgeführt wird, so dass wir stets ein Image mit aktuellen Softwarepaketen haben.
Im Job „Test E2e“ wird unser Images über DevSecOps Tools auf gute Security Praxis getestet. Hierfür verwenden wir unter anderem Chef Inspec™ um die Linux Baseline Tests via ansible-os-hardening auszuführen.
cloud image security test
$ source
"${CI_PROJECT_DIR}"
/packer/scripts/change_os_project.sh set
"${PROD_OS_PROJECT_ID}"
"${PROD_OS_PROJECT_NAME}"
"${PROD_OS_USERNAME}"
"${PROD_OS_PASSWORD}"
$ ip=$(openstack server create
"${HARDENED_IMAGE_PREFIX}-${IMAGE_NAME}-$(date +'%Y-%m-%d')_ensure"
--image
"${HARDENED_IMAGE_PREFIX}-${IMAGE_NAME}-$(date +'%Y-%m-%d')"
--flavor blc.
1
-
2
--security-group default --security-group
PACKER_SEC
--key-name packer-keypair --network ${
PACKER_NETWORK_ID
} --format value --column addresses --wait | cut -d= -f2)
$ sleep
60
$ cd
"${CI_PROJECT_DIR}/packer"
$ ./scripts/inspec.sh
"${ip}"
"${OS_VERSION}"
+ shared_ips='
<
RANDOM
-
IP
>'
+ os_version=
20
-
04
+ has_it_failed=
0
+
for
shared_ip
in
$shared_ips
+ ssh-keyscan -
H
<
RANDOM
-
IP
>
# :22 SSH-2.0-OpenSSH_8.2p1
# :22 SSH-2.0-OpenSSH_8.2p1
# :22 SSH-2.0-OpenSSH_8.2p1
+ inspec exec /builds/paas/managed-k8s/packer/inspec/controls/dev_sec_linux_baseline_xion -t ssh://ubuntu@<
RANDOM
-
IP
> --reporter cli --sudo --no-interactive --no-enable-telemetry --show-progress
.........................................*..................................................................
Profile: x-ion specific DevSec Linux Security Baseline (x-ion linux-baseline)
Version:
1
.
0
Target: ssh://ubuntu@<
RANDOM
-
IP
>:
22
No tests executed.
Profile: DevSec Linux Security Baseline (linux-baseline)
Version:
2
.
3
.
0
Target: ssh://ubuntu@<
RANDOM
-
IP
>:
22
✔ os-
01
: Trusted hosts login
✔
File
/etc/hosts.equiv is expected
not
to exist
✔ os-
02
: Check owner
and
permissions
for
/etc/shadow
✔
File
/etc/shadow is expected to exist
✔
File
/etc/shadow is expected to be file
✔
File
/etc/shadow is expected to be owned by
"root"
✔
File
/etc/shadow is expected
not
to be executable
✔
File
/etc/shadow is expected
not
to be readable by other
✔
File
/etc/shadow group is expected to eq
"shadow"
✔
File
/etc/shadow is expected to be writable by owner
✔
File
/etc/shadow is expected to be readable by owner
✔
File
/etc/shadow is expected to be readable by group
✔ os-
03
: Check owner
and
permissions
for
/etc/passwd
✔
File
/etc/passwd is expected to exist
✔
File
/etc/passwd is expected to be file
✔
File
/etc/passwd is expected to be owned by
"root"
✔
File
/etc/passwd is expected
not
to be executable
✔
File
/etc/passwd is expected to be writable by owner
✔
File
/etc/passwd is expected
not
to be writable by group
✔
File
/etc/passwd is expected
not
to be writable by other
✔
File
/etc/passwd is expected to be readable by owner
✔
File
/etc/passwd is expected to be readable by group
✔
File
/etc/passwd is expected to be readable by other
✔
File
/etc/passwd group is expected to eq
"root"
✔ os-
04
: Dot
in
PATH
variable
✔ Environment variable
PATH
split is expected
not
to include
""
✔ Environment variable
PATH
split is expected
not
to include
"."
✔ os-
05
: Check login.defs
✔
File
/etc/login.defs is expected to exist
✔
File
/etc/login.defs is expected to be file
✔
File
/etc/login.defs is expected to be owned by
"root"
✔
File
/etc/login.defs is expected
not
to be executable
✔
File
/etc/login.defs is expected to be readable by owner
✔
File
/etc/login.defs is expected to be readable by group
✔
File
/etc/login.defs is expected to be readable by other
✔
File
/etc/login.defs group is expected to eq
"root"
✔ login.defs
ENV_SUPATH
is expected to include
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
✔ login.defs
ENV_PATH
is expected to include
"/usr/local/bin:/usr/bin:/bin"
✔ login.defs
UMASK
is expected to include
"027"
✔ login.defs
PASS_MAX_DAYS
is expected to eq
"60"
✔ login.defs
PASS_MIN_DAYS
is expected to eq
"7"
✔ login.defs
PASS_WARN_AGE
is expected to eq
"7"
✔ login.defs
LOGIN_RETRIES
is expected to eq
"5"
✔ login.defs
LOGIN_TIMEOUT
is expected to eq
"60"
✔ login.defs
UID_MIN
is expected to eq
"1000"
✔ login.defs
GID_MIN
is expected to eq
"1000"
↺ os-05b: Check login.defs - RedHat specific
↺ Skipped control due to only_if condition.
✔ os-
06
: Check
for
SUID
/
SGID
blacklist
✔ suid_check diff is expected to be empty
✔ os-
08
: Entropy
✔
4066
is expected to >=
1000
✔ os-
09
: Check
for
.rhosts
and
.netrc file
✔ [] is expected to be empty
✔ os-
10
:
CIS
: Disable unused filesystems
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install cramfs /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install freevxfs /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install jffs2 /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install hfs /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install hfsplus /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install squashfs /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install udf /bin/true"
✔
File
/etc/modprobe.d/dev-sec.conf content is expected to match
"install vfat /bin/true"
✔ os-
11
: Protect log-directory
✔
File
/var/log is expected to be directory
✔
File
/var/log is expected to be owned by
"root"
✔
File
/var/log group is expected to match /^root|syslog$/
✔ package-
01
: Do
not
run deprecated inetd
or
xinetd
✔ System Package inetd is expected
not
to be installed
✔ System Package xinetd is expected
not
to be installed
✔ package-
02
: Do
not
install Telnet server
✔ System Package telnetd is expected
not
to be installed
✔ package-
03
: Do
not
install rsh server
✔ System Package rsh-server is expected
not
to be installed
✔ package-
05
: Do
not
install ypserv server (
NIS
)
✔ System Package ypserv is expected
not
to be installed
✔ package-
06
: Do
not
install tftp server
✔ System Package tftp-server is expected
not
to be installed
✔ package-
07
: Install syslog server package
✔ System Package rsyslog is expected to be installed
✔ package-
09
:
CIS
: Additional process hardening
✔ System Package prelink is expected
not
to be installed
✔ sysctl-
01
: IPv4 Forwarding
✔ Kernel Parameter net.ipv4.ip_forward value is expected to cmp ==
1
✔ Kernel Parameter net.ipv4.conf.all.forwarding value is expected to cmp ==
1
✔ sysctl-
02
: Reverse path filtering
✔ Kernel Parameter net.ipv4.conf.all.rp_filter value is expected to eq
1
✔ Kernel Parameter net.ipv4.conf.default.rp_filter value is expected to eq
1
✔ sysctl-
03
:
ICMP
ignore bogus error responses
✔ Kernel Parameter net.ipv4.icmp_ignore_bogus_error_responses value is expected to eq
1
✔ sysctl-
04
:
ICMP
echo ignore broadcasts
✔ Kernel Parameter net.ipv4.icmp_echo_ignore_broadcasts value is expected to eq
1
✔ sysctl-
05
:
ICMP
ratelimit
✔ Kernel Parameter net.ipv4.icmp_ratelimit value is expected to eq
100
✔ sysctl-
06
:
ICMP
ratemask
✔ Kernel Parameter net.ipv4.icmp_ratemask value is expected to eq
88089
✔ sysctl-
07
:
TCP
timestamps
✔ Kernel Parameter net.ipv4.tcp_timestamps value is expected to eq
0
✔ sysctl-
08
:
ARP
ignore
✔ Kernel Parameter net.ipv4.conf.all.arp_ignore value is expected to eq
1
✔ sysctl-
09
:
ARP
announce
✔ Kernel Parameter net.ipv4.conf.all.arp_announce value is expected to eq
2
✔ sysctl-
10
:
TCP
RFC1337
Protect Against
TCP
Time
-Wait
✔ Kernel Parameter net.ipv4.tcp_rfc1337 value is expected to eq
1
✔ sysctl-
11
: Protection against
SYN
flood attacks
✔ Kernel Parameter net.ipv4.tcp_syncookies value is expected to eq
1
✔ sysctl-
12
: Shared Media
IP
Architecture
✔ Kernel Parameter net.ipv4.conf.all.shared_media value is expected to eq
1
✔ Kernel Parameter net.ipv4.conf.default.shared_media value is expected to eq
1
✔ sysctl-
13
: Disable Source Routing
✔ Kernel Parameter net.ipv4.conf.all.accept_source_route value is expected to eq
0
✔ Kernel Parameter net.ipv4.conf.default.accept_source_route value is expected to eq
0
✔ sysctl-
14
: Disable acceptance of all IPv4 redirected packets
✔ Kernel Parameter net.ipv4.conf.default.accept_redirects value is expected to eq
0
✔ Kernel Parameter net.ipv4.conf.all.accept_redirects value is expected to eq
0
✔ sysctl-
15
: Disable acceptance of all secure redirected packets
✔ Kernel Parameter net.ipv4.conf.all.secure_redirects value is expected to eq
0
✔ Kernel Parameter net.ipv4.conf.default.secure_redirects value is expected to eq
0
✔ sysctl-
16
: Disable sending of redirects packets
✔ Kernel Parameter net.ipv4.conf.default.send_redirects value is expected to eq
0
✔ Kernel Parameter net.ipv4.conf.all.send_redirects value is expected to eq
0
✔ sysctl-
17
: Disable log martians
✔ Kernel Parameter net.ipv4.conf.all.log_martians value is expected to eq
1
✔ Kernel Parameter net.ipv4.conf.default.log_martians value is expected to eq
1
✔ sysctl-
18
: Disable IPv6
if
it is
not
needed
✔ Kernel Parameter net.ipv6.conf.all.disable_ipv6 value is expected to cmp ==
0
✔ sysctl-
19
: IPv6 Forwarding
✔ Kernel Parameter net.ipv6.conf.all.forwarding value is expected to cmp ==
1
✔ sysctl-
20
: Disable acceptance of all IPv6 redirected packets
✔ Kernel Parameter net.ipv6.conf.default.accept_redirects value is expected to eq
0
✔ Kernel Parameter net.ipv6.conf.all.accept_redirects value is expected to eq
0
✔ sysctl-
21
: Disable acceptance of IPv6 router solicitations messages
✔ Kernel Parameter net.ipv6.conf.default.router_solicitations value is expected to eq
0
✔ sysctl-
22
: Disable Accept Router Preference from router advertisement
✔ Kernel Parameter net.ipv6.conf.default.accept_ra_rtr_pref value is expected to eq
0
✔ sysctl-
23
: Disable learning Prefix Information from router advertisement
✔ Kernel Parameter net.ipv6.conf.default.accept_ra_pinfo value is expected to eq
0
✔ sysctl-
24
: Disable learning Hop limit from router advertisement
✔ Kernel Parameter net.ipv6.conf.default.accept_ra_defrtr value is expected to eq
0
✔ sysctl-
25
: Disable the system`s acceptance of router advertisement
✔ Kernel Parameter net.ipv6.conf.all.accept_ra value is expected to eq
0
✔ Kernel Parameter net.ipv6.conf.default.accept_ra value is expected to eq
0
✔ sysctl-
26
: Disable IPv6 autoconfiguration
✔ Kernel Parameter net.ipv6.conf.default.autoconf value is expected to eq
0
✔ sysctl-
27
: Disable neighbor solicitations to send out per address
✔ Kernel Parameter net.ipv6.conf.default.dad_transmits value is expected to eq
0
✔ sysctl-
28
: Assign one global unicast IPv6 addresses to
each
interface
✔ Kernel Parameter net.ipv6.conf.default.max_addresses value is expected to eq
1
✔ sysctl-
29
: Disable loading kernel modules
✔ Kernel Parameter kernel.modules_disabled value is expected to eq
0
✔ sysctl-
30
: Magic SysRq
✔ Kernel Parameter kernel.sysrq value is expected to eq
0
✔ sysctl-31a: Secure Core Dumps - dump settings
✔ Kernel Parameter fs.suid_dumpable value is expected to cmp == /(
0
|
2
)/
✔ sysctl-31b: Secure Core Dumps - dump path
✔ Kernel Parameter kernel.core_pattern value is expected to match /^\|?\/.*/
✔ sysctl-
32
: kernel.randomize_va_space
✔ Kernel Parameter kernel.randomize_va_space value is expected to eq
2
✔ sysctl-
33
:
CPU
No execution Flag
or
Kernel ExecShield
✔ /proc/cpuinfo Flags should include
NX
Profile Summary:
51
successful controls,
0
control failures,
1
control skipped
Test Summary:
107
successful,
0
failures,
1
skipped
Wichtig für die Sicherheitslücke CVE-2020-10749 in Kubernetes ist hier der Control sysctl-22: "Disable Accept Router Preference from router advertisement". Dieser prüft, ob router advertisments deaktiviert sind.
Das die Werte richtig gesetzt werden erledigt die Rolle ansible-os-hardening:
variables.yaml
# role/ansible-os-hardening
ufw_manage_defaults:
false
os_auth_pw_max_age:
60
os_security_kernel_enable_sysrq:
false
sysctl_config:
net.ipv6.conf.default.autoconf:
0
net.ipv6.conf.default.accept_ra_defrtr:
0
net.ipv6.conf.default.accept_ra_pinfo:
0
net.ipv6.conf.default.accept_ra_rtr_pref:
0
net.ipv6.conf.default.router_solicitations:
0
net.ipv6.conf.all.accept_redirects:
0
net.ipv6.conf.default.accept_redirects:
0
net.ipv6.conf.all.disable_ipv6:
0
net.ipv4.conf.default.log_martians:
1
net.ipv4.conf.all.log_martians:
1
net.ipv4.conf.all.send_redirects:
0
net.ipv4.conf.default.send_redirects:
0
net.ipv4.conf.default.secure_redirects:
0
net.ipv4.conf.all.secure_redirects:
0
net.ipv4.conf.all.accept_redirects:
0
net.ipv4.conf.default.accept_redirects:
0
net.ipv4.conf.default.accept_source_route:
0
net.ipv6.conf.default.dad_transmits:
0
net.ipv6.conf.all.forwarding:
1
net.ipv6.conf.default.max_addresses:
1
net.ipv6.conf.all.accept_ra:
0
net.ipv6.conf.default.accept_ra:
0
kernel.sysrq:
0
net.ipv4.ip_forward:
1
net.ipv4.conf.all.forwarding:
1
net.ipv4.icmp_ratelimit:
100
net.ipv4.icmp_ratemask:
88089
net.ipv4.tcp_timestamps:
0
net.ipv4.conf.all.arp_ignore:
1
net.ipv4.conf.all.arp_announce:
2
net.ipv4.tcp_rfc1337:
1
net.ipv4.conf.all.rp_filter:
1
net.ipv4.conf.default.rp_filter:
1
Diese Rolle wird über das harden playbook per Ansible run angewendet:
harden.yaml
---
- hosts: localhost
gather_facts:
true
become:
true
vars_files:
-
"./variables.yaml"
roles:
- role: ansible-os-hardening
tags: ansible-os-hardening
- role: ansible-ssh-hardening
tags: ansible-ssh-hardening
tasks:
- name:
"Adopt sshd config to use supported MACs"
lineinfile:
path:
"/etc/ssh/sshd_config"
regexp:
"^MACs .+"
line:
"MACs {{ sshd_macs| join(',') }}"
- name:
"Install haveged - increase Entropy"
apt:
name: haveged
state:
'present'
- name:
"Keep entropy near 4096"
replace:
path: /etc/default/haveged
regexp:
'1024'
replace:
'4096'
Fazit
Wie man sieht, ist das Auftreten der Sicherheitslücke für ein Unternehmen, welches gängige Security Best Practices einsetzt, von vornherein unterbunden. Die Security Community ist sehr aktiv und über den DevSecOps Ansatz steht jedem die Möglichkeit offen, mit einfachen Mitteln Security Empfehlungen auf seinem eigenen System zu prüfen und anzuwenden.