Let’s say you’re going on a trip for few weeks and you prefer not to take your laptop with you. But you manage some small kubernetes cluster which is behind Cloudflare Zero Trust which can be only accessed:
- using Cloudflare WARP
- using google sign-in via kubelogin
And you want to have some sort of emergency access while you’re away from your iPad.
You can do it using Github Codespaces, but whole process is not well documented, let’s start with some references which were helpful when I glued all this hack together:
- https://github.com/cmj2002/warp-docker/blob/main/entrypoint.sh
- https://community.cloudflare.com/t/how-to-register-into-a-team-with-linux-and-warp-cli/627971/3
- https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/mdm-deployment/#linux
- https://github.com/spotsnel/cloudflare-client/discussions/1
- https://community.cloudflare.com/t/cloudflare-zero-trust-for-service-token/432243
First what you wanna do is to configure zero trust
You gonna need a service token:
resource "cloudflare_zero_trust_access_service_token" "warp_codespaces_cli" {
account_id = var.cloudflare_account_id
name = "warp-cli-service-token"
duration = "forever"
}
A new policy:
resource "cloudflare_zero_trust_access_policy" "warp_cli" {
account_id = var.cloudflare_account_id
name = "warp-enrollment-cli"
session_duration = "24h"
decision = "non_identity"
include {
service_token = [cloudflare_zero_trust_access_service_token.warp_codespaces_cli.id]
}
}
Moreover - you will need a to link this policy to your existing warp settings:
resource "cloudflare_zero_trust_access_application" "warp" {
account_id = var.cloudflare_account_id
name = "Warp Login App"
domain = "<your domain>.cloudflareaccess.com/warp"
app_launcher_visible = false
type = "warp"
policies = [
< any existing policies >
cloudflare_zero_trust_access_policy.warp_cli.id
]
}
So your Device enrollment permissions should look like this:

I don’t recall how I created profile settings for the CLI, but I think that was one thing I did manually:

