Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions charts/pulsar/templates/jwt-secret-init.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

# When JWT authentication is enabled and generateSecrets is true, this pre-install/pre-upgrade
# hook job generates JWT signing keys and per-subject tokens, storing them as Kubernetes secrets.
# The job is skipped entirely (not rendered) if the signing key secret already exists.
# Individual token secrets that already exist are also left untouched.
{{- if and .Values.auth.authentication.enabled .Values.auth.authentication.jwt.enabled .Values.auth.authentication.jwt.generateSecrets.enabled }}
{{- $ns := include "pulsar.namespace" . }}
{{- $keySecret := ternary "symmetric" "asymmetric" .Values.auth.authentication.jwt.usingSecretKey }}
{{- if not (lookup "v1" "Secret" $ns (printf "%s-token-%s-key" .Release.Name $keySecret)) }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
namespace: {{ $ns }}
labels:
{{- include "pulsar.standardLabels" . | nindent 4 }}
component: jwt-secret-init
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
namespace: {{ $ns }}
labels:
{{- include "pulsar.standardLabels" . | nindent 4 }}
component: jwt-secret-init
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
namespace: {{ $ns }}
labels:
{{- include "pulsar.standardLabels" . | nindent 4 }}
component: jwt-secret-init
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
subjects:
- kind: ServiceAccount
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
roleRef:
kind: Role
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
namespace: {{ $ns }}
labels:
{{- include "pulsar.standardLabels" . | nindent 4 }}
component: jwt-secret-init
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 1
template:
metadata:
labels:
{{- include "pulsar.template.labels" . | nindent 8 }}
component: jwt-secret-init
spec:
{{- include "pulsar.imagePullSecrets" . | nindent 6 }}
serviceAccountName: "{{ template "pulsar.fullname" . }}-jwt-secret-init"
restartPolicy: Never
{{- if .Values.auth.authentication.jwt.generateSecrets.nodeSelector }}
nodeSelector:
{{ toYaml .Values.auth.authentication.jwt.generateSecrets.nodeSelector | indent 8 }}
{{- end }}
{{- if .Values.auth.authentication.jwt.generateSecrets.tolerations }}
tolerations:
{{ toYaml .Values.auth.authentication.jwt.generateSecrets.tolerations | indent 8 }}
{{- end }}
affinity:
{{- if and .Values.affinity.anti_affinity .Values.auth.authentication.jwt.generateSecrets.affinity.anti_affinity }}
podAntiAffinity:
{{- if eq .Values.auth.authentication.jwt.generateSecrets.affinity.type "requiredDuringSchedulingIgnoredDuringExecution" }}
{{ .Values.auth.authentication.jwt.generateSecrets.affinity.type }}:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- "{{ template "pulsar.name" . }}"
- key: "release"
operator: In
values:
- {{ .Release.Name }}
- key: "component"
operator: In
values:
- jwt-secret-init
topologyKey: {{ .Values.auth.authentication.jwt.generateSecrets.affinity.anti_affinity_topology_key }}
{{- else }}
{{ .Values.auth.authentication.jwt.generateSecrets.affinity.type }}:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- "{{ template "pulsar.name" . }}"
- key: "release"
operator: In
values:
- {{ .Release.Name }}
- key: "component"
operator: In
values:
- jwt-secret-init
topologyKey: {{ .Values.auth.authentication.jwt.generateSecrets.affinity.anti_affinity_topology_key }}
{{- end }}
{{- end }}
volumes:
- name: jwt-secrets
emptyDir:
sizeLimit: 1Mi
initContainers:
- name: generate-jwt-artifacts
image: "{{ template "pulsar.imageFullName" (dict "image" .Values.images.broker "root" .) }}"
imagePullPolicy: {{ .Values.images.broker.pullPolicy | default .Values.defaultPullPolicy }}
command:
- /bin/bash
- -c
- |
set -e
{{- if .Values.auth.authentication.jwt.usingSecretKey }}
echo "Generating symmetric secret key..."
bin/pulsar tokens create-secret-key --output /jwt-secrets/secret.key
{{- range $role, $subject := .Values.auth.superUsers }}
{{- if $subject }}
echo "Generating token for subject '{{ $subject }}'..."
bin/pulsar tokens create \
--secret-key /jwt-secrets/secret.key \
--subject "{{ $subject }}" | tr -d '\n' > "/jwt-secrets/{{ $subject }}.token"
{{- end }}
{{- end }}
{{- else }}
echo "Generating RSA key pair..."
bin/pulsar tokens create-key-pair \
-a RS256 \
--output-private-key=/jwt-secrets/private.key \
--output-public-key=/jwt-secrets/public.key
{{- range $role, $subject := .Values.auth.superUsers }}
{{- if $subject }}
echo "Generating token for subject '{{ $subject }}'..."
bin/pulsar tokens create \
-a RS256 \
--private-key=/jwt-secrets/private.key \
--subject "{{ $subject }}" | tr -d '\n' > "/jwt-secrets/{{ $subject }}.token"
{{- end }}
{{- end }}
{{- end }}
echo "JWT artifact generation complete"
volumeMounts:
- mountPath: /jwt-secrets
name: jwt-secrets
containers:
- name: create-secrets
image: "{{ template "pulsar.imageFullName" (dict "image" .Values.images.kubectl "root" .) }}"
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
command:
- /bin/sh
- -c
- |
set -e

create_secret() {
SECRET_NAME="$1"
shift
if output=$(kubectl create secret generic "$SECRET_NAME" -n "$NAMESPACE" "$@" 2>&1); then
echo "Created secret $SECRET_NAME"
return 0
elif echo "$output" | grep -q "already exists"; then
echo "Secret $SECRET_NAME already exists, skipping"
return 1
else
echo "Failed to create secret $SECRET_NAME: $output" >&2
exit 1
fi
}

# Create signing key secret
{{- if .Values.auth.authentication.jwt.usingSecretKey }}
create_secret "{{ .Release.Name }}-token-symmetric-key" --from-file="SECRETKEY=/jwt-secrets/secret.key"
{{- range $k, $v := .Values.auth.authentication.jwt.secretAnnotations.key }}
kubectl annotate secret "{{ $.Release.Name }}-token-symmetric-key" -n "$NAMESPACE" --overwrite "{{ $k }}={{ $v }}"
{{- end }}
{{- else }}
create_secret "{{ .Release.Name }}-token-asymmetric-key" --from-file="PRIVATEKEY=/jwt-secrets/private.key" --from-file="PUBLICKEY=/jwt-secrets/public.key"
{{- range $k, $v := .Values.auth.authentication.jwt.secretAnnotations.key }}
kubectl annotate secret "{{ $.Release.Name }}-token-asymmetric-key" -n "$NAMESPACE" --overwrite "{{ $k }}={{ $v }}"
{{- end }}
{{- end }}

# Create token secrets for each super user subject
{{- range $role, $subject := .Values.auth.superUsers }}
{{- if $subject }}
create_secret "{{ $.Release.Name }}-token-{{ $subject }}" --from-file="TOKEN=/jwt-secrets/{{ $subject }}.token"
{{- $subjectAnnotations := (index ($.Values.auth.authentication.jwt.secretAnnotations.token | default dict) $role) | default dict }}
{{- range $k, $v := $subjectAnnotations }}
kubectl annotate secret "{{ $.Release.Name }}-token-{{ $subject }}" -n "$NAMESPACE" --overwrite "{{ $k }}={{ $v }}"
{{- end }}
{{- end }}
{{- end }}

echo "JWT secret initialization complete"
volumeMounts:
- mountPath: /jwt-secrets
name: jwt-secrets
{{- end }}
{{- end }}
27 changes: 27 additions & 0 deletions charts/pulsar/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ images:
kubectl:
repository: alpine/k8s
tag: 1.32.12
# uses defaultPullPolicy when unspecified
pullPolicy:

## TLS
## templates/tls-certs.yaml
Expand Down Expand Up @@ -347,6 +349,31 @@ auth:
# If the token is generated by a secret key, set the usingSecretKey as true.
# If the token is generated by a private key, set the usingSecretKey as false.
usingSecretKey: false
# When enabled, a pre-install/pre-upgrade hook job generates JWT signing keys and
# per-subject tokens as Kubernetes secrets. Skipped if the signing key secret already exists.
generateSecrets:
enabled: false
# nodeSelector:
# cloud.google.com/gke-nodepool: default-pool
tolerations: []
affinity:
anti_affinity: false
anti_affinity_topology_key: kubernetes.io/hostname
# Valid values:
# requiredDuringSchedulingIgnoredDuringExecution - rules must be met for pod to be scheduled (hard)
# preferredDuringSchedulingIgnoredDuringExecution - scheduler will try to enforce but not guarantee
type: preferredDuringSchedulingIgnoredDuringExecution
# Annotations to apply to secrets created by the generateSecrets job
secretAnnotations:
# Annotations added to the signing key secret (asymmetric-key or symmetric-key)
key: {}
# Per-subject annotations for token secrets. Map of subject name to annotation map.
# Example:
# admin:
# reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
# reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
# broker-admin: {}
token: {}
openid:
enabled: false
# # https://pulsar.apache.org/docs/next/security-openid-connect/#enable-openid-connect-authentication-in-the-broker-and-proxy
Expand Down