Group visibility on Tailscale clients

Last validated:

You can configure devices on your Tailscale network (known as a tailnet) to receive group membership information from the Tailscale control plane. The group information includes both groups defined in the tailnet policy file and groups synchronized from identity providers by using System for Cross-domain Identity Management (SCIM). Once a Tailscale client has been configured to receive group information, that information is available by using the Tailscale CLI, the tsnet library, and LocalAPI.

This feature is currently in private alpha. Therefore, this topic is currently hidden.

Requirements

Before you can receive group information in clients, ensure you meet these requirements.

  • The node-attr-visible-groups feature flag is enabled for your tailnet. To enable this flag, contact Tailscale Support.
  • You can edit your tailnet policy file. You need to be an Owner, Admin, or Network admin to edit a tailnet policy file.
  • The devices that you want to receive group membership information are running Tailscale v1.97.8 or later.
  • (Optional) You can assign a tag to devices that need to receive group membership information.

Use cases

  • Group-specific functionality

    Applications can use group membership information to enable or disable features without requiring additional user authorization. For example, you can turn on additional features if a user is in a group for beta testers or in a group for a paid plan.

  • Visibility into group usage

    Applications can log group membership as part of their logs and audit trails. This might be useful for adoption analysis, cost tracking, and incident investigation.

How it works

Some applications need information based on Tailscale identity for the connecting user so the application can communicate that information to a user interface (UI) or its own clients. To support this, the Tailscale control plane sends each device the following information if the user has access to the device:

  • user name
  • user login
  • user profile picture URL

By default, the Tailscale control plane does not send group membership information to devices. To enable this, use the node-attr-visible-groups node attribute as an application capability, apply it to a set of devices, and specify which groups those devices have access to.

An application running on a device that contains the node-attr-visible-groups attribute can then retrieve the group membership information from the device by using the tsnet library or LocalAPI.

Update your tailnet policy file

Configure your tailnet policy file to update the Tailscale control plane with which devices can receive group information. You need to make the following updates to your tailnet policy file:

  • (Optional) Create a tag to identify which devices will have the capability to receive group membership information.
  • Create a node attribute to identity which groups will be visible to the devices.

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

(Optional) Create a tag

Create a tag so that you can apply the tag to the devices for which you want to receive group information. For purposes of this example, name the tag prod.

  1. Go to the Access controls page of the admin console.

  2. If you don't already have a tagOwners field in your tailnet policy file, create it.

    {
    
    "tagOwners": {
    },
    }
  3. Within the tagOwners field, create a tag named prod and assign autogroup:admin as the tag owner.

    {
      "tagOwners": {
    
    "tag:prod": ["autogroup:admin"],
    }, }

    This lets any admin of your tailnet assign the prod tag to devices.

Create the node attribute

Add a node attribute (nodeAttrs) that grants application layer access of the tailscale.com/visible-groups capability for devices that your target.

  1. Create a nodeAttr field.

  2. Within the nodeAttr field, create a target field, and assign its array value. If you created a tag, assign that as the value. Otherwise, assign a set of users, hosts, or IP addresses.

  3. Also within the nodeAttr field, create an app field.

  4. Within the app field, create a tailscale.com/visible-groups field, which is the app capability you are going to assign to your devices.

  5. Within the tailscale.com/visible-groups field, create a groups array and assign it the set of groups that you want to make visible to your devices. The set of groups can be a single wildcard entry ["*"] to make all groups visible, or can be a list of groups.

    The tailnet policy file validation does not check whether any of the groups exist in your tailnet.

Your entry for the node attribute should be similar to:

"nodeAttrs": [
  {
    "target": ["tag:prod"],
	// If not using a tag, use the following format for the target instead:
	// "target": ["my-prod-app", "100.123.123.123", ...],
      "app": {
      "tailscale.com/visible-groups": [
        {
	      // "groups": ["*"] // to match all groups
          "groups": [
            "group:engineering",        # group defined in policy file
            "group:sales@example.com",  # synced group
            "group:support@example.com" # synced group
          ]
        }
      ]
	}
  }
]

Verify group visibility using the Tailscale CLI

On a device that has the node-attr-visible-groups node attribute, you can verify group visibility by running the tailscale status command:

tailscale status -json

The output contains a Groups field that lists a user's group membership:

{
  [...]

  "User": {
    "1234567890": {
      "ID": 1234567890,
      "LoginName": "amelie@example.com",
      "DisplayName": "Amelie Pangolin",
      "ProfilePicURL": "https://example.com/a/1122334455",
"Groups": [
"group:engineering",
"group:sales@example.com"
]
}, [...] }, [...] }

Verify group visibility using tsnet

tsnet is a library that lets you embed Tailscale inside a Go program. You can modify the Hello tsnet example to also report group membership.

[...]

log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  who, err := lc.WhoIs(r.Context(), r.RemoteAddr)
    if err != nil {
      http.Error(w, err.Error(), 500)
			return
    }
    fmt.Fprintf(w, "<html><body><h1>Hello, world!</h1>\n")
    fmt.Fprintf(w, "<p>You are <b>%s</b> from <b>%s</b> (%s)</p>",
      html.EscapeString(who.UserProfile.LoginName),
	  html.EscapeString(who.UserProfile.LoginName),
      html.EscapeString(firstLabel(who.Node.ComputedName)),
        r.RemoteAddr)
fmt.Fprintf(w, "<p><strong>Your Groups:</strong> %s</p>", strings.Join(who.UserProfile.Groups, ", "))
[...]