Control access to Tailscale PAM services

Last validated:
Tailscale PAM is currently in beta.

Tailscale PAM uses grants to control who can connect to a PAM service and what they can do after connecting.

A PAM grant combines:

  • A source, such as a user, group, device, or tag.
  • A destination, such as a database, SSH server, or Kubernetes service.
  • Application-level permissions that are specific to the destination service.

This lets you grant access according to the operation a user needs to do. For example, you can give a support team read-only database access, allow an operator to run a limited set of SSH commands, or let a developer list Kubernetes resources in a specific namespace.

Tailscale grants use a deny-by-default model. A connection is permitted only when a matching grant provides the required network access and PAM application permissions.

Prerequisites

Before creating a PAM grant to control access, confirm you have done the following:

  1. Install and configure a Tailscale PAM connector.
  2. Create the Tailscale PAM service or services you want users to access.
  3. Decide which users, groups, or devices need access.
  4. Decide what each source should be allowed to do within each service.

After you create a PAM service, you can select it as a destination in an access rule. In the tailnet policy file, a service destination uses a selector such as svc:production-database. Tailscale grants let you use a service as a destination alongside users, devices, groups, tags, and other selectors.

You need to be an Owner, Admin, or Network admin of a tailnet to edit the tailnet policy file.

How Tailscale PAM grants work

A Tailscale PAM grant is a Tailscale grant with the tailscale.com/cap/pam application capability.

A Tailscale PAM grant primarily defines three things:

  • Who can access a service.
  • Which Tailscale PAM service they can access.
  • What they can do within that service.

The source can be an identity, group, device, or tag. The destination is a Tailscale PAM service. For example, consider the following grants definition.

{
  "grants": [
    {
      "src": ["group:platform"],
      "dst": ["svc:production-database"],
      "ip": ["*"],
      "app": {
        "tailscale.com/cap/pam": [
          {
            "version": "v1",
            "permissions": {
              "database": {
                "allowed_databases": [
                  {
                    "database": "*",
                    "allowed_query_types": ["ReadOnly"]
                  }
                ]
              }
            }
          }
        ]
      }
    }
  ]
}

The fields have the following purposes:

  • src identifies who is requesting access. In this example, the group:platform group.
  • dst identifies the PAM service, svc:production-database.
  • app contains the application-level PAM permissions, which is read-only access to all databases.

The app field contains the PAM permissions that apply when the source accesses the destination service. The available permissions depend on the type of PAM service, such as database, SSH, Kubernetes, or Amazon S3.

Grants are additive

When multiple grants match a connection, Tailscale combines the capabilities provided by all of them. A more specific grant does not override a broader grant.

As a result, restricting a capability in one grant does not revoke the same capability if another matching grant provides it. Review all grants that can match a source and destination when applying least-privileged access.

Create a grant in the admin console

To create a grant using the visual policy editor:

  1. Go to the Access controls page of the admin console.
  2. Select Add rule from General access rules tab.
  3. For Source, select the users, groups, devices, or tags that need access.
  4. For Destination, select one or more Tailscale PAM services.
  5. (Optional) Use the Note field to document your rationale for the rule.
  6. Expand Application-level options. When the destination includes a Tailscale PAM service, the editor automatically selects the tailscale.com/cap/pam application.
  7. Configure the permissions for each destination protocol.
  8. Review the generated grant in JSON preview.
  9. Select Save grant.

The visual policy editor translates general access rules into grants in the tailnet policy file.

Control database access

Database permissions let you control which databases a user can access and which kinds of queries they can execute.

Without database-specific rules, the database capability permits access to all databases and query types supported by the service. To limit access, select Allow only specific database rules and add one or more rules.

