You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The operator provides the `HeadscaleAutoApprover` custom resource to declaratively manage [auto-approved subnet routes and exit nodes](https://headscale.net/stable/ref/routes/#automatically-approve-routes-of-a-subnet-router) in your Headscale instance. Each `HeadscaleAutoApprover` contributes entries to the parent Headscale's policy document; the operator merges all auto-approvers targeting a Headscale and pushes the result via the gRPC `SetPolicy` API.
334
+
335
+
#### Prerequisites
336
+
337
+
Auto-approval lives in the Headscale policy document, which the operator can only write when Headscale is configured with `policy.mode: database`. The parent `Headscale` resource must also declare the tag owners that the auto-approver references. Both fields are set on the `Headscale` spec:
338
+
339
+
```yaml
340
+
apiVersion: headscale.infrado.cloud/v1beta1
341
+
kind: Headscale
342
+
metadata:
343
+
name: headscale-sample
344
+
namespace: headscale
345
+
spec:
346
+
# ...
347
+
config:
348
+
policy:
349
+
mode: database # required for SetPolicy
350
+
acl_policy:
351
+
tag_owners:
352
+
"tag:router": ["admin@example.com"]
353
+
"tag:exit": ["admin@example.com"]
354
+
inline: | # optional base policy for acls/groups/hosts/ssh
`acl_policy.inline`accepts JSON or HuJSON (comments and trailing commas are fine). The operator parses it, merges in `tag_owners` and any `HeadscaleAutoApprover` entries, then pushes the result via `SetPolicy`. The CR is the source of truth — `headscale policy get` shows the rendered output as strict JSON.
361
+
362
+
#### Creating an Auto-Approver
363
+
364
+
```yaml
365
+
apiVersion: headscale.infrado.cloud/v1beta1
366
+
kind: HeadscaleAutoApprover
367
+
metadata:
368
+
name: k8s-network
369
+
namespace: headscale
370
+
spec:
371
+
# Reference to the Headscale instance (same namespace)
372
+
headscaleRef: headscale-sample
373
+
374
+
# Routes a node carrying any of the listed tags will have
375
+
# auto-approved when announced via --advertise-routes.
376
+
routes:
377
+
- cidr: 10.10.0.0/16
378
+
tags: ["tag:router"]
379
+
380
+
# Tags whose nodes will be auto-approved as exit nodes.
381
+
exitNodeTags: ["tag:exit"]
382
+
```
383
+
384
+
Apply:
385
+
386
+
```sh
387
+
kubectl apply -f headscaleautoapprover.yaml
388
+
```
389
+
390
+
#### Auto-Approver Properties
391
+
392
+
- **headscaleRef**: Name of the Headscale instance in the same namespace. Required.
393
+
- **routes**: List of `{cidr, tags}` pairs. A node registered with one of the listed tags has the matching CIDR auto-approved when it advertises that route. Each tag must be declared in the parent's `acl_policy.tag_owners`.
394
+
- **exitNodeTags**: List of tags whose nodes are auto-approved as exit nodes when they advertise themselves as such.
395
+
396
+
At least one of `routes` or `exitNodeTags` must be specified. Multiple `HeadscaleAutoApprover` resources may target the same Headscale; the operator merges them deterministically into a single `autoApprovers` block.
397
+
398
+
#### Viewing Auto-Approvers
399
+
400
+
```sh
401
+
# List all auto-approvers in a namespace
402
+
kubectl get headscaleautoapprover -n headscale
403
+
404
+
# Inspect the Ready condition (status=True / reason=PolicyApplied means push succeeded)
405
+
kubectl get headscaleautoapprover k8s-network -n headscale \
- `PolicyApplied`— the merged policy was successfully pushed via gRPC.
415
+
- `HeadscaleNotFound`— the referenced Headscale doesn't exist in the same namespace.
416
+
- `PolicyModeUnsupported` — the parent Headscale isn't configured with `policy.mode: database`.
417
+
- `PolicyPushFailed`— the gRPC `SetPolicy` call returned an error (see `.status.conditions[].message` for details).
418
+
419
+
#### Combining With PreAuth Keys
420
+
421
+
An auto-approver only takes effect when a node is actually carrying the matching tag. The most common pattern is to issue a tagged preauth key for the subnet router:
422
+
423
+
```yaml
424
+
apiVersion: headscale.infrado.cloud/v1beta1
425
+
kind: HeadscalePreAuthKey
426
+
metadata:
427
+
name: subnet-router-key
428
+
namespace: headscale
429
+
spec:
430
+
headscaleRef: headscale-sample
431
+
headscaleUserRef: alice
432
+
reusable: true
433
+
tags: ["tag:router"]
434
+
```
435
+
436
+
A node registered with this key, advertising e.g. `--advertise-routes=10.10.0.0/16`, will have its routes auto-approved.
437
+
438
+
#### Auto-Approver Examples
439
+
440
+
**Auto-approve a Kubernetes pod CIDR via a subnet router:**
441
+
442
+
```yaml
443
+
apiVersion: headscale.infrado.cloud/v1beta1
444
+
kind: HeadscaleAutoApprover
445
+
metadata:
446
+
name: pod-network
447
+
spec:
448
+
headscaleRef: headscale-sample
449
+
routes:
450
+
- cidr: 10.244.0.0/16
451
+
tags: ["tag:router"]
452
+
```
453
+
454
+
**Auto-approve a tagged exit node:**
455
+
456
+
```yaml
457
+
apiVersion: headscale.infrado.cloud/v1beta1
458
+
kind: HeadscaleAutoApprover
459
+
metadata:
460
+
name: home-exit-node
461
+
spec:
462
+
headscaleRef: headscale-sample
463
+
exitNodeTags: ["tag:exit"]
464
+
```
465
+
466
+
**Per-team segmentation — each team owns its own CIDR:**
467
+
468
+
```yaml
469
+
---
470
+
apiVersion: headscale.infrado.cloud/v1beta1
471
+
kind: HeadscaleAutoApprover
472
+
metadata:
473
+
name: team-prod-routes
474
+
namespace: headscale
475
+
spec:
476
+
headscaleRef: headscale-sample
477
+
routes:
478
+
- cidr: 10.10.0.0/16
479
+
tags: ["tag:router"]
480
+
---
481
+
apiVersion: headscale.infrado.cloud/v1beta1
482
+
kind: HeadscaleAutoApprover
483
+
metadata:
484
+
name: team-staging-routes
485
+
namespace: headscale
486
+
spec:
487
+
headscaleRef: headscale-sample
488
+
routes:
489
+
- cidr: 10.20.0.0/16
490
+
tags: ["tag:router"]
491
+
```
492
+
493
+
The operator merges both into a single `autoApprovers.routes` map and pushes one consolidated policy.
494
+
323
495
### API Key Management
324
496
325
497
When API key auto-management is enabled, the sidecar creates a Kubernetes Secret containing the API key:
0 commit comments