Skip to content

Latest commit

 

History

History
176 lines (154 loc) · 5.93 KB

acls.md

File metadata and controls

176 lines (154 loc) · 5.93 KB

ACLs use case example

Let's build an example use case for a small business (It may be the place where ACL's are the most useful).

We have a small company with a boss, an admin, two developers and an intern.

The boss should have access to all servers but not to the user's hosts. Admin should also have access to all hosts except that their permissions should be limited to maintaining the hosts (for example purposes). The developers can do anything they want on dev hosts but only watch on productions hosts. Intern can only interact with the development servers.

There's an additional server that acts as a router, connecting the VPN users to an internal network 10.20.0.0/16. Developers must have access to those internal resources.

Each user have at least a device connected to the network and we have some servers.

  • database.prod
  • database.dev
  • app-server1.prod
  • app-server1.dev
  • billing.internal
  • router.internal

ACL implementation example

ACL setup

Note: Namespaces will be created automatically when users authenticate with the Headscale server.

ACLs could be written either on huJSON or YAML. Check the test ACLs for further information.

When registering the servers we will need to add the flag --advertise-tags=tag:<tag1>,tag:<tag2>, and the user (namespace) that is registering the server should be allowed to do it. Since anyone can add tags to a server they can register, the check of the tags is done on headscale server and only valid tags are applied. A tag is valid if the namespace that is registering it is allowed to do it.

Here are the ACL's to implement the same permissions as above:

{
  // groups are collections of users having a common scope. A user can be in multiple groups
  // groups cannot be composed of groups
  "groups": {
    "group:boss": ["boss"],
    "group:dev": ["dev1", "dev2"],
    "group:admin": ["admin1"],
    "group:intern": ["intern1"]
  },
  // tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.
  // This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)
  // and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)
  "tagOwners": {
    // the administrators can add servers in production
    "tag:prod-databases": ["group:admin"],
    "tag:prod-app-servers": ["group:admin"],

    // the boss can tag any server as internal
    "tag:internal": ["group:boss"],

    // dev can add servers for dev purposes as well as admins
    "tag:dev-databases": ["group:admin", "group:dev"],
    "tag:dev-app-servers": ["group:admin", "group:dev"]

    // interns cannot add servers
  },
  // hosts should be defined using its IP addresses and a subnet mask.
  // to define a single host, use a /32 mask. You cannot use DNS entries here,
  // as they're prone to be hijacked by replacing their IP addresses.
  // see https://github.com/tailscale/tailscale/issues/3800 for more information.
  "Hosts": {
    "postgresql.internal": "10.20.0.2/32",
    "webservers.internal": "10.20.10.1/29"
  },
  "acls": [
    // boss have access to all servers
    {
      "action": "accept",
      "src": ["group:boss"],
      "dst": [
        "tag:prod-databases:*",
        "tag:prod-app-servers:*",
        "tag:internal:*",
        "tag:dev-databases:*",
        "tag:dev-app-servers:*"
      ]
    },

    // admin have only access to administrative ports of the servers, in tcp/22
    {
      "action": "accept",
      "src": ["group:admin"],
      "proto": "tcp",
      "dst": [
        "tag:prod-databases:22",
        "tag:prod-app-servers:22",
        "tag:internal:22",
        "tag:dev-databases:22",
        "tag:dev-app-servers:22"
      ]
    },

    // we also allow admin to ping the servers
    {
      "action": "accept",
      "src": ["group:admin"],
      "proto": "icmp",
      "dst": [
        "tag:prod-databases:*",
        "tag:prod-app-servers:*",
        "tag:internal:*",
        "tag:dev-databases:*",
        "tag:dev-app-servers:*"
      ]
    },

    // developers have access to databases servers and application servers on all ports
    // they can only view the applications servers in prod and have no access to databases servers in production
    {
      "action": "accept",
      "src": ["group:dev"],
      "dst": [
        "tag:dev-databases:*",
        "tag:dev-app-servers:*",
        "tag:prod-app-servers:80,443"
      ]
    },
    // developers have access to the internal network through the router.
    // the internal network is composed of HTTPS endpoints and Postgresql
    // database servers. There's an additional rule to allow traffic to be
    // forwarded to the internal subnet, 10.20.0.0/16. See this issue
    // https://github.com/juanfont/headscale/issues/502
    {
      "action": "accept",
      "src": ["group:dev"],
      "dst": ["10.20.0.0/16:443,5432", "router.internal:0"]
    },

    // servers should be able to talk to database in tcp/5432. Database should not be able to initiate connections to
    // applications servers
    {
      "action": "accept",
      "src": ["tag:dev-app-servers"],
      "proto": "tcp",
      "dst": ["tag:dev-databases:5432"]
    },
    {
      "action": "accept",
      "src": ["tag:prod-app-servers"],
      "dst": ["tag:prod-databases:5432"]
    },

    // interns have access to dev-app-servers only in reading mode
    {
      "action": "accept",
      "src": ["group:intern"],
      "dst": ["tag:dev-app-servers:80,443"]
    },

    // We still have to allow internal namespaces communications since nothing guarantees that each user have
    // their own namespaces.
    { "action": "accept", "src": ["boss"], "dst": ["boss:*"] },
    { "action": "accept", "src": ["dev1"], "dst": ["dev1:*"] },
    { "action": "accept", "src": ["dev2"], "dst": ["dev2:*"] },
    { "action": "accept", "src": ["admin1"], "dst": ["admin1:*"] },
    { "action": "accept", "src": ["intern1"], "dst": ["intern1:*"] }
  ]
}