Then you need some stuff in .devcontainer/ directory
devcontainer.json example:
{
"name": "Development Container",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"customizations": {
"vscode": {
"extensions": [
"redhat.ansible",
"ms-python.python",
"ms-azuretools.vscode-docker"
],
"settings": {
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash",
"icon": "terminal-bash"
}
},
"terminal.integrated.defaultProfile.linux": "bash"
}
}
},
"workspaceFolder": "/workspaces",
"secrets": {
"WARP_CLIENT_ID": {
"description": "WARP client id, provided by organization secret"
},
"WARP_CLIENT_SECRET": {
"description": "WARP client secret, provided by organization secret"
},
"OIDC_CLIENT_SECRET": {
"description": "OIDC client secret, provided by organization secret"
}
},
"capAdd": ["NET_ADMIN"],
"remoteUser": "vscode"
}
You will want to set secrets in the codespaces settings. Warp id/secret is obtained from warp_codespaces_cli (if created via terraform). You can remove OIDC if you’re not using kubelogin.
Then goes Dockerfile - I used python as I also had ansible in that repo:
FROM mcr.microsoft.com/devcontainers/python:3.13
RUN apt-get update && apt-get install -y --no-install-recommends \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release \
git \
iputils-ping \
iptables \
&& rm -rf /var/lib/apt/lists/*
# Install Cloudflare WARP client
RUN curl https://pkg.cloudflareclient.com/pubkey.gpg | gpg --dearmor -o /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/cloudflare-client.list \
&& apt-get update && apt-get install -y cloudflare-warp \
&& rm -rf /var/lib/apt/lists/*
# Install kubectl and k9s
ARG KUBECTL_VERSION=v1.33.1
ARG K9S_VERSION=v0.50.9
RUN set -eux; \
ARCH="$(uname -m)"; \
if [ "$ARCH" = "x86_64" ]; then \
KARCH="amd64"; \
elif [ "$ARCH" = "aarch64" ] || echo "$ARCH" | grep -q '^arm'; then \
KARCH="arm64"; \
else \
echo "Unsupported architecture: $ARCH"; exit 1; \
fi; \
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${KARCH}/kubectl" \
&& install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \
&& rm kubectl \
&& curl -L "https://github.com/derailed/k9s/releases/download/${K9S_VERSION}/k9s_Linux_${KARCH}.tar.gz" -o k9s.tar.gz \
&& tar -zxvf k9s.tar.gz -C /usr/local/bin k9s \
&& rm k9s.tar.gz
USER vscode
# Install krew and oidc-login plugin under vscode user
ARG KREW_VERSION=v0.4.5
RUN ARCH="$(uname -m)"; \
if [ "$ARCH" = "x86_64" ]; then \
KARCH="amd64"; \
elif [ "$ARCH" = "aarch64" ] || echo "$ARCH" | grep -q '^arm'; then \
KARCH="arm64"; \
else \
echo "Unsupported architecture: $ARCH"; exit 1; \
fi; \
curl -L "https://github.com/kubernetes-sigs/krew/releases/download/${KREW_VERSION}/krew-linux_${KARCH}.tar.gz" -o /tmp/krew.tar.gz \
&& tar -zxvf /tmp/krew.tar.gz -C /tmp \
&& /tmp/krew-linux_${KARCH} install krew \
&& rm /tmp/krew.tar.gz \
&& echo 'export PATH="${HOME}/.krew/bin:$PATH"' >> ~/.bashrc \
&& export PATH="${HOME}/.krew/bin:$PATH" \
&& kubectl krew install oidc-login \
&& rm -rf ~/.krew/tmp
That should give you a container with python, kubectl, k9s, Cloudflare WARP and kubelogin ready to go in Codespaces - fire it up and see!
Now what’s left is to glue it all together, I decided to go with a simple bash script which fires all this machinery up:
#!/bin/bash
if [ ! -e /dev/net/tun ]; then
echo "- TUN device not found, creating..."
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 600 /dev/net/tun
fi
echo "- Starting dbus service..."
sudo service dbus start
# give dbus some time to start
sleep 3
# Check if envs are set
if [ -z "$WARP_CLIENT_ID" ] || [ -z "$WARP_CLIENT_SECRET" ]; then
echo "WARP_CLIENT_ID and WARP_CLIENT_SECRET must be set as environment variables."
exit 1
fi
file_content=$(cat <<EOF
<dict>
<key>organization</key>
<string>!!! --- PUT YOUR CLOUDFLARE ORG NAME HERE -- !!!</string>
<key>auth_client_id</key>
<string>${WARP_CLIENT_ID}</string>
<key>auth_client_secret</key>
<string>${WARP_CLIENT_SECRET}</string>
</dict>
EOF
)
sudo mkdir -p /var/lib/cloudflare-warp
echo "$file_content" | sudo tee /var/lib/cloudflare-warp/mdm.xml > /dev/null
echo "- Starting Cloudflare WARP service (logs at /tmp/cloudflare-warp)..."
sudo warp-svc --accept-tos >/tmp/cloudflare-warp 2>&1 &
# give warp some time to start
echo "- Waiting for Cloudflare WARP to start..."
sleep 5
echo "- Connecting to Cloudflare WARP..."
warp-cli --accept-tos connect
if [ -z "$OIDC_CLIENT_SECRET" ]; then
echo "OIDC_CLIENT_SECRET must be set as an environment variable."
exit 1
fi
# I keep kube-config in my repo, reference below
echo "- Configuring kube config..."
mkdir -p ~/.kube
envsubst < .devcontainer/kube-config.yaml > ~/.kube/config
echo "All set! You can now access your Kubernetes cluster using k9s."
Run that while in codespace’s terminal and that will configure Cloudflare WARP and now you should be able to access your internal network from codespaces container which is pretty neat-o.
For reference my kube-config.yaml (truncated)
# ...
clusters:
- cluster:
certificate-authority-data: Redacted
server: https://<internal network ip>:6443
name: my-cluster
# ...
users:
- name: my-oidc
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://accounts.google.com
- --oidc-extra-scope=email
- --oidc-client-id=<<MY CLIENT ID>>
- --oidc-client-secret=${OIDC_CLIENT_SECRET}
- --grant-type=device-code
command: kubectl
env: null
interactiveMode: IfAvailable
provideClusterInfo: false
Remember if you want to use device-code you need to configure oauth for limited input - as there is no way to open a browser while in codespaces container, but PIN login still should work nicely.