StarSling
Integrations

Tailscale Integration

Connect StarSling Runners to your Tailscale tailnet for secure, private network access to databases, NFS volumes, and internal APIs using OIDC authentication

View Markdown

Tailscale is a zero-config VPN that connects your devices, services, and cloud networks using encrypted WireGuard tunnels.

By connecting StarSling Runners to your Tailscale network, your CI jobs get secure access to private services — databases, NFS volumes, internal APIs — without exposing them to the public internet or maintaining static IP allow lists.

Prerequisites

Before you start, make sure you have:

  • A Tailscale account with admin access to the ACL editor
  • A GitHub repository with Actions enabled
  • Tailscale 1.90.1+ on your tailnet for OIDC (any version for OAuth fallback)

By the end of this guide, you'll have configured:

  • A Tailscale tag for your runners
  • An OAuth client with OIDC federation
  • A GitHub Actions workflow step that connects to your tailnet

Connecting StarSling Runners to your tailnet

Create a tag in your Tailnet ACLs

Tailscale tags group non-user devices and let you manage access control policies based on device role. Create a tag for your StarSling Runners in the admin console ACL editor by adding it under tagOwners:

{
  "tagOwners": {
    "tag:starslingdev": ["autogroup:admin"]
  }
}

This tag will be assigned to every StarSling Runner that joins your tailnet. You'll use it in ACL rules to control what runners can access.

Create a trust credential

Go to the Trust credentials page in the Tailscale admin console and click Credential.

  1. Select OpenID Connect
  2. Set Issuer to GitHub — Tailscale auto-fills the issuer URL
  3. Set Subject to match your repository: repo:<your-org>/<your-repo>:*
  4. Click Continue to reach the Scopes page
  5. Expand the Keys section, enable Auth Keys (Read & Write), and add the tag tag:starslingdev
  6. Click Generate credential

Save the Client ID and Audience values shown after generation — you'll need them in the next step.

For the OAuth fallback path, create an OAuth credential instead of OpenID Connect. See the OAuth Client Secret tab in the next step.

Add the Tailscale step to your workflow

OIDC uses short-lived tokens issued by GitHub — no long-lived secrets are stored. Requires Tailscale 1.90.1+.

Add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions > Secrets):

SecretValue
TS_OAUTH_CLIENT_IDClient ID from the previous step
TS_AUDIENCEAudience value from the previous step

Then add the Tailscale connection step to your workflow:

.github/workflows/ci.yml
jobs:
  build:
    runs-on: starsling-ubuntu-24.04
    permissions:
      id-token: write # Required for OIDC
      contents: read
    steps:
      - uses: actions/checkout@v6

      - name: Connect to Tailscale
        uses: tailscale/github-action@v4
        with:
          oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
          audience: ${{ secrets.TS_AUDIENCE }}
          tags: tag:starslingdev

      # Your runner is now on the tailnet.
      # Access any private service allowed by your ACLs.
      - name: Run tests
        run: npm test

If your tailnet doesn't support OIDC, create an OAuth credential instead. Go to Trust credentials, click Credential, select the OAuth tab, and configure with auth_keys (Read & Write) scope and tag:starslingdev. Save the Client ID and Client Secret.

Add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions > Secrets):

SecretValue
TS_OAUTH_CLIENT_IDOAuth Client ID
TS_OAUTH_SECRETOAuth Client Secret

Then add the Tailscale connection step to your workflow:

.github/workflows/ci.yml
jobs:
  build:
    runs-on: starsling-ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6

      - name: Connect to Tailscale
        uses: tailscale/github-action@v4
        with:
          oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
          oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
          tags: tag:starslingdev

      # Your runner is now on the tailnet.
      # Access any private service allowed by your ACLs.
      - name: Run tests
        run: npm test

The OAuth client secret is a long-lived credential stored in GitHub. Rotate it periodically — you can regenerate it in the Tailscale admin console and update the GitHub secret without downtime.

Your StarSling Runner is now connected to your tailnet as an ephemeral node. When the job completes, the node is automatically removed.

Verify it works: Add a step to your workflow to confirm connectivity:

- name: Verify Tailscale connection
  run: |
    tailscale status
    tailscale ping <your-tailscale-hostname>

Granting access to private services

With runners connected, use Tailscale ACLs to control what they can reach. Runners are tagged with tag:starslingdev, which you reference in ACL rules.

Access a specific service

Grant runners access to a private database by hostname and port:

{
  "acls": [
    {
      "action": "accept",
      "src": ["tag:starslingdev"],
      "dst": ["database-hostname:5432"]
    }
  ]
}

Access a subnet

Using Tailscale subnet routers, grant runners access to a VPC or on-premises network:

{
  "acls": [
    {
      "action": "accept",
      "src": ["tag:starslingdev"],
      "dst": ["192.0.2.0/24:5432"]
    }
  ]
}

Always restrict to specific ports (e.g. :5432). Avoid :* (all ports) in production — overly broad rules are the most common ACL mistake.

Security and compliance

StarSling's Tailscale integration is designed for zero-trust CI environments:

  • No stored secrets (OIDC) — each job receives a short-lived GitHub OIDC token that Tailscale validates directly. No long-lived credentials are stored in GitHub.
  • Ephemeral nodes — runners are automatically removed from your tailnet when the job completes, leaving no persistent footprint.
  • Audit visibility — connected runners appear as tagged nodes in the Tailscale admin console and are recorded in your tailnet audit logs.
  • Scoped access — tag-based ACLs enforce least-privilege per job. Runners can only reach services explicitly permitted by your ACL rules.

Troubleshooting

Runner can't reach private services

  1. Check the ACL. Confirm your ACL rules allow tag:starslingdev to reach the target service and port. Verify the ACL has been applied in the admin console.
  2. Verify the host. If using an IP, confirm it hasn't changed. If using a MagicDNS hostname, check for stale nodes — run tailscale status from the runner to see what's reachable.
  3. Look for stale nodes. If tailscale status shows your target hostname as "offline" alongside a -2 variant, delete the stale entries from the Tailscale Admin Console.

On this page