cmd/k8s-operator: workload identity support for multi-tailnet (#20016)

This commit modifies the reconciler for the `Tailnet` custom resource
to allow referenced secrets to specify an `audience` field. If a
referenced secret contains both an `audience` and `client_id` we assume
the user's intention is to use workload identity.

In that case, we configure the tailscale API client to authenticate
using the Kubernetes token request API against the operator's service
account. This requires the operator to be aware of its own service
account name.

A small change has also been made to the messages added to the `Tailnet`
CRD's status field in the even that it is missing scopes to make it
clearer that certain scopes may not be applied.

Closes: #19090
Updates: #19471

Signed-off-by: David Bond <davidsbond93@gmail.com>
This commit is contained in:
David Bond
2026-06-10 10:22:19 +01:00
committed by GitHub
parent 632293de7d
commit e4ea65d32d
9 changed files with 157 additions and 29 deletions
@@ -81,6 +81,10 @@ spec:
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
@@ -76,6 +76,10 @@ rules:
- apiGroups: [""]
resources: ["secrets", "serviceaccounts", "configmaps"]
verbs: ["create","delete","deletecollection","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["serviceaccounts/token"]
resourceNames: ["operator"]
verbs: ["create"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch", "update"]
@@ -58,15 +58,18 @@ spec:
- credentials
properties:
credentials:
description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet.
description: Denotes the location of the credentials to use for authenticating with this Tailnet.
type: object
required:
- secretName
properties:
secretName:
description: |-
The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and
"client_secret".
The name of the secret containing the credentials used to authenticate with this Tailnet. The secret must always
contain a "client_id" field. To authenticate with a static OAuth client, also set "client_secret". To authenticate
via workload identity federation, set "audience" to the audience value expected by the Tailscale OAuth
client; the operator will mint a ServiceAccount token for itself with that audience and exchange it for an API
token. "client_secret" and "audience" are mutually exclusive.
type: string
loginUrl:
description: URL of the control plane to be used by all resources managed by the operator using this Tailnet.
@@ -6151,12 +6151,15 @@ spec:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
credentials:
description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet.
description: Denotes the location of the credentials to use for authenticating with this Tailnet.
properties:
secretName:
description: |-
The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and
"client_secret".
The name of the secret containing the credentials used to authenticate with this Tailnet. The secret must always
contain a "client_id" field. To authenticate with a static OAuth client, also set "client_secret". To authenticate
via workload identity federation, set "audience" to the audience value expected by the Tailscale OAuth
client; the operator will mint a ServiceAccount token for itself with that audience and exchange it for an API
token. "client_secret" and "audience" are mutually exclusive.
type: string
required:
- secretName
@@ -6409,6 +6412,14 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resourceNames:
- operator
resources:
- serviceaccounts/token
verbs:
- create
- apiGroups:
- ""
resources:
@@ -6560,6 +6571,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: OPERATOR_SERVICE_ACCOUNT_NAME
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: OPERATOR_LOGIN_SERVER
value: null
- name: OPERATOR_INGRESS_CLASS_NAME
+7
View File
@@ -97,6 +97,7 @@ func main() {
isDefaultLoadBalancer = defaultBool("OPERATOR_DEFAULT_LOAD_BALANCER", false)
loginServer = strings.TrimSuffix(defaultEnv("OPERATOR_LOGIN_SERVER", ""), "/")
ingressClassName = defaultEnv("OPERATOR_INGRESS_CLASS_NAME", "tailscale")
operatorSAName = defaultEnv("OPERATOR_SERVICE_ACCOUNT_NAME", "operator")
)
var opts []kzap.Opts
@@ -157,6 +158,7 @@ func main() {
tsServer: s,
tsClient: tsc,
tailscaleNamespace: tsNamespace,
operatorSAName: operatorSAName,
restConfig: restConfig,
proxyImage: image,
k8sProxyImage: k8sProxyImage,
@@ -349,6 +351,7 @@ func runReconcilers(opts reconcilerOpts) {
tailnetOptions := tailnet.ReconcilerOptions{
Client: mgr.GetClient(),
TailscaleNamespace: opts.tailscaleNamespace,
OperatorSAName: opts.operatorSAName,
Clock: tstime.DefaultClock{},
Logger: opts.log,
Registry: clients,
@@ -818,6 +821,10 @@ type reconcilerOpts struct {
// ingressClassName is the name of the ingress class used by reconcilers of Ingress resources. This defaults
// to "tailscale" but can be customised.
ingressClassName string
// operatorSAName is the name of the ServiceAccount that the operator pod runs as. It is used as the target
// ServiceAccount when minting tokens via the Kubernetes TokenRequest API for Tailnets that authenticate using
// workload identity federation.
operatorSAName string
}
// enqueueAllIngressEgressProxySvcsinNS returns a reconcile request for each