Kubernetes-Cluster absichern mit Hashicorp Consul/Vault | K&C Beratung

CloudUPDATED ON September 20, 2023

Author

Nutzen von Hashicorp bei Einsatz im Kubernetes-Umfeld

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.

Hashicorp Consul: Service-Discovery und System-Orchestrierung

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.

Hashicorp in Kubernets

Hashicorp Vault: die erstklassige Secrets-Management-Lösung

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.

Technologiekombination für mehr Sicherheit

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.

Implementierung von Consul

Vor der Implementierung von Consul müssen Sie folgende Komponenten herunterladen und installieren:

1.consul

2.cfssl and cfssljson 1.2

Generieren der TLS-Zertifikate

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
Consul Gossip Encryption Key generieren

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)
Consul Secret und ConfigMap erstellen

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
  }
}
Consul Service
kubectl create -f service.yaml
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
StatfulSet

3 Pods deployen:

kubectl create -f statefulset.yaml
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
Überprüfung der gestarteten Knoten:
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
Letzter Check

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
Web-UI prüfen

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

Implementierung von Vault

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"
ConfigMap erstellen:
$ kubectl create configmap vault --from-file=vault.hcl
service.yaml erstellen:
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
deployment.yaml ebenfalls erstellen:
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
Änderungen übernehmen
$ 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.