Files
tailscale/cmd/k8s-operator/deploy/chart/templates/deployment.yaml
T
chaosinthecrd 97a75c837d cmd/k8s-operator,ipn/store/kubestore,kube/kubetypes: share ACME account key per tailnet
Introduce a per-tailnet shared ACME account key so that all ingress
ProxyGroup replicas on a tailnet present the same account identity to
Let's Encrypt. This lets renewals claim the ARI "replaces" exemption
from the 50-certs-per-week rate limit, surviving Pod restarts,
ProxyGroup recreation, and cluster migrations.

The operator provisions a "tailscale-acme-accounts" Secret in its
namespace, guarded by a finalizer and a deletion warning event, and
watched so it is recreated promptly if removed. Proxies migrate any
pre-existing per-pod key into the shared Secret on first boot, adopt
the shared key on subsequent boots, and restore it on cert writes if
the Secret was recreated empty. Certs are stamped with the fingerprint
of the issuing account so renewals skip the "replaces" claim when the
account doesn't match.

Opt-in per-ProxyGroup via the tailscale.com/share-acme-account
annotation, or operator-wide via OPERATOR_SHARED_ACME_ACCOUNT_KEY.

Updates #18251
Updates #20288

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-07-27 12:06:41 +01:00

161 lines
5.9 KiB
YAML

# Copyright (c) Tailscale Inc & contributors
# SPDX-License-Identifier: BSD-3-Clause
apiVersion: apps/v1
kind: Deployment
metadata:
name: operator
namespace: {{ .Release.Namespace }}
{{- if .Values.annotations }}
annotations: {{- toYaml .Values.annotations | nindent 4 }}
{{- end }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: operator
template:
metadata:
{{- with .Values.operatorConfig.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
app: operator
{{- with .Values.operatorConfig.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: operator
{{- with .Values.operatorConfig.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.oauthSecretVolume }}
- name: oauth
{{- toYaml .Values.oauthSecretVolume | nindent 10 }}
{{- else if .Values.oauth.audience }}
- name: oidc-jwt
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: {{ .Values.oauth.audience }}
expirationSeconds: 3600
path: token
{{- else }}
- name: oauth
secret:
secretName: operator-oauth
{{- end }}
containers:
- name: operator
{{- with .Values.operatorConfig.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.operatorConfig.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- $operatorTag:= printf ":%s" ( .Values.operatorConfig.image.tag | default .Chart.AppVersion )}}
image: {{ coalesce .Values.operatorConfig.image.repo .Values.operatorConfig.image.repository }}{{- if .Values.operatorConfig.image.digest -}}{{ printf "@%s" .Values.operatorConfig.image.digest}}{{- else -}}{{ printf "%s" $operatorTag }}{{- end }}
imagePullPolicy: {{ .Values.operatorConfig.image.pullPolicy }}
env:
- name: OPERATOR_INITIAL_TAGS
value: {{ join "," .Values.operatorConfig.defaultTags }}
- name: OPERATOR_HOSTNAME
value: {{ .Values.operatorConfig.hostname }}
- name: OPERATOR_SECRET
value: operator
- name: OPERATOR_LOGGING
value: {{ .Values.operatorConfig.logging }}
- name: OPERATOR_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: OPERATOR_SERVICE_ACCOUNT_NAME
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: OPERATOR_LOGIN_SERVER
value: {{ .Values.loginServer }}
- name: OPERATOR_INGRESS_CLASS_NAME
value: {{ .Values.ingressClass.name }}
{{- if .Values.oauthSecretVolume }}
- name: CLIENT_ID_FILE
value: /oauth/client_id
- name: CLIENT_SECRET_FILE
value: /oauth/client_secret
{{- else if .Values.oauth.audience }}
- name: CLIENT_ID
value: {{ .Values.oauth.clientId }}
{{- else }}
- name: CLIENT_ID_FILE
value: /oauth/client_id
- name: CLIENT_SECRET_FILE
value: /oauth/client_secret
{{- end }}
{{- $proxyTag := printf ":%s" ( .Values.proxyConfig.image.tag | default .Chart.AppVersion )}}
- name: PROXY_IMAGE
value: {{ coalesce .Values.proxyConfig.image.repo .Values.proxyConfig.image.repository }}{{- if .Values.proxyConfig.image.digest -}}{{ printf "@%s" .Values.proxyConfig.image.digest}}{{- else -}}{{ printf "%s" $proxyTag }}{{- end }}
- name: PROXY_TAGS
value: {{ .Values.proxyConfig.defaultTags }}
- name: APISERVER_PROXY
value: "{{ .Values.apiServerProxyConfig.mode }}"
- name: PROXY_FIREWALL_MODE
value: {{ .Values.proxyConfig.firewallMode }}
{{- if .Values.proxyConfig.defaultProxyClass }}
- name: PROXY_DEFAULT_CLASS
value: {{ .Values.proxyConfig.defaultProxyClass }}
{{- end }}
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: OPERATOR_SHARED_ACME_ACCOUNT_KEY
value: {{ .Values.operatorConfig.sharedACMEAccountKey | quote }}
{{- with .Values.operatorConfig.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.oauthSecretVolume }}
- name: oauth
mountPath: /oauth
readOnly: true
{{- else if .Values.oauth.audience }}
- name: oidc-jwt
mountPath: /var/run/secrets/tailscale/serviceaccount
readOnly: true
{{- else }}
- name: oauth
mountPath: /oauth
readOnly: true
{{- end }}
{{- with .Values.operatorConfig.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operatorConfig.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operatorConfig.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operatorConfig.priorityClassName }}
priorityClassName: {{ . }}
{{- end }}