diff --git a/.gitignore b/.gitignore index 54a5c8b..efcb378 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +values.yaml /build/artifacts/** !/build/artifacts/.keepme /build/helm/**.tgz /build/helm/values.yaml +/build/helm/*/*.lock +/build/helm/*/charts/mysql* diff --git a/Makefile b/Makefile index 065f533..a760feb 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,10 @@ VERSION ?= latest # helm chart version must be semver 2 compliant HELM_CHART_KEYLIME_VERSION ?= 0.1.0 +HELM_CHART_RELEASE_NAME ?= hhkl +HELM_CHART_NAMESPACE ?= keylime +HELM_CHART_CUSTOM_VALUES ?= values.yaml +HELM_CHART_DEBUG_FILE ?= /tmp/keylime.helm.debug HELM_CHART_KEYLIME_DIR := $(BUILD_DIR)/helm/keylime HELM_CHART_KEYLIME_FILES := $(shell find $(HELM_CHART_KEYLIME_DIR) -type f) HELM_CHART_REPO ?= ghcr.io/keylime/helm-charts @@ -40,21 +44,70 @@ all: helm ##@ Build -helm: helm-keylime ## Builds all helm charts +helm: helm-build ## Builds all helm charts .PHONY: helm-clean helm-clean: helm-keylime-clean ## Cleans all packaged helm charts -helm-keylime: $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz ## Builds the keylime helm chart +.PHONY: helm-undeploy +helm-undeploy: helm-keylime-undeploy + +.PHONY: helm-deploy +helm-deploy: helm-keylime-deploy + +.PHONY: helm-update +helm-deploy: helm-keylime-update + +.PHONY: helm-debug +helm-debug: helm-keylime-debug + +helm-build: $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz ## Builds the keylime helm chart $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz: $(HELM_CHART_KEYLIME_FILES) helm lint $(HELM_CHART_KEYLIME_DIR) + helm dependency update $(HELM_CHART_KEYLIME_DIR) helm package $(HELM_CHART_KEYLIME_DIR) --version $(HELM_CHART_KEYLIME_VERSION) --app-version $(VERSION) -d $(BUILD_ARTIFACTS_DIR) .PHONY: helm-keylime-clean helm-keylime-clean: ## Cleans the packaged keylime helm chart rm -v $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz 2>/dev/null || true +.PHONY: helm-keylime-undeploy +helm-keylime-undeploy: ## Undeploy the keylime helm chart + { \ + helm list --namespace $(HELM_CHART_NAMESPACE) | grep -q $(HELM_CHART_RELEASE_NAME);\ + if [[ $$? -eq 0 ]]; then helm uninstall $(HELM_CHART_RELEASE_NAME) --namespace $(HELM_CHART_NAMESPACE); fi;\ + kubectl get persistentvolumeclaim/data-$(HELM_CHART_RELEASE_NAME)-mysql-0 --namespace $(HELM_CHART_NAMESPACE) > /dev/null 2>&1;\ + if [[ $$? -eq 0 ]]; then kubectl delete persistentvolumeclaim/data-$(HELM_CHART_RELEASE_NAME)-mysql-0 --namespace $(HELM_CHART_NAMESPACE); fi;\ + kubectl get secret/$(HELM_CHART_RELEASE_NAME)-keylime-ca-password --namespace $(HELM_CHART_NAMESPACE) > /dev/null 2>&1;\ + if [[ $$? -eq 0 ]]; then kubectl delete secret/$(HELM_CHART_RELEASE_NAME)-keylime-ca-password --namespace $(HELM_CHART_NAMESPACE); fi;\ + kubectl get secret/$(HELM_CHART_RELEASE_NAME)-keylime-mysql-password --namespace $(HELM_CHART_NAMESPACE) > /dev/null 2>&1;\ + if [[ $$? -eq 0 ]]; then kubectl delete secret/$(HELM_CHART_RELEASE_NAME)-keylime-mysql-password --namespace $(HELM_CHART_NAMESPACE); fi;\ + kubectl get secret/$(HELM_CHART_RELEASE_NAME)-keylime-certs --namespace $(HELM_CHART_NAMESPACE) > /dev/null 2>&1;\ + if [[ $$? -eq 0 ]]; then kubectl delete secret/$(HELM_CHART_RELEASE_NAME)-keylime-certs --namespace $(HELM_CHART_NAMESPACE); fi;\ + } + +.PHONY: helm-keylime-deploy +helm-keylime-deploy: ## Deploy the keylime helm chart + { \ + touch $(HELM_CHART_CUSTOM_VALUES);\ + helm install $(HELM_CHART_RELEASE_NAME) $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz --namespace $(HELM_CHART_NAMESPACE) --create-namespace -f $(HELM_CHART_CUSTOM_VALUES);\ + } + +.PHONY: helm-keylime-update +helm-keylime-update: ## Update the deployed keylime helm chart + { \ + touch $(HELM_CHART_CUSTOM_VALUES);\ + helm upgrade $(HELM_CHART_RELEASE_NAME) $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz --namespace $(HELM_CHART_NAMESPACE) --create-namespace -f $(HELM_CHART_CUSTOM_VALUES);\ + } + +.PHONY: helm-keylime-debug +helm-keylime-debug: ## Attempt to debug the keylime helm chart, without deploying it + { \ + touch $(HELM_CHART_CUSTOM_VALUES);\ + helm install $(HELM_CHART_RELEASE_NAME) $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz --namespace $(HELM_CHART_NAMESPACE) --create-namespace --debug --dry-run -f $(HELM_CHART_CUSTOM_VALUES)>$(HELM_CHART_DEBUG_FILE);\ + } + .PHONY: helm-keylime-push helm-keylime-push: helm ## Builds AND pushes the keylime helm chart helm push $(BUILD_ARTIFACTS_DIR)/keylime-$(HELM_CHART_KEYLIME_VERSION).tgz oci://$(HELM_CHART_REPO) diff --git a/build/helm/keylime/Chart.yaml b/build/helm/keylime/Chart.yaml index 76044fd..2d37d9e 100644 --- a/build/helm/keylime/Chart.yaml +++ b/build/helm/keylime/Chart.yaml @@ -43,26 +43,35 @@ sources: # all dependencies and subcharts of this helm chart dependencies: - name: keylime-agent + version: "0.1.0" tags: - agent import-values: - child: service parent: agent.service - name: keylime-init + version: "0.1.0" tags: - init - name: keylime-registrar + version: "0.1.0" tags: - registrar import-values: - child: service parent: registrar.service - name: keylime-tenant + version: "0.1.0" tags: - tenant - name: keylime-verifier + version: "0.1.0" tags: - verifier import-values: - child: service parent: verifier.service + - name: mysql + version: "9.3.4" + repository: https://charts.bitnami.com/bitnami + condition: global.database.mysql.enable diff --git a/build/helm/keylime/charts/keylime-agent/templates/_helpers.tpl b/build/helm/keylime/charts/keylime-agent/templates/_helpers.tpl index 2ab803b..c81c966 100644 --- a/build/helm/keylime/charts/keylime-agent/templates/_helpers.tpl +++ b/build/helm/keylime/charts/keylime-agent/templates/_helpers.tpl @@ -77,8 +77,8 @@ Expand to the secret name for the certificate volume to be used */}} {{- define "agent.cvca.secret" -}} {{- if .Values.global.ca.generate }} -{{- include "keylime.ca.secret" . }} +{{- include "keylime.ca.secret.certs" . }} {{- else }} -{{- default (include "keylime.ca.secret" .) .Values.global.ca.agentName }} +{{- default (include "keylime.ca.secret.certs" .) .Values.global.ca.agentName }} {{- end }} {{- end }} diff --git a/build/helm/keylime/charts/keylime-init/templates/ca-job.yaml b/build/helm/keylime/charts/keylime-init/templates/ca-job.yaml index 9eff901..f217c7a 100644 --- a/build/helm/keylime/charts/keylime-init/templates/ca-job.yaml +++ b/build/helm/keylime/charts/keylime-init/templates/ca-job.yaml @@ -37,7 +37,12 @@ spec: fieldRef: fieldPath: metadata.namespace - name: KEYLIME_SECRETS_NAME - value: "{{ include "keylime.ca.secret" . }}" + value: "{{ include "keylime.ca.secret.certs" . }}" + - name: KEYLIME_CA_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keylime.ca.secret.password" . }} + key: KEYLIME_CA_PASSWORD - name: KEYLIME_SECRETS_CA_PW_NAME value: "{{ include "keylime.ca.secret.password" . }}" command: @@ -45,37 +50,56 @@ spec: - -c - | ls /usr/local/bin/kubectl - if [ $? -ne 0 ] + if [[ $? -ne 0 ]] then pushd /usr/local/bin curl -LO https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl chmod +x /usr/local/bin/kubectl popd fi + + if [[ -z $KEYLIME_CA_PASSWORD ]] + then + echo "ERROR: unable to find created secret" + exit 1 + fi + # check if the secrets exist already in which case we'll just respect them - kubectl get secret $KEYLIME_SECRETS_NAME $KEYLIME_SECRETS_CA_PW_NAME - if [ $? -eq 0 ] ; then - echo "NOTE: secrets already exist, we will *NOT* recreate them!" + kubectl get secret $KEYLIME_SECRETS_NAME --namespace ${KEYLIME_NAMESPACE} + if [[ $? -eq 0 ]] + then + echo "NOTE: secret containing TLS certificates already exist, we will *NOT* recreate it!" exit 0 fi - # now fail if any of the commands fail - set -e # create a directory where we'll generate the certs to mkdir -p /tmp/certs cd /tmp # this generates a password for the CA which is required - export KEYLIME_CA_PASSWORD=$(openssl rand -base64 32) # now generate the CV CA - keylime_ca -d /tmp/certs --command init - keylime_ca -d /tmp/certs --command create --name server - keylime_ca -d /tmp/certs --command create --name client + keylime_ca -d /tmp/certs --command init && keylime_ca -d /tmp/certs --command create --name server && keylime_ca -d /tmp/certs --command create --name client + if [[ $? -ne 0 ]] + then + echo "ERROR: unable to generete certificates" + exit 1 + fi # create Kubernetes secrets from this - we'll create a separate secret for the CA password kubectl create secret generic $KEYLIME_SECRETS_NAME --namespace ${KEYLIME_NAMESPACE} --from-file=/tmp/certs - kubectl create secret generic $KEYLIME_SECRETS_CA_PW_NAME --namespace ${KEYLIME_NAMESPACE} --from-literal=KEYLIME_CA_PASSWORD=$KEYLIME_CA_PASSWORD + if [[ $? -ne 0 ]] + then + echo "ERROR: unable to create secret with certificates" + exit 1 + fi + kubectl get secret $KEYLIME_SECRETS_NAME --namespace ${KEYLIME_NAMESPACE} + if [[ $? -ne 0 ]] + then + echo "ERROR: unable to check if secret with certificates was indeed created" + exit 1 + fi + exit 0 securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" diff --git a/build/helm/keylime/charts/keylime-init/templates/capw-secret.yaml b/build/helm/keylime/charts/keylime-init/templates/capw-secret.yaml new file mode 100644 index 0000000..483d1f1 --- /dev/null +++ b/build/helm/keylime/charts/keylime-init/templates/capw-secret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.ca.generate -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "keylime.ca.secret.password" . }} + labels: + {{- include "init.labels" . | nindent 4 }} + annotations: + "helm.sh/resource-policy": "keep" + "helm.sh/hook": pre-install +type: Opaque +data: + KEYLIME_CA_PASSWORD: {{ printf "%s" (include "keylime.ca.secret.passwordcontents" .) }} +{{- end -}} diff --git a/build/helm/keylime/charts/keylime-init/templates/mysql-secret.yaml b/build/helm/keylime/charts/keylime-init/templates/mysql-secret.yaml new file mode 100644 index 0000000..c7e41aa --- /dev/null +++ b/build/helm/keylime/charts/keylime-init/templates/mysql-secret.yaml @@ -0,0 +1,15 @@ +{{- if .Values.global.database.mysql.enable }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "keylime.mysql.secret.password" . }} + labels: + {{- include "init.labels" . | nindent 4 }} + annotations: + "helm.sh/resource-policy": "keep" + "helm.sh/hook": pre-install +type: Opaque +data: + mysql-root-password: {{ printf "%s" (include "keylime.mysql.secret.passwordcontents" .) }} + mysql-password: {{ printf "%s" (include "keylime.mysql.secret.passwordcontents" .) }} +{{- end -}} \ No newline at end of file diff --git a/build/helm/keylime/charts/keylime-registrar/templates/_helpers.tpl b/build/helm/keylime/charts/keylime-registrar/templates/_helpers.tpl index 0ff3199..7ae9171 100644 --- a/build/helm/keylime/charts/keylime-registrar/templates/_helpers.tpl +++ b/build/helm/keylime/charts/keylime-registrar/templates/_helpers.tpl @@ -77,9 +77,9 @@ Expand to the secret name for the certificate volume to be used */}} {{- define "registrar.ca.secret" -}} {{- if .Values.global.ca.generate }} -{{- include "keylime.ca.secret" . }} +{{- include "keylime.ca.secret.certs" . }} {{- else }} -{{- default (include "keylime.ca.secret" .) .Values.global.ca.registrarName }} +{{- default (include "keylime.ca.secret.certs" .) .Values.global.ca.registrarName }} {{- end }} {{- end }} diff --git a/build/helm/keylime/charts/keylime-tenant/templates/_helpers.tpl b/build/helm/keylime/charts/keylime-tenant/templates/_helpers.tpl index b85ecb0..6548411 100644 --- a/build/helm/keylime/charts/keylime-tenant/templates/_helpers.tpl +++ b/build/helm/keylime/charts/keylime-tenant/templates/_helpers.tpl @@ -77,9 +77,9 @@ Expand to the secret name for the certificate volume to be used */}} {{- define "tenant.ca.secret" -}} {{- if .Values.global.ca.generate }} -{{- include "keylime.ca.secret" . }} +{{- include "keylime.ca.secret.certs" . }} {{- else }} -{{- default (include "keylime.ca.secret" .) .Values.global.ca.tenantName }} +{{- default (include "keylime.ca.secret.certs" .) .Values.global.ca.tenantName }} {{- end }} {{- end }} diff --git a/build/helm/keylime/charts/keylime-verifier/templates/_helpers.tpl b/build/helm/keylime/charts/keylime-verifier/templates/_helpers.tpl index e4e4325..b6a494b 100644 --- a/build/helm/keylime/charts/keylime-verifier/templates/_helpers.tpl +++ b/build/helm/keylime/charts/keylime-verifier/templates/_helpers.tpl @@ -77,9 +77,9 @@ Expand to the secret name for the certificate volume to be used */}} {{- define "verifier.ca.secret" -}} {{- if .Values.global.ca.generate }} -{{- include "keylime.ca.secret" . }} +{{- include "keylime.ca.secret.certs" . }} {{- else }} -{{- default (include "keylime.ca.secret" .) .Values.global.ca.verifierName }} +{{- default (include "keylime.ca.secret.certs" .) .Values.global.ca.verifierName }} {{- end }} {{- end }} diff --git a/build/helm/keylime/templates/NOTES.txt b/build/helm/keylime/templates/NOTES.txt index f1fccf9..50a2341 100644 --- a/build/helm/keylime/templates/NOTES.txt +++ b/build/helm/keylime/templates/NOTES.txt @@ -10,7 +10,7 @@ To control and manage your keylime installation, you probably want to use the `k You can achieve this by "execing" into a keylime-tenant pod that got deployed with this installation by running the following command: {{ $klt := index .Subcharts "keylime-tenant" }} - kubectl exec -ti $(kubectl get pods -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "tenant.name" $klt }} -o name | head -n 1) -c {{ $klt.Chart.Name }} -- /bin/bash + kubectl exec -ti -n {{ .Release.Namespace }} $(kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "tenant.name" $klt }} -o name | head -n 1) -c {{ $klt.Chart.Name }} -- /bin/bash From within the pod you can interact with keylime with the common `keylime_tenant` commands. The pod comes preconfigured with all registrar and verifier URLs. As it is a Kubernetes pod, it will also have direct access to the agent pods. Therefore the keylime-tenant pod is in a perfect position to run all `keylime_tenant` commands. {{- else }} diff --git a/build/helm/keylime/templates/_helpers.tpl b/build/helm/keylime/templates/_helpers.tpl index 8fa8c3b..1ed7486 100644 --- a/build/helm/keylime/templates/_helpers.tpl +++ b/build/helm/keylime/templates/_helpers.tpl @@ -71,7 +71,7 @@ Expand to the name of the keylime config map {{/* Always expands to the name of the secret used for certificates when the init job runs. */}} -{{- define "keylime.ca.secret" -}} +{{- define "keylime.ca.secret.certs" -}} {{- printf "%s-%s" .Release.Name "keylime-certs" | trunc 63 | trimSuffix "-" }} {{- end }} @@ -82,6 +82,51 @@ Always expands to the name of the secret used for the CA certificate when the in {{- printf "%s-%s" .Release.Name "keylime-ca-password" | trunc 63 | trimSuffix "-" }} {{- end }} +{{- define "generate_static_password" -}} +{{- if not (index .Release "tmp_vars") -}} +{{- $_ := set .Release "tmp_vars" dict -}} +{{- end }} +{{- $key := printf "%s_%s" .Release.Name "password" -}} +{{- if not (index .Release.tmp_vars $key) -}} +{{- $_ := set .Release.tmp_vars $key (randAlphaNum 32) -}} +{{- end -}} +{{- /* Retrieve previously generated value. */ -}} +{{- index .Release.tmp_vars $key -}} +{{- end -}} + +{{/* +Generate a random password if one is not defined +*/}} +{{- define "keylime.ca.secret.passwordcontents" -}} +{{- $capwsecretname := printf "%s" (include "keylime.ca.secret.password" .) }} +{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "$capwsecretname") }} +{{- if $existingSecret -}} +{{- index $existingSecret.data "KEYLIME_CA_PASSWORD" -}} +{{- else -}} +{{- default (include "generate_static_password" .) .Values.global.ca.password | b64enc | quote -}} +{{- end -}} +{{- end -}} + +{{/* +Need to find a way to override .Values.mysql.auth.existingSecret to include Release.Name +*/}} +{{- define "keylime.mysql.secret.password" -}} +{{- printf "%s-%s" .Release.Name "keylime-mysql-password" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Generate a random password if one is not defined +*/}} +{{- define "keylime.mysql.secret.passwordcontents" -}} +{{- $mysqlpwsecretname := printf "%s" (include "keylime.mysql.secret.password" .) -}} +{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "$mysqlpwsecretname") -}} +{{- if $existingSecret -}} +{{- index $existingSecret.data "mysql-root-password" -}} +{{- else -}} +{{- default (include "generate_static_password" .) .Values.global.database.mysql.password | b64enc | quote -}} +{{- end -}} +{{- end -}} + {{/* Always expands to the name of the secret used for the TPM cert store when the init job runs. */}} diff --git a/build/helm/keylime/templates/configmap.yaml b/build/helm/keylime/templates/configmap.yaml index 3c20647..e78101f 100644 --- a/build/helm/keylime/templates/configmap.yaml +++ b/build/helm/keylime/templates/configmap.yaml @@ -6,6 +6,13 @@ metadata: labels: {{- include "keylime.labels" . | nindent 4 }} data: + +{{- if .Values.global.database.mysql.enable }} + {{- $mysqlPassword := printf "%s" (include "keylime.mysql.secret.passwordcontents" .) | replace "\"" "" | b64dec }} + KEYLIME_REGISTRAR_DATABASE_URL: "mysql+pymysql://root:{{ $mysqlPassword }}@{{ template "mysql.primary.fullname" ( index .Subcharts "mysql" ) }}.{{ .Release.Namespace }}.svc.cluster.local:3306/{{ .Values.mysql.auth.database }}?charset=utf8" + KEYLIME_VERIFIER_DATABASE_URL: "mysql+pymysql://root:{{ $mysqlPassword }}@{{ template "mysql.primary.fullname" ( index .Subcharts "mysql" ) }}.{{ .Release.Namespace }}.svc.cluster.local:3306/{{ .Values.mysql.auth.database }}?charset=utf8" +{{- end }} + {{- if .Values.tags.agent }} KEYLIME_AGENT_UUID: "hash_ek" KEYLIME_AGENT_IP: "0.0.0.0" diff --git a/build/helm/keylime/values.yaml b/build/helm/keylime/values.yaml index 52245c1..f9f2e84 100644 --- a/build/helm/keylime/values.yaml +++ b/build/helm/keylime/values.yaml @@ -47,6 +47,8 @@ global: registrarName: "" # verifierName is the name of the secret to be used for the "cv_ca" folder for the registrar if generate is not true verifierName: "" + # leave it empty, and new password, maintained accross multiple upgrades, will be generated + password: "" # tpmCertStore manages the TPM cert store which is used for verifying the EK of the TPMs tpmCertStore: # create means that an init job will run which will create a Kubernetes secret with the "well known" CAs for EKs. @@ -111,3 +113,14 @@ global: # This will pull in a PostgreSQL helm chart for deployment. # TODO: implement enable: false + # mysql enables a MySQL database backend + mysql: + # enable activates the PostgreSQL database backend + # This will pull in a MySQL helm chart for deployment. + enable: false + # leave it empty, and a new password, maintained accross multiple upgrades, will be generated + password: "" +mysql: + auth: + existingSecret: "{{ .Release.Name }}-keylime-mysql-password" + database: "keylimedb"