← Files TailscaleARCHIVED FILE

skills/tailscale/references/session-recording.md

6.26 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

# Session Recording

Tailscale records two kinds of sessions to a **`tsrecorder`** node in your tailnet:

1. **Tailscale SSH sessions** — terminal output (stdout/stderr) from Tailscale SSH connections.
2. **Kubernetes sessions via the operator** — `kubectl exec` / `attach` / `debug` / `run`, plus (optionally) all Kubernetes API requests.

Both ride on the same recorder image. Output is written in `asciinema` format (`.cast`, newline-delimited JSON — grep-able and replayable).

> **Recording more than SSH/`kubectl`?** `tsrecorder` covers Tailscale SSH and `kubectl` sessions. For RDP, VNC, or database session recording, or full privileged-access-management (JIT access, approvals, credential elimination), refer to [Border0 by Tailscale](border0.md). For SSH/`kubectl` recording today, `tsrecorder` is the established path.

> The Kubernetes recorder docs live under `/docs/kubernetes-operator/recorder/` **WebFetch the matching page** for current CRD fields, flag names, and IAM/IRSA specifics before applying configuration.

## Mental model

- A `tsrecorder` node joins your tailnet like any other device (Docker container, or K8s `Recorder` CR managed by the operator).
- The SSH server (or K8s operator) **streams session data over WireGuard** to the recorder.
- Recorder writes to local disk or **S3-compatible storage** (Amazon S3, MinIO, GCS, Wasabi, R2).
- Recording is wired up by **policy**, not by per-host config:
  - SSH: a `recorder` field on an `ssh` access rule.
  - K8s: a `tailscale.com/cap/kubernetes` grant pointing at the recorder tag.
- **`enforceRecorder: true`** = "fail closed" (deny the session if the recorder is unreachable). Default is fail-open.
- Multiple recorders sharing one tag give automatic failover (lowest tailnet IP first).

What's **not** captured: stdin/keystrokes (so typed passwords are not recorded). Output is captured, so anything printed to the terminal is.

## Canonical shapes

### Deploy `tsrecorder` (Docker, S3 backend)

```bash
docker run --name tsrecorder --rm -it \
  -e TS_AUTHKEY=$TS_AUTHKEY \
  -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  -v $HOME/tsrecorder:/data \
  tailscale/tsrecorder:stable \
  /tsrecorder \
    --dst='s3://s3.us-east-2.amazonaws.com' \
    --bucket=$S3_BUCKET_NAME \
    --statedir=/data/state \
    --ui
```

Drop the AWS vars and use `--dst=/data/recordings` for local storage. The `--ui` flag enables the web viewer (requires HTTPS on your tailnet). On EC2 with an IAM role attached, omit the access/secret keys entirely.

### Deploy `tsrecorder` in Kubernetes

The operator manages this via the `Recorder` CRD:

```yaml
apiVersion: tailscale.com/v1alpha1
kind: Recorder
metadata: {name: recorder}
spec:
  enableUI: true
  tags: ["tag:k8s-recorder"]
  storage:
    s3:
      endpoint: s3.us-east-1.amazonaws.com
      bucket: tsrecorder-bucket
      credentials:
        secret: {name: s3-auth}
```

Requires the Tailscale Kubernetes operator already installed and `tag:k8s-recorder` owned by `tag:k8s-operator`.

### Turn on **SSH** session recording

In the tailnet policy file:

```json
"tagOwners": {
  "tag:session-recorder": ["<owner>"]
},
"ssh": [
  {
    "action": "check",
    "src": ["group:engineering"],
    "dst": ["tag:server"],
    "users": ["autogroup:nonroot"],
    "recorder": ["tag:session-recorder"],
    "enforceRecorder": true
  }
]
```

Sessions matching this rule are recorded. `enforceRecorder: true` = deny if the recorder is down.

### Turn on **Kubernetes** session recording

In the tailnet policy file, via a Kubernetes capability grant:

```json
"grants": [
  {
    "src": ["group:engineering"],
    "dst": ["tag:k8s-operator"],
    "app": {
      "tailscale.com/cap/kubernetes": [{
        "recorder": ["tag:tsrecorder"],
        "enforceRecorder": true,
        "enableEvents": true
      }]
    }
  }
]
```

- `recorder` — tag of your tsrecorder instance.
- `enforceRecorder: true` — fail closed (deny sessions when recorder is unreachable).
- `enableEvents: true` — also record Kubernetes API requests (not just `kubectl` sessions). Without this, only the interactive session types are captured. This is an **alpha** feature (Tailscale v1.90+) that additionally requires setting `TS_EXPERIMENTAL_KUBE_API_EVENTS=true` on the API server proxy and an `acls` rule allowing `tag:k8s-operator:443` — so `enableEvents` alone is not sufficient. Fetch the Kubernetes recorder docs for the current setup before promising it works.

### Viewing recordings

- **Review recordings**: recordings can be reviewed in the Tailscale admin console.
- **Web UI** at `https://<recorder-name>.<tailnet-dns>.ts.net` (needs `--ui` and tailnet HTTPS).
- **CLI**: `asciinema play <file.cast>` to replay, `grep` directly on the file to search.
- **Storage layout**: `<stablenodeid>/<timestamp>.cast` under the destination root.

## Where to find current information

| User is asking about… | Fetch |
|---|---|
| SSH session recording — full setup | https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording |
| SSH recording to S3 (IAM policy, R2/MinIO/GCS specifics, IRSA) | https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3 |
| Multiple recorders / failover | https://tailscale.com/docs/reference/multiple-recorder-nodes |
| Kubernetes recorder — overview | https://tailscale.com/docs/kubernetes-operator/recorder |
| Deploying tsrecorder via the operator (Recorder CRD, storage, IRSA) | https://tailscale.com/docs/kubernetes-operator/recorder/deploy-tsrecorder |
| kubectl session + API event recording | https://tailscale.com/docs/kubernetes-operator/recorder/kubectl-session-recording |

## Worked examples

| If the user wants to… | Fetch |
|---|---|
| Record SSH sessions to satisfy a compliance or audit requirement (SOC2 and similar) | https://tailscale.com/docs/use-cases/infrastructure-access/record-ssh-sessions-compliance |

## Answering pattern

The inline shapes above are usually enough to answer "how do I turn this on" questions. For specifics that drift — IAM policy JSON, full `tsrecorder` flag list, S3-compatible backend quirks (R2's `S3_SEND_CONTENT_MD5`, GCS `interop` keys), or the latest `Recorder` CRD fields (IRSA annotations, `statefulSet` overrides). WebFetch the matching page and quote field names and flags verbatim from the fetched content.

SHA-256: 1e22bc969f32bfc776243354e9d76acf7e57de24d436d47aebcd360909e17fc7