Each rule contains:

  • Database: The database name as understood by the upstream database server, or * to match all databases. For MySQL, this is a database returned by SHOW DATABASES (MySQL also refers to databases as schemas). For PostgreSQL, this is the database you connect to, such as the database specified with psql -d; it does not refer to a PostgreSQL schema within that database.
  • Allowed query types: The query types the source can execute. You can use broader categories such as ReadOnly and ReadWrite, or allow individual query types such as SELECT, INSERT, UPDATE, DELETE, and more. Query-type restrictions are currently supported and enforced for MySQL and PostgreSQL services.

For example, the following permission gives users read-only access to every database and additionally lets them run DELETE queries against the books database:

"database": {
  "allowed_databases": [
    {
      "database": "*",
      "allowed_query_types": ["ReadOnly"]
    },
    {
      "database": "books",
      "allowed_query_types": ["ReadOnly", "DELETE"]
    }
  ]
}

The matching database rules are cumulative. In this example, the books database receives both the wildcard read-only permission and its database-specific permissions.

To prevent this grant from providing database access, select Disable all Database access.

Database access examples

The following shows example permissions for databases.

Read-only access to all databases

"database": {
  "allowed_databases": [
    {
      "database": "*",
      "allowed_query_types": ["ReadOnly"]
    }
  ]
}

Read-only access to one database

"database": {
  "allowed_databases": [
    {
      "database": "inventory",
      "allowed_query_types": ["ReadOnly"]
    }
  ]
}

Use the query types displayed in the visual editor when creating more granular rules.

Control SSH access

SSH permissions let you control several parts of an SSH session independently, including interactive shell access, command execution, SFTP, and TCP forwarding.

By default, SSH access includes shell, exec, and SFTP access. You can disable SSH entirely or disable individual capabilities.

Disable SSH capabilities

You can configure the following options:

  • Disable all SSH access: Prevents this grant from providing SSH access.
  • Disable shell access: Prevents interactive shell sessions.
  • Disable SFTP access: Prevents file transfers using SFTP.
  • Disable exec access: Prevents commands from being run using SSH exec requests.

These capabilities are independent. For example, disabling shell access does not disable exec or SFTP access.

Control exec access

SSH exec lets a user run a command without starting an interactive shell. For example, by running ssh user@server uptime.

You can disable exec access entirely or select Allow only specific exec commands (regex) to restrict which commands can be run using SSH exec.

Exec command restrictions do not apply to commands entered in an interactive shell. If a user has shell access, they can run commands from that shell independently of the exec command allowlist.

Commands are evaluated as regular expressions. Anchor an expression when you need to match an exact command. For example, ^ls$ matches only ls, while ls can also match commands containing that sequence, such as lsof.

"exec": {
  "commands": [
    "^systemctl status nginx$",
    "^journalctl -u nginx --since today$"
  ]
}

Keep interactive shell access disabled when users only need to run the allowed commands. Otherwise, an interactive shell can provide access beyond the exec command allowlist.

Restrict SSH usernames

Select Allow only specific usernames to control which upstream usernames a user can request.

The following permits a support group to connect as support without permitting access as root.

"allowed_usernames": [
  "support",
  "readonly"
]

Control TCP forwarding

TCP forwarding is disabled by default. Select Allow TCP Forwarding to permit users to create SSH TCP forwarding connections.

To restrict forwarding to specific destinations, select Allow only specific connections and specify the permitted destination addresses and ports.

"tcp_forwarding": {
  "allowed_connections": [
    {
      "destination_address": "10.10.10.10",
      "destination_port": "443"
    }
  ]
}

The destination address must match the address requested by the SSH client. A wildcard can be used to permit any address or port, but use wildcards carefully.

Wildcards (*) allow access to any IP address or port. Use them only when broad access is intended.

SSH grant example

The following permissions allow SFTP, permit two non-interactive exec commands, and allow TCP forwarding to one destination. Interactive shell access is not granted.

"ssh": {
  "exec": {
    "commands": [
      "^systemctl status nginx$",
      "^journalctl -u nginx --since today$"
    ]
  },
  "sftp": {},
  "tcp_forwarding": {
    "allowed_connections": [
      {
        "destination_address": "10.10.10.10",
        "destination_port": "443"
      }
    ]
  }
}

