GitOps for Tailscale ACLs

Access Control Lists (ACLs) define what users or devices are permitted to access in your Tailscale network. An alternative to managing the ACL changes in the Access Controls page of the admin console to use GitOps for Tailscale ACLs to manage the ACL changes. With GitOps, your GitHub pull requests will send your tailnet policy file to Tailscale to determine whether the ACL is valid and whether all ACL tests pass. Your GitHub pushes also check validity and run tests, and if successful, will automatically apply your tailnet policy file changes to your tailnet.

Using GitOps for ACLs:

  • gives you a single source of truth for your tailnet policy file, that you can manage outside of the Tailscale admin console
  • allows you to version tailnet policy files
  • gives you an audit trail of commits to change tailnet policy files, including what changed and who made the change

With GitOps for Tailscale ACLs, you maintain your tailnet policy file with Git. This provides a central reference that can use the same controls for tailnet policy file changes as for code changes (“config as code”). For example, you can set up your GitHub repository to require reviews by another person, invoke tests for your tailnet policy file changes, and then use GitHub Actions to automatically apply the changes to your tailnet.

Tailscale publishes a GitHub Action to Sync Tailscale ACLs, available in the GitHub Actions Marketplace.

Prerequisites

In addition to already having your own Tailscale network, you need:

  • A GitHub account.
  • Working knowledge of GitHub (committing changes, creating and merging pull requests, etc.).
  • A private GitHub repository that will contain your tailnet policy file.
    Make sure this repo is private, as tailnet policy files contain personally identifiable information (PII), such as your users’ email addresses.
  • A Tailscale API key for your tailnet. You can create an API key in the Keys page of the admin console.

GitHub Action to Sync Tailscale ACLs

Tailscale publishes a GitHub Action to Sync Tailscale ACLs, available in the GitHub Actions Marketplace.

You do not need to use this GitHub Action to set up a GitOps pattern — you can also write your own Action or use another CI/CD tool.

What this action does

On pull requests that target the main branch, the Test ACL step (action test) will send your tailnet policy file to Tailscale to determine whether the ACL is valid and whether all ACL tests pass. Tailscale will provide your workflow with the result of those checks.

On push (merge) operations that target the main branch, the Deploy ACL step (action apply) will again check ACL validity and run ACL tests, with Tailscale reporting back the results to your workflow. If the tests succeed, the merge will complete and the workflow will automatically apply your tailnet policy file changes to your tailnet. If the tests fail, an error will prevent the merge from continuing.

You can see the status of the workflow in the GitHub UI’s Actions tab for your pull request. Look for a workflow with the name you used in .github/workflows/tailscale.yml, Tailscale ACL syncing.

A screenshot of the Sync Tailscale ACLs GitHub Actions workflow.

Inputs

The following inputs apply to the Sync Tailscale ACLs action.

  • action: Required. One of test or apply. If test, the action will run ACL tests only—it won’t update your ACLs in Tailscale. If apply, the action will run ACL tests and, if the ACL tests succeed, then update your ACLs in Tailscale. This enables you to use pull requests to make changes, with CI preventing you from pushing a bad change out to production.

  • api-key: Required. An API key authorized for your tailnet. You can create an API key in the keys page of the admin console.

  • policy-file: Optional. The path to your tailnet policy file in the repository. If not set this defaults to policy.hujson in the root of your repository.

  • tailnet: Required. You can find the name of your tailnet in the Settings page of the admin console.

