Tailscale: Cluster Integration

Level 2 (Topic) — Talos System Extension, AuthKey injection, raw IP vs DNS gap.

Concept

Talos Linux nodes join the tailnet via the official Tailscale System Extension. The extension is configured during the Proxmox/Talos creation path with short-lived auth keys minted by create.sh.

Technical Details

  • Talos nodes receive Tailscale bootstrap material during the Proxmox/Talos creation path
  • The patching step is driven by create.sh after the platform layer succeeds
  • The AuthKey is generated in memory and not persisted into Terraform state
  • The bridge patch applies ExtensionServiceConfig with:
    • TS_EXTRA_ARGS=--advertise-tags=tag:tazlab-k8s --accept-routes=false
    • TS_STATE_DIR=/var/lib/tailscale
  • The same patch narrows kubelet node-IP selection to keep node identity on the lab subnet while the OS carries Tailscale traffic

Connectivity Model

The bridge provides:

  • Raw pod egress to tailnet IPs: pods can reach tailnet IPs (e.g., 100.x.x.x:8200) through the node OS path
  • No workload DNS for MagicDNS: magellanic-gondola.ts.net names are not resolved by pod DNS — CoreDNS pods run with hostNetwork: false and cannot reach 100.100.100.100 (node-local Tailscale virtual address)

DNS Resolution Path (2026-05-09 — Native Operator Egress)

The legacy relay DaemonSet (which used hostNetwork and static hosts mapping) has been replaced by the native Tailscale Kubernetes Operator Egress Proxy architecture.

Architecture: ExternalName + DNSConfig

This solution leverages the Operator’s ability to provision egress proxies for specific tailnet targets and register them with an in-cluster nameserver.

pod query → CoreDNS (standard pod DNS, 10.96.0.10)
  → forward ts.net → nameserver Service (ClusterIP 10.108.193.103)
    → nameserver pod (DNSConfig CRD, auto-registers ExternalName IPs)
      → egress proxy pod (connected to tailnet)
        → target tailnet node (e.g., Vault)

Key design decisions:

  • ExternalName Service: Target nodes (like Vault) are declared as ExternalName services with the annotation tailscale.com/tailnet-fqdn.
  • Egress Proxy: For each ExternalName service, the Operator creates a dedicated egress proxy pod in the tailscale namespace.
  • DNSConfig Integration: The DNSConfig nameserver (provided by the Operator) automatically resolves the MagicDNS name to the ClusterIP/PodIP of the egress proxy.
  • CoreDNS Forwarding: Standard cluster CoreDNS forwards the ts.net zone to the nameserver’s ClusterIP (10.108.193.103).

CoreDNS Hardening: Disable & Replace

On Talos v1.12+, the built-in CoreDNS controller overwrites any manual ConfigMap changes. To maintain custom ts.net forwarding, we implemented the “Disable & Replace” strategy:

  1. Set cluster.coreDNS.disabled: true in Talos config.
  2. Set machine.kubelet.clusterDNS: ["10.96.0.10"] on all nodes.
  3. Deploy a user-managed CoreDNS stack (SA, RBAC, ConfigMap, Deployment, Service) via Terraform inlineManifest.

This ensures the Corefile is durable and GitOps-native.

This was delivered by CRISP project 15-tailscale-operator-hardening.

Verified Behavior (2026-05-09)

  • DNS Resolution: A pod using standard cluster DNS can resolve lushycorp-vault.magellanic-gondola.ts.net and receives the IP of the egress proxy pod.
  • Egress Flow: Traffic to the resolved IP flows through the egress proxy into the tailnet, reaching Vault securely.
  • Zero Workarounds: No relay DaemonSet, no static host mappings in Corefile, and no kubectl patches in create.sh.

Verified Behavior (2026-04-27)

  • A pod in external-secrets could open TCP to the Vault tailnet IP at the time
  • HTTPS to Vault succeeded from a pod with explicit SNI mapping: curl -k --resolve lushycorp-vault.magellanic-gondola.ts.net:8200:100.82.13.87 https://lushycorp-vault.magellanic-gondola.ts.net:8200/v1/sys/health
  • Plain pod DNS resolution of the old alias lushycorp-api.ts.tazlab.net pointed to the public tazlab.net

The gap is not in basic network reachability — the Talos bridge gives pods raw access to tailnet IPs. The gap is in hostname consumption: pods cannot resolve Tailscale MagicDNS names unless explicitly configured. This gap is being closed by the Tailscale Kubernetes Operator DNS path.

See Also