diff --git a/.env.example b/.env.example index 44a5e08..c037f0f 100644 --- a/.env.example +++ b/.env.example @@ -11,7 +11,7 @@ AGENT_PORT=3100 AGENT_GATEWAY_TOKEN= HEADSCALE_BASE_URL=https://atnmy-hs.duckdns.org HEADSCALE_API_KEY= -HEADSCALE_GATEWAY_USERS=local-platform-gateway,platform-gateway +HEADSCALE_GATEWAY_USERS=platform-gateway HEADSCALE_TEACHING_DEVICE_OWNER=tagged-devices LMS_GATEWAY_TOKEN=change-me diff --git a/.woodpecker.yml b/.woodpecker.yml index 59d3374..99b1f1a 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -24,6 +24,7 @@ steps: volumes: - /home/future2/Studio_v2/woodpecker/kube/config:/tmp/kubeconfig:ro commands: + - kubectl --kubeconfig=/tmp/kubeconfig apply -f k8s/platform-gateway.yaml - kubectl --kubeconfig=/tmp/kubeconfig -n studio-system rollout restart deployment/platform-gateway - kubectl --kubeconfig=/tmp/kubeconfig -n studio-system rollout status deployment/platform-gateway when: diff --git a/README.md b/README.md index bcbef11..2cd135f 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,16 @@ Default URL: `http://localhost:4500` ## Network Model Students and instructors never connect directly to Headscale/Tailscale device endpoints. Clients call this gateway only. The gateway verifies auth, checks platform grants/session ownership, records audit context, and proxies allowed operations to the mobility agent running on each platform node. + +## Kubernetes Tailscale Sidecar + +The Kubernetes deployment runs a Tailscale sidecar in the same network namespace as +the gateway. Create the one-time Headscale key secret before the first deployment: + +```bash +kubectl -n studio-system create secret generic platform-gateway-tailscale-auth \ + --from-literal=authkey='tskey-auth-...' +``` + +The sidecar persists its machine state in `platform-gateway-tailscale-state`, so +subsequent Gateway pod restarts do not consume another pre-auth key. diff --git a/k8s/platform-gateway.yaml b/k8s/platform-gateway.yaml index 05cea4b..d8bd321 100644 --- a/k8s/platform-gateway.yaml +++ b/k8s/platform-gateway.yaml @@ -1,3 +1,33 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: platform-gateway + namespace: studio-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: platform-gateway-tailscale-state + namespace: studio-system +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: platform-gateway-tailscale-state + namespace: studio-system +subjects: + - kind: ServiceAccount + name: platform-gateway + namespace: studio-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: platform-gateway-tailscale-state +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -15,7 +45,30 @@ spec: labels: app: platform-gateway spec: + serviceAccountName: platform-gateway containers: + - name: tailscale + image: tailscale/tailscale:stable + env: + - name: TS_KUBE_SECRET + value: platform-gateway-tailscale-state + - name: TS_AUTHKEY + valueFrom: + secretKeyRef: + name: platform-gateway-tailscale-auth + key: authkey + - name: TS_HOSTNAME + value: platform-gateway + - name: TS_USERSPACE + value: "false" + - name: TS_EXTRA_ARGS + value: --login-server=https://atnmy-hs.duckdns.org --accept-routes + securityContext: + capabilities: + add: ["NET_ADMIN"] + volumeMounts: + - name: dev-net-tun + mountPath: /dev/net/tun - name: platform-gateway image: localhost:5000/platform-gateway:0.1.0 imagePullPolicy: Always @@ -37,6 +90,11 @@ spec: port: 4500 initialDelaySeconds: 15 periodSeconds: 20 + volumes: + - name: dev-net-tun + hostPath: + path: /dev/net/tun + type: CharDevice --- apiVersion: v1 kind: Service diff --git a/src/config.ts b/src/config.ts index 5bd47b4..6984894 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,7 +21,7 @@ export const config = { headscaleBaseUrl: required('HEADSCALE_BASE_URL', 'http://localhost:8080'), headscaleApiKey: process.env.HEADSCALE_API_KEY ?? '', headscaleTeachingDeviceOwner: (process.env.HEADSCALE_TEACHING_DEVICE_OWNER ?? 'tagged-devices').trim().replace(/@+$/, ''), - headscaleGatewayUsers: (process.env.HEADSCALE_GATEWAY_USERS ?? process.env.HEADSCALE_GATEWAY_USER ?? 'local-platform-gateway') + headscaleGatewayUsers: (process.env.HEADSCALE_GATEWAY_USERS ?? process.env.HEADSCALE_GATEWAY_USER ?? 'platform-gateway') .split(',') .map((user) => user.trim().replace(/@+$/, '')) .filter(Boolean),