Control Kubernetes access

Kubernetes permissions let you define which operations a user can do against a Kubernetes API service.

An unrestricted Kubernetes permission grants access to all operations supported by the service. Select Allow only specific Kubernetes rules to change the grant to an allowlist. When Kubernetes rules are enabled, only operations matching at least one rule are permitted.

A Kubernetes rule can match:

  • Verbs, such as get, list, create, or delete.
  • Namespaces.
  • Resource types, such as pods, deployments, or services.
  • Resource names.
  • API groups.

The following example permits listing resources across all namespaces:

"kubernetes": {
  "rules": [
    {
      "namespaces": ["*"],
      "verbs": ["list"],
      "resources": ["*"]
    }
  ]
}

Use multiple rules when different resources require different permissions. For example, one rule could grant listing pods in every namespace, while another grants creating pods/exec resources only in a development namespace.

The connector's Kubernetes identity must also have permission to do the requested operation. Tailscale PAM permissions can restrict the connector's available privileges, but cannot expand the permissions provided by upstream Kubernetes role-based access control (RBAC).

To prevent this grant from providing Kubernetes access, select Disable all Kubernetes access.

Control Amazon S3 access

Amazon S3 permissions let you restrict access by S3 action, bucket, and path.

Without S3-specific rules, users receive the S3 access available through the service and its AWS credentials. Select Allow only specific S3 rules to create an allowlist of permitted bucket, path, and action combinations.

When S3 rules are enabled, an operation must match at least one rule. Rules can use * wildcards to match multiple buckets or paths.

For example, the rules below show the following permissions:

  • Allow list and read access to all buckets and paths.
  • Allow list, read, write, and delete access to buckets whose names start with api-.
  • Allow list, read, write, and delete access to the images/ path in the ui-assets bucket.
{
  "aws_s3": {
    "rules": [
      {
        "buckets": ["*"],
        "paths": ["*"],
        "actions": ["list", "read"]
      },
      {
        "buckets": ["api-*"],
        "paths": ["*"],
        "actions": ["list", "read", "write", "delete"]
      },
      {
        "buckets": ["ui-assets"],
        "paths": ["images/*"],
        "actions": ["list", "read", "write", "delete"]
      }
    ]
  }
}

The rules are cumulative. In this example, all buckets receive the wildcard list and read permissions, while the more specific rules add write and delete access for matching buckets and paths.

The effective access is also limited by the AWS IAM permissions assigned to the Tailscale PAM connector. A grant cannot provide access to an S3 operation that the connector's AWS identity does not have permission to execute.

To prevent this grant from providing S3 access, select Disable all AWS S3 access.

Control HTTP, RDP, VNC, and TCP access

HTTP, RDP, VNC, and TCP services support protocol-level application access.

For each protocol, you can either leave the default capability enabled or select its disable option:

  • Disable all HTTP access
  • Disable all RDP access
  • Disable all VNC access
  • Disable all TCP access

Tailscale grants use a deny-by-default model. Access is permitted only when a matching grant permits the source to access the destination with the required PAM permissions.

Configure multiple service types in one grant

A single grant can contain permissions for multiple Tailscale PAM service types. The relevant permission is applied according to the destination service. The following shows a grant for multiple service types.

{
  "grants": [
    {
      "src": ["group:platform"],
      "dst": [
        "svc:production-mysql",
        "svc:production-ssh",
        "svc:production-kubernetes"
      ],
      "ip": ["*"],
      "app": {
        "tailscale.com/cap/pam": [
          {
            "version": "v1",
            "permissions": {
              "database": {
                "allowed_databases": [
                  {
                    "database": "*",
                    "allowed_query_types": ["ReadOnly"]
                  }
                ]
              },
              "ssh": {
                "exec": {
                  "commands": [
                    "^systemctl status nginx$"
                  ]
                },
                "allowed_usernames": ["support"]
              },
              "kubernetes": {
                "rules": [
                  {
                    "namespaces": ["*"],
                    "verbs": ["list"],
                    "resources": ["*"]
                  }
                ]
              }
            }
          }
        ]
      }
    }
  ]
}

