RBAC, ABAC, ACLs, 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.

Note that the Solo and Connectivity plans do not officially support centralized ACLs, but you are free to experiment with ACL features 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. Plus the engineering workstations and servers into one subnet, and 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 ok, 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 wifi, laptops, multiple offices and remote workers. A newer method, now in vogue, 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 create 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 Attributed-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 (ie. roles and attributes) or your ACL policies, the changes are immediately reflected out to all Tailscale nodes to update their enforcement rules. All this happens in a few hundreds of milliseconds.

Tailscale ACL policy format

The Tailscale ACL policy is in a format called “human usable json.” It’s almost exactly JSON, but it allows you to include comments and trailing commas, which makes the files easy to maintain, while staying read/writable by both humans and machines.

For simple cases, you can easily edit the policy file yourself. Advanced organizations with complex policies can write automated scripts to generate Tailscale ACL configurations however and whenever they want. This makes it easy to integrate Tailscale ACLs with virtually any existing authorization system you might already use.

There are several main sections in the ACL file. Let’s go through them one by one.

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:”.

Note: 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.

Note: we suggest putting a comma at the end of each line, even though this is not allowed in regular json format. That makes it easier to cut, paste, and rearrange rows without worrying about adding and removing commas.

Hosts

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

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.

Right now, a Host can only refer to a single IP address, not a subnet. Later, we will extend the ACL policy to allow subnet ACLs.

Some people have asked whether the Hosts section can allow lists of hosts, like the Groups section allows lists of users. This is not necessary, because ACL rules (the next section) allow you to create lists of hosts at that 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:*"],
    },

    // 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 (ie. roles in RBAC), to a set of servers and ports.

The “Action” field must always be present, but only the value “accept” is allowed. There is no way to explicitly reject connections. Instead, no connections are allowed unless granted by an “accept” rule. Once again, this makes compliance audits much simpler: there is no way to hide a rule inside complex logic.

If you’re a Tailscale domain administrator, you can visit the ACL admin page.

Last updated