Die aktuelle Ausgabe unserer Kubernetes-Beratung Serie befasst sich mit der Rolle von Hashipcorps Consul und Vault als zusätzliche Security Layer beim Einsatz von Kubernetes zur Container-Orchrestrierung.
Kubernetes ist eine der umfangreichsten und innovativsten Plattformen auf dem Markt. Die Technologie ermöglicht die Einhaltung höchster Sicherheitsstandards, eine hervorragende Skalierbarkeit von Applikationen und vor allen Dingen eine Isolation verschiedener Produktarten. Dieses Feature ist nach dem Entwicklungsbeginn deshalb so wichtig, weil es gewährleistet, dass Microservices in Containern voneinander getrennt werden. Wäre dies nicht der Fall, hätten Angreifer die Möglichkeit, in Ihre gesamte Systemlandschaft einzudringen. In manchen Fällen können die Ansprüche in puncto Security, Richtlinien-Management, Audit-Logging, System-Monitoring und Service-Discovery jedoch noch höher sein. An dieser Stelle tragen Hashicorp Consul und Vault maßgeblich zur Robustheit der Infrastruktur bei. Die Implementierung von Consul oder Vault in Kubernetes oder die Implementierung von Kubernetes selbst ist jedoch herausfordernd.
K&C besitzt einen umfangreichen Erfahrungsschatz in der Beratung und Umsetzung von Projekten im Bereich DevOps services deployment. Darüber hinaus wird die Entwicklung, Bereitstellung und Sicherheit der Applikationsinfrastruktur dank der Automatisierung von Cloud-Infrastrukturen mit HashiCorp deutlich vereinfacht.
Die statische Ausgestaltung von Netzwerken gehört der Vergangenheit an. Systeme erfordern heute Skalierbarkeit und eine schnelle Service-Discovery, sobald neue Elemente hinzugefügt wurden. Consul ermöglicht dynamische Umgebungen und einen Wechsel von klassischen host-basierten Systemen hin zu einem service-basierten Ansatz. Abgesehen davon sind keine statischen Firewalls erforderlich, da Consul eine dynamische Segmentierung von Services unterstützt. Hierdurch entsteht ein völlig neues Sicherheitslevel.
Unsere Erfahrung aus der Beratung zeigt außerdem: Aufgrund der komplexen Kubernetes-Umgebungen ist es bedeutsam, stets den Systemzustand hinsichtlich der Auslastung und weiterer wichtiger Indikatoren zu überwachen. Consul ist ein Service Discovery Tool, mit dem Sie die Auslastung jedes Pods in der Infrastruktur im Blick behalten. Dies realisieren Sie, indem Sie sämtliche Services registrieren und HTTP- sowie DNS-Schnittstellen bereitstellen. Auf diese Weise ist eine Statusabfrage für verschiedene Netzwerkelemente möglich. Zudem ist dann jeder Service in der Lage, den anderen Service in einer definierten Reihenfolge dynamisch zu erkennen, zu überwachen und mit ihm zu kommunizieren.
Consul-Cluster speichern alle Informationen über den Cluster selbst, über Health Checks und die Services des Clients in /consul/data als Volume. Der Container zeigt sein Verzeichnis an, sodass es auf Client-Seite sichtbar ist. Ist es dort nicht mehr sichtbar, so hat dies keine Auswirkung auf Cluster-Operationen. Serverseitig werden Informationen zu Clients, Snapshots und andere wichtige Daten gespeichert, sodass der Server nach einem Ausfall wiederhergestellt werden kann. Behalten die Mitarbeiter die Container mit Consul-Cluster-Daten nicht im Blick, können diese bei einen Neustart hingegen zerstört werden. Wenn Sie Consul in einem Container betreiben, müssen Sie sicherstellen, dass dem Client und den Clustern geeignete Adressen zugewiesen sind. Außerdem sollten Sie die Cluster-Adresse direkt zu Beginn hinzufügen. Sie stellen damit sicher, dass eine korrekte und für die anderen Beteiligten funktionierende Schnittstelle gefunden wird.
Die Sicherheit wird über TLS-Zertifikate, Service-to-Service-Kommunikation und identitätsbasierte Autorisierung gewährleistet. Consul ist in der Lage, das Netzwerk in unterschiedliche Segmente aufzuteilen. Jedem davon können eigene Berechtigungen, Kommunikationsrichtlinien und IP-basierte Regeln zugewiesen werden. Wenn Ihnen dies noch nicht ausreicht, so kommt eine weitere Sicherheitsinstanz ins Spiel: Vault.
Im Rahmen unserer Beratung werden wir immer wieder gefragt, welche Lösung wir für das Management von Secrets empfehlen. Im Kubernetes-Bereich lautet die Antwort oftmals „Vault“. Hierfür gibt es verschiedene Gründe.
Zunächst kann die Kommunikation zwischen Anwendungen und Systemen eine Schwachstelle darstellen. Das Risiko unberechtigter Zugriffe lässt sich mit dynamisch erstellten Secrets jedoch signifikant minimieren. Secrets werden nur erstellt und existieren ausschließlich so lange, wie sie von Apps oder Services tatsächlich benötigt werden. Somit ist sichergestellt, dass Secrets samt ihrer Passwörter niemandem bekannt sind. Darüber hinaus erwarten Apps und Services, dass Secrets zu einem bestimmten Zeitpunkt ungültig werden. Die Kommunikation zwischen Apps und Services gestaltet sich dank Vault also deutlich zuverlässiger.
Mit der beschriebenen Vorgehensweise vermeidet es Vault zudem, dass zufälligen Benutzern Root-Berechtigungen für die zugrunde liegenden Systeme zugewiesen werden. Nicht zuletzt kann Vault Secrets widerrufen und Key Rolling durchführen.
Kubernetes-Cluster beinhalten mehrere leistungsfähige Security-Funktionen, mit denen Sie Ihr System in jeder Hinsicht effektiv absichern können. Darüber hinaus ermöglicht es Consul, den Zustand Ihres Systems zu überwachen. Bestehende Probleme werden auf einfache Weise erkannt. Vault erlaubt es Ihnen, eine zusätzliche Sicherheitsinstanz in der Kommunikation zwischen den verschiedenen Netzwerkkomponenten hinzuzufügen. Dynamische Secrets schützen Ihr Unternehmen nicht nur vor Passwort-Schwachstellen. Sie stellen auch sicher, dass gar kein aktuelles Passwort existiert, welches entwendet werden könnte.
Nachfolgend möchten wir unsere Erfahrung aus der Kubernetes- und Container-Beratung an Sie weitergeben. Wir zeigen daher exemplarisch auf, wie sich Consul und Vault in der Praxis implementieren lassen.
Vor der Implementierung von Consul müssen Sie folgende Komponenten herunterladen und installieren:
1.consul
Die RPS-Kommunikation zwischen den einzelnen Consul-Elementen wird mittels TLS verschlüsselt. Nun müssen wir die Certificate Authority (CA) initiieren:
ca-config.json { "signing": { "default": { "expiry": "8760h" }, "profiles": { "default": { "usages": ["signing", "key encipherment", "server auth", "client auth"], "expiry": "8760h" } } } } ca-csr.json { "hosts": [ "cluster.local" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "Kubernetes", "OU": "CA", "ST": "Oregon" } ] consul-csr.json { "CN": "server.dc1.cluster.local", "hosts": [ "server.dc1.cluster.local", "127.0.0.1" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "Comnsul", "OU": "Consul", "ST": "Oregon" } ] } cfssl gencert -initca ca-csr.json | cfssljson -bare ca
Zertifikat und Key für Consul erstellen:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca/ca-config.json -profile=default ca/consul-csr.json | cfssljson -bare consul
Nun liegen uns folgende Dateien vor:
ca-key.pem ca.pem consul-key.pem consul.pem
Die Gossip-Kommunikation zwischen den Consul-Elementen wird mithilfe eines gemeinsamen Keys verschlüsselt. Generieren und speichern Sie den Key:
GOSSIP_ENCRYPTION_KEY=$(consul keygen)
Gossip Key und TLS-Zertifikate im Secret speichern:
kubectl create secret generic consul --from-literal="gossip-encryption-key=${GOSSIP_ENCRYPTION_KEY}" --from-file=ca.pem --from-file=consul.pem --from-file=consul-key.pem
Nun speichern wir die Consul-Konfiguration in der ConfigMap:
kubectl create configmap consul --from-file=server.json server.json: { "ca_file": "/etc/tls/ca.pem", "cert_file": "/etc/tls/consul.pem", "key_file": "/etc/tls/consul-key.pem", "verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "ports": { "https": 8443 } }
kubectl create -f service.yaml
apiVersion: v1 kind: Service metadata: name: consul labels: name: consul spec: clusterIP: None ports: - name: http port: 8500 targetPort: 8500 - name: https port: 8443 targetPort: 8443 - name: rpc port: 8400 targetPort: 8400 - name: serflan-tcp protocol: "TCP" port: 8301 targetPort: 8301 - name: serflan-udp protocol: "UDP" port: 8301 targetPort: 8301 - name: serfwan-tcp protocol: "TCP" port: 8302 targetPort: 8302 - name: serfwan-udp protocol: "UDP" port: 8302 targetPort: 8302 - name: server port: 8300 targetPort: 8300 - name: consuldns port: 8600 targetPort: 8600 selector: app: consul
3 Pods deployen:
kubectl create -f statefulset.yaml
apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: consul spec: serviceName: consul replicas: 5 template: metadata: labels: app: consul spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - consul topologyKey: kubernetes.io/hostname terminationGracePeriodSeconds: 10 securityContext: fsGroup: 1000 containers: - name: consul image: "consul:1.2.0" env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: GOSSIP_ENCRYPTION_KEY valueFrom: secretKeyRef: name: consul key: gossip-encryption-key - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace args: - "agent" - "-advertise=$(POD_IP)" - "-bind=0.0.0.0" - "-bootstrap-expect=3" - "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local" - "-client=0.0.0.0" - "-config-file=/consul/myconfig/server.json" - "-datacenter=dc1" - "-data-dir=/consul/data" - "-domain=cluster.local" - "-encrypt=$(GOSSIP_ENCRYPTION_KEY)" - "-server" - "-ui" - "-disable-host-node-id" volumeMounts: - name: config mountPath: /consul/myconfig - name: tls mountPath: /etc/tls lifecycle: preStop: exec: command: - /bin/sh - -c - consul leave ports: - containerPort: 8500 name: ui-port - containerPort: 8400 name: alt-port - containerPort: 53 name: udp-port - containerPort: 8443 name: https-port - containerPort: 8080 name: http-port - containerPort: 8301 name: serflan - containerPort: 8302 name: serfwan - containerPort: 8600 name: consuldns - containerPort: 8300 name: server volumes: - name: config configMap: name: consul - name: tls secret: secretName: consul
kubectl get pods NAME READY STATUS RESTARTS AGE consul-0 1/1 Running 0 50s consul-1 1/1 Running 0 29s consul-2 1/1 Running 0 15s
Leiten Sie den Port an die Local Machine weiter:
kubectl port-forward consul-1 8500:8500 Forwarding from 127.0.0.1:8500 -> 8500 Forwarding from [::1]:8500 -> 8500
Folgenden Befehl ausführen:
consul members Node Address Status Type Build Protocol DC consul-0 10.176.4.30:8301 alive server 1.2.0 2 dc1 consul-1 10.176.4.31:8301 alive server 1.2.0 2 dc1 consul-2 10.176.1.16:8301 alive server 1.2.0 2 dc1
Öffnen Sie einfach https://127.0.0.1:8500 in Ihrem Browser.
Haben Sie alle oben genannten Schritte korrekt durchgeführt, steht Consul nun für Ihre individuellen Anforderungen zur Verfügung.
Nun werden wir Vault in Kubernetes laufen lassen. Zunächst müssen wir jedoch die Konfigurationsdatei vault.hcl erstellen und Folgendes einfügen:
disable_cache = true disable_mlock = true ui = true listener "tcp" { address = "127.0.0.1:8200" tls_disable = 1 } storage "consul" { address = "127.0.0.1:8500" path = "mycompany/" disable_registration = "true" } max_lease_ttl = "10h" default_lease_ttl = "10h" raw_storage_endpoint = true cluster_name = "mycompany-vault"
$ kubectl create configmap vault --from-file=vault.hcl
apiVersion: v1 kind: Service metadata: name: vault labels: app: vault spec: type: ClusterIP ports: - port: 8200 targetPort: 8200 protocol: TCP name: vault selector: app: vault
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: vault labels: app: vault spec: replicas: 1 template: metadata: labels: app: vault spec: containers: - name: vault command: ["vault", "server", "-config", "/vault/config/vault.hcl"] image: "vault:0.10.3" imagePullPolicy: IfNotPresent securityContext: capabilities: add: - IPC_LOCK volumeMounts: - name: configurations mountPath: /vault/config/vault.hcl subPath: vault.hcl - name: consul-vault-agent image: "consul:1.2.0" env: - name: GOSSIP_ENCRYPTION_KEY valueFrom: secretKeyRef: name: consul key: gossip-encryption-key - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace args: - "agent" - "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local" - "-encrypt=$(GOSSIP_ENCRYPTION_KEY)" - "-domain=cluster.local" - "-datacenter=dc1" - "-disable-host-node-id" - "-node=vault-1" volumeMounts: - name: config mountPath: /consul/config - name: tls mountPath: /etc/tls volumes: - name: configurations configMap: name: vault - name: config configMap: name: consul - name: tls secret: secretName: consul
$ kubectl apply -f service.yaml $ kubectl apply -f deployment.yaml
Wenn alles richtig gemacht wurde, haben wir nun einen funktionierenden Service. Beginnen wir mit der Initialisierung und der Port-Weiterleitung auf Ihre Local Machine:
$ kubectl port-forward vault-6f8-z2rrj 8200:8200
Überprüfen Sie Folgendes im anderen Fenster:
$ export VAULT_ADDR=https://127.0.0.1:8200
Zur Vereinfachung führen wir die Initialisierung mit einem Unseal Key durch.
$ vault operator init -key-shares=1 -key-threshold=1 Unseal Key 1: DKoe652D**************yio9idW******BlkY8= Initial Root Token: 95633ed2-***-***-***-faaded3c711e Vault initialized with 1 key shares and a key threshold of 1. Please securely distribute the key shares printed above. When the Vault is re-sealed, restarted, or stopped, you must supply at least 1 of these keys to unseal it before it can start servicing requests. Vault does not store the generated master key. Without at least 1 key to reconstruct the master key, Vault will remain permanently sealed! It is possible to generate new unseal keys, provided you have a quorum of existing unseal keys shares. See "vault rekey" for more information.
Speichern Sie in jedem Fall den Output, die Sie zu diesem Zeitpunkt erhalten werden, da wir die Unseal Keys und den Root Token benötigen. Nun entpacken wir die Vault mit einem Unseal Key:
$ vault operator unseal <key 1> Key Value --- ----- Seal Type shamir Sealed false Total Shares 1 Threshold 1 Version 0.10.1 Cluster Name vault-cluster-c9499a92 Cluster ID 3b8cce45-d64e-64bb-e41d-575c6d3a7e03 HA Enabled false
In die Vault einloggen mit dem Root Token:
$ vault login <root token> $ vault secrets list Path Type Description ---- ---- ----------- cubbyhole/ cubbyhole per-token private secret storage identity/ identity identity store secret/ kv key/value secret storage sys/ system system endpoints used for control, policy and debugging
Secret speichern:
$ vault kv put secret/apikey key="my-test-key" Key Value --- ----- created_time 2018-07-13T11:03:22.584234492Z deletion_time n/a destroyed false version 1
Folgendes können wir bei Bedarf ebenfalls prüfen:
$ vault kv get secret/apikey ====== Metadata ====== Key Value --- ----- created_time 2018-07-13T11:03:22.584234492Z deletion_time n/a destroyed false version 1 === Data === Key Value --- ----- key my-test-key
Secret aktualisieren:
$ vault kv put secret/apikey key="my-test-key" owner="dev" Key Value --- ----- created_time 2018-07-13T11:06:00.514309494Z deletion_time n/a destroyed false version 2
Die zweite Version der Daten in secret/apikey wurde erstellt. Aktualisieren Sie nochmals:
$ vault kv put secret/apikey owner="ops" Key Value --- ----- created_time 2018-07-13T11:09:52.457793677Z deletion_time n/a destroyed false version 3
Sehen wir uns nun das Ergebnis an:
$ vault kv get secret/apikey ====== Metadata ====== Key Value --- ----- created_time 2018-07-13T11:09:52.457793677Z deletion_time n/a destroyed false version 3 ==== Data ==== Key Value --- ----- owner ops
PUT aktualisiert alle Daten im Secret. Um Änderungen hinzuzufügen, ohne die alten Daten zu verlieren, müssen wir den folgenden Befehl ausführen:
$ vault kv patch secret/apikey year="2018" Key Value --- ----- created_time 2018-07-13T11:12:38.832500503Z deletion_time n/a destroyed false version 4
Überprüfen wir das Ergebnis:
$ vault kv get secret/apikey ====== Metadata ====== Key Value --- ----- created_time 2018-07-13T11:12:38.832500503Z deletion_time n/a destroyed false version 4 ==== Data ==== Key Value --- ----- owner ops year 2018
Im Übrigen können Sie mit verschiedenen Versionen arbeiten:
$ vault kv get -version=1 secret/apikey ====== Metadata ====== Key Value --- ----- created_time 2018-07-13T11:03:22.584234492Z deletion_time n/a destroyed false version 1 === Data === Key Value --- ----- key my-test-key
Wenn alle Schritte erfolgreich ausgeführt wurden, sollten Sie nun über eine voll funktionsfähige, bereitgestellte Vault verfügen. Diese stellt eine leistungsfähige Ergänzung für Ihre Umgebung dar.