Skip to content

Commit a023570

Browse files
committed
proxy-group instead of device per ingress
1 parent 247089a commit a023570

2 files changed

Lines changed: 83 additions & 4 deletions

File tree

containers/tailscale-operator-manager/examples/rbac.yaml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,36 @@ rules:
5050
resources: [clusterrolebindings]
5151
verbs: [get, list, update, patch]
5252

53-
# Delegated permissions — the operator creates a Role granting these,
54-
# so it must hold them itself (K8s RBAC escalation prevention).
53+
# Must hold all permissions from the tailscale-operator ClusterRole
54+
# (RBAC escalation prevention when modifying its ClusterRoleBinding),
55+
# plus all permissions from the namespace-scoped operator Role.
56+
- apiGroups: [""]
57+
resources: [nodes]
58+
verbs: [get, list, watch]
5559
- apiGroups: [""]
5660
resources: [configmaps, events, pods, services, services/status]
57-
verbs: [get, list, watch, create, update, patch, delete]
61+
verbs: [get, list, watch, create, update, patch, delete, deletecollection]
62+
- apiGroups: [apiextensions.k8s.io]
63+
resources: [customresourcedefinitions]
64+
verbs: [get, list, watch]
5865
- apiGroups: [apps]
59-
resources: [statefulsets]
66+
resources: [statefulsets, deployments]
6067
verbs: [get, list, watch, create, update, patch, delete]
6168
- apiGroups: [coordination.k8s.io]
6269
resources: [leases]
6370
verbs: [get, list, watch, create, update, patch]
71+
- apiGroups: [discovery.k8s.io]
72+
resources: [endpointslices]
73+
verbs: [get, list, watch]
6474
- apiGroups: [networking.k8s.io]
6575
resources: [ingresses, ingresses/status]
76+
verbs: [get, list, watch, create, update, patch, delete, deletecollection]
77+
- apiGroups: [networking.k8s.io]
78+
resources: [ingressclasses]
6679
verbs: [get, list, watch, update, patch]
80+
- apiGroups: [rbac.authorization.k8s.io]
81+
resources: [roles, rolebindings]
82+
verbs: [get, list, watch, create, update, patch, delete]
6783
- apiGroups: [tailscale.com]
6884
resources: ["*"]
6985
verbs: [get, list, watch, create, update, patch, delete]

containers/tailscale-operator-manager/operator.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ def _oauth_secret_name(crd_name):
2323
return f"tailscale-oauth-{crd_name}"
2424

2525

26+
def _proxy_group_name(crd_name):
27+
return f"ingress-{crd_name}"
28+
29+
30+
PROXY_GROUP_API = {
31+
"group": "tailscale.com",
32+
"version": "v1alpha1",
33+
"plural": "proxygroups",
34+
}
35+
36+
2637
def _labels(org_slug):
2738
return {
2839
LABEL_KEY: LABEL_VALUE,
@@ -213,6 +224,52 @@ def _delete_if_exists(fn, *args, logger=None):
213224
logger.warning(f"Failed to delete {args}: {exc}")
214225

215226

227+
def _ensure_proxy_group(custom_api, namespace, crd_name, labels, logger):
228+
"""Create the ProxyGroup so ingresses appear as Tailscale Services."""
229+
pg_name = _proxy_group_name(crd_name)
230+
body = {
231+
"apiVersion": f"{PROXY_GROUP_API['group']}/{PROXY_GROUP_API['version']}",
232+
"kind": "ProxyGroup",
233+
"metadata": {
234+
"name": pg_name,
235+
"namespace": namespace,
236+
"labels": labels,
237+
},
238+
"spec": {
239+
"type": "ingress",
240+
},
241+
}
242+
try:
243+
custom_api.get_namespaced_custom_object(
244+
PROXY_GROUP_API["group"], PROXY_GROUP_API["version"],
245+
namespace, PROXY_GROUP_API["plural"], pg_name,
246+
)
247+
logger.info(f"ProxyGroup {pg_name} already exists")
248+
except ApiException as exc:
249+
if exc.status == 404:
250+
custom_api.create_namespaced_custom_object(
251+
PROXY_GROUP_API["group"], PROXY_GROUP_API["version"],
252+
namespace, PROXY_GROUP_API["plural"], body,
253+
)
254+
logger.info(f"Created ProxyGroup {pg_name}")
255+
else:
256+
raise
257+
258+
259+
def _delete_proxy_group(custom_api, namespace, crd_name, logger):
260+
"""Delete the ProxyGroup."""
261+
pg_name = _proxy_group_name(crd_name)
262+
try:
263+
custom_api.delete_namespaced_custom_object(
264+
PROXY_GROUP_API["group"], PROXY_GROUP_API["version"],
265+
namespace, PROXY_GROUP_API["plural"], pg_name,
266+
)
267+
logger.info(f"Deleted ProxyGroup {pg_name}")
268+
except ApiException as exc:
269+
if exc.status != 404:
270+
logger.warning(f"Failed to delete ProxyGroup {pg_name}: {exc}")
271+
272+
216273
# ---------------------------------------------------------------------------
217274
# Operator role rules (what the Tailscale operator needs within its namespace)
218275
# ---------------------------------------------------------------------------
@@ -305,6 +362,7 @@ def reconcile_operator(spec, name, namespace, memo, logger, retry, **kwargs):
305362
core_api = kubernetes.client.CoreV1Api()
306363
apps_api = kubernetes.client.AppsV1Api()
307364
rbac_api = kubernetes.client.RbacAuthorizationV1Api()
365+
custom_api = kubernetes.client.CustomObjectsApi()
308366

309367
# 1. OAuth Secret
310368
_ensure_secret(core_api, namespace, secret_name, {
@@ -395,6 +453,9 @@ def reconcile_operator(spec, name, namespace, memo, logger, retry, **kwargs):
395453
raise
396454
logger.info(f"Ensured Deployment {deploy_name}")
397455

456+
# 7. ProxyGroup for HA ingress (Tailscale Services instead of devices)
457+
_ensure_proxy_group(custom_api, namespace, name, labels, logger)
458+
398459
# Extract version from image tag
399460
version = operator_image.rsplit(":", 1)[-1] if ":" in operator_image else "unknown"
400461

@@ -410,8 +471,10 @@ def delete_operator(spec, name, namespace, logger, **kwargs):
410471
core_api = kubernetes.client.CoreV1Api()
411472
apps_api = kubernetes.client.AppsV1Api()
412473
rbac_api = kubernetes.client.RbacAuthorizationV1Api()
474+
custom_api = kubernetes.client.CustomObjectsApi()
413475

414476
# Delete in reverse order
477+
_delete_proxy_group(custom_api, namespace, name, logger)
415478
_delete_if_exists(apps_api.delete_namespaced_deployment, operator_sa, namespace, logger=logger)
416479
_delete_if_exists(rbac_api.delete_namespaced_role_binding, operator_sa, namespace, logger=logger)
417480
_delete_if_exists(rbac_api.delete_namespaced_role, operator_sa, namespace, logger=logger)

0 commit comments

Comments
 (0)