diff --git a/charts/spire/.helmignore b/charts/spire/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/spire/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/spire/Chart.yaml b/charts/spire/Chart.yaml new file mode 100644 index 0000000..0bb99c5 --- /dev/null +++ b/charts/spire/Chart.yaml @@ -0,0 +1,38 @@ +apiVersion: v2 +name: spire +description: | + A Helm chart for deploying spire-server and spire-agent. + + > :warning: Please note this chart requires Projected Service Account Tokens which has to be enabled on your k8s api server. + + To enable Projected Service Account Tokens on Docker for Mac/Windows run the following + command to SSH into the Docker Desktop K8s VM. + + ```bash + docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh + ``` + + Then add the following to `/etc/kubernetes/manifests/kube-apiserver.yaml` + + ```yaml + spec: + containers: + - command: + - kube-apiserver + - --api-audiences=api,spire-server + - --service-account-issuer=api,spire-agent + - --service-account-key-file=/run/config/pki/sa.pub + - --service-account-signing-key-file=/run/config/pki/sa.key + ``` +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: ["spiffe", "spire", "spire-server", "spire-agent"] +home: https://github.com/philips-labs/helm-charts/charts/spire +sources: + - https://github.com/philips-labs/helm-charts/charts/spire +maintainers: + - name: marcofranssen + email: marco.franssen@gmail.com + url: https://marcofranssen.nl +kubeVersion: ">=1.19.0-0" diff --git a/charts/spire/LICENSE b/charts/spire/LICENSE new file mode 100644 index 0000000..fff6bbb --- /dev/null +++ b/charts/spire/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Koninklijke Philips N.V, https://www.philips.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/charts/spire/README.md b/charts/spire/README.md new file mode 100644 index 0000000..775c3ce --- /dev/null +++ b/charts/spire/README.md @@ -0,0 +1,98 @@ +# spire + +![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: 1.0.0](https://img.shields.io/badge/AppVersion-1.0.0-informational?style=flat-square) + +A Helm chart for deploying spire-server and spire-agent. + +> :warning: Please note this chart requires Projected Service Account Tokens which has to be enabled on your k8s api server. + +To enable Projected Service Account Tokens on Docker for Mac/Windows run the following +command to SSH into the Docker Desktop K8s VM. + +```bash +docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh +``` + +Then add the following to `/etc/kubernetes/manifests/kube-apiserver.yaml` + +```yaml +spec: + containers: + - command: + - kube-apiserver + - --api-audiences=api,spire-server + - --service-account-issuer=api,spire-agent + - --service-account-key-file=/run/config/pki/sa.pub + - --service-account-signing-key-file=/run/config/pki/sa.key +``` + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| marcofranssen | | | + +## Source Code + +* + +## Requirements + +Kubernetes: `>=1.19.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| agent.image.pullPolicy | string | `"IfNotPresent"` | | +| agent.image.repository | string | `"gcr.io/spiffe-io/spire-agent"` | | +| agent.image.tag | string | `""` | | +| autoscaling.enabled | bool | `false` | | +| autoscaling.maxReplicas | int | `100` | | +| autoscaling.minReplicas | int | `1` | | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| fullnameOverride | string | `""` | | +| imagePullSecrets | list | `[]` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| oidc.enabled | bool | `false` | | +| oidc.image.pullPolicy | string | `"IfNotPresent"` | | +| oidc.image.repository | string | `"gcr.io/spiffe-io/oidc-discovery-provider"` | | +| oidc.image.tag | string | `""` | | +| oidc.ingress.domain | string | `"oidc-discovery.example.org"` | | +| oidc.ingress.enabled | bool | `false` | | +| oidc.letsEncrypt.emailAddress | string | `"letsencrypt@example.org"` | | +| oidc.logLevel | string | `"INFO"` | | +| oidc.service.port | int | `80` | | +| oidc.service.type | string | `"NodePort"` | | +| podAnnotations | object | `{}` | | +| podSecurityContext | object | `{}` | | +| replicaCount | int | `1` | | +| resources | object | `{}` | | +| securityContext | object | `{}` | | +| server.dataStorage.accessMode | string | `"ReadWriteOnce"` | | +| server.dataStorage.enabled | bool | `true` | | +| server.dataStorage.size | string | `"1Gi"` | | +| server.dataStorage.storageClass | string | `nil` | | +| server.image.pullPolicy | string | `"IfNotPresent"` | | +| server.image.repository | string | `"gcr.io/spiffe-io/spire-server"` | | +| server.image.tag | string | `""` | | +| server.service.port | int | `8081` | | +| server.service.type | string | `"NodePort"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| spire.agent.logLevel | string | `"INFO"` | | +| spire.clusterName | string | `"example-cluster"` | | +| spire.server.logLevel | string | `"INFO"` | | +| spire.trustDomain | string | `"example.org"` | | +| tolerations | list | `[]` | | +| workloadRegistrar.image.pullPolicy | string | `"IfNotPresent"` | | +| workloadRegistrar.image.repository | string | `"gcr.io/spiffe-io/k8s-workload-registrar"` | | +| workloadRegistrar.image.tag | string | `""` | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/charts/spire/templates/NOTES.txt b/charts/spire/templates/NOTES.txt new file mode 100644 index 0000000..2356615 --- /dev/null +++ b/charts/spire/templates/NOTES.txt @@ -0,0 +1,4 @@ +1. Get the currently registered SPIFFE entries from the server: + + kubectl exec -n {{ .Release.Namespace }} {{ include "spire.fullname" . }}-server-0 -c spire-server -- \ + bin/spire-server entry show -socketPath {{ include "spire.sockets" . }}/registration.sock diff --git a/charts/spire/templates/_helpers.tpl b/charts/spire/templates/_helpers.tpl new file mode 100644 index 0000000..c80282a --- /dev/null +++ b/charts/spire/templates/_helpers.tpl @@ -0,0 +1,106 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "spire.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 "spire.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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "spire.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "spire.labels" -}} +helm.sh/chart: {{ include "spire.chart" . }} +{{ include "spire.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "spire.selectorLabels" -}} +app.kubernetes.io/name: {{ include "spire.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Common server labels +*/}} +{{- define "spire.server.labels" -}} +helm.sh/chart: {{ include "spire.chart" . }} +{{ include "spire.server.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector server labels +*/}} +{{- define "spire.server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "spire.name" . }}-server +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Common agent labels +*/}} +{{- define "spire.agent.labels" -}} +helm.sh/chart: {{ include "spire.chart" . }} +{{ include "spire.agent.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector agent labels +*/}} +{{- define "spire.agent.selectorLabels" -}} +app.kubernetes.io/name: {{ include "spire.name" . }}-agent +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "spire.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "spire.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{- define "spire.sockets" -}} +{{- "/run/spire/sockets" -}} +{{- end -}} diff --git a/charts/spire/templates/agent-cluster-role.yaml b/charts/spire/templates/agent-cluster-role.yaml new file mode 100644 index 0000000..298a655 --- /dev/null +++ b/charts/spire/templates/agent-cluster-role.yaml @@ -0,0 +1,25 @@ +# Required cluster role to allow spire-agent to query k8s API server +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-agent-cluster-role +rules: + - apiGroups: [""] + resources: ["pods", "nodes", "nodes/proxy"] + verbs: ["get"] + +--- +# Binds above cluster role to spire-agent service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-agent-cluster-role-binding + namespace: {{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ include "spire.fullname" . }}-agent + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "spire.fullname" . }}-agent-cluster-role + apiGroup: rbac.authorization.k8s.io diff --git a/charts/spire/templates/agent-configmap.yaml b/charts/spire/templates/agent-configmap.yaml new file mode 100644 index 0000000..577b5c6 --- /dev/null +++ b/charts/spire/templates/agent-configmap.yaml @@ -0,0 +1,51 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire.fullname" . }}-agent + namespace: {{ .Release.Namespace }} +data: + agent.conf: | + agent { + data_dir = "/run/spire" + log_level = "{{ .Values.spire.agent.logLevel }}" + server_address = "{{ include "spire.fullname" . }}-server" + server_port = "{{ .Values.server.service.port }}" + socket_path = "/run/spire/sockets/agent.sock" + trust_bundle_path = "/run/spire/bundle/bundle.crt" + trust_domain = {{ .Values.spire.trustDomain | quote }} + } + + plugins { + NodeAttestor "k8s_psat" { + plugin_data { + cluster = {{ .Values.spire.clusterName | quote }} + } + } + + KeyManager "memory" { + plugin_data { + } + } + + WorkloadAttestor "k8s" { + plugin_data { + # Defaults to the secure kubelet port by default. + # Minikube does not have a cert in the cluster CA bundle that + # can authenticate the kubelet cert, so skip validation. + skip_kubelet_verification = true + } + } + + WorkloadAttestor "unix" { + plugin_data { + } + } + } + + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/charts/spire/templates/agent-daemonset.yaml b/charts/spire/templates/agent-daemonset.yaml new file mode 100644 index 0000000..17d57a1 --- /dev/null +++ b/charts/spire/templates/agent-daemonset.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "spire.fullname" . }}-agent + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.agent.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "spire.agent.selectorLabels" . | nindent 6 }} + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: + {{- include "spire.agent.selectorLabels" . | nindent 8 }} + spec: + hostPID: true + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: {{ include "spire.serviceAccountName" . }}-agent + initContainers: + - name: init + # This is a small image with wait-for-it, choose whatever image + # you prefer that waits for a service to be up. This image is built + # from https://github.com/lqhl/wait-for-it + image: gcr.io/spiffe-io/wait-for-it:latest + args: ["-t", "30", "{{ include "spire.fullname" . }}-server:8081"] + containers: + - name: {{ .Chart.Name }}-agent + image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag | default .Chart.AppVersion }}" + args: ["-config", "/run/spire/config/agent.conf"] + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-bundle + mountPath: /run/spire/bundle + readOnly: true + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: false + - name: spire-token + mountPath: /var/run/secrets/tokens + livenessProbe: + httpGet: + path: /live + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 60 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 60 + volumes: + - name: spire-config + configMap: + name: {{ include "spire.fullname" . }}-agent + - name: spire-bundle + configMap: + name: {{ include "spire.fullname" . }}-bundle + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: DirectoryOrCreate + - name: spire-token + projected: + sources: + - serviceAccountToken: + path: spire-agent + expirationSeconds: 7200 + audience: spire-server diff --git a/charts/spire/templates/agent-serviceaccount.yaml b/charts/spire/templates/agent-serviceaccount.yaml new file mode 100644 index 0000000..750f1da --- /dev/null +++ b/charts/spire/templates/agent-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "spire.serviceAccountName" . }}-agent + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.agent.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/spire/templates/hpa.yaml b/charts/spire/templates/hpa.yaml new file mode 100644 index 0000000..000dc45 --- /dev/null +++ b/charts/spire/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "spire.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "spire.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/spire/templates/image-pull-secrets.yaml b/charts/spire/templates/image-pull-secrets.yaml new file mode 100644 index 0000000..71b675f --- /dev/null +++ b/charts/spire/templates/image-pull-secrets.yaml @@ -0,0 +1,21 @@ +{{- $fullname := include "spire.fullname" . }} +{{- $ns := .Release.Namespace }} +{{- $labels := include "spire.labels" . }} +{{- range .Values.imagePullSecrets }} +{{- $registryAuthDocument := dict -}} +{{- $authMessage := printf "%s:%s" .username .password | b64enc }} +{{- $_ := set $registryAuthDocument "auth" $authMessage }} +{{- $dockerAuthDocument := dict .registryURL $registryAuthDocument }} +{{- $dockerConfigJSONDocument := dict "auths" $dockerAuthDocument -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ (printf "%s-%s" $fullname .name) }} + namespace: {{ $ns }} + labels: + {{- $labels | nindent 4 }} +data: + .dockerconfigjson: {{ $dockerConfigJSONDocument | toJson | b64enc | quote }} +type: kubernetes.io/dockerconfigjson +{{- end }} diff --git a/charts/spire/templates/oidc-dp-configmap.yaml b/charts/spire/templates/oidc-dp-configmap.yaml new file mode 100644 index 0000000..4500ca0 --- /dev/null +++ b/charts/spire/templates/oidc-dp-configmap.yaml @@ -0,0 +1,22 @@ +{{- if eq (.Values.oidc.enabled | toString) "true" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire.fullname" . }}-oidc-discovery-provider + namespace: {{ .Release.Namespace }} +data: + oidc-discovery-provider.conf: | + log_level = "{{ .Values.oidc.logLevel }}" + + domain = "{{ .Values.oidc.ingress.domain }}" + insecure_addr = ":{{ .Values.oidc.service.port }}" + # acme { + # directory_url = "https://acme-v02.api.letsencrypt.org/directory" + # cache_dir = "/run/spire" + # tos_accepted = true + # email = "{{ .Values.oidc.letsEncrypt.emailAddress }}" + # } + server_api { + address = "unix://{{ include "spire.sockets" . }}/registration.sock" + } +{{ end }} diff --git a/charts/spire/templates/oidc-ingress.yaml b/charts/spire/templates/oidc-ingress.yaml new file mode 100644 index 0000000..c6063de --- /dev/null +++ b/charts/spire/templates/oidc-ingress.yaml @@ -0,0 +1,30 @@ +{{- if and (eq (.Values.oidc.enabled | toString) "true") (eq (.Values.oidc.ingress.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "spire.fullname" . }}-oidc-ingress + namespace: spire +spec: + tls: + - hosts: + - {{ .Values.oidc.ingress.domain }} + secretName: oidc-secret + rules: + - host: {{ .Values.oidc.ingress.domain }} + http: + paths: + - path: /.well-known/openid-configuration + pathType: ImplementationSpecific + backend: + service: + name: {{ include "spire.fullname" . }}-oidc + port: + name: http + - path: /keys + pathType: ImplementationSpecific + backend: + service: + name: {{ include "spire.fullname" . }}-oidc + port: + name: http +{{ end }} diff --git a/charts/spire/templates/oidc-service.yaml b/charts/spire/templates/oidc-service.yaml new file mode 100644 index 0000000..77638c8 --- /dev/null +++ b/charts/spire/templates/oidc-service.yaml @@ -0,0 +1,15 @@ +{{- if eq (.Values.oidc.enabled | toString) "true" }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "spire.fullname" . }}-oidc + namespace: {{ .Release.Namespace }} +spec: + type: {{ .Values.oidc.service.type }} + ports: + - name: http + port: {{ .Values.oidc.service.port }} + targetPort: spire-oidc-port + selector: + {{- include "spire.server.selectorLabels" . | nindent 4 }} +{{ end }} diff --git a/charts/spire/templates/server-cluster-role.yaml b/charts/spire/templates/server-cluster-role.yaml new file mode 100644 index 0000000..22b4996 --- /dev/null +++ b/charts/spire/templates/server-cluster-role.yaml @@ -0,0 +1,31 @@ +# ClusterRole to allow spire-server node attestor to query Token Review API +# and to be able to push certificate bundles to a configmap +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-server-cluster-role +rules: + # allow TokenReview requests (to verify service account tokens for PSAT + # attestation) + - apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["pods", "nodes"] + verbs: ["get", "list", "watch"] + +--- +# Binds above cluster role to spire-server service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-server-cluster-role-binding + namespace: {{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ include "spire.serviceAccountName" . }}-server + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "spire.fullname" . }}-server-cluster-role + apiGroup: rbac.authorization.k8s.io diff --git a/charts/spire/templates/server-configmap.yaml b/charts/spire/templates/server-configmap.yaml new file mode 100644 index 0000000..e6d1ba6 --- /dev/null +++ b/charts/spire/templates/server-configmap.yaml @@ -0,0 +1,67 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire.fullname" . }}-server + namespace: {{ .Release.Namespace }} +data: + server.conf: | + server { + bind_address = "0.0.0.0" + bind_port = "8081" + socket_path = "{{ include "spire.sockets" . }}/registration.sock" + trust_domain = {{ .Values.spire.trustDomain | quote }} + data_dir = "/run/spire/data" + log_level = "{{ .Values.spire.server.logLevel }}" + #AWS requires the use of RSA. EC cryptography is not supported + ca_key_type = "rsa-2048" + + {{- if eq (.Values.oidc.enabled | toString) "true" }} + jwt_issuer = "{{ .Values.oidc.domain }}" + {{ end }} + default_svid_ttl = "1h" + ca_subject = { + country = ["NL"], + organization = ["EXAMPLE"], + common_name = "", + } + } + + plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/run/spire/data/datastore.sqlite3" + } + } + + NodeAttestor "k8s_psat" { + plugin_data { + clusters = { + {{ .Values.spire.clusterName | quote }} = { + service_account_allow_list = ["{{ .Release.Namespace }}:{{ include "spire.serviceAccountName" . }}-agent"] + } + } + } + } + + KeyManager "disk" { + plugin_data { + keys_path = "/run/spire/data/keys.json" + } + } + + Notifier "k8sbundle" { + plugin_data { + namespace = "{{ .Release.Namespace }}" + config_map = "{{ include "spire.fullname" . }}-bundle" + } + } + } + + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/charts/spire/templates/server-role.yaml b/charts/spire/templates/server-role.yaml new file mode 100644 index 0000000..7b3c8d6 --- /dev/null +++ b/charts/spire/templates/server-role.yaml @@ -0,0 +1,43 @@ +# ClusterRole to allow spire-server node attestor to query Token Review API +# and to be able to push certificate bundles to a configmap +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-server-role + namespace: {{ .Release.Namespace }} +rules: + # allow "get" access to pods (to resolve selectors for PSAT attestation) + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] + # allow access to "get" and "patch" the spire-bundle ConfigMap (for SPIRE + # agent bootstrapping, see the spire-bundle ConfigMap below) + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["{{ include "spire.fullname" . }}-bundle"] + verbs: ["get", "patch"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["create"] + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["spire-k8s-registrar-leader-election"] + verbs: ["update", "get"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create"] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "spire.fullname" . }}-server-role-binding + namespace: {{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ include "spire.serviceAccountName" . }}-server + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ include "spire.fullname" . }}-server-role + apiGroup: rbac.authorization.k8s.io diff --git a/charts/spire/templates/server-service.yaml b/charts/spire/templates/server-service.yaml new file mode 100644 index 0000000..a3a5d06 --- /dev/null +++ b/charts/spire/templates/server-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "spire.fullname" . }}-server + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.server.labels" . | nindent 4 }} +spec: + type: {{ .Values.server.service.type }} + ports: + - name: grpc + port: {{ .Values.server.service.port }} + targetPort: grpc + protocol: TCP + selector: + {{- include "spire.server.selectorLabels" . | nindent 4 }} diff --git a/charts/spire/templates/server-serviceaccount.yaml b/charts/spire/templates/server-serviceaccount.yaml new file mode 100644 index 0000000..d3e9d3a --- /dev/null +++ b/charts/spire/templates/server-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "spire.serviceAccountName" . }}-server + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.server.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/spire/templates/server-statefulset.yaml b/charts/spire/templates/server-statefulset.yaml new file mode 100644 index 0000000..6e587c4 --- /dev/null +++ b/charts/spire/templates/server-statefulset.yaml @@ -0,0 +1,162 @@ +{{- $fullname := include "spire.fullname" . }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ $fullname }}-server + labels: + {{- include "spire.server.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + serviceName: {{ include "spire.fullname" . }}-server + selector: + matchLabels: + {{- include "spire.server.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "spire.server.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- range . }} + - name: {{ printf "%s-%s" $fullname .name }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "spire.serviceAccountName" . }}-server + shareProcessNamespace: true + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }}-server + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.server.image.repository }}:{{ .Values.server.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + args: + - -config + - /run/spire/config/server.conf + ports: + - name: grpc + containerPort: 8081 + protocol: TCP + volumeMounts: + - name: spire-server-socket + mountPath: {{ include "spire.sockets" . }} + readOnly: false + - name: spire-config + mountPath: /run/spire/config + readOnly: true + {{- if eq (.Values.server.dataStorage.enabled | toString) "true" }} + - name: spire-data + mountPath: /run/spire/data + readOnly: false + {{ end }} + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + {{- toYaml .Values.resources | nindent 12 }} + - name: {{ .Chart.Name }}-workload-registrar + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.workloadRegistrar.image.repository }}:{{ .Values.workloadRegistrar.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.workloadRegistrar.image.pullPolicy }} + args: + - -config + - /run/spire/k8s-workload-registrar/config/workload-registrar.conf + ports: + - containerPort: 8443 + name: registrar-port + volumeMounts: + - name: spire-server-socket + mountPath: {{ include "spire.sockets" . }} + readOnly: true + - name: spire-workload-registrar-config + mountPath: /run/spire/k8s-workload-registrar/config + readOnly: true + {{- if eq (.Values.oidc.enabled | toString) "true" }} + - name: spire-oidc + image: "{{ .Values.oidc.image.repository }}:{{ .Values.oidc.image.tag | default .Chart.AppVersion }}" + args: + - -config + - /run/spire/oidc/config/oidc-discovery-provider.conf + ports: + - containerPort: {{ .Values.oidc.service.port }} + name: spire-oidc-port + volumeMounts: + - name: spire-server-socket + mountPath: {{ include "spire.sockets" . }} + readOnly: true + - name: spire-oidc-config + mountPath: /run/spire/oidc/config/ + readOnly: true + {{- if eq (.Values.server.dataStorage.enabled | toString) "true" }} + - name: spire-data + mountPath: /run/spire/data + readOnly: false + {{ end }} + readinessProbe: + exec: + command: ["/bin/ps", "aux", " ||", "grep", "oidc-discovery-provider -config /run/spire/oidc/config/oidc-discovery-provider.conf"] + initialDelaySeconds: 5 + periodSeconds: 5 + {{ end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: spire-workload-registrar-config + configMap: + name: {{ include "spire.fullname" . }}-workload-registrar + - name: spire-config + configMap: + name: {{ include "spire.fullname" . }}-server + {{- if eq (.Values.oidc.enabled | toString) "true" }} + - name: spire-oidc-config + configMap: + name: {{ include "spire.fullname" . }}-oidc-discovery-provider + {{ end }} + - name: spire-server-socket + hostPath: + path: /run/spire/server-sockets + type: DirectoryOrCreate + volumeClaimTemplates: + {{- if eq (.Values.server.dataStorage.enabled | toString) "true" }} + - metadata: + name: spire-data + spec: + accessModes: + - {{ .Values.server.dataStorage.accessMode | default "ReadWriteOnce" }} + resources: + requests: + storage: {{ .Values.server.dataStorage.size }} + {{- if .Values.server.dataStorage.storageClass }} + storageClassName: {{ .Values.server.dataStorage.storageClass }} + {{- end }} + {{ end }} diff --git a/charts/spire/templates/spire-bundle-configmap.yaml b/charts/spire/templates/spire-bundle-configmap.yaml new file mode 100644 index 0000000..017dd54 --- /dev/null +++ b/charts/spire/templates/spire-bundle-configmap.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire.fullname" . }}-bundle + namespace: {{ .Release.Namespace }} diff --git a/charts/spire/templates/tests/server-test-connection.yaml b/charts/spire/templates/tests/server-test-connection.yaml new file mode 100644 index 0000000..2f15a90 --- /dev/null +++ b/charts/spire/templates/tests/server-test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "spire.fullname" . }}-server-test-connection" + labels: + {{- include "spire.server.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: check-port + image: busybox + command: ['nc'] + args: ['-zvw3', '{{ include "spire.fullname" . }}-server', '{{ .Values.server.service.port }}'] + restartPolicy: Never diff --git a/charts/spire/templates/workload-registrar-configmap.yaml b/charts/spire/templates/workload-registrar-configmap.yaml new file mode 100644 index 0000000..0e08f0a --- /dev/null +++ b/charts/spire/templates/workload-registrar-configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire.fullname" . }}-workload-registrar + namespace: {{ .Release.Namespace }} +data: + workload-registrar.conf: | + log_level = "debug" + mode = "reconcile" + trust_domain = {{ .Values.spire.trustDomain | quote }} + cluster = {{ .Values.spire.clusterName | quote }} + server_address = "unix://{{ include "spire.sockets" . }}/registration.sock" + leader_election = true + metrics_addr = "0.0.0.0:18080" diff --git a/charts/spire/templates/workload-registrar-service.yaml b/charts/spire/templates/workload-registrar-service.yaml new file mode 100644 index 0000000..5e8722a --- /dev/null +++ b/charts/spire/templates/workload-registrar-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "spire.fullname" . }}-k8s-workload-registrar + namespace: {{ .Release.Namespace }} + labels: + {{- include "spire.server.labels" . | nindent 4 }} +spec: + type: {{ .Values.server.service.type }} + ports: + - name: https + port: 443 + targetPort: registrar-port + protocol: TCP + selector: + {{- include "spire.server.selectorLabels" . | nindent 4 }} diff --git a/charts/spire/values.yaml b/charts/spire/values.yaml new file mode 100644 index 0000000..28e2bf5 --- /dev/null +++ b/charts/spire/values.yaml @@ -0,0 +1,123 @@ +# Default values for spire. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +workloadRegistrar: + image: + repository: gcr.io/spiffe-io/k8s-workload-registrar + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +server: + image: + repository: gcr.io/spiffe-io/spire-server + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + + dataStorage: + enabled: true + size: 1Gi + accessMode: ReadWriteOnce + storageClass: null + + service: + type: NodePort + port: 8081 + +oidc: + enabled: false + + image: + repository: gcr.io/spiffe-io/oidc-discovery-provider + pullPolicy: IfNotPresent + tag: "" + + logLevel: INFO + + service: + type: NodePort + port: 80 + + ingress: + enabled: false + domain: "oidc-discovery.example.org" + + letsEncrypt: + emailAddress: letsencrypt@example.org + +agent: + image: + repository: gcr.io/spiffe-io/spire-agent + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] + # - name: my-docker-registry + # username: my-docker-user + # password: my-docker-password + # registryURL: my-private.docker-registry.com + +nameOverride: "" +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: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +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 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# spireSettings + +spire: + clusterName: "example-cluster" + trustDomain: "example.org" + agent: + logLevel: INFO + server: + logLevel: INFO