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.

$ 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 SUIDSGID 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-10CIS: 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-09CIS: 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-03ICMP ignore bogus error responses
      ✔  Kernel Parameter net.ipv4.icmp_ignore_bogus_error_responses value is expected to eq 1
   ✔  sysctl-04ICMP echo ignore broadcasts
      ✔  Kernel Parameter net.ipv4.icmp_echo_ignore_broadcasts value is expected to eq 1
   ✔  sysctl-05ICMP ratelimit
      ✔  Kernel Parameter net.ipv4.icmp_ratelimit value is expected to eq 100
   ✔  sysctl-06ICMP ratemask
      ✔  Kernel Parameter net.ipv4.icmp_ratemask value is expected to eq 88089
   ✔  sysctl-07TCP timestamps
      ✔  Kernel Parameter net.ipv4.tcp_timestamps value is expected to eq 0
   ✔  sysctl-08ARP ignore
      ✔  Kernel Parameter net.ipv4.conf.all.arp_ignore value is expected to eq 1
   ✔  sysctl-09ARP announce
      ✔  Kernel Parameter net.ipv4.conf.all.arp_announce value is expected to eq 2
   ✔  sysctl-10TCP 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-33CPU 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:

# 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:

---
- 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.

  • Neuigkeiten von x-ion
  • Produkte & Services
  • Datenschutz & IT-Sicherheit