diff --git a/charts/platform/README.md b/charts/platform/README.md index da7c4fc..a8af6c6 100644 --- a/charts/platform/README.md +++ b/charts/platform/README.md @@ -352,5 +352,7 @@ realms: | services.kas.config.keyring | list | `[{"alg":"ec:secp256r1","kid":"e1"},{"alg":"rsa:2048","kid":"r1"}]` | Default keys for clients to use | | services.kas.privateKeysSecret | string | `"kas-private-keys"` | KAS secret containing keys kas-private.pem , kas-cert.pem , kas-ec-private.pem , kas-ec-cert.pem | | tolerations | list | `[]` | Tolerations to apply to the pod (https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| volumeMountTemplate | string | `"platform.volumeMountsEmpty.tpl"` | Add ability for downstream chart to merge additional volumeMounts | | volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition. | +| volumeTemplate | string | `"platform.volumesEmpty.tpl"` | Add ability for downstream chart to merge additional volumes | | volumes | list | `[]` | Additional volumes on the output Deployment definition. | \ No newline at end of file diff --git a/charts/platform/templates/_helpers.tpl b/charts/platform/templates/_helpers.tpl index 088f105..b689f66 100644 --- a/charts/platform/templates/_helpers.tpl +++ b/charts/platform/templates/_helpers.tpl @@ -79,4 +79,31 @@ Create the name of the service account to use {{- if and ( .Values.sdk_config.clientsecret) ( .Values.sdk_config.existingSecret.name) ( .Values.sdk_config.existingSecret.key)}} {{- fail "You cannot set both clientsecret and existingSecret in sdk_config." }} {{- end -}} +{{- end -}} + +{{- /* +platform.util.merge will merge two YAML templates and output the result. +This takes an array of three values: +- the top context +- the template name of the overrides (destination) +- the template name of the base (source) +*/ -}} +{{- define "platform.util.merge.list" -}} +{{- $top := first . -}} +{{- $filterKey := (index . 1) }} +{{- $overrides := fromYaml (include (index . 2) $top) | default (dict) -}} +{{- $tpl := fromYaml (include (index . 3) $top) | default (dict) -}} + +{{- $mergedList := index $tpl $filterKey | default (list) -}} + +{{- range $key, $values := $overrides -}} + {{- if kindIs "slice" $values }} + {{- range $key2, $value := $values }} + {{- $mergedList = append $mergedList $value -}} + {{- end }} + {{- end -}} +{{- end -}} + +{{- (dict $filterKey $mergedList) | toYaml }} + {{- end -}} \ No newline at end of file diff --git a/charts/platform/templates/_volume.tpl b/charts/platform/templates/_volume.tpl new file mode 100644 index 0000000..abe509f --- /dev/null +++ b/charts/platform/templates/_volume.tpl @@ -0,0 +1,64 @@ +{{ define "platform.volumesEmpty.tpl" }} +{{ end }} +{{ define "platform.volumes.tpl" }} +volumes: + - name: config + configMap: + name: {{ include "chart.fullname" . }} + {{- if or (contains .Values.mode "all") (contains .Values.mode "core") (contains .Values.mode "kas") }} + - name: kas-private-keys + secret: + secretName: {{ .Values.services.kas.privateKeysSecret }} + {{- if .Values.server.tls.enabled }} + {{- end }} + - name: tls + secret: + secretName: {{ .Values.server.tls.secret | default (printf "%s-tls" (include "chart.fullname" .)) }} + {{- end }} + {{- if or (and .Values.playground .Values.keycloak.ingress.enabled .Values.keycloak.ingress.tls) .Values.server.tls.additionalTrustedCerts }} + - name: trusted-certs + projected: + sources: + {{- if and .Values.playground .Values.keycloak.ingress.enabled .Values.keycloak.ingress.tls }} + - secret: + name: {{ .Values.keycloak.ingress.hostname }}-tls # If the fullnameOverride is set, this will break + optional: false + items: + - key: ca.crt + path: kc-ca.crt + {{- end -}} + {{- with .Values.server.tls.additionalTrustedCerts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + {{- with .Values.volumes }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{ end }} + + +{{ define "platform.volumeMountsEmpty.tpl" }} +{{ end }} + +{{ define "platform.volumeMounts.tpl" }} +volumeMounts: + - name: config + readOnly: true + mountPath: /etc/platform/config + {{- if or (contains .Values.mode "all") (contains .Values.mode "core") (contains .Values.mode "kas") }} + - name: kas-private-keys + readOnly: true + mountPath: /etc/platform/kas + {{- end }} + - name: trusted-certs + readOnly: true + mountPath: /etc/ssl/certs/platform + {{- if .Values.server.tls.enabled }} + - name: tls + readOnly: true + mountPath: /etc/platform/certs + {{- end -}} + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{ end }} \ No newline at end of file diff --git a/charts/platform/templates/config.yaml b/charts/platform/templates/config.yaml index 2475500..0aa4026 100644 --- a/charts/platform/templates/config.yaml +++ b/charts/platform/templates/config.yaml @@ -22,12 +22,18 @@ data: clientsecret: {{ .Values.sdk_config.clientsecret | quote }} {{- end }} services: + {{- if or (contains .Values.mode "all") (contains .Values.mode "core") }} entityresolution: {{- .Values.services.entityresolution | toYaml | nindent 8 }} + {{- end }} + {{- if or (contains .Values.mode "all") (contains .Values.mode "core") (contains .Values.mode "kas") }} kas: {{- .Values.services.kas.config | toYaml | nindent 8 }} + {{- end }} + {{- if or (contains .Values.mode "all") (contains .Values.mode "core") }} authorization: {{- .Values.services.authorization | toYaml | nindent 8 }} + {{- end }} {{- with .Values.services.extraServices }} {{- toYaml . | nindent 6 }} {{- end }} diff --git a/charts/platform/templates/deployment.yaml b/charts/platform/templates/deployment.yaml index a47fdb4..f9c0cd6 100644 --- a/charts/platform/templates/deployment.yaml +++ b/charts/platform/templates/deployment.yaml @@ -1,3 +1,4 @@ +{{ $data := dict "Release" $.Release "Chart" $.Chart "Values" $.Values "Files" $.Files "Capabilities" .Capabilities }} apiVersion: apps/v1 kind: Deployment metadata: @@ -33,7 +34,8 @@ spec: {{- if .Values.hostAliases }} hostAliases: {{- toYaml .Values.hostAliases | nindent 8 }} - {{- end }} + {{- end -}} + {{ include "platform.util.merge.list" (list $data "volumes" .Values.volumeTemplate "platform.volumes.tpl" ) | nindent 6 }} containers: - name: {{ .Chart.Name }} args: @@ -62,24 +64,7 @@ spec: {{ end }} resources: {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: config - readOnly: true - mountPath: /etc/platform/config - - name: kas-private-keys - readOnly: true - mountPath: /etc/platform/kas - - name: trusted-certs - readOnly: true - mountPath: /etc/ssl/certs/platform - {{- if .Values.server.tls.enabled }} - - name: tls - readOnly: true - mountPath: /etc/platform/certs - {{- end -}} - {{- with .Values.volumeMounts }} - {{- toYaml . | nindent 12 }} - {{- end }} + {{ include "platform.util.merge.list" (list $data "volumeMounts" .Values.volumeMountTemplate "platform.volumeMounts.tpl" ) | nindent 10}} env: - name: SSL_CERT_DIR value: '/etc/ssl/certs:/etc/ssl/certs/platform' @@ -102,35 +87,6 @@ spec: envFrom: {{- toYaml . | nindent 10 }} {{- end }} - volumes: - - name: config - configMap: - name: {{ include "chart.fullname" . }} - - name: kas-private-keys - secret: - secretName: {{ .Values.services.kas.privateKeysSecret }} - {{- if .Values.server.tls.enabled }} - - name: tls - secret: - secretName: {{ .Values.server.tls.secret | default (printf "%s-tls" (include "chart.fullname" .)) }} - {{- end }} - - name: trusted-certs - projected: - sources: - {{- if and .Values.playground .Values.keycloak.ingress.enabled .Values.keycloak.ingress.tls }} - - secret: - name: {{ .Values.keycloak.ingress.hostname }}-tls # If the fullnameOverride is set, this will break - optional: false - items: - - key: ca.crt - path: kc-ca.crt - {{- end -}} - {{- with .Values.server.tls.additionalTrustedCerts }} - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.volumes }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/platform/values.yaml b/charts/platform/values.yaml index 7adb01d..586a577 100644 --- a/charts/platform/values.yaml +++ b/charts/platform/values.yaml @@ -119,6 +119,8 @@ autoscaling: targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- Add ability for downstream chart to merge additional volumes +volumeTemplate: "platform.volumesEmpty.tpl" # -- Additional volumes on the output Deployment definition. volumes: [] # - name: foo @@ -126,6 +128,8 @@ volumes: [] # secretName: mysecret # optional: false +# -- Add ability for downstream chart to merge additional volumeMounts +volumeMountTemplate: "platform.volumeMountsEmpty.tpl" # -- Additional volumeMounts on the output Deployment definition. volumeMounts: [] # - name: foo diff --git a/tests/chart_platform_template_test.go b/tests/chart_platform_template_test.go index 920be91..722af65 100644 --- a/tests/chart_platform_template_test.go +++ b/tests/chart_platform_template_test.go @@ -72,3 +72,101 @@ func (suite *PlatformChartTemplateSuite) Test_SDK_Config_Set_Client_Secret_AND_E suite.Require().Error(err) suite.Require().ErrorContains(err, "You cannot set both clientsecret and existingSecret in sdk_config.") } + +func (suite *PlatformChartTemplateSuite) Test_Playground_Enabled_AND_Keycloak_Ing_Enabled_Trusted_Cert_Mounted() { + releaseName := "basic" + + namespaceName := "opentdf-" + strings.ToLower(random.UniqueId()) + + options := &helm.Options{ + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + SetValues: map[string]string{ + "image.tag": "latest", + "playground": "true", + }, + } + + output := helm.RenderTemplate(suite.T(), options, suite.chartPath, releaseName, []string{"templates/deployment.yaml"}) + var deployment appv1.Deployment + helm.UnmarshalK8SYaml(suite.T(), output, &deployment) + + // Find projected volume trusted-certs and check if keycloak cert is mounted + found := false + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Projected != nil { + for _, source := range volume.Projected.Sources { + suite.T().Log("Secret Name: ", source.Secret.Name) + if source.Secret != nil && source.Secret.Name == "keycloak.local-tls" { + suite.Require().Equal("ca.crt", source.Secret.Items[0].Key) + suite.Require().Equal("kc-ca.crt", source.Secret.Items[0].Path) + } + } + } + } + suite.Require().True(found) +} + +func (suite *PlatformChartTemplateSuite) Test_Playground_Enabled_AND_Keycloak_Ing_Disabled_Trusted_Cert_Not_Mounted() { + releaseName := "basic" + + namespaceName := "opentdf-" + strings.ToLower(random.UniqueId()) + + options := &helm.Options{ + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + SetValues: map[string]string{ + "image.tag": "latest", + "playground": "true", + "keycloak.ingress.enabled": "false", + }, + } + + output := helm.RenderTemplate(suite.T(), options, suite.chartPath, releaseName, []string{"templates/deployment.yaml"}) + var deployment appv1.Deployment + helm.UnmarshalK8SYaml(suite.T(), output, &deployment) + + // Find projected volume trusted-certs and check if keycloak cert is mounted + found := false + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Projected != nil { + for _, source := range volume.Projected.Sources { + if source.Secret != nil && source.Secret.Name == "keycloak.local-tls" { + found = true + } + } + } + } + suite.Require().False(found) +} + +func (suite *PlatformChartTemplateSuite) Test_Playground_Enabled_AND_Keycloak_Ing_Enabled_AND_TLS_Disabled_Trusted_Cert_Not_Mounted() { + releaseName := "basic" + + namespaceName := "opentdf-" + strings.ToLower(random.UniqueId()) + + options := &helm.Options{ + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + SetValues: map[string]string{ + "image.tag": "latest", + "playground": "true", + "keycloak.ingress.enabled": "true", + "keycloak.ingress.tls": "false", + }, + } + + output := helm.RenderTemplate(suite.T(), options, suite.chartPath, releaseName, []string{"templates/deployment.yaml"}) + var deployment appv1.Deployment + helm.UnmarshalK8SYaml(suite.T(), output, &deployment) + + // Find projected volume trusted-certs and check if keycloak cert is mounted + found := false + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Projected != nil { + for _, source := range volume.Projected.Sources { + if source.Secret != nil && source.Secret.Name == "keycloak.local-tls" { + found = true + } + } + } + } + suite.Require().False(found) +} diff --git a/tests/kubeconform/extra_volumes-values.yaml b/tests/kubeconform/extra_volumes-values.yaml new file mode 100644 index 0000000..f8b7d84 --- /dev/null +++ b/tests/kubeconform/extra_volumes-values.yaml @@ -0,0 +1,9 @@ +volumeMounts: + - name: test + mountPath: /extra-config + readOnly: true +volumes: + - name: test + secret: + secretName: mysecret + optional: false \ No newline at end of file diff --git a/tests/kubeconform/mode_all-values.yaml b/tests/kubeconform/mode_all-values.yaml new file mode 100644 index 0000000..f52409a --- /dev/null +++ b/tests/kubeconform/mode_all-values.yaml @@ -0,0 +1 @@ +mode: all \ No newline at end of file diff --git a/tests/kubeconform/mode_combo-values.yaml b/tests/kubeconform/mode_combo-values.yaml new file mode 100644 index 0000000..14f6d37 --- /dev/null +++ b/tests/kubeconform/mode_combo-values.yaml @@ -0,0 +1 @@ +mode: test,kas \ No newline at end of file diff --git a/tests/kubeconform/mode_core-values.yaml b/tests/kubeconform/mode_core-values.yaml new file mode 100644 index 0000000..ec09534 --- /dev/null +++ b/tests/kubeconform/mode_core-values.yaml @@ -0,0 +1 @@ +mode: core \ No newline at end of file diff --git a/tests/kubeconform/mode_kas-values.yaml b/tests/kubeconform/mode_kas-values.yaml new file mode 100644 index 0000000..5ce5733 --- /dev/null +++ b/tests/kubeconform/mode_kas-values.yaml @@ -0,0 +1 @@ +mode: kas \ No newline at end of file diff --git a/tests/kubeconform/mode_test-values.yaml b/tests/kubeconform/mode_test-values.yaml new file mode 100644 index 0000000..6ccc2ac --- /dev/null +++ b/tests/kubeconform/mode_test-values.yaml @@ -0,0 +1 @@ +mode: test \ No newline at end of file diff --git a/tests/traefik.yaml b/tests/traefik.yaml new file mode 100644 index 0000000..6a6fdd7 --- /dev/null +++ b/tests/traefik.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: platform +spec: + entryPoints: + - websecure + routes: + - match: Host(`keycloak.opentdf.local`) + kind: Rule + services: + - name: platform-keycloak + namespace: opentdf-sd1jsk + port: 80 + scheme: http + passHostHeader: true + - match: Host(`platform.opentdf.local`) + kind: Rule + services: + - name: opentdf-platform + namespace: opentdf-sd1jsk + port: 9000 + scheme: h2c + passHostHeader: true + tls: + secretName: platform-tls \ No newline at end of file