Docs / Admin

ACLs, ABAC, RBAC, and network security policies

Access control lists (ACLs) restrict who can access which nodes on your network. ACLs can be defined in the admin console.

Our Solo and Connectivity plans do not officially support centralized ACLs but you are free to experiment with them during your trial period. You may also find our “Block incoming connections” feature enough for your needs.

Background

Let’s say you have 11 employees in your organization. Five of them are in the accounting department, five are in the engineering department, and then there’s the president.

How can you make it so that engineers can access the engineering servers, accountants can access the accounting servers, and the president can access all of them?

On old-fashioned physical networks, you could try using physical segregation: put the engineering workstations and servers into one subnet, the accounting servers into another subnet, and then use a firewall to stop traffic flowing from one subnet to the other. This complicates things for the president, who wants access to both networks, but that’s okay: perhaps the president can have two computers, one on each network.

That was a relatively common setup before but is harder to do now in the days of Wi-Fi, laptops, multiple offices, and remote workers. A newer method is to move all your servers into the cloud and then use BeyondCorp-style proxies to restrict which users can access which servers. This works, as long as all your servers are web based, and as long as you want to put them all in the cloud, and as long as you don’t mind running a network of proxy servers that creates a network bottleneck between clients and servers.

Tailscale’s security architecture is different. Instead of proxy servers, physical networks, and firewalls, each node can talk directly to each other node, no matter where they are. But to prevent that from becoming a security problem, Tailscale also lets you define security policies - basically, a list of which nodes can access which other nodes. Architecturally, you can think of this as a firewall (packet filter) running on every Tailscale node.

With a traditional firewall, security policies are expressed as a set of packet filtering rules allowing one IP address to talk to another IP address. There’s a rule for Engineer A’s laptop (10.1.1.42) to let it talk to Git Server G (10.2.2.3), and so on for Engineers B..K. When someone brings up CI Server C (10.2.2.4), you file a ticket with the security department to open a firewall rule that lets 10.1.1.x/24 access 10.2.2.4, and so on.

Tailscale ACLs are similar, but refer to users (and groups) and services, instead of just IP addresses. You can write rules like “everyone in the engineering group should be allowed to access servers tagged as ‘engineering’.” The rules are stored in the central coordination server, and compiled into a set of traditional packet filter rules (“these IP addresses can talk to these other IP addresses”) behind the scenes, then sent to each node for enforcement (also known as access control).

Role-based Access Control (RBAC) and Attribute-based Access Control (ABAC) are variations of the same concept. In RBAC, each user acts in exactly one role at a time (engineering, accounting, IT administrator, etc). In ABAC, each user or server is tagged with a flexible set of attributes that describe their job (employee + engineer + Montreal), and those attributes each grant access to relevant services (employees can access the payroll viewer, engineers can access the Git server, and Montreal users can access the printers in Montreal).

Tailscale allows you to build complex RBAC/ABAC rule sets in a simple way, based on identities pulled in from your identity provider. When you make a change to a user’s group memberships (i.e. roles and attributes) or your ACL policies, the changes are immediately reflected out to all Tailscale nodes to update their enforcement rules. All this typically happens in a few hundred milliseconds.

Tailscale ACL file syntax

Tailscale ACL policies are written in “human usable json,” a superset of JSON that allows comments and trailing commas. This makes ACL files easy to maintain while staying read/writable by both humans and machines.

For simple cases, you can edit the policy file by hand. Organizations with complex policies can write automated scripts to generate Tailscale ACL configurations on their behalf. This makes it easy to integrate Tailscale ACLs with any existing authorization system you already use.

If you’re a Tailscale domain admin, you can view and edit your domain’s ACL policy from the ACL page.

The ACL file has several main sections:

Groups

// Declare static groups of users beyond those in the identity service.
"Groups": {
	"group:example": [
		"user1@example.com",
		"user2@example.com",
	],
},

The Groups section lets you define groups of users (in RBAC, these are called “roles”). A user can be in more than one group. Every group name must start with the prefix group:.

For explicitness, you must include the full email address of each user. However, Tailscale currently only lets you share your nodes with users inside the same domain as you. If you write an ACL that permits users outside your domain, it will be silently ignored. Eventually, we will allow inviting users outside your domain.

Hosts

// Declare hostname aliases to use in place of IP addresses or subnets.
"Hosts": {
	"example-host-1": "100.100.100.100",
	"example-host-2": "100.100.101.100/24",
},

Hosts are convenient names that you can use to refer to particular servers when writing ACL rules, so that the ACLs themselves are more human-readable and thus auditable by non-technical users who need to check regulatory compliance.

Hosts can refer to either a single IP address, or a CIDR IP address range.

The "Hosts" section does not allow lists of hosts, like the Groups section. This is not necessary, because ACL rules (the next section) allow you to create lists of hosts at the rule level instead. Disallowing host lists in this section makes it easier to audit ACL rules.

ACLs

