Kodiak is a Kubernetes operator for logical Tailscale subnet-router services. It can use either:
- an existing Tailscale-compatible control plane and auth-key Secret; or
- an externally installed Ionscale control plane managed through Kodiak
Tailnetresources.
The Helm chart can optionally install Ionscale. Kodiak does not install a DERP server or cert-manager.
Connectordefines a logical subnet-router service. Every replica slot is fulfilled by a replaceableConnectorInstanceand a direct kernel-networking Pod. Kodiak advertises routes but never approves them.ConnectorInstanceis an internal lifecycle record for one Pod incarnation and its external device. It does not contain tailscaled private state.Tailnetmanages Ionscale Tailnet policy, DNS, and feature settings.AuthKeyissues user/manual enrollment credentials for a managed Tailnet. Connectors do not depend onAuthKeyresources.
Managed Connectors use a tailnetRef. Kodiak creates a short-lived, ephemeral,
pre-authorized bootstrap credential for each new instance, records the
registered device, then revokes and deletes the bootstrap credential. Route
approval belongs in the Tailnet ACL autoApprovers policy.
External Connectors use an authKeySecretRef. If loginURL is omitted, the
Tailscale client uses its official default control plane.
The Helm chart installs a bundled Ionscale control plane by default. Disable it when using Tailscale or another external compatible control plane:
ionscale:
enabled: false
managedControlPlane:
apiEndpoint: https://ionscale-api.example.com
loginURL: https://vpn.example.com
adminKeySecretRef:
name: ionscale-admin
key: systemAdminKeyhelm upgrade --install kodiak ./dist/chart \
--namespace kodiak-system \
--create-namespace \
-f values.yamlTo terminate TLS in the bundled Ionscale server, reference an existing Kubernetes TLS Secret:
ionscale:
serverUrl: https://vpn.example.com
tls:
disable: false
existingSecret: vpn-example-com-tlsFor Kustomize development installs:
make install
make deploy IMG=ghcr.io/mni-cloud/kodiak:0.2.4Patch IONSCALE_API_ENDPOINT, IONSCALE_LOGIN_URL, and the optional
ionscale-admin Secret reference in config/manager/manager.yaml when using
managed Tailnets.
apiVersion: v1
kind: Secret
metadata:
name: connector-auth
stringData:
TS_AUTH_KEY: tskey-auth-...
---
apiVersion: kodiak.mnicloud.jp/v1alpha1
kind: Connector
metadata:
name: office
spec:
authKeySecretRef:
name: connector-auth
key: TS_AUTH_KEY
subnetRouter:
advertiseRoutes:
- 10.0.1.0/24For a custom control plane, add spec.loginURL.
apiVersion: kodiak.mnicloud.jp/v1alpha1
kind: Connector
metadata:
name: office
spec:
tailnetRef:
name: production
tags:
- tag:office-router
subnetRouter:
advertiseRoutes:
- 10.0.1.0/24The referenced Tailnet ACL must approve the route for the Connector tag.
Kodiak exposes advertised and enabled routes separately in
status.devices[], so policy failures remain visible.
Kodiak never calls the Ionscale route-enable API; autoApprovers is the only
managed route-approval authority. Managed bootstrap keys are transient
controller-owned Secrets and are revoked after the external device is
recorded.
Connector Pods use the stock Tailscale image with an emptyDir state
directory. They do not receive a service-account token or Kubernetes API
credentials. A Pod replacement intentionally creates a new external device;
Kodiak brings it to readiness before deleting the previous instance.
Ionscale must also enforce that tags requested during registration are a subset of the tags carried by the auth key. Until that control-plane check is present, do not treat separation between user and Connector tags as a tenant security boundary.
make test
helm lint dist/chart
kustomize build config/defaultE2E tests require Docker and an isolated Kind cluster:
make test-e2e