Tailscale Integration
Connect StarSling Runners to your Tailscale tailnet for secure, private network access to databases, NFS volumes, and internal APIs using OIDC authentication
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.
- Select OpenID Connect
- Set Issuer to GitHub — Tailscale auto-fills the issuer URL
- Set Subject to match your repository:
repo:<your-org>/<your-repo>:* - Click Continue to reach the Scopes page
- Expand the Keys section, enable Auth Keys (Read & Write), and add the tag
tag:starslingdev - 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):
| Secret | Value |
|---|---|
TS_OAUTH_CLIENT_ID | Client ID from the previous step |
TS_AUDIENCE | Audience value from the previous step |
Then add the Tailscale connection step to your workflow:
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 testIf 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):
| Secret | Value |
|---|---|
TS_OAUTH_CLIENT_ID | OAuth Client ID |
TS_OAUTH_SECRET | OAuth Client Secret |
Then add the Tailscale connection step to your workflow:
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 testThe 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
- Check the ACL. Confirm your ACL rules allow
tag:starslingdevto reach the target service and port. Verify the ACL has been applied in the admin console. - Verify the host. If using an IP, confirm it hasn't changed. If using a MagicDNS hostname, check for stale nodes — run
tailscale statusfrom the runner to see what's reachable. - Look for stale nodes. If
tailscale statusshows your target hostname as "offline" alongside a-2variant, delete the stale entries from the Tailscale Admin Console.