diff --git a/docs/proposals/001-acls.md b/docs/proposals/001-acls.md index 92e000a7..23435a2a 100644 --- a/docs/proposals/001-acls.md +++ b/docs/proposals/001-acls.md @@ -1,47 +1,72 @@ - # ACLs -A key component of tailscale is the notion of Tailnet. This notion is hidden but the implications that it have on how to use tailscale are not. +A key component of tailscale is the notion of Tailnet. This notion is hidden +but the implications that it have on how to use tailscale are not. -For tailscale an [tailnet](https://tailscale.com/kb/1136/tailnet/) is the following: +For tailscale an [tailnet](https://tailscale.com/kb/1136/tailnet/) is the +following: -> For personal users, you are a tailnet of many devices and one person. Each device gets a private Tailscale IP address in the CGNAT range and every device can talk directly to every other device, wherever they are on the internet. +> For personal users, you are a tailnet of many devices and one person. Each +> device gets a private Tailscale IP address in the CGNAT range and every +> device can talk directly to every other device, wherever they are on the +> internet. > -> For businesses and organizations, a tailnet is many devices and many users. It can be based on your Microsoft Active Directory, your Google Workspace, a GitHub organization, Okta tenancy, or other identity provider namespace. All of the devices and users in your tailnet can be seen by the tailnet administrators in the Tailscale admin console. There you can apply tailnet-wide configuration, such as ACLs that affect visibility of devices inside your tailnet, DNS settings, and more. +> For businesses and organizations, a tailnet is many devices and many users. +> It can be based on your Microsoft Active Directory, your Google Workspace, a +> GitHub organization, Okta tenancy, or other identity provider namespace. All +> of the devices and users in your tailnet can be seen by the tailnet +> administrators in the Tailscale admin console. There you can apply +> tailnet-wide configuration, such as ACLs that affect visibility of devices +> inside your tailnet, DNS settings, and more. ## Current implementation and issues -Currently in headscale, the namespaces are used both as tailnet and users. The issue is that if we want to use the ACL's we can't use both at the same time. +Currently in headscale, the namespaces are used both as tailnet and users. The +issue is that if we want to use the ACL's we can't use both at the same time. -Tailnet's cannot communicate with each others. So we can't have an ACL that authorize tailnet (namespace) A to talk to tailnet (namespace) B. +Tailnet's cannot communicate with each others. So we can't have an ACL that +authorize tailnet (namespace) A to talk to tailnet (namespace) B. -We also can't write ACLs based on the users (namespaces in headscale) since all devices belong to the same user. +We also can't write ACLs based on the users (namespaces in headscale) since all +devices belong to the same user. -With the current implementation the only ACL that we can user is to associate each headscale IP to a host manually then write the ACLs according to this manual mapping. +With the current implementation the only ACL that we can user is to associate +each headscale IP to a host manually then write the ACLs according to this +manual mapping. ```json { - "hosts":{ - "host1": "100.64.0.1", - "server": "100.64.0.2" - }, - "acls": [ - {"action": "accept", "users":["host1"], "ports":["host2:80,443"]} - ] + "hosts": { + "host1": "100.64.0.1", + "server": "100.64.0.2" + }, + "acls": [ + { "action": "accept", "users": ["host1"], "ports": ["host2:80,443"] } + ] } ``` -While this works, it requires a lot of manual editing on the configuration and to keep track of all devices IP address. +While this works, it requires a lot of manual editing on the configuration and +to keep track of all devices IP address. ## Proposition for a next implementation -In order to ease the use of ACL's we need to split the tailnet and users notion. +In order to ease the use of ACL's we need to split the tailnet and users +notion. -A solution could be to consider a headscale server (in it's entirety) as a tailnet. +A solution could be to consider a headscale server (in it's entirety) as a +tailnet. -For personal users the default behavior could either allow all communications between all namespaces (like tailscale) or dissallow all communications between namespaces (current behavior). +For personal users the default behavior could either allow all communications +between all namespaces (like tailscale) or dissallow all communications between +namespaces (current behavior). -For businesses and organisations, viewing a headscale instance a single tailnet would allow users (namespace) to talk to each other with the ACLs. As described in tailscale's documentation [[1]], a server should be tagged and personnal devices should be tied to a user. Translated in headscale's terms each user can have multiple devices and all those devices should be in the same namespace. The servers should be tagged and used as such. +For businesses and organisations, viewing a headscale instance a single tailnet +would allow users (namespace) to talk to each other with the ACLs. As described +in tailscale's documentation [[1]], a server should be tagged and personnal +devices should be tied to a user. Translated in headscale's terms each user can +have multiple devices and all those devices should be in the same namespace. +The servers should be tagged and used as such. This implementation would render useless the sharing feature that is currently implemented since an ACL could do the same. Simplifying to only one user @@ -119,43 +144,63 @@ need to add the following ACLs ```json { - "hosts":{ - "boss-computer": "100.64.0.1", - "admin1-computer": "100.64.0.2", - "dev1-computer": "100.64.0.3", - "dev1-phone": "100.64.0.4", - "dev2-computer": "100.64.0.5", - "intern1-computer": "100.64.0.6", - "prod-app-server1": "100.64.0.8", + "hosts": { + "boss-computer": "100.64.0.1", + "admin1-computer": "100.64.0.2", + "dev1-computer": "100.64.0.3", + "dev1-phone": "100.64.0.4", + "dev2-computer": "100.64.0.5", + "intern1-computer": "100.64.0.6", + "prod-app-server1": "100.64.0.8" + }, + "groups": { + "group:dev": ["dev1-computer", "dev1-phone", "dev2-computer"], + "group:admin": ["admin1-computer"], + "group:boss": ["boss-computer"], + "group:intern": ["intern1-computer"] + }, + "acls": [ + // boss have access to all servers but no users hosts + { + "action": "accept", + "users": ["group:boss"], + "ports": ["prod:*", "dev:*", "internal:*"] }, - "groups":{ - "group:dev": ["dev1-computer", "dev1-phone", "dev2-computer"], - "group:admin": ["admin1-computer"], - "group:boss": ["boss-computer"], - "group:intern": ["intern1-computer"], + + // admin have access to adminstration port (lets only consider port 22 here) + { + "action": "accept", + "users": ["group:admin"], + "ports": ["prod:22", "dev:22", "internal:22"] }, - "acls":[ - // boss have access to all servers but no users hosts - {"action":"accept", "users":["group:boss"], "ports":["prod:*","dev:*","internal:*"]}, - // admin have access to adminstration port (lets only consider port 22 here) - {"action":"accept", "users":["group:admin"], "ports":["prod:22","dev:22","internal:22"]}, + // dev can do anything on dev servers and check access on prod servers + { + "action": "accept", + "users": ["group:dev"], + "ports": ["dev:*", "prod-app-server1:80,443"] + }, - // dev can do anything on dev servers and check access on prod servers - {"action":"accept", "users":["group:dev"], "ports":["dev:*","prod-app-server1:80,443"]}, + // interns only have access to port 80 and 443 on dev servers (lame internship) + { "action": "accept", "users": ["group:intern"], "ports": ["dev:80,443"] }, - // interns only have access to port 80 and 443 on dev servers (lame internship) - {"action":"accept", "users":["group:intern"], "ports":["dev:80,443"]}, + // users can access their own devices + { + "action": "accept", + "users": ["dev1-computer"], + "ports": ["dev1-phone:*"] + }, + { + "action": "accept", + "users": ["dev1-phone"], + "ports": ["dev1-computer:*"] + }, - // users can access their own devices - {"action":"accept", "users":["dev1-computer"], "ports":["dev1-phone:*"]}, - {"action":"accept", "users":["dev1-phone"], "ports":["dev1-computer:*"]}, - - // internal namespace communications should still be allowed within the namespace - {"action":"accept", "users":["dev"], "ports":["dev:*"]}, - {"action":"accept", "users":["prod"], "ports":["prod:*"]}, - {"action":"accept", "users":["internal"], "ports":["internal:*"]}, - ] + // internal namespace communications should still be allowed within the namespace + { "action": "accept", "users": ["dev"], "ports": ["dev:*"] }, + { "action": "accept", "users": ["prod"], "ports": ["prod:*"] }, + { "action": "accept", "users": ["internal"], "ports": ["internal:*"] } + ] } ``` @@ -192,7 +237,8 @@ multiple devices we have to allow communication to each of them one by one. If business conduct a change in the organisations we may have to rewrite all acls and reorganise all namespaces. -If we add servers in production we should also update the ACLs to allow dev access to certain category of them (only app servers for example). +If we add servers in production we should also update the ACLs to allow dev +access to certain category of them (only app servers for example). ### example based on the proposition in this document @@ -218,74 +264,93 @@ Here are the ACL's to implement the same permissions as above: ```json { - // groups are simpler and only list the namespaces name - "groups": { - "group:boss": ["boss"], - "group:dev": ["dev1","dev2"], - "group:admin": ["admin1"], - "group:intern": ["intern1"], + // groups are simpler and only list the namespaces name + "groups": { + "group:boss": ["boss"], + "group:dev": ["dev1", "dev2"], + "group:admin": ["admin1"], + "group:intern": ["intern1"] + }, + "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 + }, + "acls": [ + // boss have access to all servers + { + "action": "accept", + "users": ["group:boss"], + "ports": [ + "tag:prod-databases:*", + "tag:prod-app-servers:*", + "tag:internal:*", + "tag:dev-databases:*", + "tag:dev-app-servers:*" + ] }, - "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 + // admin have only access to administrative ports of the servers + { + "action": "accept", + "users": ["group:admin"], + "ports": [ + "tag:prod-databases:22", + "tag:prod-app-servers:22", + "tag:internal:22", + "tag:dev-databases:22", + "tag:dev-app-servers:22" + ] }, - "acls": [ - // boss have access to all servers - {"action":"accept", - "users":["group:boss"], - "ports":[ - "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 - {"action":"accept", - "users":["group:admin"], - "ports":[ - "tag:prod-databases:22", - "tag:prod-app-servers:22", - "tag:internal:22", - "tag:dev-databases:22", - "tag:dev-app-servers:22", - ] - }, + { + "action": "accept", + "users": ["group:dev"], + "ports": [ + "tag:dev-databases:*", + "tag:dev-app-servers:*", + "tag:prod-app-servers:80,443" + ] + }, - {"action":"accept", "users":["group:dev"], "ports":[ - "tag:dev-databases:*", - "tag:dev-app-servers:*", - "tag:prod-app-servers:80,443", - ] - }, + // servers should be able to talk to database. Database should not be able to initiate connections to server + { + "action": "accept", + "users": ["tag:dev-app-servers"], + "ports": ["tag:dev-databases:5432"] + }, + { + "action": "accept", + "users": ["tag:prod-app-servers"], + "ports": ["tag:prod-databases:5432"] + }, - // servers should be able to talk to database. Database should not be able to initiate connections to server - {"action":"accept", "users":["tag:dev-app-servers"], "ports":["tag:dev-databases:5432"]}, - {"action":"accept", "users":["tag:prod-app-servers"], "ports":["tag:prod-databases:5432"]}, + // interns have access to dev-app-servers only in reading mode + { + "action": "accept", + "users": ["group:intern"], + "ports": ["tag:dev-app-servers:80,443"] + }, - // interns have access to dev-app-servers only in reading mode - {"action":"accept", "users":["group:intern"], "ports":["tag:dev-app-servers:80,443"]}, - - // we still have to allow internal namespaces communications since nothing guarantees that each user have their own namespaces. This could be talked over. - {"action":"accept", "users":["boss"], "ports":["boss:*"]}, - {"action":"accept", "users":["dev1"], "ports":["dev1:*"]}, - {"action":"accept", "users":["dev2"], "ports":["dev2:*"]}, - {"action":"accept", "users":["admin1"], "ports":["admin1:*"]}, - {"action":"accept", "users":["intern1"], "ports":["intern1:*"]}, - ] + // we still have to allow internal namespaces communications since nothing guarantees that each user have their own namespaces. This could be talked over. + { "action": "accept", "users": ["boss"], "ports": ["boss:*"] }, + { "action": "accept", "users": ["dev1"], "ports": ["dev1:*"] }, + { "action": "accept", "users": ["dev2"], "ports": ["dev2:*"] }, + { "action": "accept", "users": ["admin1"], "ports": ["admin1:*"] }, + { "action": "accept", "users": ["intern1"], "ports": ["intern1:*"] } + ] } ``` -With this implementation, the sharing step is not necessary. Maintenance cost of the ACL file is lower and less tedious (no need to map hostname and IP's into it). +With this implementation, the sharing step is not necessary. Maintenance cost +of the ACL file is lower and less tedious (no need to map hostname and IP's +into it).