From 00b6581bd2bd430dd32ac27592a90b2bc0b5f4bc Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Mon, 28 Apr 2025 22:16:45 -0400 Subject: [PATCH 1/3] migrate ziti-host identity to volume --- charts/ziti-host/Chart.yaml | 4 +- charts/ziti-host/README.md | 8 ++-- charts/ziti-host/templates/_helpers.tpl | 7 ++++ charts/ziti-host/templates/deployment.yaml | 26 ++++++++----- .../templates/identity-migrate-job.yaml | 39 +++++++++++++++++++ .../templates/identity-migrate-role.yaml | 8 ++++ .../identity-migrate-rolebinding.yaml | 12 ++++++ .../templates/identity-migrate-script-cm.yaml | 29 ++++++++++++++ charts/ziti-host/templates/identity-pvc.yaml | 10 +++++ charts/ziti-host/values.yaml | 11 +++++- 10 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 charts/ziti-host/templates/identity-migrate-job.yaml create mode 100644 charts/ziti-host/templates/identity-migrate-role.yaml create mode 100644 charts/ziti-host/templates/identity-migrate-rolebinding.yaml create mode 100644 charts/ziti-host/templates/identity-migrate-script-cm.yaml create mode 100644 charts/ziti-host/templates/identity-pvc.yaml diff --git a/charts/ziti-host/Chart.yaml b/charts/ziti-host/Chart.yaml index c8ddb9cab..4d1a09450 100644 --- a/charts/ziti-host/Chart.yaml +++ b/charts/ziti-host/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 -appVersion: 1.3.9 +appVersion: 1.5.12 description: Reverse proxy cluster services with an OpenZiti tunneler pod kubeVersion: '>= 1.20.0-0' name: ziti-host type: application -version: 1.0.1 +version: 1.1.0 diff --git a/charts/ziti-host/README.md b/charts/ziti-host/README.md index ecafce9d2..7b95d86cf 100644 --- a/charts/ziti-host/README.md +++ b/charts/ziti-host/README.md @@ -1,7 +1,7 @@ # ziti-host -![Version: 1.0.1](https://img.shields.io/badge/Version-1.0.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.3.9](https://img.shields.io/badge/AppVersion-1.3.9-informational?style=flat-square) +![Version: 1.1.0](https://img.shields.io/badge/Version-1.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.5.12](https://img.shields.io/badge/AppVersion-1.5.12-informational?style=flat-square) Reverse proxy cluster services with an OpenZiti tunneler pod @@ -56,7 +56,7 @@ When you don't want to use the default key name `persisted-identity` you can def | affinity | object | `{}` | | | dnsPolicy | string | `"ClusterFirstWithHostNet"` | | | fullnameOverride | string | `""` | | -| hostNetwork | bool | `false` | | +| hostNetwork | bool | `false` | bool: host network mode | | image.args | list | `[]` | | | image.pullPolicy | string | `"Always"` | | | image.repository | string | `"openziti/ziti-host"` | | @@ -64,7 +64,9 @@ When you don't want to use the default key name `persisted-identity` you can def | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | | podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | +| podSecurityContext.fsGroup | int | `65534` | int: fsGroup for podSecurityContext (default: nogroup) | +| podSecurityContext.runAsGroup | int | `65534` | int: GID to run the container as (default: nogroup) | +| podSecurityContext.runAsUser | int | `65534` | int: UID to run the container as (default: nobody) | | ports | list | `[]` | | | replicas | int | `1` | | | resources | object | `{}` | | diff --git a/charts/ziti-host/templates/_helpers.tpl b/charts/ziti-host/templates/_helpers.tpl index b4b129ffe..32ec43159 100644 --- a/charts/ziti-host/templates/_helpers.tpl +++ b/charts/ziti-host/templates/_helpers.tpl @@ -61,3 +61,10 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Define the mount path for the identity PVC. +*/}} +{{- define "ziti-host.identityMountPath" -}} +/ziti-edge-tunnel +{{- end }} diff --git a/charts/ziti-host/templates/deployment.yaml b/charts/ziti-host/templates/deployment.yaml index 146040bba..f6a86da69 100644 --- a/charts/ziti-host/templates/deployment.yaml +++ b/charts/ziti-host/templates/deployment.yaml @@ -26,6 +26,13 @@ spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} dnsPolicy: {{ .Values.dnsPolicy }} + initContainers: + - name: chown-identity-dir + image: busybox:latest + command: ["sh", "-c", "chown -R {{ .Values.podSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.runAsGroup }} {{ include "ziti-host.identityMountPath" . }}"] + volumeMounts: + - name: persisted-identity + mountPath: {{ include "ziti-host.identityMountPath" . }} containers: - name: {{ .Chart.Name }} securityContext: @@ -45,9 +52,11 @@ spec: - name: ZITI_IDENTITY_BASENAME value: {{ include "ziti-host.fullname" . }}-identity volumeMounts: - - mountPath: /ziti-edge-tunnel + - mountPath: {{ include "ziti-host.identityMountPath" . }} name: persisted-identity + {{- if .Values.secret.existingSecretName }} readOnly: true + {{- end }} {{- if .Values.spireAgent.enabled }} - mountPath: {{ .Values.spireAgent.spireSocketMnt }} name: spire-agent-socket @@ -71,21 +80,19 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} volumes: + {{- if .Values.secret.existingSecretName }} - name: persisted-identity secret: - {{- if .Values.secret.existingSecretName }} secretName: {{ .Values.secret.existingSecretName }} defaultMode: 0444 items: - key: {{ .Values.secret.keyName | default "persisted-identity" | quote }} path: {{ include "ziti-host.fullname" . }}-identity.json - {{- else }} - secretName: {{ include "ziti-host.fullname" . }}-identity - defaultMode: 0444 - items: - - key: persisted-identity - path: {{ include "ziti-host.fullname" . }}-identity.json - {{- end }} + {{- else }} + - name: persisted-identity + persistentVolumeClaim: + claimName: {{ include "ziti-host.fullname" . }}-identity-pvc + {{- end }} {{- if .Values.spireAgent.enabled }} - name: spire-agent-socket hostPath: @@ -104,4 +111,3 @@ spec: emptyDir: {} {{- end }} {{- end }} - diff --git a/charts/ziti-host/templates/identity-migrate-job.yaml b/charts/ziti-host/templates/identity-migrate-job.yaml new file mode 100644 index 000000000..e44058ea8 --- /dev/null +++ b/charts/ziti-host/templates/identity-migrate-job.yaml @@ -0,0 +1,39 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "ziti-host.fullname" . }}-identity-migrate + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + template: + spec: + restartPolicy: OnFailure + serviceAccountName: default + initContainers: + - name: chown-identity-dir + image: busybox:latest + command: ["sh", "-c", "chown -R {{ .Values.podSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.runAsGroup }} {{ include "ziti-host.identityMountPath" . }}"] + volumeMounts: + - name: identity-pvc + mountPath: {{ include "ziti-host.identityMountPath" . }} + containers: + - name: migrate-identity + image: bitnami/kubectl:latest + command: ["/bin/bash", "/scripts/migrate.sh"] + securityContext: + runAsUser: {{ .Values.podSecurityContext.runAsUser }} + runAsGroup: {{ .Values.podSecurityContext.runAsGroup }} + volumeMounts: + - name: identity-pvc + mountPath: {{ include "ziti-host.identityMountPath" . }} + - name: migrate-script + mountPath: /scripts + volumes: + - name: identity-pvc + persistentVolumeClaim: + claimName: {{ include "ziti-host.fullname" . }}-identity-pvc + - name: migrate-script + configMap: + name: {{ include "ziti-host.fullname" . }}-identity-migrate + defaultMode: 0755 diff --git a/charts/ziti-host/templates/identity-migrate-role.yaml b/charts/ziti-host/templates/identity-migrate-role.yaml new file mode 100644 index 000000000..4dac30583 --- /dev/null +++ b/charts/ziti-host/templates/identity-migrate-role.yaml @@ -0,0 +1,8 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "ziti-host.fullname" . }}-identity-migrate +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "delete"] diff --git a/charts/ziti-host/templates/identity-migrate-rolebinding.yaml b/charts/ziti-host/templates/identity-migrate-rolebinding.yaml new file mode 100644 index 000000000..1b82c9dce --- /dev/null +++ b/charts/ziti-host/templates/identity-migrate-rolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "ziti-host.fullname" . }}-identity-migrate +subjects: + - kind: ServiceAccount + name: default + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ include "ziti-host.fullname" . }}-identity-migrate + apiGroup: rbac.authorization.k8s.io diff --git a/charts/ziti-host/templates/identity-migrate-script-cm.yaml b/charts/ziti-host/templates/identity-migrate-script-cm.yaml new file mode 100644 index 000000000..5424ed599 --- /dev/null +++ b/charts/ziti-host/templates/identity-migrate-script-cm.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ziti-host.fullname" . }}-identity-migrate + labels: + app.kubernetes.io/name: {{ include "ziti-host.name" . }} + helm.sh/chart: {{ include "ziti-host.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + migrate.sh: | + #!/bin/bash + set -e + SECRET_NAME="{{ include "ziti-host.fullname" . }}-identity" + NAMESPACE="{{ .Release.Namespace }}" + PVC_MOUNT="{{ include "ziti-host.identityMountPath" . }}" + IDENTITY_FILE="${PVC_MOUNT}/{{ include "ziti-host.fullname" . }}-identity.json" + if [ -f "$IDENTITY_FILE" ]; then + echo "Identity file already exists in PVC, skipping migration." + exit 0 + fi + if kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" &>/dev/null; then + IDENTITY=$(kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" -o jsonpath='{.data.persisted-identity}' | base64 -d) + echo "$IDENTITY" > "$IDENTITY_FILE" + kubectl delete secret "$SECRET_NAME" -n "$NAMESPACE" + echo "Identity migrated to PVC and secret deleted." + else + echo "Secret $SECRET_NAME not found, nothing to migrate." + fi diff --git a/charts/ziti-host/templates/identity-pvc.yaml b/charts/ziti-host/templates/identity-pvc.yaml new file mode 100644 index 000000000..f931614c6 --- /dev/null +++ b/charts/ziti-host/templates/identity-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ziti-host.fullname" . }}-identity-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi diff --git a/charts/ziti-host/values.yaml b/charts/ziti-host/values.yaml index 502d7f174..9f01c32ea 100644 --- a/charts/ziti-host/values.yaml +++ b/charts/ziti-host/values.yaml @@ -32,10 +32,17 @@ serviceAccount: podAnnotations: {} -podSecurityContext: {} - # fsGroup: 2000 +podSecurityContext: + # -- int: UID to run the container as (default: nobody) + runAsUser: 65534 + # -- int: GID to run the container as (default: nogroup) + runAsGroup: 65534 + # -- int: fsGroup for podSecurityContext (default: nogroup) + fsGroup: 65534 +# -- bool: host network mode hostNetwork: False + securityContext: {} # capabilities: # add: From d0a4c8dab6f4d00cda9353f48829eb657f794cd0 Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Mon, 28 Apr 2025 22:32:00 -0400 Subject: [PATCH 2/3] finish converting identity secret to pvc --- charts/ziti-host/templates/deployment.yaml | 8 ++++++++ charts/ziti-host/templates/secrets.yaml | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 charts/ziti-host/templates/secrets.yaml diff --git a/charts/ziti-host/templates/deployment.yaml b/charts/ziti-host/templates/deployment.yaml index f6a86da69..42c91bce0 100644 --- a/charts/ziti-host/templates/deployment.yaml +++ b/charts/ziti-host/templates/deployment.yaml @@ -51,6 +51,14 @@ spec: env: - name: ZITI_IDENTITY_BASENAME value: {{ include "ziti-host.fullname" . }}-identity + {{- if .Values.zitiIdentity }} + - name: ZITI_IDENTITY_JSON + value: {{ .Values.zitiIdentity | quote }} + {{- end }} + {{- if .Values.zitiEnrollToken }} + - name: ZITI_ENROLL_TOKEN + value: {{ .Values.zitiEnrollToken }} + {{- end }} volumeMounts: - mountPath: {{ include "ziti-host.identityMountPath" . }} name: persisted-identity diff --git a/charts/ziti-host/templates/secrets.yaml b/charts/ziti-host/templates/secrets.yaml deleted file mode 100644 index c0d040954..000000000 --- a/charts/ziti-host/templates/secrets.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{ if not .Values.secret.existingSecretName }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "ziti-host.fullname" . }}-identity -type: Opaque -data: - persisted-identity: {{ required "You must set .Values.zitiIdentity to the JSON of a Ziti identity. Try adding --set-file zitiIdentity=/tmp/ziti_id.json to your Helm command" .Values.zitiIdentity | b64enc }} -{{ end }} From ac6b4452c9323e0642a6f543791db6cf8c2c6b56 Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Mon, 28 Apr 2025 23:33:03 -0400 Subject: [PATCH 3/3] update ziti-host readme --- charts/ziti-host/README.md | 28 +++++++++++++++++++++------- charts/ziti-host/README.md.gotmpl | 28 +++++++++++++++++++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/charts/ziti-host/README.md b/charts/ziti-host/README.md index 7b95d86cf..ea5756baa 100644 --- a/charts/ziti-host/README.md +++ b/charts/ziti-host/README.md @@ -17,36 +17,50 @@ You may use this chart to publish cluster services to your Ziti network. For exa This chart deploys a pod running `ziti-edge-tunnel`, [the OpenZiti Linux tunneler](https://docs.openziti.io/docs/reference/tunnelers/linux/), in service hosting mode. The chart uses container image `docker.io/openziti/ziti-host` which runs `ziti-edge-tunnel run-host`. This puts the Linux tunneler in "hosting" mode which is useful for binding Ziti services without any need for elevated permissions and without any Ziti nameserver or intercepting proxy. You'll be able to publish any server that is known by an IP address or domain name that is reachable from the pod deployed by this chart. +The enrolled Ziti identity JSON is persisted in a volume, and the chart will migrate the identity from a secret to the volume if the legacy secret exists. + ## Installation ```bash helm repo add openziti https://docs.openziti.io/helm-charts/ ``` -After adding the charts repo to Helm then you may enroll the identity and install the chart. You must supply a Ziti identity JSON file when you install the chart. +After adding the charts repo to Helm then you may enroll the identity and install the chart. You may supply a Ziti identity JSON file when you install the chart. This approach enables you to use any option available to the `ziti edge enroll` command. ```bash ziti edge enroll --jwt /tmp/k8s-tunneler.jwt --out /tmp/k8s-tunneler.json -helm install ziti-release03 openziti/ziti-host --set-file zitiIdentity=/tmp/k8s-tunneler-03.json +helm install ziti-host openziti/ziti-host --set-file zitiIdentity=/tmp/k8s-tunneler.json +``` + +Alternatively, you may supply the JWT directly to the chart. In this case, a private key will be generated on first run and the identity will be enrolled. + +```bash +helm install ziti-host openziti/ziti-host --set-file zitiEnrollToken=/tmp/k8s-tunneler.jwt ``` -### Installation using a existing / pre-created secret +### Installation using an existing secret -Alternatively when you want to use a existing / pre-created secret (i.e. you have sealed-secrets enabled in your setup), you could refer to an existing secret with the ziti identity to use. +**Warning:** this approach does not allow the tunneler to autonomously renew its identity certificate, so you must renew the identity certificate out of band and supply it as an existing secret. -This sample shows you how to create the secret: +Create the secret: ```bash kubectl create secret generic k8s-tunneler-identity --from-file=persisted-identity=k8s-tunneler.json ``` -When you deploy the helm chart refer to the existing secret: +Deploy the Helm chart, referring to the existing secret: ```bash helm install ziti-host openziti/ziti-host --set secret.existingSecretName=k8s-tunneler-identity ``` -When you don't want to use the default key name `persisted-identity` you can define your own name by adding `--set secret.keyName=myKeyName`. +If desired, change the key name `persisted-identity` with `--set secret.keyName=myKeyName`. + +### Identity Directory and Volume + +The Ziti identity is stored in a directory inside the container, which is backed by a PersistentVolumeClaim (PVC) by default. This ensures that identity renewals and updates are preserved across pod restarts. If you use an existing secret instead, the identity directory will be read-only, and renewals will not be persisted. + +**Warning:** If the identity directory is not writable or not backed by a persistent volume, identity renewals and updates will NOT be preserved across container restarts. ## Values Reference diff --git a/charts/ziti-host/README.md.gotmpl b/charts/ziti-host/README.md.gotmpl index 9e2dd1de8..975997458 100644 --- a/charts/ziti-host/README.md.gotmpl +++ b/charts/ziti-host/README.md.gotmpl @@ -22,36 +22,50 @@ You may use this chart to publish cluster services to your Ziti network. For exa This chart deploys a pod running `ziti-edge-tunnel`, [the OpenZiti Linux tunneler](https://docs.openziti.io/docs/reference/tunnelers/linux/), in service hosting mode. The chart uses container image `docker.io/openziti/ziti-host` which runs `ziti-edge-tunnel run-host`. This puts the Linux tunneler in "hosting" mode which is useful for binding Ziti services without any need for elevated permissions and without any Ziti nameserver or intercepting proxy. You'll be able to publish any server that is known by an IP address or domain name that is reachable from the pod deployed by this chart. +The enrolled Ziti identity JSON is persisted in a volume, and the chart will migrate the identity from a secret to the volume if the legacy secret exists. + ## Installation ```bash helm repo add openziti https://docs.openziti.io/helm-charts/ ``` -After adding the charts repo to Helm then you may enroll the identity and install the chart. You must supply a Ziti identity JSON file when you install the chart. +After adding the charts repo to Helm then you may enroll the identity and install the chart. You may supply a Ziti identity JSON file when you install the chart. This approach enables you to use any option available to the `ziti edge enroll` command. ```bash ziti edge enroll --jwt /tmp/k8s-tunneler.jwt --out /tmp/k8s-tunneler.json -helm install ziti-release03 openziti/ziti-host --set-file zitiIdentity=/tmp/k8s-tunneler-03.json +helm install ziti-host openziti/ziti-host --set-file zitiIdentity=/tmp/k8s-tunneler.json +``` + +Alternatively, you may supply the JWT directly to the chart. In this case, a private key will be generated on first run and the identity will be enrolled. + +```bash +helm install ziti-host openziti/ziti-host --set-file zitiEnrollToken=/tmp/k8s-tunneler.jwt ``` -### Installation using a existing / pre-created secret +### Installation using an existing secret -Alternatively when you want to use a existing / pre-created secret (i.e. you have sealed-secrets enabled in your setup), you could refer to an existing secret with the ziti identity to use. +**Warning:** this approach does not allow the tunneler to autonomously renew its identity certificate, so you must renew the identity certificate out of band and supply it as an existing secret. -This sample shows you how to create the secret: +Create the secret: ```bash kubectl create secret generic k8s-tunneler-identity --from-file=persisted-identity=k8s-tunneler.json ``` -When you deploy the helm chart refer to the existing secret: +Deploy the Helm chart, referring to the existing secret: ```bash helm install ziti-host openziti/ziti-host --set secret.existingSecretName=k8s-tunneler-identity ``` -When you don't want to use the default key name `persisted-identity` you can define your own name by adding `--set secret.keyName=myKeyName`. +If desired, change the key name `persisted-identity` with `--set secret.keyName=myKeyName`. + +### Identity Directory and Volume + +The Ziti identity is stored in a directory inside the container, which is backed by a PersistentVolumeClaim (PVC) by default. This ensures that identity renewals and updates are preserved across pod restarts. If you use an existing secret instead, the identity directory will be read-only, and renewals will not be persisted. + +**Warning:** If the identity directory is not writable or not backed by a persistent volume, identity renewals and updates will NOT be preserved across container restarts. ## Values Reference