Cilium

Mit Cilium steht Kubernetes ein mit von Google vorangetriebenes CNI zur Verfügung. Das Projekt Cilium nutzt eBPF Filter im Linux Kernel, um Kubernetes Network Policies effizient und performant umzusetzen. Durch die Verwendung von eBPF ergibt sich ein weiterer Vorteil. Es lassen sich alle Verbindungungen im Cluster tracken. Dieses kann mit Hubble UI im Browser visualisiert werden. Dadurch kann man schnell einen Überblick über die bestehenden Verbindungen erlangen und ggf. eingreifen, wenn unerwünschter Traffic erkannt wird.

Hubble

Für die Visualisierung der Verbidnungen im Browser kommt Hubble UI zu Einsatz. Zunächst muss man den gewünschten Namespace wählen und schon wird ein Graph gezeichnet, der die Verbindungen im Browser anzeigt. Es wird ein gerichteter Graph mit den Abhängigkeiten der Pods erzeugt und auch eine Tabellarische übersicht geboten. Mit klick auf einen Pod kann eine Filterung gesetzt werden. So lässt sich schnell der angezeigte Traffic filtern und analysieren.

Setup K3D Cluster

Für das Beispiel verwende ich K3D, um einen Demo Cluster zu erstellen. Dieses geht wie immer schnell von der Hand.

Konfiguration

Zunächst definieren wir den Clusternamen, die Anzahl der Worker und den API Port als Environment Variablen, um sie später zu nutzen:

export CLUSTERNAME=cilium-demo
export CLUSTERWORKER=2
export CLUSTER_API_PORT=6443

Cluster anlegen

Nun können wir nach der Konfiguration den Cluster anlegen.

k3d cluster create $CLUSTERNAME \
-a $CLUSTERWORKER \
--api-port=$CLUSTER_API_PORT \
--k3s-server-arg "--disable=servicelb" \
--k3s-server-arg "--disable=traefik"  \
--no-lb \
--k3s-server-arg "--disable-network-policy" \
--k3s-server-arg "--flannel-backend=none" \
--image rancher/k3s:v1.20.5-alpha1-k3s2

Hinweis: Um Cilium zu nutzen müssen der LoadBalancer und Traefik nicht disabled werden. Die Komponenten habe ich nur aus Performancegründen ausgeschaltet, damit der Cluster schneller hochfährt. Sie sind für die Demo nicht relevant. Wichtig ist, dass mit –flannel-backend=none kein CNI Plugin automatisch installiert und das über –disable-network-policy der Defautl Network Policy Controller nicht geladen wird.

Korrektur der Initialisierung von Cilium

Da K3S keine Bash unter /bin/bash vorhält, funktioniert die Initialisierung von Cilium nicht korrekt. Daher muss hier manuell eingegriffen werden, um BPF Filter zu kompilieren.

for AGENT in $(seq 0 ${CLUSTERWORKER+1} 1)
do
  docker exec -it k3d-$CLUSTERNAME-agent-$AGENT mount bpffs /sys/fs/bpf -t bpf
  docker exec -it k3d-$CLUSTERNAME-agent-$AGENT mount --make-shared /sys/fs/bpf
done

docker exec -it k3d-$CLUSTERNAME-server-0 mount bpffs /sys/fs/bpf -t bpf
docker exec -it k3d-$CLUSTERNAME-server-0 mount --make-shared /sys/fs/bpf

Die shared mount müssen auf allen Nodes (agents) und Server ausgeführt werden. Dazu muss ggf. der Server Block angepasst werden. Je nach dem wieviele Agents und Server ihr beim Erstellen des Clusters angegeben habt.

Cilium und Hubble installieren

Die Installation von Cilium und Hubble ist eigentlich trivial.

helm repo add cilium https://helm.cilium.io/

helm install cilium cilium/cilium --version 1.9.5 \
   --namespace kube-system \
   --set kubeProxyReplacement=disabled \
   --set debug.enabled=true \
   --set hostServices.enabled=false \
   --set externalIPs.enabled=true \
   --set nodePort.enabled=true \
   --set hostPort.enabled=true \
   --set bpf.masquerade=false \
   --set image.pullPolicy=IfNotPresent \
   --set ipam.mode=kubernetes

helm upgrade cilium cilium/cilium --version 1.9.5 \
   --namespace kube-system \
   --reuse-values \
   --set hubble.listenAddress=":4244" \
   --set hubble.relay.enabled=true \
   --set hubble.ui.enabled=true

Nun watch warten bis alle Pods up and running sind

watch kubectl get pods -n kube-system

Hinweise zum Parameter KubeProxyReplacement

In den Blogeinträgen die man zum Thema Cilium und K3D im Internewt findet beschreiben immer das der Parameter kubeProxyReplacement auf partial stehen soll. Damit lies sich der Cluster aber nicht zum Laufen bewegen. Nicht einmal CoreDNS konnte mit der Einstellung deployt werden. Mit der Einstellung –set kubeProxyReplacement=disabled hingegen fährt der Cluster hoch und alles funtkioniert.

