Skip to content

Commit 5ff91d5

Browse files
committed
tailscale-operator-manager
1 parent f13ba60 commit 5ff91d5

7 files changed

Lines changed: 869 additions & 3 deletions

File tree

.github/workflows/build-and-push.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ jobs:
1818
outputs:
1919
matrix: ${{ steps.set-matrix.outputs.matrix }}
2020
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v6
23+
2124
- name: Determine which containers to build
2225
id: set-matrix
2326
run: |
2427
TAG="${GITHUB_REF#refs/tags/}"
25-
ALL_CONTAINERS='["base","cabotage-ca-admission","enrollment-operator","registry","sidecar"]'
2628
27-
# Check if tag is "{container-name}-{version}" or just "{version}"
29+
# Build list of containers from directory names
30+
ALL_CONTAINERS=$(ls -d containers/*/Dockerfile | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))')
31+
32+
# Check if tag matches "{container-name}-{version}"
2833
MATCHED=""
29-
for name in base cabotage-ca-admission enrollment-operator registry sidecar; do
34+
for name in $(echo "$ALL_CONTAINERS" | jq -r '.[]'); do
3035
if [[ "$TAG" == "$name-"* ]]; then
3136
MATCHED="$name"
3237
break
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM cabotage/base:4
2+
3+
USER root
4+
COPY requirements.txt /opt/cabotage/requirements.txt
5+
6+
RUN pip --no-cache-dir --disable-pip-version-check install -r /opt/cabotage/requirements.txt
7+
USER nobody
8+
9+
COPY operator.py /opt/cabotage/operator.py
10+
ENTRYPOINT ["kopf", "run", "/opt/cabotage/operator.py", "--all-namespaces"]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: cabotagetailscaleoperatorconfigs.cabotage.io
5+
spec:
6+
scope: Namespaced
7+
group: cabotage.io
8+
names:
9+
kind: CabotageTailscaleOperatorConfig
10+
plural: cabotagetailscaleoperatorconfigs
11+
singular: cabotagetailscaleoperatorconfig
12+
shortNames:
13+
- tsconfig
14+
- tsconfigs
15+
versions:
16+
- name: v1
17+
served: true
18+
storage: true
19+
additionalPrinterColumns:
20+
- name: State
21+
type: string
22+
jsonPath: .status.reconcile_operator.state
23+
- name: Version
24+
type: string
25+
jsonPath: .status.reconcile_operator.operatorVersion
26+
- name: Age
27+
type: date
28+
jsonPath: .metadata.creationTimestamp
29+
subresources:
30+
status: {}
31+
schema:
32+
openAPIV3Schema:
33+
type: object
34+
properties:
35+
spec:
36+
type: object
37+
required:
38+
- vaultPath
39+
- operatorImage
40+
- organizationSlug
41+
properties:
42+
vaultPath:
43+
type: string
44+
description: >
45+
Vault path where the Tailscale OAuth credentials
46+
(client_id + client_secret) are stored.
47+
operatorImage:
48+
type: string
49+
description: >
50+
Full image reference for the Tailscale operator
51+
(e.g. ghcr.io/tailscale/k8s-operator:v1.80.3).
52+
defaultTags:
53+
type: string
54+
description: >
55+
Default ACL tags for operator-created nodes
56+
(e.g. tag:k8s).
57+
organizationSlug:
58+
type: string
59+
description: >
60+
Cabotage organization slug for labeling.
61+
status:
62+
type: object
63+
x-kubernetes-preserve-unknown-fields: true
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
namespace: cabotage
5+
name: tailscale-operator-manager
6+
labels:
7+
app: tailscale-operator-manager
8+
spec:
9+
replicas: 1
10+
strategy:
11+
type: Recreate
12+
selector:
13+
matchLabels:
14+
app: tailscale-operator-manager
15+
template:
16+
metadata:
17+
labels:
18+
app: tailscale-operator-manager
19+
ca-admission.cabotage.io: "true"
20+
spec:
21+
serviceAccountName: cabotage-tailscale-operator-manager-account
22+
initContainers:
23+
- name: cabotage-sidecar
24+
image: cabotage/sidecar:4.1
25+
restartPolicy: Always
26+
args:
27+
- "kube-login-and-maintain"
28+
- "--vault-ca-file=/var/run/secrets/cabotage.io/ca.crt"
29+
- "--vault-auth-kubernetes-role=cabotage-tailscale-operator-manager"
30+
volumeMounts:
31+
- name: vault-token
32+
mountPath: /var/run/secrets/vault
33+
containers:
34+
- name: tailscale-operator-manager
35+
image: ghcr.io/cabotage/containers/tailscale-operator-manager:latest
36+
imagePullPolicy: Always
37+
args:
38+
- "--verbose"
39+
- "--liveness"
40+
- "http://0.0.0.0:8080/healthz"
41+
livenessProbe:
42+
httpGet:
43+
path: /healthz
44+
port: 8080
45+
volumeMounts:
46+
- name: vault-token
47+
mountPath: /var/run/secrets/vault
48+
resources:
49+
limits:
50+
memory: "256Mi"
51+
cpu: "100m"
52+
requests:
53+
memory: "100Mi"
54+
cpu: "50m"
55+
securityContext:
56+
readOnlyRootFilesystem: true
57+
volumes:
58+
- name: vault-token
59+
emptyDir:
60+
medium: "Memory"
61+
sizeLimit: "1M"
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
apiVersion: v1
3+
kind: ServiceAccount
4+
metadata:
5+
namespace: cabotage
6+
name: cabotage-tailscale-operator-manager-account
7+
---
8+
apiVersion: rbac.authorization.k8s.io/v1
9+
kind: ClusterRole
10+
metadata:
11+
name: cabotage-tailscale-operator-manager-role-cluster
12+
rules:
13+
14+
# Framework: knowing which other operators are running (i.e. peering).
15+
- apiGroups: [kopf.dev]
16+
resources: [clusterkopfpeerings]
17+
verbs: [list, watch, patch, get]
18+
19+
# Framework: runtime observation of namespaces & CRDs (addition/deletion).
20+
- apiGroups: [apiextensions.k8s.io]
21+
resources: [customresourcedefinitions]
22+
verbs: [get, list, watch]
23+
- apiGroups: [""]
24+
resources: [namespaces]
25+
verbs: [list, watch]
26+
27+
# Framework: posting the events about the handlers progress/errors.
28+
- apiGroups: [""]
29+
resources: [events]
30+
verbs: [create]
31+
32+
# Application: watch the CabotageTailscaleOperatorConfig CRD cluster-wide.
33+
- apiGroups: [cabotage.io]
34+
resources: [cabotagetailscaleoperatorconfigs, cabotagetailscaleoperatorconfigs/status]
35+
verbs: [list, watch, patch, get]
36+
37+
# Manage K8s resources in org namespaces for the Tailscale operator.
38+
- apiGroups: [""]
39+
resources: [secrets, serviceaccounts]
40+
verbs: [get, list, watch, create, update, patch, delete]
41+
- apiGroups: [apps]
42+
resources: [deployments]
43+
verbs: [get, list, watch, create, update, patch, delete]
44+
- apiGroups: [rbac.authorization.k8s.io]
45+
resources: [roles, rolebindings]
46+
verbs: [get, list, watch, create, update, patch, delete, deletecollection]
47+
48+
# Manage ClusterRoleBinding subjects (add/remove operator SAs).
49+
- apiGroups: [rbac.authorization.k8s.io]
50+
resources: [clusterrolebindings]
51+
verbs: [get, list, update, patch]
52+
53+
# Delegated permissions — the manager must hold every permission it grants
54+
# via the tailscale-operator ClusterRole (K8s RBAC escalation prevention).
55+
- apiGroups: [""]
56+
resources: [nodes]
57+
verbs: [get, list, watch]
58+
- apiGroups: [""]
59+
resources: [configmaps, events, pods]
60+
verbs: [get, list, watch, create, update, patch, delete]
61+
- apiGroups: [""]
62+
resources: [services, services/status]
63+
verbs: [get, list, watch, create, update, patch, delete, deletecollection]
64+
- apiGroups: [apps]
65+
resources: [statefulsets]
66+
verbs: [get, list, watch, create, update, patch, delete]
67+
- apiGroups: [coordination.k8s.io]
68+
resources: [leases]
69+
verbs: [get, list, watch, create, update, patch]
70+
- apiGroups: [discovery.k8s.io]
71+
resources: [endpointslices]
72+
verbs: [get, list, watch]
73+
- apiGroups: [networking.k8s.io]
74+
resources: [ingresses, ingresses/status, ingressclasses]
75+
verbs: [get, list, watch, update, patch]
76+
- apiGroups: [tailscale.com]
77+
resources: ["*"]
78+
verbs: [get, list, watch, create, update, patch, delete]
79+
---
80+
apiVersion: rbac.authorization.k8s.io/v1
81+
kind: Role
82+
metadata:
83+
namespace: cabotage
84+
name: cabotage-tailscale-operator-manager-role-namespaced
85+
rules:
86+
87+
# Framework: knowing which other operators are running (i.e. peering).
88+
- apiGroups: [kopf.dev]
89+
resources: [kopfpeerings]
90+
verbs: [list, watch, patch, get]
91+
92+
# Framework: posting the events about the handlers progress/errors.
93+
- apiGroups: [""]
94+
resources: [events]
95+
verbs: [create]
96+
97+
# Application: watching & handling for the custom resource we declare.
98+
- apiGroups: [cabotage.io]
99+
resources: [cabotagetailscaleoperatorconfigs]
100+
verbs: [list, watch, patch]
101+
---
102+
apiVersion: rbac.authorization.k8s.io/v1
103+
kind: ClusterRoleBinding
104+
metadata:
105+
name: cabotage-tailscale-operator-manager-rolebinding-cluster
106+
roleRef:
107+
apiGroup: rbac.authorization.k8s.io
108+
kind: ClusterRole
109+
name: cabotage-tailscale-operator-manager-role-cluster
110+
subjects:
111+
- kind: ServiceAccount
112+
name: cabotage-tailscale-operator-manager-account
113+
namespace: cabotage
114+
---
115+
apiVersion: rbac.authorization.k8s.io/v1
116+
kind: RoleBinding
117+
metadata:
118+
namespace: cabotage
119+
name: cabotage-tailscale-operator-manager-rolebinding-namespaced
120+
roleRef:
121+
apiGroup: rbac.authorization.k8s.io
122+
kind: Role
123+
name: cabotage-tailscale-operator-manager-role-namespaced
124+
subjects:
125+
- kind: ServiceAccount
126+
name: cabotage-tailscale-operator-manager-account

0 commit comments

Comments
 (0)