apiVersion: v1 data: init.sh: | #!/bin/bash CERT=$CA_CERT # Get the CA path from environment vars CA_ONELINE=$(awk '{printf "%s\\n", $0}' $CERT) # Store cert as a oneliner for curl purposes DOMAIN={{ .Release.Namespace }}.pod.cluster.local # Set the domain for resolving pod names SVCDOMAIN={{ .Release.Namespace }}.svc.cluster.local WORKDIR=$PVCDIR # PVC location so that keys can be persisted # FUNCTIONS # Creates a list of all k8s vault pods and stores in text file. # Converts ips from X.X.X.X or a:b:c::d to X-X-X-X for use as pod dns names function getVaultPods { kubectl get pods \ -n {{ .Release.Namespace }} \ -l component=server,app.kubernetes.io/name=vault \ -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIPs[].ip}{"\n"}{end}' \ > $WORKDIR/pods.txt sed -i 's/\.\|:/-/g' $WORKDIR/pods.txt } # Wait for the vault servers in the stateful set to be created before initializing function waitForPods { CURRENT_PODS=$(kubectl get pods \ -l component=server,app.kubernetes.io/name=vault \ -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIPs[].ip}{"\t"}{.status.phase}{"\n"} \ {end}' \ | grep Running \ | wc -l) DESIRED_PODS={{ .Values.server.ha.replicas }} while [ $CURRENT_PODS != $DESIRED_PODS ]; do sleep 5 echo "Waiting for {{ template "vault.fullname" . }} statefulset running pods ($CURRENT_PODS) to equal desired pods ($DESIRED_PODS)" CURRENT_PODS=$(kubectl get pods \ -l component=server,app.kubernetes.io/name=vault \ -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIPs[].ip}{"\t"}{.status.phase}{"\n"} \ {end}' \ | grep Running \ | wc -l) done } # Initializes the first vault pod, only needs to be performed once after deploying the helm chart # Stores the root token and master key shards in plaintext in working directory as cluster_keys.json - insecure. function initVault { V0=$(awk 'NR==1{print $2}' $WORKDIR/pods.txt) echo "Initializing $V0" curl -s \ --cacert $CERT \ --request POST \ --data '{"secret_shares": 5, "secret_threshold": 3}' \ https://$V0.$DOMAIN:8200/v1/sys/init \ > $WORKDIR/cluster_keys.json } # Uses the master key shards in cluster_keys.json to unseal vault function unsealVault { for shard in $(cat $WORKDIR/cluster_keys.json | jq -r .keys_base64[]); do echo {\"key\": \"$shard\"} | curl -s --cacert $CERT --request POST -d @- https://$VAULT.$DOMAIN:8200/v1/sys/unseal > /dev/null sleep 3 #Some sleep is required to allow Raft convergence done } # Takes the address of vault-0 as the cluster leader and joins other nodes to raft function joinRaft { CLUSTER_LEAD=$(awk 'NR==1{print $2}' $WORKDIR/pods.txt) ROOT_TOKEN=$(cat $WORKDIR/cluster_keys.json | jq -r .root_token) RAFT_STATUS="" while [ "$RAFT_STATUS" != "true" ]; do RAFT_STATUS=$(curl -s \ --cacert $CERT \ -H "X-Vault-Token: $ROOT_TOKEN" \ --request POST \ --data "{\"leader_api_addr\": \"https://sva-{{ template "vault.name" .}}-active.$SVCDOMAIN:8200\", \"leader_ca_cert\": \"$CA_ONELINE\"}" \ https://$row.$DOMAIN:8200/v1/sys/storage/raft/join) echo "$row $RAFT_STATUS" RAFT_STATUS=$(echo $RAFT_STATUS | jq -r .joined) sleep 1 done } # Simply calls the status check of a vault, used to check if it is initialized, unsealed, or part of raft cluster function vaultServerStatus { curl --cacert $CERT -s https://$row.$DOMAIN:8200/v1/sys/health } # # LOGIC # # Waiting for vault servers to come up waitForPods echo "" echo "Putting a list of vault pods and ip in $WORKDIR/pods.txt" getVaultPods echo "" row=$(awk 'NR==1{print $2}' $WORKDIR/pods.txt) vaultServerStatus > $WORKDIR/healthcheck.txt TEMP=$(cat $WORKDIR/healthcheck.txt | jq -r .initialized) grep $row $WORKDIR/pods.txt & echo "Initialized status is $TEMP" if [ ! -z $TEMP ] && [ $TEMP = false ]; then echo "Initializing the vault on vault-0 and storing keys in $WORKDIR/cluster_keys.json" initVault cp $WORKDIR/cluster_keys.json $WORKDIR/cluster_init.json sleep 10 #Some sleep required to allow convergence" echo "" echo "Unsealing vault-0 using the init shards" for row in $(awk 'NR==1{print $2}' $WORKDIR/pods.txt); do VAULT=$row unsealVault done echo "" echo "Joining other vault servers to the HA Raft cluster" for row in $(awk 'NR>1{print $2}' $WORKDIR/pods.txt); do grep $row $WORKDIR/pods.txt joinRaft sleep 5 done echo "" echo "Unsealing the remaining vaults" for row in $(awk 'NR>1{print $2}' $WORKDIR/pods.txt); do grep $row $WORKDIR/pods.txt VAULT=$row unsealVault sleep 10 done fi # Loop forever to check the seal status of vaults and unseal if required while true; do sleep 5 echo "Checking vault pods seal status" rm $WORKDIR/pods.txt getVaultPods for row in $(awk '{print $2}' $WORKDIR/pods.txt); do vaultServerStatus > $WORKDIR/healthcheck.txt TEMP=$(cat $WORKDIR/healthcheck.txt | jq -r .sealed) grep $row $WORKDIR/pods.txt & echo "Sealed status is $TEMP" if [ ! -z $TEMP ] && [ $TEMP = true ]; then VAULT=$row echo "Unsealing $row" unsealVault fi done done kind: ConfigMap metadata: managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:data: .: {} f:init.sh: {} manager: vault-init-unseal name: vault-init-unseal namespace: {{ .Release.Namespace }} --- {{- if and (eq (.Values.injector.enabled | toString) "true" ) (eq (.Values.global.enabled | toString) "true") }} # Deployment for the unsealer apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ template "vault.fullname" . }}-manager namespace: {{ .Release.Namespace }} labels: app.kubernetes.io/name: {{ include "vault.name" . }}-manager app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} component: webhook spec: replicas: 1 selector: matchLabels: app.kubernetes.io/instance: {{ .Release.Name }} component: webhook template: metadata: labels: app.kubernetes.io/name: {{ template "vault.name" . }}-manager app.kubernetes.io/instance: {{ .Release.Name }} component: webhook spec: serviceAccountName: "{{ template "vault.fullname" . }}" {{- if .Values.global.imagePullSecrets }} imagePullSecrets: {{- toYaml .Values.global.imagePullSecrets | nindent 8 }} {{- end }} containers: - name: manager image: starlingx/stx-vault-manager:stx.5.0-v1.18.3 imagePullPolicy: "{{ .Values.injector.image.pullPolicy }}" args: - bash - /opt/script/init.sh env: - name: PVCDIR value: /mnt/data - name: CA_CERT value: /mnt/data/ca/tls.crt volumeMounts: - name: vault-init-unseal mountPath: /opt/script readOnly: false - name: manager-pvc mountPath: /mnt/data readOnly: false - name: vault-ca mountPath: /mnt/data/ca readOnly: true volumes: - name: vault-init-unseal configMap: name: vault-init-unseal - name: vault-ca secret: secretName: vault-ca volumeClaimTemplates: - metadata: name: manager-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi {{ end }}