Port-Forwarding

Um Hubble im Browser zu öffnen muss noch ein Port-Forwarding eingerichtet werden. Dann kann unter http://localhost:12000/kube-system aufgerufen werden.

kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 --address :: 12000:80    

Demo Applications installieren

Das Projekt Cilium bietet eine Demoanwendung, mit der man schnell erste Tests durchführen kann. Die Anwendungen sind einfach deployt…

# create namespace
kubectl create namespace cilium-test

# deploy demo apps
kubectl apply -n cilium-test -f https://raw.githubusercontent.com/cilium/cilium/v1.9/examples/kubernetes/connectivity-check/connectivity-check.yaml

Jetzt kann man in der UI zuschauen, wie sich der Traffic ändert und die verschiedenen Verbindungen in der UI analysieren.

Network Policies überprüfen

Wenn man eine neue Regel für Kubernetes Network Policies erstellt hat, dann taucht bestimmt die Frage der Wirksamkeit auf. Auch dieses lässt sich schön über die Oberfläche der Hubble UI darstellen. Dazu muss man oben in der Toolbar

die Auswahl auf Dropped ändern.

Trivy

Trivy ist ein kleines in GO geschriebenes Programm. Die Installation unter Manjaro oder Arch erfolgt mir yay und ist schnell erledigt.

yay -S --noconfirm trivy

Was macht Trivy?

Trivy ist ein Securityscanner und untersucht Docker Images auf mögliche CVEs und gibt das Ergebnis in tabellarischer Form auf der Konsole aus.

Der Erste Check

Der Erster Check eines Images dauer in der Regel etwas länger, da die Datenbank zunächst aktualisiert werden muss.

