|
| 1 | +# Tailscale |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +xyOps pairs well with [Tailscale](https://tailscale.com/) (specifically [Tailscale Serve](https://tailscale.com/kb/1312/serve)), which acts as an SSO auth system by forwarding trusted headers. This document describes how to set up xyOps on your Tailnet using the Tailscale Serve CLI, and also as a "sidecar" with Docker Compose. |
| 6 | + |
| 7 | +First, please read the [xyOps SSO Guide](sso.md), as it introduces several concepts we will be referencing here. |
| 8 | + |
| 9 | +## Header Map |
| 10 | + |
| 11 | +Here is how you should configure your [SSO Header Map](sso.md#header-map) for Tailscale Serve use: |
| 12 | + |
| 13 | +```json |
| 14 | +"header_map": { |
| 15 | + "username": "Tailscale-User-Login", |
| 16 | + "full_name": "Tailscale-User-Name", |
| 17 | + "email": "Tailscale-User-Login", |
| 18 | + "avatar": "Tailscale-User-Profile-Pic", |
| 19 | + "groups": "Tailscale-App-Capabilities" |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +Alternatively, you can simply enable SSO and set the `preset` property to `tailscale`, which configures the header map (among other things) for you. Example with environment variables: |
| 24 | + |
| 25 | +```sh |
| 26 | +XYOPS_SSO__enabled="true" |
| 27 | +XYOPS_SSO__preset="tailscale" |
| 28 | +``` |
| 29 | + |
| 30 | +## Usernames |
| 31 | + |
| 32 | +Please read the [SSO Header Cleanup](sso.md#header-cleanup) section regarding username cleanup, so you understand how your Tailscale username (which comes in as an email address) will be translated on the xyOps side. By default, the email domain is stripped, and your username becomes the first part of your email address. |
| 33 | + |
| 34 | +## Admin Bootstrap |
| 35 | + |
| 36 | +If you want to just get up and running quickly, you can use the [SSO Admin Bootstrap](sso.md#admin-bootstrap) feature to automatically promote yourself to a full administrator. Just note that this is designed as a one-time setup shortcut, and will log a loud warning in the xyOps activity log on every login. |
| 37 | + |
| 38 | +## Capabilities |
| 39 | + |
| 40 | +Tailscale can forward along what they call "capabilities", which can translate to roles and privileges on the xyOps side. In short, you can use Tailscale's capability grants to automatically assign your xyOps users appropriate privileges and/or roles. Here is how to set it up: |
| 41 | + |
| 42 | +1. Login to your [Tailscale Admin Console](https://login.tailscale.com/welcome). |
| 43 | +2. Click on the "Access Controls" tab. |
| 44 | +3. Click on "JSON Editor". |
| 45 | + |
| 46 | +Locate the `grants` array in the JSON editor, or create it if needed. Here is an example: |
| 47 | + |
| 48 | +```json |
| 49 | +"grants": [ |
| 50 | + { |
| 51 | + "src": ["autogroup:admin"], |
| 52 | + "dst": ["autogroup:admin"], |
| 53 | + "app": { |
| 54 | + "xyops.io/cap/ts": [ {"privileges": ["admin"], "roles": []} ], |
| 55 | + } |
| 56 | + } |
| 57 | +] |
| 58 | +``` |
| 59 | + |
| 60 | +This will automatically promote anyone in your `autogroup:admin` Tailscale group to a full xyOps administrator. The `autogroup:admin` group should already exist in your account, as it is automatically created for you by Tailscale. You can see all your groups, and also also add new groups, by clicking on the "Visual Editor" tab, and then the "Groups" sub-tab. |
| 61 | + |
| 62 | +The `app` section of the grant JSON is very important -- the syntax must be exact: |
| 63 | + |
| 64 | +```json |
| 65 | +"app": { |
| 66 | + "xyops.io/cap/ts": [ {"privileges": ["admin"], "roles": []} ], |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +The `xyops.io/cap/ts` is a unique key that xyOps looks for when it parses the incoming headers. It must be set exactly to `xyops.io/cap/ts`, and the value must be an array of objects, as shown above. The properties inside the object are passed directly to xyOps: |
| 71 | + |
| 72 | +- The `privileges` sub-array can contain any valid [xyOps Privilege IDs](privileges.md), e.g. `admin`. |
| 73 | +- The `roles` sub-array can contain any valid [xyOps Role IDs](users.md#roles), which are user-created. |
| 74 | + |
| 75 | +Note that by default, roles and privileges are applied "additively" to user records. Meaning, the SSO sync will never *remove* a role or privilege. This is so you can manually apply your own user roles and permissions using the xyOps Admin UI, and everything plays nice. However, if you do not want this behavior, and instead want Tailscale to be the single source of truth for all user roles and privileges, set the SSO [replace_roles](sso.md#configuration) and/or [replace_privileges](sso.md#configuration) properties to `true`. Those will replace **all** the user roles and/or privileges with whatever we get from the Tailscale capabilities trusted header. This sync happens on every user login and session refresh, wiping out any local changes made in xyOps. |
| 76 | + |
| 77 | +Make sure you click the "Save" button when you are finished editing the JSON in your Tailscale Admin Console. |
| 78 | + |
| 79 | +## Base URL |
| 80 | + |
| 81 | +xyOps needs to know the base URL of the hosted application so it can generate self-referencing URLs in things like outbound emails and web hooks. To do this, you'll need to set the [base_app_url](config.md#base_app_url) configuration property to your Tailscale-provided app URL. The hostname will be the current machine name, followed by your custom Tailnet domain. Example: |
| 82 | + |
| 83 | +```json |
| 84 | +"base_app_url": "https://joemax.taild89302.ts.net" |
| 85 | +``` |
| 86 | + |
| 87 | +Or via environment variable: |
| 88 | + |
| 89 | +```sh |
| 90 | +XYOPS_base_app_url="https://joemax.taild89302.ts.net" |
| 91 | +``` |
| 92 | + |
| 93 | +Additionally, if you plan on adding remote satellite servers, xyOps needs to know what hostname to use for advertising itself to its cluster (i.e. how satellite servers will connect back to the primary conductor server). To do this, add a `hostname` top-level configuration property set to your machine's Tailnet hostname: |
| 94 | + |
| 95 | +```json |
| 96 | +"hostname": "joemax.taild89302.ts.net" |
| 97 | +``` |
| 98 | + |
| 99 | +Or via environment variable: |
| 100 | + |
| 101 | +```sh |
| 102 | +XYOPS_hostname="joemax.taild89302.ts.net" |
| 103 | +``` |
| 104 | + |
| 105 | +By default xyOps uses the current machine's local hostname for this, so we want to override it to use our special Tailnet hostname. |
| 106 | + |
| 107 | +## HTTPS |
| 108 | + |
| 109 | +Assuming you have [Tailscale HTTPS](https://login.tailscale.com/admin/dns) enabled on your Tailnet (highly recommended), this means that Tailscale Serve will **only** support HTTPS URLs to your app. So, we need to set a couple of extra configuration properties so you can add satellite servers and have them communicate over HTTPS as well: |
| 110 | + |
| 111 | +- `satellite.config.secure`: `true` |
| 112 | +- `satellite.config.port`: `443` |
| 113 | + |
| 114 | +Or via environment variables: |
| 115 | + |
| 116 | +```sh |
| 117 | +XYOPS_satellite__config__secure="true" |
| 118 | +XYOPS_satellite__config__port="443" |
| 119 | +``` |
| 120 | + |
| 121 | +## Serve |
| 122 | + |
| 123 | +Once you have xyOps configured and running with SSO enabled, start [Tailscale Serve](https://tailscale.com/kb/1312/serve) using these command-line arguments: |
| 124 | + |
| 125 | +```sh |
| 126 | +tailscale serve --accept-app-caps=xyops.io/cap/ts 5522 |
| 127 | +``` |
| 128 | + |
| 129 | +The special `--accept-app-caps=xyops.io/cap/ts` argument tells Tailscale to forward the special `Tailscale-App-Capabilities` HTTP header with all incoming requests, which xyOps uses to apply user privileges and roles (see [Capabilities](#capabilities) above). |
| 130 | + |
| 131 | +Note that the first time you visit your Tailscale-provided secure HTTPS URL for your served app, there may be a short delay as Tailscale provisions the TLS certificate. If you receive a timeout error, please wait a few seconds and refresh. This is normal. |
| 132 | + |
| 133 | +## Logout |
| 134 | + |
| 135 | +When a user clicks the "Logout" button in the xyOps UI, the user's session data and cookie are deleted. However, with an SSO provider like Tailscale we cannot "fully" log the user out, because they will still be connected and authenticated with their Tailnet, and thus if they just navigate their browser back to the app, they'll instantly log back in (this is by design). |
| 136 | + |
| 137 | +So, for Tailscale SSO the best way to handle this is to set the [logout_url](sso.md#logging-out) to the following value, which displays a message to the user describing the "partial logout" situation: |
| 138 | + |
| 139 | +```sh |
| 140 | +XYOPS_SSO__logout_url="/api/app/sso_logout" |
| 141 | +``` |
| 142 | + |
| 143 | +Note that if you use the `preset` feature to enable Tailscale (see above), the `logout_url` is set automatically for you. |
| 144 | + |
| 145 | +## Security |
| 146 | + |
| 147 | +For security hardening, set your [SSO IP Whitelist](sso.md#ip-whitelist) to only accept trusted headers from localhost, as that is how Tailscale Serve routes traffic: |
| 148 | + |
| 149 | +```json |
| 150 | +"whitelist": ["127.0.0.1", "::1/128"] |
| 151 | +``` |
| 152 | + |
| 153 | +The whitelist can also be specified via environment variable. Just use a CSV list of IPs and/or CIDRs in that case: |
| 154 | + |
| 155 | +```sh |
| 156 | +XYOPS_SSO__whitelist="127.0.0.1,::1/128" |
| 157 | +``` |
| 158 | + |
| 159 | +Finally, remember to **delete the stock admin user account** that xyOps automatically creates on first install. Since you are using SSO this account is technically "unreachable", but deleting it is safest as it has an insecure password by default. |
| 160 | + |
| 161 | +## Sidecar |
| 162 | + |
| 163 | +Tailscale has a really cool feature which allows xyOps to run in a Docker container as its own dedicated "node" on your Tailnet. This is done by running Tailscale as a [sidecar container](https://tailscale.com/blog/docker-tailscale-guide) alongside a xyOps container. Tailscale then handles everything including DNS, TLS, and proxying requests to xyOps, including forwarding trusted headers for automatic login and privilege / role assignments. This guide contains everything you need to get up and running. |
| 164 | + |
| 165 | +### Tailscale Admin Setup |
| 166 | + |
| 167 | +The first thing you'll need to do is login to your [Tailscale Admin Console](https://login.tailscale.com/welcome), and then: |
| 168 | + |
| 169 | +1. Read the [Capabilities](#capabilities) section above, to add a grant into your Tailnet policy file, so xyOps can automatically assign privileges and roles to your users. |
| 170 | +2. Create an Auth Key. See [Generate An Auth Key]( https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key) for instructions. |
| 171 | + |
| 172 | +Remember, auth keys expire by default. See [Tailscale Key Expiry](https://tailscale.com/docs/features/access-control/auth-keys#key-expiry) to learn more. For permanent access, you can disable key expiry, and add a tag to the machine. |
| 173 | + |
| 174 | +### Host Setup |
| 175 | + |
| 176 | +Make sure you have [Docker](https://www.docker.com/) and the Docker CLI with Docker Compose all installed and running. |
| 177 | + |
| 178 | +Create a directory on your host machine for xyOps to use, and `cd` into it. It just needs to store a couple of configuration files and some volume-mapped directories for the containers. |
| 179 | + |
| 180 | +### Env File |
| 181 | + |
| 182 | +Create an `.env` file in our host directory with the following contents: |
| 183 | + |
| 184 | +```sh |
| 185 | +# Tailscale Configuration |
| 186 | +TS_AUTHKEY="YOUR_TAILSCALE_AUTHKEY" # Get auth key from https://tailscale.com/admin/authkeys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions. |
| 187 | +TS_HOST="xyops.taild89302.ts.net" # Change to your own tailnet domain, keeping `xyops.` at the front. |
| 188 | +TZ="America/Los_Angeles" # Change to your own local timezone, or comment out if this is set in your shell already. |
| 189 | +``` |
| 190 | + |
| 191 | +- Change the `TS_AUTHKEY` value to your own auth key you just created on your Tailscale console. |
| 192 | +- Change the `TS_HOST` value to your own Tailnet domain name, leaving in the `xyops.` prefix. See the [DNS](https://login.tailscale.com/admin/dns) tab in your Tailscale Admin Console. |
| 193 | +- Change the `TZ` to your own local timezone, so xyOps can rotate logs and reset daily stats at "your" midnight. |
| 194 | + |
| 195 | +### Docker Compose |
| 196 | + |
| 197 | +Create a `compose.yaml` file in our host directory with the following contents: |
| 198 | + |
| 199 | +```yaml |
| 200 | +configs: |
| 201 | + ts-serve: |
| 202 | + content: | |
| 203 | + {"TCP":{"443":{"HTTPS":true}}, |
| 204 | + "Web":{"$${TS_CERT_DOMAIN}:443": |
| 205 | + {"Handlers":{"/": |
| 206 | + {"Proxy":"http://127.0.0.1:5522","AcceptAppCaps":["xyops.io/cap/ts"]}}}}, |
| 207 | + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} |
| 208 | +
|
| 209 | +services: |
| 210 | + # Tailscale Sidecar Configuration |
| 211 | + tailscale: |
| 212 | + image: tailscale/tailscale:latest |
| 213 | + container_name: tailscale-xyops # Name for local container management |
| 214 | + hostname: xyops # Name used within your Tailscale environment |
| 215 | + environment: |
| 216 | + TS_AUTHKEY: "${TS_AUTHKEY}" |
| 217 | + TS_STATE_DIR: "/var/lib/tailscale" |
| 218 | + TS_SERVE_CONFIG: "/config/serve.json" |
| 219 | + TS_USERSPACE: "false" |
| 220 | + TS_ENABLE_HEALTH_CHECK: "true" # Enable healthcheck endpoint: "/healthz" |
| 221 | + TS_LOCAL_ADDR_PORT: "127.0.0.1:41234" # The <addr>:<port> for the healthz endpoint |
| 222 | + TS_ACCEPT_DNS: "true" # Use Tailscale MagicDNS |
| 223 | + TS_AUTH_ONCE: "true" |
| 224 | + configs: |
| 225 | + - source: ts-serve |
| 226 | + target: /config/serve.json |
| 227 | + volumes: |
| 228 | + - ./ts-config:/config # Config folder used to store Tailscale files |
| 229 | + - ./ts-state:/var/lib/tailscale # Tailscale requirement |
| 230 | + devices: |
| 231 | + - /dev/net/tun:/dev/net/tun |
| 232 | + cap_add: |
| 233 | + - net_admin |
| 234 | + healthcheck: |
| 235 | + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] |
| 236 | + interval: 1m |
| 237 | + timeout: 10s |
| 238 | + retries: 3 |
| 239 | + start_period: 10s |
| 240 | + restart: always |
| 241 | + |
| 242 | + xyops01: |
| 243 | + image: ghcr.io/pixlcore/xyops:latest |
| 244 | + container_name: xyops01 |
| 245 | + network_mode: service:tailscale # Use Sidecar |
| 246 | + init: true |
| 247 | + restart: unless-stopped |
| 248 | + environment: |
| 249 | + XYOPS_xysat_local: "true" |
| 250 | + XYOPS_hostname: "${TS_HOST}" |
| 251 | + XYOPS_masters: "${TS_HOST}" |
| 252 | + XYOPS_base_app_url: "https://${TS_HOST}" |
| 253 | + XYOPS_satellite__config__secure: "true" |
| 254 | + XYOPS_satellite__config__port: "443" |
| 255 | + XYOPS_SSO__enabled: "true" |
| 256 | + XYOPS_SSO__preset: "tailscale" |
| 257 | + XYOPS_SSO__whitelist: "127.0.0.1,::1/128" |
| 258 | + TZ: "${TZ}" |
| 259 | + volumes: |
| 260 | + - xy-data:/opt/xyops/data |
| 261 | + - ./xyops01-conf:/opt/xyops/conf |
| 262 | + - /var/run/docker.sock:/var/run/docker.sock |
| 263 | + depends_on: |
| 264 | + tailscale: |
| 265 | + condition: service_healthy |
| 266 | + |
| 267 | +volumes: |
| 268 | + xy-data: |
| 269 | +``` |
| 270 | +
|
| 271 | +You should not need to edit anything in the `compose.yaml` file, but here are a couple of notes: |
| 272 | + |
| 273 | +- The `XYOPS_xysat_local` environment variable causes xyOps to launch [xySat](hosting.md#satellite) in the background, in the same container. This is so you can start running jobs right away -- it is great for testing and home labs, but *not recommended for production*. |
| 274 | +- The `/var/run/docker.sock` bind is optional, and allows xyOps to launch its own containers (i.e. for the [Docker Plugin](plugins.md#docker-plugin), and the [Plugin Marketplace](marketplace.md)). |
| 275 | + |
| 276 | +### Start Up |
| 277 | + |
| 278 | +Type this command to start everything up (the `-d` switch runs it in the background): |
| 279 | + |
| 280 | +```sh |
| 281 | +docker compose -p xyops-tailscale up -d |
| 282 | +``` |
| 283 | + |
| 284 | +And then visit your `TS_HOST` URL in your favorite browser. Example: |
| 285 | + |
| 286 | +https://xyops.taild89302.ts.net/ |
| 287 | + |
| 288 | +Note that the first time you visit the URL, there may be a short delay as Tailscale provisions the TLS certificate. |
0 commit comments