tailcfg: reintroduce UserProfile.Groups

This change reintroduces UserProfile.Groups, a slice that contains
the ACL-defined and synced groups that a user is a member of.

The slice will only be non-nil for clients with the node attribute
see-groups, and will only contain groups that the client is allowed
to see as per the app payload of the see-groups node attribute.

For example:
```
"nodeAttrs": [
  {
    "target": ["tag:dev"],
    "app": {
      "tailscale.com/see-groups": [{"groups": ["group:dev"]}]
    }
  },

  [...]

]
```

UserProfile.Groups will also be gated by a feature flag for the time
being.

Updates tailscale/corp#31529

Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
This commit is contained in:
Gesa Stupperich
2026-02-17 13:15:02 +00:00
committed by Gesa Stupperich
parent ac74dfa5cd
commit 6a19995f13
11 changed files with 35 additions and 15 deletions
+9 -1
View File
@@ -282,6 +282,13 @@ type UserProfile struct {
LoginName string // "alice@smith.com"; for display purposes only (provider is not listed)
DisplayName string // "Alice Smith"
ProfilePicURL string `json:",omitzero"`
// Groups is a subset of SCIM groups (e.g. "engineering@example.com")
// or group names in the tailnet policy document (e.g. "group:eng")
// that contain this user and that the coordination server was
// configured to report to this node.
// The list is always sorted when loaded from storage.
Groups []string `json:",omitempty"`
}
func (p *UserProfile) Equal(p2 *UserProfile) bool {
@@ -294,7 +301,8 @@ func (p *UserProfile) Equal(p2 *UserProfile) bool {
return p.ID == p2.ID &&
p.LoginName == p2.LoginName &&
p.DisplayName == p2.DisplayName &&
p.ProfilePicURL == p2.ProfilePicURL
p.ProfilePicURL == p2.ProfilePicURL &&
slices.Equal(p.Groups, p2.Groups)
}
// RawMessage is a raw encoded JSON value. It implements Marshaler and