// Access control lists.
"ACLs": [
	// Engineering users, plus the president, can access port 22 (ssh)
	// and port 3389 (remote desktop protocol) on all servers, and all
	// ports on development servers.
	{
		"Action": "accept",
		"Users": ["group:engineering", "president@example.com"],
		"Ports": ["*:22,3389", "git-server:*", "ci-server:*"],
	},

// Servers in the my-subnet host and 192.168.1.0/24 are allowed to
	// access hosts on both networks.
	{
		"Action": "accept",
		"Users": ["my-subnet", "192.168.1.0/24"],
		"Ports": ["my-subnet:*", "192.168.1.0/24:*"],
	},

	// Permit absolutely everything (for logged-in users only). Comment
	// out this section if you want to define specific ACL restrictions
	// above.
	{
		"Action": "accept",
		"Users": ["*"],
		"Ports": ["*:*"],
	},
]

Each entry in the "ACLs" section defines a rule that grants access by a set of users or groups (i.e. roles in RBAC), to a set of servers and ports.

There is no way to explicitly reject connections. Instead, no connections are allowed unless granted by an "accept" rule. This makes compliance audits much simpler: there is no way to hide a rule inside complex logic.

The "Action" field must always be present, but only the value "accept" is allowed.

The "Users" field may contain any valid group, email, or * meaning all users. They may also include IP addresses or CIDR ranges, which can be helpful for applying rules to subnet relayed traffic.

The "Ports" field must contain a host:port string.

Acceptable values for host are:

  • an IP address 100.101.102.103
  • a CIDR range 100.101.102.100/24
  • a “Hosts” entry my-host
  • a “TagOwners” entry tag:montreal-webserver
  • or all hosts *

Acceptable values for port are:

  • a single port 443
  • a range of ports 8000-9000
  • a comma-delimited set of port / ranges 80,443,8000-9000
  • or all ports *

TagOwners

// Define who is allowed to use which tags.
"TagOwners": {
	// Everyone in the montreal-admins or global-admins group are
	// allowed to tag servers as montreal-webserver.
	"tag:montreal-webserver": [
		"group:montreal-admins",
		"group:global-admins",
	],
	// Only a few admins are allowed to create API servers.
	"tag:api-server": [
		"group:global-admins",
		"president@example.com",
	],
}

TagOwners define tags, and a list of users allowed to configure devices with that tag. All tag names must start with tag:. Users can be groups or specific email addresses.

You can refer to tags you define from ACL rules:

"ACLs": {
	//
	// ... other ACL rules ...
	//
	// All users in Montreal are allowed to access the Montreal web
	// servers.
	{
		"Action": "accept",
		"Users": ["group:montreal-users"],
		"Ports": ["tag:montreal-webserver:80,443"],
	},

	// Montreal web servers are allowed to make outgoing connections to
	// the API servers, but only on https port 443.
	// In contrast, this doesn't grant API servers the right to initiate
	// any connections.
	{
		"Action": "accept",
		"Users": ["tag:montreal-webserver"],
		"Ports": ["tag:api-server:443"],
	},
},

Once you’ve created some tags, be sure to tag your devices to ensure the ACLs are applied.

Tests

// Declare tests to check functionality of ACL rules
"Tests": [
	{
		"User": "user1@example.com",
		"Allow": ["example-host-1:22", "example-host-2:80"],
		"Deny": ["exapmle-host-2:100"],
	},
	{
		"User": "user2@example.com",
		"Allow": ["100.60.3.4:22"],
	},
],

ACLs can get large and it may be hard to keep track of what groups, roles, tags, etc, give permissions to what machines. The "Tests" section of your ACL lets you declare tests to ensure updates to your ACL don’t accidentally revoke access to key systems, or expose resources to users you didn’t intend. If a test fails due to an ACL change, you’ll receive an error.

A test requires a target User (email), and either a list of Allow’d destinations, Deny’d destinations, or both. Acceptable items in Allow/Deny lists are specific ports to either a user or IP address as shown above.

Editing ACLs

We provide a simple text editor in the admin console, or you can choose to maintain your ACL elsewhere (e.g. to take advantage of version control systems) and paste it in to save. We also hope to offer an API for editing your ACL in the near future.

The editor also has a diff view to view git-style diffs for your current changes and a “Preview Rules” feature, explained in the next section.

Preview Rules

You can preview what rules will affect a user by selecting a user from the dropdown. A list of ports (one per line) accessible to the specified user is shown, as well the line number that defines that rule. It also includes any other users/groups that can access that port due to that rule.

ACL tags

ACL tags are a beta feature. During the beta it is possible to re-tag a node without reauthenticating. This means someone with root access on the machine can change or delete tags (to tags permitted by TagOwners) at any time. This could be used to get access that would normally not be available to the node. When this feature is officially supported, it will require an interactive login before changing tags.

Every node on Tailscale needs to be authenticated as some user, which defines the permissions of that node. This works well for end user devices, but creates problems for servers.

Tags allow defining custom permissions for servers and infrastructure devices without inheriting permissions from the user (often an IT or devops admin) who authenticated the device.

To begin using ACL tags, first define "TagOwners" and "ACLs" rules to define the shape of your network. Next, you can start to re-tag your nodes using the --advertise-tags option to tailscale up. Right now, only Linux-based servers can have tags. A node can request multiple tags, if you separate them with commas.

sudo tailscale up --advertise-tags=tag:montreal-webserver,tag:api-server

To undo the tagging operation, re-run tailscale up without the extra flag:

sudo tailscale up

Last updated