Set up your GitOps configuration

  1. In your GitHub repo, create a file named policy.hujson. Copy your tailnet policy file contents from the Access Controls page into this file.

    If you want to change the tailnet policy file name to something else, you will need to add the policy-file argument to the with blocks in your GitHub Actions configuration discussed below. Otherwise, the GitHub action will default to policy.hujson as the tailnet policy file.

    To prevent others admins in your organization from accidentally changing your tailnet policy file, add a comment to the policy file to show a warning in the Tailscale admin console:

    // This tailnet's ACLs are maintained in <url>
    
  2. Commit the tailnet policy file and push it to GitHub. For example, if you are using the command line:

    git add .
    git commit -sm "policy: import from admin console"
    git push -u origin main
    
  3. Create the following GitHub Actions secrets in your repository’s settings:

    • TS_API_KEY: Use your Tailscale API key as the value.
    • TS_TAILNET: Use the name of your tailnet as the value. For example, example.com, myemail@example.com, example.github, example.org.github, etc. You can find the name of your tailnet in the Settings page of the admin console.

    These secrets provide the configuration for the action.

  4. Make a new GitHub Actions workflow that uses the Sync Tailscale ACLs GitHub Action.

    Create a file named .github/workflows/tailscale.yml. Paste in the following YAML:

    name: Sync Tailscale ACLs
    
    on:
      push:
        branches: [ "main" ]
      pull_request:
        branches: [ "main" ]
    
    jobs:
      acls:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v3
    
          - name: Deploy ACL
            if: github.event_name == 'push'
            id: deploy-acl
            uses: tailscale/gitops-acl-action@v1
            with:
              api-key: ${{ secrets.TS_API_KEY }}
              tailnet: ${{ secrets.TS_TAILNET }}
              action: apply
    
          - name: Test ACL
            if: github.event_name == 'pull_request'
            id: test-acl
            uses: tailscale/gitops-acl-action@v1
            with:
              api-key: ${{ secrets.TS_API_KEY }}
              tailnet: ${{ secrets.TS_TAILNET }}
              action: test
    

    If you don’t use the name main for your default branch, update the branches fields in the YAML.

    Note this workflow appropriately uses the secrets.TS_API_KEY and secrets.TS_API_TAILNET environment variables to access your secrets. Do not place your actual secret values in .github/workflows/tailscale.yml—use the environment values as shown.

    For an example repo that has set up this workflow, see https://github.com/cetacean/tailscale-acls. Note this repo is for documentation purposes only, so it is OK for it to be public.

  5. Commit and push .github/workflows/tailscale.yml.

The main steps in the action are installing gitops-pusher, setting the correct environment variables and flags, and then running the workflow. For more details, see GitHub Action configuration.

With this setup, you have created a a continuous integration (CI) that automatically tests and pushes your tailnet policy file changes to Tailscale!

Push changes to your Tailscale ACL

Now that your configuration has been set up, any time your want to update your ACL, modify the tailnet policy file in your repo and use the typical GitHub authoring/review/merge flow. You can check the status of the ACL validity checks and ACL tests in the GitHub UI in the Actions tab for your pull request. Look for a workflow with the name you used in .github/workflows/tailscale.yml, Sync Tailscale ACLs.

Prevent others from accidentally modifying your tailnet policy file

To prevent other admins from accidentally modifying the tailnet policy file in the Tailscale admin console, you can add a special comment to the policy file. Add a comment of the form:

// This tailnet's ACLs are maintained in <url>

With this comment in place, the Access Controls page of the admin console will display a warning.

A screenshot of the warning shown in the tailnet policy file.
You can link to any Git repository or URL. You can use your existing source management system and do not need to use the Sync Tailscale ACLs GitHub Action to display this warning.

Any admin with permissions to edit the tailnet policy file will still be able to edit it directly by selecting Edit anyways, for example in case of emergency.

A screenshot of the warning shown when trying to edit a tailnet policy file which is using GitOps.

Any changes made in the admin console will be overwritten next time the Sync Tailscale ACLs GitHub Action is used.

Reverting the most recent change to your ACL

If you need to revert the most recent change, use the GitHub UI to revert the merged pull request.

Additional information

  • Any manual tailnet policy file changes in the admin console won’t be reflected in your GitHub version of the tailnet policy file. The next time you use the Sync Tailscale ACLs GitHub Action, any changes made in the Tailscale admin console will be overwritten.

  • Tailscale API keys expire and currently there is no mechanism to have them automatically renewed. To handle the expiration, create a new API key and update the GitHub TS_API_KEY secret to use the new value. Tailscale API keys expires in 90 days, but updating the GitHub secret monthly is a good practice.

    When you no longer need to use a Tailscale API key, make sure you revoke it in the keys page of the admin console.

  • Tailscale tailnet policy files are in HuJSON, a JSON format with trailing commas and comments. If you don’t want to write your tailnet policy files in HuJSON directly, you can use a tool that lets you generate JSON in the same schema as the HuJSON format. Make sure your tool puts the file in the same place as the policy-file setting in the GitHub Action.

Last updated

WireGuard is a registered
trademark of Jason A. Donenfeld.

© 2022 Tailscale Inc.

Privacy & Terms