2020-09-03T08:16:52.070+0200    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-09-03T08:16:52.088+0200    INFO    Need to update DB
2020-09-03T08:16:52.088+0200    INFO    Downloading DB...
18.25 MiB / 18.25 MiB [-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 2.38 MiB p/s 8s

Auch ein folgender Scan sollte schneller ablaufen, da Trivy die Scanergebnisse einzelner Layer in dem Docker Image im Cache zwischenspeichert.

Das Secret Management System Vault

Vault ermöglicht das verteilte Arbeiten mit Geheimnissen. Diese können über AppRoles und ACLs geschützt werden, was Vault sehr flexibel macht.

Hier zeige ich wie man aus dem internen KV Store in der Version 2 Daten abrufen kann. Der Aufbau ist wie folgt: Es gibt einen Mountpoint administrator in dem ein KV2 Secretstore gemountet ist. In diesem werden Passswörter für Benutzer auf Systemen abgelegt. Damit eine Sortierung einfacher wird, ist eine weitere Ebene im Pfad ORT vorhanden. Der Server heißt server und das Geheimnis password.

Die neuste Version eines Secrets ermitteln

Da der KV2 gegenüber der ersten Version nun über eine Versionierung der Geheimnisse verfügt, muss man nun auch immer die Version abgeben die man abrufen möchte. Dazu kann man aus den Metadaten die neuste Versionsnummer ermitteln.

Allgemeiner Abruf

Die Metadaten sind unterhalb des Mountpoint unter /metadata zu finden. Der Pfad zum Geheimnis muss zusätzlich angehangen werden.

curl -H "X-Vault-Token:s.XXX" -X GET https://vault.XXX.org:443/v1/administrator/metadata/ORT/server/password

Als Ausgabe erhält man ein JSON welches die Metadaten enthält. In versions sind die aktuellen Versionen zu finden. Hier im Beispiel also 1.

{
    "request_id": "680ec30e-3013-6416-8ee8-2937f694b5e2",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
    "cas_required": false,
    "created_time": "2020-03-08T08:51:56.895667901Z",
    "current_version": 1,
    "delete_version_after": "0s",
    "max_versions": 0,
    "oldest_version": 0,
    "updated_time": "2020-03-08T08:51:56.895667901Z",
    "versions": {
        "1": {
        "created_time": "2020-03-08T08:51:56.895667901Z",
        "deletion_time": "",
        "destroyed": false
        }
    }
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
}

Mit JQ die Version filtern

Mit JQ kann mit die Information recht einfach herausfiltern.

curl -H "X-Vault-Token:s.XXX" -X GET https://vault.XXX.org:443/v1/administrator/metadata/ORT/server/password | jq '.data.versions | keys[0]'

"1"

KV2 Value auslesen

Wenn man die aktuelle Version bestimmt hat, dann kann man die Version eines Geheimisses auslesen. Die Daten befinden sich unterhalb /data.

curl -H "X-Vault-Token:s.XXX" -X GET https://vault.mrpeacock.duckdns.org:443/v1/administrator/data/ORT/server/password?version=1 | jq '.'

Als Antwort erhält man auch hier ein JSON.

{
    "request_id": "4b7613ef-c752-f8b7-dc94-aecd0999d1c0",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
    "data": {
        "password": "admin123"
    },
    "metadata": {
        "created_time": "2020-03-08T08:51:56.895667901Z",
        "deletion_time": "",
        "destroyed": false,
        "version": 1
    }
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
}

Value mit JQ herausfiltern

Auch hier können wir JQ zur Hilfe nehmen und direkt das Passwort auslesen. Die Informationen stecken, Achtung in data.data. Siehe JSON.

curl -H "X-Vault-Token:s.XXX" -X GET https://vault.XXX.org:443/v1/administrator/data/ORT/server/password?version=1 | jq '.data.data.password'

JQ gibt dann das Passwort aus:

"admin123"

HashiCorp Vault

In diesem Artikel zeige ich wie man HashiCorp Vault hinter einem Reverse Proxy (Nginx) aufsetzt.

Was ist HashiCorp Vault?

Ist eine Secret Management, Encryption as a Service Anwendung von der Amerikanischen Firma HashiCorp.

Installation von Vault mit Docker

Für diesen Artikel setze ich eine funktionieren Docker und NginX Umgebung voraus und gehe nicht weiter auf dessen Konfiguration im Vorfeld ein.

Das Docker-Compose Skript

Ich empfehle die Volumes immer in der nähe des Docker-Compose Skriptes zu halten, so kann alles auf einmal in einem Backup gesichert werden und man hat einen schnelleren überblick was in den Container gemountet wird. Der Container wird Vault auf dem Port 9000 exposen, sodass wir diesen später in der NginX Konfiguration angeben müssen. Der Port kann natürlich beliebig gewählt werden.

version: '2'
services:
  vault:
    image: vault
    container_name: vault
    ports:
      - "9000:9000"
    restart: always
    volumes:
      - ./volumes/logs:/vault/logs
      - ./volumes/file:/vault/file
      - ./volumes/config:/vault/config
    cap_add:
      - IPC_LOCK
    entrypoint: vault server -config=/vault/config/vault.json
    restart: always

Die Konfiguration der Servers erfolgt mit der Datei vault.config in dem ./volumes/config Verzeichnis. Diese verwendet Vault beim starten der Anwendung. Es muss hier der Port angegeben werden unter dem dann die Anwendung erreichbar sein soll. In diesem Beispiel ist es also der Port 9000. Der Schalter proxy_protocol_behaviour ist wichtig, da sonst die Anwendung hinter dem Reverse Proxy nicht zu erreichen ist.

{
  "backend": {
    "file": {
      "path": "/vault/file"
    }
  },
  "listener": {
    "tcp":{
      "address": "0.0.0.0:9000",
      "tls_disable": 1,
      "proxy_protocol_behaviour": "use_always"
    }
  },
  "ui": true
}

Starten sie nun mit docker-compose up den Container, um zu sehen das die Konfigurationsdatei geladen worden ist und prüfen sie ob der Port in dem Listener korrekt gesetzt ist. Prüfen Sie ob nun den Setup Dialog von Vault unter der IP Adresse und Port angezeigt bekommen.

Stoppen sie nun den Container CTRL-C und starten sie ihn neu und verfolgen das Log mit:

docker-compose up -d
docker-compose logs -f

NginX als Reverse Proxy einrichten

Erstellen sie folgende Konfiguration für NginX:

##
#
# Hashicorp Vault
#
##

#
# HTTP -> HTTPS redirect
#
server {
  listen 80;
  server_name vault.XXX.org;

  return 301 https://$server_name$request_uri;
}


server {
  listen 443 ssl http2;
  server_name vault.XXX.org;

  # SSL * cert
  include /etc/nginx/wildcard_ssl.conf;

  location / {
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;

    # Fix the “It appears that your reverse proxy set up is broken" error.
    proxy_pass          http://vault.XXX.org:9000;
    proxy_read_timeout  90;

    proxy_redirect      http://vault.XXX.org:9000 https://vault.XXX.de;
  }
}

Starten sie den Nginx neu, damit die geänderte Konfiguration gelesen werden kann. Vault ist nun fertig installiert und kann konfiguriert werden. Vault sollte nun über HTTPS in der angegebenen FQDN erreichbar sein.

Initialisierung von Vault

Bei dem ersten Start von Vault muss man einen Masterschlüssel erzeugen und diesen in mindestens einen abgeleiteten Schlüssel (derived key). Für Testzwecke reicht es also aus in dem Dialog 1 und 1 einzugeben. Die Anzahl der Schlüssel kann man später per rekeying ändern.

Nun muss das Root-Token und die Schlüssel an einem sicheren Ort gespeichert bzw. an die entsprechenden Personen verteilt werden. Es kann aber auch eine JSON Datei mit allen Schlüssel runter geladen werden.

Unsealing

Wenn Vault gestartet wird, so ist es versiegelt (sealed). Um Vault nutzen zu können (nicht nur UI sondern auch über REST), muss die Versiegelung aufgehoben werden. Dazu müssen die Anzahl der im Threshold vorgegebenen derived Keys nun eigegeben werden, um das Siegel zu entfernen. Dieses ist ein Sicherheitsmerkmal von HashiCorp Vault.