You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR changes tags to be something that exists on nodes in addition to users, to being its own thing. It is part of moving our tags support towards the correct tailscale compatible implementation.
There are probably rough edges in this PR, but the intention is to get it in, and then start fixing bugs from 0.28.0 milestone (long standing tags issue) to discover what works and what doesnt.
Updates juanfont#2417Closesjuanfont#2619
Headscale implements a **tags-as-identity** model where tags and user ownership are mutually exclusive ways to identify nodes. This is a fundamental architectural principle that affects node registration, ownership, ACL evaluation, and API behavior.
710
+
711
+
### Core Principle: Tags XOR User Ownership
712
+
713
+
Every node in Headscale is **either** tagged **or** user-owned, never both:
714
+
715
+
-**Tagged Nodes**: Ownership is defined by tags (e.g., `tag:server`, `tag:database`)
716
+
- Tags are set during registration via tagged PreAuthKey
717
+
- Tags are immutable after registration (cannot be changed via API)
718
+
- May have `UserID` set for "created by" tracking, but ownership is via tags
719
+
- Identified by: `node.IsTagged()` returns `true`
720
+
721
+
-**User-Owned Nodes**: Ownership is defined by user assignment
722
+
- Registered via OIDC, web auth, or untagged PreAuthKey
2.**Don't assume tagged nodes never have UserID set**
870
+
- Tagged nodes MAY have UserID for "created by" tracking
871
+
- Always use `IsTagged()` to determine ownership type
872
+
873
+
3.**Don't allow setting tags on user-owned nodes**
874
+
- This violates the tags XOR user principle
875
+
- Use API validation to prevent this
876
+
877
+
4.**Don't forget TaggedDevices in mapper**
878
+
- All tagged nodes MUST use `TaggedDevices.ID` in MapResponse
879
+
- User ID is only for actual user-owned nodes
880
+
881
+
### Migration Considerations
882
+
883
+
When nodes transition between ownership models:
884
+
885
+
-**No automatic migration**: Tags-as-identity is set at registration and immutable
886
+
-**Re-registration required**: To change from user-owned to tagged (or vice versa), node must be deleted and re-registered
887
+
-**UserID persistence**: UserID on tagged nodes is informational and not cleared
888
+
889
+
### Architecture Benefits
890
+
891
+
The tags-as-identity model provides:
892
+
893
+
1.**Clear ownership semantics**: No ambiguity about who/what owns a node
894
+
2.**ACL simplicity**: Tag-based access control without user conflicts
895
+
3.**API safety**: Validation prevents invalid ownership states
896
+
4.**Protocol compatibility**: TaggedDevices special user aligns with Tailscale's model
897
+
898
+
## Logging Patterns
899
+
900
+
### Incremental Log Event Building
901
+
902
+
When building log statements with multiple fields, especially with conditional fields, use the **incremental log event pattern** instead of long single-line chains. This improves readability and allows conditional field addition.
903
+
904
+
**Pattern:**
905
+
906
+
```go
907
+
// GOOD: Incremental building with conditional fields
1.**Assign chained calls back to the variable**: `logEvent = logEvent.Str(...)` - zerolog methods return a new event, so you must capture the return value
927
+
2.**Use for conditional fields**: When fields depend on runtime conditions, build incrementally
928
+
3.**Use for long log lines**: When a log line exceeds ~100 characters, split it for readability
929
+
4.**Call `.Msg()` at the end**: The final `.Msg()` or `.Msgf()` sends the log event
930
+
931
+
**Anti-pattern to avoid:**
932
+
933
+
```go
934
+
// BAD: Long single-line chains are hard to read and can't have conditional fields
Prefer the incremental log event pattern over creating helper functions that return multiple logging closures. Helper functions like `logPollFunc` create unnecessary indirection and allocate closures.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,10 @@ at creation time. When listing keys, only the prefix is shown (e.g.,
21
21
`hskey-auth-{prefix}-{secret}`. Legacy plaintext keys continue to work for
22
22
backwards compatibility.
23
23
24
+
### Tags
25
+
26
+
Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based authentication. See the [Tailscale tags documentation](https://tailscale.com/kb/1068/tags) for details on how tags work.
27
+
24
28
### Database migration support removed for pre-0.25.0 databases
25
29
26
30
Headscale no longer supports direct upgrades from databases created before
@@ -30,6 +34,8 @@ release.
30
34
31
35
### BREAKING
32
36
37
+
-**Tags**: The gRPC `SetTags` endpoint now allows converting user-owned nodes to tagged nodes by setting tags. Once a node is tagged, it cannot be converted back to a user-owned node.
38
+
33
39
- Database migration support removed for pre-0.25.0 databases [#2883](https://github.com/juanfont/headscale/pull/2883)
34
40
- If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
35
41
- See the [upgrade path documentation](https://headscale.net/stable/about/faq/#what-is-the-recommended-update-path-can-i-skip-multiple-versions-while-updating) for detailed guidance
0 commit comments