Skip to content

GitHub Copilot Cloud Agent

This page documents how the Copilot cloud agent (the background agent that opens PRs from Copilot tasks) is wired into this homelab so it can reach in-cluster MCP servers over the private tailnet.

The pattern is intentionally the same as our other GitHub Actions workflows: pull a Tailscale OAuth client from Bitwarden Secrets Manager and join the runner to the tailnet with a dedicated tag.


Architecture

flowchart LR
  task["Copilot task"] --> runner["Ephemeral GitHub Actions runner"]
  runner -- "copilot-setup-steps.yml:<br/>1. bitwarden/sm-action pulls TS OAuth creds<br/>2. tailscale/github-action joins as tag:copilot<br/>3. MagicDNS enabled" --> tailnet["tailnet (daggertooth-scala.ts.net)"]
  tailnet --> ingress["mcp-kubernetes.daggertooth-scala.ts.net:443<br/>(Tailscale Ingress, ProxyClass: prod)"]
  ingress --> svc["mcp-kubernetes Service<br/>(cluster, port 8000)"]

The runner is only allowed to reach MCP proxy tags on tcp:443 (tag:k8s-operator, tag:k8s-operator-proxy, tag:k8s). It has no other grants of its own - see opentofu/tailscale/acl.tf.


In-repo pieces

File Purpose
.github/workflows/copilot-setup-steps.yml Runs before every Copilot session. Fast-fails if BW_ACCESS_TOKEN is missing in the copilot environment, verifies Bitwarden returned the Tailscale OAuth values, joins the runner to the tailnet as tag:copilot, enables MagicDNS, and validates MCP DNS resolution.
opentofu/tailscale/acl.tf Declares tag:copilot as a tagOwner and grants tag:copilot → {tag:k8s-operator, tag:k8s-operator-proxy, tag:k8s}:443.

Bitwarden items reused

The workflow reuses the same Bitwarden items as ansible-k3s.yml:

Bitwarden item ID Env var
<bws-uuid-tailscale-oauth-client-id> TAILSCALE_OAUTH_CLIENT_ID
<bws-uuid-tailscale-oauth-client-secret> TAILSCALE_OAUTH_CLIENT_SECRET

Out-of-band (one-time) setup

These steps cannot be expressed in code and must be done manually:

  1. Tailscale admin UI - open the OAuth client in use (the same one used by ansible-k3s.yml etc.) and add tag:copilot to its allowed tags. Without this, tailscale/github-action@v2 will fail with a tag not permitted error.

  2. GitHub copilot Actions environment - Copilot-triggered runs of copilot-setup-steps.yml only see secrets from the repository's copilot environment. Create the environment (Settings → Environments → New environment → copilot) and add:

  3. BW_ACCESS_TOKEN - same Bitwarden machine account access token used by the other workflows.

Regular (workflow_dispatch / PR) runs of the same workflow continue to use repository-level secrets, so keep BW_ACCESS_TOKEN configured there too.

  1. Copilot MCP configuration - in the repository settings (Settings → Copilot → Coding agent → MCP configuration) paste:
{
  "mcpServers": {
    "kubernetes": {
      "type": "http",
      "url": "https://mcp-kubernetes.daggertooth-scala.ts.net/mcp",
      "tools": ["*"]
    }
  }
}

No auth header is required - being on the tailnet is the credential.

  1. Copilot firewall (only if the first run fails) - the Copilot cloud agent ships with an egress firewall. If the Setup Tailscale step fails with a network error, relax the firewall (Settings → Copilot → Coding agent → Firewall) to allow:
  2. login.tailscale.com
  3. controlplane.tailscale.com
  4. *.tailscale.com (DERP relays)

Hardening notes

  • tag:copilot is deliberately narrower than tag:ci. It cannot SSH to autogroup:tagged, cannot reach tag:server, and is not a Funnel node.
  • The existing ACL still contains a broad {src: ["*"], dst: ["*"]} grant. Tightening that is out of scope for this change but would make the tag:copilot → {tag:k8s-operator, tag:k8s-operator-proxy, tag:k8s}:443 grant the only path available.