Consider using separate grants when different teams own the services, different source groups need access, or each permission set has a separate operational purpose. Smaller grants can be easier to review and audit.

Complete PAM grant example

As mentioned, a single grant can provide access to multiple service types. The permissions that apply depend on the destination service.

The following example:

  • Gives the group:platform group read-only access to all databases, with additional DELETE access to the books database.
  • Permits SSH exec for two commands, SFTP, and TCP forwarding to one destination.
  • Permits users to list Kubernetes resources across all namespaces.
  • Grants list and read access to all S3 buckets, with additional write and delete access to api-* buckets and the images/ path in the ui-assets bucket.
  • Grants access to HTTP, RDP, VNC, and TCP services.
{
  "src": ["group:platform"],
  "dst": [
    "svc:production-database",
    "svc:production-ssh",
    "svc:production-kubernetes",
    "svc:production-s3",
    "svc:internal-http",
    "svc:admin-rdp",
    "svc:admin-vnc",
    "svc:internal-tcp"
  ],
  "ip": ["*"],
  "app": {
    "tailscale.com/cap/pam": [
      {
        "version": "v1",
        "permissions": {
          "database": {
            "allowed_databases": [
              {
                "database": "*",
                "allowed_query_types": ["ReadOnly"]
              },
              {
                "database": "books",
                "allowed_query_types": ["ReadOnly", "DELETE"]
              }
            ]
          },
          "ssh": {
            "exec": {
              "commands": [
                "^systemctl status nginx$",
                "^journalctl -u nginx --since today$"
              ]
            },
            "sftp": {},
            "tcp_forwarding": {
              "allowed_connections": [
                {
                  "destination_address": "10.10.10.10",
                  "destination_port": "443"
                }
              ]
            }
          },
          "kubernetes": {
            "rules": [
              {
                "namespaces": ["*"],
                "verbs": ["list"],
                "resources": ["*"]
              }
            ]
          },
          "aws_s3": {
            "rules": [
              {
                "buckets": ["*"],
                "paths": ["*"],
                "actions": ["list", "read"]
              },
              {
                "buckets": ["api-*"],
                "paths": ["*"],
                "actions": ["list", "read", "write", "delete"]
              },
              {
                "buckets": ["ui-assets"],
                "paths": ["images/*"],
                "actions": ["list", "read", "write", "delete"]
              }
            ]
          },
          "http": {},
          "rdp": {},
          "vnc": {},
          "tcp": {}
        }
      }
    ]
  }
}

Tailscale PAM applies the relevant permissions according to the destination service. For example, the database rules apply when accessing svc:production-database, while the S3 rules apply when accessing svc:production-s3.

When rules overlap, their permissions are cumulative. For example, the wildcard S3 rule provides list and read access to all buckets, while the more specific rules add write and delete access to matching buckets and paths.

Consider using separate grants when different groups need access to different services or when the services have separate administrative purposes. Smaller grants can be easier to review and audit.

Troubleshoot PAM access

When a user cannot access a Tailscale PAM service, check the following:

  • Confirm that the service exists and has an available connector.
  • Confirm that the user or device matches the grant's src.
  • Confirm that the service matches the grant's dst.
  • Confirm that the tailscale.com/cap/pam application capability is present.
  • Confirm that the relevant protocol has not been disabled.
  • For database, Kubernetes, or S3 allowlists, confirm that the requested operation matches a rule.
  • Review other matching grants, since their capabilities are combined.
  • Confirm that the upstream service credentials or role permit the operation.
  • Review the generated policy in the JSON editor for unexpected selectors or permissions.