diff --git a/.github/tests/tornjak/install.sh b/.github/tests/tornjak/install.sh new file mode 100755 index 000000000..bfb0139b0 --- /dev/null +++ b/.github/tests/tornjak/install.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -x + +SCRIPT=$(readlink -f "$0") +SCRIPTPATH=$(dirname "$SCRIPT") + +helm install \ + --namespace spire-server \ + --values "${SCRIPTPATH}/../../../examples/production/values.yaml" \ + --values "${SCRIPTPATH}/../../../examples/tornjak/values.yaml" \ + spire charts/spire --wait +helm test spire -n spire-server diff --git a/.github/tests/tornjak/post-install.sh b/.github/tests/tornjak/post-install.sh new file mode 100755 index 000000000..c5040ec2a --- /dev/null +++ b/.github/tests/tornjak/post-install.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -x + +SCRIPT="$(readlink -f "$0")" +SCRIPTPATH="$(dirname "${SCRIPT}")" +scenario="${scenario:-$(basename "${SCRIPTPATH}")}" + +# shellcheck source=/dev/null +source "${SCRIPTPATH}/../common.sh" + +print_helm_releases +print_spire_workload_status spire-server spire-system + +kubectl rollout status --watch --timeout 180s --namespace spire-server deployments.apps spire-tornjak-frontend +kubectl -n spire-server get deploy spire-tornjak-frontend +kubectl -n spire-server get service spire-tornjak-frontend + + +if [[ "$1" -ne 0 ]]; then + get_namespace_details spire-server + get_namespace_details spire-system +fi diff --git a/.github/tests/tornjak/pre-install.sh b/.github/tests/tornjak/pre-install.sh new file mode 100755 index 000000000..b33d1edbc --- /dev/null +++ b/.github/tests/tornjak/pre-install.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +kubectl create namespace spire-system +kubectl label namespace spire-system pod-security.kubernetes.io/enforce=privileged +kubectl create namespace spire-server +kubectl label namespace spire-server pod-security.kubernetes.io/enforce=restricted diff --git a/charts/spire/Chart.yaml b/charts/spire/Chart.yaml index 62af05f56..12af3d9c7 100644 --- a/charts/spire/Chart.yaml +++ b/charts/spire/Chart.yaml @@ -38,6 +38,10 @@ dependencies: condition: spiffe-oidc-discovery-provider.enabled repository: file://./charts/spiffe-oidc-discovery-provider version: 0.1.0 + - name: tornjak-frontend + condition: tornjak-frontend.enabled + repository: file://./charts/tornjak-frontend + version: 0.1.0 annotations: artifacthub.io/category: security artifacthub.io/license: Apache-2.0 diff --git a/charts/spire/README.md b/charts/spire/README.md index e77bf5c1b..582fdfe5f 100644 --- a/charts/spire/README.md +++ b/charts/spire/README.md @@ -104,6 +104,7 @@ Kubernetes: `>=1.21.0-0` | file://./charts/spiffe-oidc-discovery-provider | spiffe-oidc-discovery-provider | 0.1.0 | | file://./charts/spire-agent | spire-agent | 0.1.0 | | file://./charts/spire-server | spire-server | 0.1.0 | +| file://./charts/tornjak-frontend | tornjak-frontend | 0.1.0 | ## Values @@ -121,6 +122,7 @@ Kubernetes: `>=1.21.0-0` | spire-server.controllerManager.enabled | bool | `true` | | | spire-server.enabled | bool | `true` | | | spire-server.nameOverride | string | `"server"` | | +| tornjak-frontend.enabled | bool | `false` | | | spiffe-csi-driver.agentSocketPath | string | `"/run/spire/agent-sockets/spire-agent.sock"` | The unix socket path to the spire-agent | | spiffe-csi-driver.fullnameOverride | string | `""` | | | spiffe-csi-driver.healthChecks.port | int | `9809` | | @@ -312,7 +314,7 @@ Kubernetes: `>=1.21.0-0` | spire-server.image.pullPolicy | string | `"IfNotPresent"` | The image pull policy | | spire-server.image.registry | string | `"ghcr.io"` | The OCI registry to pull the image from | | spire-server.image.repository | string | `"spiffe/spire-server"` | The repository within the registry | -| spire-server.image.version | string | `""` | | +| spire-server.image.version | string | `""` | Overrides the image tag whose default is the chart appVersion. | | spire-server.imagePullSecrets | list | `[]` | | | spire-server.initContainers | list | `[]` | | | spire-server.jwtIssuer | string | `"oidc-discovery.example.org"` | The JWT issuer domain | @@ -343,6 +345,14 @@ Kubernetes: `>=1.21.0-0` | spire-server.telemetry.prometheus.podMonitor.namespace | string | `""` | Override where to install the podMonitor, if not set will use the same namespace as the spire-server | | spire-server.tolerations | list | `[]` | | | spire-server.topologySpreadConstraints | list | `[]` | | +| spire-server.tornjak.config.dataStore | object | `{"driver":"sqlite3","file":"/run/spire/data/tornjak.sqlite3"}` | persistent DB for storing Tornjak specific information | +| spire-server.tornjak.enabled | bool | `false` | Deploys Tornjak API (backend) | +| spire-server.tornjak.image | object | `{"pullPolicy":"IfNotPresent","registry":"ghcr.io","repository":"spiffe/tornjak-backend","version":"v1.2.0"}` | Tornjak API image | +| spire-server.tornjak.image.version | string | `"v1.2.0"` | Overrides the image tag whose default is the chart appVersion. | +| spire-server.tornjak.resources | object | `{}` | | +| spire-server.tornjak.service.annotations | object | `{}` | | +| spire-server.tornjak.service.port | int | `10000` | | +| spire-server.tornjak.service.type | string | `"ClusterIP"` | | | spire-server.trustDomain | string | `"example.org"` | Set the trust domain to be used for the SPIFFE identifiers | | spire-server.upstreamAuthority.certManager.enabled | bool | `false` | | | spire-server.upstreamAuthority.certManager.issuer_group | string | `"cert-manager.io"` | | @@ -355,5 +365,24 @@ Kubernetes: `>=1.21.0-0` | spire-server.upstreamAuthority.disk.secret.create | bool | `true` | If disabled requires you to create a secret with the given keys (certificate, key and optional bundle) yourself. | | spire-server.upstreamAuthority.disk.secret.data | object | `{"bundle":"","certificate":"","key":""}` | If secret creation is enabled, will create a secret with following certificate info | | spire-server.upstreamAuthority.disk.secret.name | string | `"spiffe-upstream-ca"` | If secret creation is disabled, the secret with this name will be used. | +| tornjak-frontend.apiServerURL | string | `"http://localhost:10000/"` | URL of the Tornjak APIs (backend) Since Tornjak Frontend runs in the browser, this URL must be accessible from the machine running a browser. | +| tornjak-frontend.fullnameOverride | string | `""` | | +| tornjak-frontend.image.pullPolicy | string | `"IfNotPresent"` | | +| tornjak-frontend.image.registry | string | `"ghcr.io"` | | +| tornjak-frontend.image.repository | string | `"spiffe/tornjak-frontend"` | | +| tornjak-frontend.image.version | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| tornjak-frontend.imagePullSecrets | list | `[]` | | +| tornjak-frontend.labels | object | `{}` | | +| tornjak-frontend.nameOverride | string | `""` | | +| tornjak-frontend.namespaceOverride | string | `""` | | +| tornjak-frontend.podSecurityContext | object | `{}` | | +| tornjak-frontend.securityContext | object | `{}` | | +| tornjak-frontend.service.annotations | object | `{}` | | +| tornjak-frontend.service.port | int | `3000` | | +| tornjak-frontend.service.type | string | `"ClusterIP"` | | +| tornjak-frontend.serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| tornjak-frontend.serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| tornjak-frontend.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| tornjak-frontend.spireHealthCheck.enabled | bool | `true` | Enables the SPIRE Healthchecker indicator | ---------------------------------------------- diff --git a/charts/spire/charts/spire-server/README.md b/charts/spire/charts/spire-server/README.md index efc2ba583..d6ac19025 100644 --- a/charts/spire/charts/spire-server/README.md +++ b/charts/spire/charts/spire-server/README.md @@ -75,7 +75,7 @@ A Helm chart to install the SPIRE server. | image.pullPolicy | string | `"IfNotPresent"` | The image pull policy | | image.registry | string | `"ghcr.io"` | The OCI registry to pull the image from | | image.repository | string | `"spiffe/spire-server"` | The repository within the registry | -| image.version | string | `""` | | +| image.version | string | `""` | Overrides the image tag whose default is the chart appVersion. | | imagePullSecrets | list | `[]` | | | initContainers | list | `[]` | | | jwtIssuer | string | `"oidc-discovery.example.org"` | The JWT issuer domain | @@ -106,6 +106,14 @@ A Helm chart to install the SPIRE server. | telemetry.prometheus.podMonitor.namespace | string | `""` | Override where to install the podMonitor, if not set will use the same namespace as the spire-server | | tolerations | list | `[]` | | | topologySpreadConstraints | list | `[]` | | +| tornjak.config.dataStore | object | `{"driver":"sqlite3","file":"/run/spire/data/tornjak.sqlite3"}` | persistent DB for storing Tornjak specific information | +| tornjak.enabled | bool | `false` | Deploys Tornjak API (backend) | +| tornjak.image | object | `{"pullPolicy":"IfNotPresent","registry":"ghcr.io","repository":"spiffe/tornjak-backend","version":"v1.2.0"}` | Tornjak API image | +| tornjak.image.version | string | `"v1.2.0"` | Overrides the image tag whose default is the chart appVersion. | +| tornjak.resources | object | `{}` | | +| tornjak.service.annotations | object | `{}` | | +| tornjak.service.port | int | `10000` | | +| tornjak.service.type | string | `"ClusterIP"` | | | trustDomain | string | `"example.org"` | Set the trust domain to be used for the SPIFFE identifiers | | upstreamAuthority.certManager.enabled | bool | `false` | | | upstreamAuthority.certManager.issuer_group | string | `"cert-manager.io"` | | diff --git a/charts/spire/charts/spire-server/templates/NOTES.txt b/charts/spire/charts/spire-server/templates/NOTES.txt index b0137988f..1c008830e 100644 --- a/charts/spire/charts/spire-server/templates/NOTES.txt +++ b/charts/spire/charts/spire-server/templates/NOTES.txt @@ -4,3 +4,20 @@ Installed {{ .Chart.Name }}… kubectl exec -n {{ .Release.Namespace }} {{ include "spire-server.fullname" . }}-0 -c spire-server -- \ spire-server entry show + +{{- if eq (.Values.tornjak.enabled | toString) "true" }} + +Installed {{ include "spire-tornjak.fullname" . }}… + +### WARNING ### + +Tornjak runs without authentication and is therefore NOT suitable to run in production environments. +Only use in test environments! + +Access Tornjak: + + kubectl -n {{ include "spire-server.namespace" . }} port-forward service/{{ include "spire-tornjak.backend" . }} {{ .Values.tornjak.service.port }}:10000 + +Open browser to: http://localhost:{{ .Values.tornjak.service.port }} + +{{- end }} diff --git a/charts/spire/charts/spire-server/templates/_helpers.tpl b/charts/spire/charts/spire-server/templates/_helpers.tpl index 3fcff92e5..7df362661 100644 --- a/charts/spire/charts/spire-server/templates/_helpers.tpl +++ b/charts/spire/charts/spire-server/templates/_helpers.tpl @@ -153,3 +153,19 @@ Create the name of the service account to use {{- end }} {{- $config | toYaml }} {{- end }} + +{{/* +Tornjak specific section +*/}} + +{{- define "spire-tornjak.fullname" -}} +{{ include "spire-server.fullname" . | trimSuffix "-server" }}-tornjak +{{- end }} + +{{- define "spire-tornjak.config" -}} +{{ include "spire-tornjak.fullname" . }}-config +{{- end }} + +{{- define "spire-tornjak.backend" -}} +{{ include "spire-tornjak.fullname" . }}-backend +{{- end }} diff --git a/charts/spire/charts/spire-server/templates/statefulset.yaml b/charts/spire/charts/spire-server/templates/statefulset.yaml index f9e6262e8..dc67dab96 100644 --- a/charts/spire/charts/spire-server/templates/statefulset.yaml +++ b/charts/spire/charts/spire-server/templates/statefulset.yaml @@ -1,6 +1,7 @@ {{- $configSum := (include (print $.Template.BasePath "/configmap.yaml") . | sha256sum) }} {{- $configSum2 := (include (print $.Template.BasePath "/secret.yaml") . | sha256sum) }} {{- $configSum3 := (include (print $.Template.BasePath "/controller-manager-configmap.yaml") . | sha256sum) }} +{{- $configSumTornjak := (include (print $.Template.BasePath "/tornjak-config.yaml") . | sha256sum) }} {{- $fullname := include "spire-server.fullname" . }} apiVersion: apps/v1 kind: StatefulSet @@ -26,6 +27,7 @@ spec: checksum/config: {{ $configSum }} checksum/config2: {{ $configSum2 }} checksum/config3: {{ $configSum3 }} + checksum/configTornjak: {{ $configSumTornjak }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -155,6 +157,49 @@ spec: mountPath: /tmp readOnly: false {{- end }} + + {{- if eq (.Values.tornjak.enabled | toString) "true" }} + - name: tornjak + securityContext: + {{- toYaml .Values.controllerManager.securityContext | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tornjak.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.tornjak.image.pullPolicy }} + startupProbe: + httpGet: + scheme: HTTP + port: 10000 + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + args: + - -c + - /run/spire/config/server.conf + - -t + - /run/spire/tornjak-config/server.conf + ports: + - name: tornjak + containerPort: 10000 + protocol: TCP + resources: + {{- toYaml .Values.tornjak.resources | nindent 12 }} + volumeMounts: + - name: {{ include "spire-tornjak.config" . }} + mountPath: /run/spire/tornjak-config + - name: spire-server-socket + mountPath: /tmp/spire-server/private + readOnly: true + - name: spire-config + mountPath: /run/spire/config + readOnly: true + {{- if eq (.Values.dataStorage.enabled | toString) "true" }} + - name: spire-data + mountPath: /run/spire/data + readOnly: false + {{- end }} + {{- end }} + {{- if gt (len .Values.extraContainers) 0 }} {{- toYaml .Values.extraContainers | nindent 8 }} {{- end }} @@ -192,6 +237,14 @@ spec: configMap: name: {{ include "spire-controller-manager.fullname" . }} {{- end }} + {{- if eq (.Values.tornjak.enabled | toString) "true" }} + {{- if .Values.tornjak.config }} + - name: {{ include "spire-tornjak.config" . }} + configMap: + defaultMode: 420 + name: {{ include "spire-tornjak.config" . }} + {{- end }} + {{- end }} {{- if gt (len .Values.extraVolumes) 0 }} {{- toYaml .Values.extraVolumes | nindent 8 }} {{- end }} diff --git a/charts/spire/charts/spire-server/templates/tests/test-tornjak-connection.yaml b/charts/spire/charts/spire-server/templates/tests/test-tornjak-connection.yaml new file mode 100644 index 000000000..5b386925f --- /dev/null +++ b/charts/spire/charts/spire-server/templates/tests/test-tornjak-connection.yaml @@ -0,0 +1,22 @@ +{{- if eq (.Values.tornjak.enabled | toString) "true" }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "spire-tornjak.fullname" . }}-test-connection" + namespace: {{ include "spire-server.namespace" . }} + labels: + {{- include "spire-server.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 4 }} + containers: + - name: curl-tornjak-backend + image: cgr.dev/chainguard/bash:latest + command: ['curl'] + args: ['-k', '-s', '-f', 'http://{{ include "spire-tornjak.backend" . }}.{{ include "spire-server.namespace" . }}.svc.{{ include "spire-lib.cluster-domain" . }}:{{ .Values.tornjak.service.port }}'] + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + restartPolicy: Never +{{- end }} diff --git a/charts/spire/charts/spire-server/templates/tornjak-config.yaml b/charts/spire/charts/spire-server/templates/tornjak-config.yaml new file mode 100644 index 000000000..a112051a7 --- /dev/null +++ b/charts/spire/charts/spire-server/templates/tornjak-config.yaml @@ -0,0 +1,23 @@ +{{- if eq (.Values.tornjak.enabled | toString) "true" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire-tornjak.config" . }} + namespace: {{ include "spire-server.namespace" . }} +data: + server.conf: | + server { + metadata = "insert metadata" + } + + plugins { + {{- if .Values.tornjak.config.dataStore }} + DataStore "sql" { + plugin_data { + drivername = "{{ .Values.tornjak.config.dataStore.driver }}" + filename = "{{ .Values.tornjak.config.dataStore.file }}" + } + } + {{- end }} + } +{{- end }} diff --git a/charts/spire/charts/spire-server/templates/tornjak-service.yaml b/charts/spire/charts/spire-server/templates/tornjak-service.yaml new file mode 100644 index 000000000..a4a95bdf4 --- /dev/null +++ b/charts/spire/charts/spire-server/templates/tornjak-service.yaml @@ -0,0 +1,22 @@ +{{- if eq (.Values.tornjak.enabled | toString) "true" }} +apiVersion: v1 +kind: Service +metadata: + namespace: {{ include "spire-server.namespace" . }} + name: {{ include "spire-tornjak.backend" . }} + {{- with .Values.tornjak.service.annotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "spire-server.labels" . | nindent 4 }} +spec: + type: {{ .Values.tornjak.service.type }} + selector: + {{- include "spire-server.selectorLabels" . | nindent 4 }} + ports: + - name: {{ include "spire-tornjak.backend" . }} + port: {{ .Values.tornjak.service.port }} + targetPort: tornjak + protocol: TCP +{{- end }} diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index 2d31c5ce8..96fc42553 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -12,7 +12,7 @@ image: repository: spiffe/spire-server # -- The image pull policy pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + # -- Overrides the image tag whose default is the chart appVersion. version: "" imagePullSecrets: [] @@ -255,3 +255,35 @@ nodeAttestor: k8sPsat: enabled: true serviceAccountAllowList: [] + +# tornjak - Tornjak specific configuration +tornjak: + # -- Deploys Tornjak API (backend) + enabled: false + # -- Tornjak API image + image: + registry: ghcr.io + repository: spiffe/tornjak-backend + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion. + version: "v1.2.0" + service: + type: ClusterIP + port: 10000 + annotations: {} + config: + # -- persistent DB for storing Tornjak specific information + dataStore: + driver: "sqlite3" + file: "/run/spire/data/tornjak.sqlite3" + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi diff --git a/charts/spire/charts/tornjak-frontend/Chart.yaml b/charts/spire/charts/tornjak-frontend/Chart.yaml new file mode 100644 index 000000000..15167cfc1 --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: tornjak-frontend +description: A Helm chart to deploy Tornjak frontend +type: application +version: 0.1.0 +appVersion: "v1.2.0" +home: https://github.com/spiffe/helm-charts/tree/main/charts/spire +sources: + - https://github.com/spiffe/tornjak +icon: https://raw.githubusercontent.com/spiffe/tornjak/main/logos/logo%2Btornjak.2132x1291.png +maintainers: + - name: mrsabath + email: mrsabath@gmail.com + url: https://mrsabath.github.io diff --git a/charts/spire/charts/tornjak-frontend/README.md b/charts/spire/charts/tornjak-frontend/README.md new file mode 100644 index 000000000..fba5649cf --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/README.md @@ -0,0 +1,71 @@ +# tornjak-frontend + + + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.2.0](https://img.shields.io/badge/AppVersion-v1.2.0-informational?style=flat-square) +[![Development Phase](https://github.com/spiffe/spiffe/blob/main/.img/maturity/dev.svg)](https://github.com/spiffe/spiffe/blob/main/MATURITY.md#development) + +A Helm chart to deploy Tornjak frontend + +**Homepage:** + +## Version support + +> **Note**: This Chart is still in development and still subject to change the API (`values.yaml`). +> Until we reach a `1.0.0` version of the chart we can't guarantee backwards compatibility although +> we do aim for as much stability as possible. + +| Dependency | Supported Versions | +|:-----------|:-------------------| +| SPIRE | `1.5.3+`, `1.6.x` | +| Tornjak | `1.0.x` | +| Helm | `3.x` | + +## Prerequisites + +This chart requires access to Tornjak Backend (`tornjakFrontend.apiServerURL`). +This URL needs to be reachable from your webbrowser and can therefore not be a cluster internal URL. + +Obtain the URL for Tornjak APIs. If deployed in the same cluster, locally, +Tornjak APIs are typically available at `http://localhost:10000`. +Review Tornjak documentation for more details. + +## Usage + +Since this is just a demo version, to access Tornjak APIs you can use +port forwarding. See the chart NOTES output for more details. + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| mrsabath | | | + +## Source Code + +* + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| apiServerURL | string | `"http://localhost:10000/"` | URL of the Tornjak APIs (backend) Since Tornjak Frontend runs in the browser, this URL must be accessible from the machine running a browser. | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.registry | string | `"ghcr.io"` | | +| image.repository | string | `"spiffe/tornjak-frontend"` | | +| image.version | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | | +| labels | object | `{}` | | +| nameOverride | string | `""` | | +| namespaceOverride | string | `""` | | +| podSecurityContext | object | `{}` | | +| securityContext | object | `{}` | | +| service.annotations | object | `{}` | | +| service.port | int | `3000` | | +| service.type | string | `"ClusterIP"` | | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| spireHealthCheck.enabled | bool | `true` | Enables the SPIRE Healthchecker indicator | +---------------------------------------------- diff --git a/charts/spire/charts/tornjak-frontend/README.md.gotmpl b/charts/spire/charts/tornjak-frontend/README.md.gotmpl new file mode 100644 index 000000000..eaf51ce4b --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/README.md.gotmpl @@ -0,0 +1,47 @@ +{{ template "chart.header" . }} + + + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} +[![Development Phase](https://github.com/spiffe/spiffe/blob/main/.img/maturity/dev.svg)](https://github.com/spiffe/spiffe/blob/main/MATURITY.md#development) + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +## Version support + +> **Note**: This Chart is still in development and still subject to change the API (`values.yaml`). +> Until we reach a `1.0.0` version of the chart we can't guarantee backwards compatibility although +> we do aim for as much stability as possible. + +| Dependency | Supported Versions | +|:-----------|:-------------------| +| SPIRE | `1.5.3+`, `1.6.x` | +| Tornjak | `1.0.x` | +| Helm | `3.x` | + +## Prerequisites + +This chart requires access to Tornjak Backend (`tornjakFrontend.apiServerURL`). +This URL needs to be reachable from your webbrowser and can therefore not be a cluster internal URL. + +Obtain the URL for Tornjak APIs. If deployed in the same cluster, locally, +Tornjak APIs are typically available at `http://localhost:10000`. +Review Tornjak documentation for more details. + +## Usage + +Since this is just a demo version, to access Tornjak APIs you can use +port forwarding. See the chart NOTES output for more details. + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} +---------------------------------------------- diff --git a/charts/spire/charts/tornjak-frontend/templates/NOTES.txt b/charts/spire/charts/tornjak-frontend/templates/NOTES.txt new file mode 100644 index 000000000..85a568e41 --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/NOTES.txt @@ -0,0 +1,30 @@ +Installed {{ .Chart.Name }}… + +Name: {{ include "tornjak-frontend.name" . }} +Fullname: {{ include "tornjak-frontend.fullname" . }} +Your release is named: {{ .Release.Name }} +Namespace: {{ include "tornjak-frontend.namespace" . }} + +Tornjak UI (Frontend) + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.image "global" .Values.global) }} + pull policy: {{ .Values.image.pullPolicy }} + Tornjak API (Backend): {{ include "tornjak-frontend.apiURL" . }} + SPIRE health check enabled: "{{ .Values.spireHealthCheck.enabled }}" + +### WARNING ### + +Tornjak runs without authentication and is therefore NOT suitable to run in production environments. +Only use in test environments! + +Access Tornjak: + + kubectl -n {{ include "tornjak-frontend.namespace" . }} port-forward service/{{ include "tornjak-frontend.fullname" . }} {{ .Values.service.port }}:3000 + +Ensure you have port-forwarding for tornjak-backend as well. + +Open browser to: http://localhost:{{ .Values.service.port }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} diff --git a/charts/spire/charts/tornjak-frontend/templates/_helpers.tpl b/charts/spire/charts/tornjak-frontend/templates/_helpers.tpl new file mode 100644 index 000000000..1ad456776 --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/_helpers.tpl @@ -0,0 +1,93 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "tornjak-frontend.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "tornjak-frontend.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts +*/}} +{{- define "tornjak-frontend.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "tornjak-frontend.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "tornjak-frontend.cluster-domain" -}} +{{- if ne (len (dig "k8s" "clusterDomain" "" .Values.global)) 0 }} +{{- .Values.global.k8s.clusterDomain }} +{{- else }} +{{- .Values.clusterDomain }} +{{- end }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "tornjak-frontend.labels" -}} +helm.sh/chart: {{ include "tornjak-frontend.chart" . }} +{{ include "tornjak-frontend.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "tornjak-frontend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "tornjak-frontend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "tornjak-frontend.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "tornjak-frontend.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create URL for accessing Tornjak APIs +*/}} +{{- define "tornjak-frontend.apiURL" -}} +{{- if .Values.apiServerURL -}} +{{- .Values.apiServerURL -}} +{{- else }} +{{- $feurl := print "http://localhost:" .Values.service.port }} +{{- $feurl }} +{{- end }} +{{- end }} diff --git a/charts/spire/charts/tornjak-frontend/templates/deployment.yaml b/charts/spire/charts/tornjak-frontend/templates/deployment.yaml new file mode 100644 index 000000000..141b77113 --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "tornjak-frontend.fullname" . }} + namespace: {{ include "tornjak-frontend.namespace" . }} + labels: + {{- include "tornjak-frontend.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "tornjak-frontend.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "tornjak-frontend.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "tornjak-frontend.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "tornjak-frontend.fullname" . }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + env: + - name: REACT_APP_API_SERVER_URI + value: {{ include "tornjak-frontend.apiURL" . }} + {{- if eq (.Values.spireHealthCheck.enabled | toString) "true" }} + - name: REACT_APP_SPIRE_HEALTH_CHECK_ENABLE + value: "{{ .Values.spireHealthCheck.enabled }}" + {{- end }} + startupProbe: + httpGet: + scheme: HTTP + port: {{ .Values.service.port }} + failureThreshold: 6 + initialDelaySeconds: 120 + periodSeconds: 45 + successThreshold: 1 + timeoutSeconds: 20 + volumeMounts: + - name: cache + mountPath: /usr/src/app/node_modules/.cache + volumes: + - name: cache + emptyDir: {} diff --git a/charts/spire/charts/tornjak-frontend/templates/service.yaml b/charts/spire/charts/tornjak-frontend/templates/service.yaml new file mode 100644 index 000000000..6208d9a87 --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + namespace: {{ include "tornjak-frontend.namespace" . }} + name: {{ include "tornjak-frontend.fullname" . }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "tornjak-frontend.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "tornjak-frontend.selectorLabels" . | nindent 4 }} + ports: + - name: {{ include "tornjak-frontend.fullname" . }} + port: {{ .Values.service.port }} + targetPort: http diff --git a/charts/spire/charts/tornjak-frontend/templates/serviceaccount.yaml b/charts/spire/charts/tornjak-frontend/templates/serviceaccount.yaml new file mode 100644 index 000000000..15640ac0d --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "tornjak-frontend.serviceAccountName" . }} + namespace: {{ include "tornjak-frontend.namespace" . }} + labels: + {{- include "tornjak-frontend.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/spire/charts/tornjak-frontend/templates/tests/test-tornjak-connection.yaml b/charts/spire/charts/tornjak-frontend/templates/tests/test-tornjak-connection.yaml new file mode 100644 index 000000000..af4aea49a --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/templates/tests/test-tornjak-connection.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "tornjak-frontend.fullname" . }}-test-connection" + namespace: {{ include "tornjak-frontend.namespace" . }} + labels: + {{- include "tornjak-frontend.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 4 }} + containers: + - name: curl-tornjak-frontend + image: cgr.dev/chainguard/bash:latest + command: ['curl'] + args: ['-k', '-s', '-f', 'http://{{ include "tornjak-frontend.fullname" . }}.{{ include "tornjak-frontend.namespace" . }}.svc.{{ include "tornjak-frontend.cluster-domain" . }}:{{ .Values.service.port }}'] + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + restartPolicy: Never + diff --git a/charts/spire/charts/tornjak-frontend/values.yaml b/charts/spire/charts/tornjak-frontend/values.yaml new file mode 100644 index 000000000..896769f9e --- /dev/null +++ b/charts/spire/charts/tornjak-frontend/values.yaml @@ -0,0 +1,62 @@ +# Default values for Tornjak UI (Frontend). +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + registry: ghcr.io + repository: spiffe/tornjak-frontend + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion. + version: "" + +imagePullSecrets: [] +nameOverride: "" +namespaceOverride: "" +fullnameOverride: "" + +serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Annotations to add to the service account + annotations: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +labels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 3000 + annotations: {} + +# -- Provide minimal resources to prevent accidental crashes due to resource exhaustion +# resources: +# requests: +# cpu: 50m +# memory: 128Mi +# limits: +# cpu: 100m +# memory: 512Mi + +# -- URL of the Tornjak APIs (backend) +# Since Tornjak Frontend runs in the browser, this URL must be accessible from +# the machine running a browser. +apiServerURL: "http://localhost:10000/" # 👈 Use it for minikube or kind + +# SPIRE Healthchecker indicator +spireHealthCheck: + # -- Enables the SPIRE Healthchecker indicator + enabled: true + diff --git a/charts/spire/values.yaml b/charts/spire/values.yaml index 76ca44537..3f0a7a434 100644 --- a/charts/spire/values.yaml +++ b/charts/spire/values.yaml @@ -41,3 +41,6 @@ spiffe-csi-driver: spiffe-oidc-discovery-provider: enabled: false + +tornjak-frontend: + enabled: false diff --git a/examples/production/values.yaml b/examples/production/values.yaml index d81d285ce..fcbb883b5 100644 --- a/examples/production/values.yaml +++ b/examples/production/values.yaml @@ -77,3 +77,18 @@ spiffe-oidc-discovery-provider: drop: [ALL] seccompProfile: type: RuntimeDefault + +tornjak-frontend: + podSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + readOnlyRootFilesystem: false + capabilities: + drop: [ALL] + seccompProfile: + type: RuntimeDefault diff --git a/examples/tornjak/README.md b/examples/tornjak/README.md new file mode 100644 index 000000000..4108c3b7e --- /dev/null +++ b/examples/tornjak/README.md @@ -0,0 +1,37 @@ +# Recommended setup to deploy Tornjak + +> **Warning**: The current version of Tornjak in this chart is deployed without authentication. Therefore it is not suitable to run this version in production. + +To install Spire with the least privileges possible we deploy spire across 2 namespaces. + +```shell +kubectl create namespace "spire-system" +kubectl label namespace "spire-system" pod-security.kubernetes.io/enforce=privileged +kubectl create namespace "spire-server" +kubectl label namespace "spire-server" pod-security.kubernetes.io/enforce=restricted + +# deploy SPIRE with Tornjak enabled +helm upgrade --install --namespace spire-server \ + --values ../production/values.yaml \ + --values ./values.yaml \ + --render-subchart-notes \ + spire charts/spire + +# test the Tornjak deployment +helm test spire -n spire-server +``` + +## Access tornjak + +To access Tornjak you will have to use port-forwarding for the time being *(until we add authentication and ingress)*. + +Run following commands from your shell, if you ran with different values your namespace might differ. Consult the install notes printed when running above `helm upgrade` command in that case. + +```shell +kubectl -n spire-server port-forward service/spire-tornjak-backend 10000:10000 +kubectl -n spire-server port-forward service/spire-tornjak-frontend 3000:3000 +``` + +You can now access Tornjak at [localhost:3000](http://localhost:3000). + +See [values.yaml](./values.yaml) for more details on the chart configurations to achieve this setup. diff --git a/examples/tornjak/values.yaml b/examples/tornjak/values.yaml new file mode 100644 index 000000000..a4072655c --- /dev/null +++ b/examples/tornjak/values.yaml @@ -0,0 +1,17 @@ +spire-server: + tornjak: + enabled: true + +tornjak-frontend: + enabled: true + service: + type: ClusterIP + port: 3000 + apiServerURL: "http://localhost:10000/" + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 100m + memory: 512Mi