Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4d264c0
policy: simplify tag and autogroup resolution for tags-as-identity
kradalby Dec 3, 2025
3ffb2c7
policy: update tests for tags-as-identity model
kradalby Dec 3, 2025
30a5d66
state: process advertise-tags during registration
kradalby Dec 3, 2025
d95f3c4
integration: add CreateAuthKeyWithTags helper
kradalby Dec 3, 2025
eeeb36b
integration: add tests for advertise-tags behavior
kradalby Dec 3, 2025
9cacfa4
changelog: document tags-as-identity changes
kradalby Dec 3, 2025
8c2e5f5
integration: initial full tag matrix test
kradalby Dec 4, 2025
f29988a
.github/workflows: regen
kradalby Dec 4, 2025
6651911
auth: fixup tests
kradalby Dec 4, 2025
7326d61
integration: cleanup duplicate tests
kradalby Dec 4, 2025
d5f0055
integration: fix missing and left over imports
kradalby Dec 5, 2025
f159d66
state: align error message with Tailscale SaaS format
kradalby Dec 5, 2025
a792a5b
policy: add user-based fallback for new node tag authorization
kradalby Dec 5, 2025
5122cfc
policy: fix stale comment in autogroup:member
kradalby Dec 5, 2025
093ba4e
policy: extend test coverage for NodeCanHaveTag and userMatchesOwner
kradalby Dec 5, 2025
f4d81e2
hscontrol: update tests for new error message format
kradalby Dec 5, 2025
024a8d1
integration: rename test and improve error handling
kradalby Dec 5, 2025
a957a23
integration: remove unused tag option in TestAutoApproveMultiNetwork
kradalby Dec 5, 2025
6254279
ci: update integration test name
kradalby Dec 5, 2025
5323366
integration: more time for route tests
kradalby Dec 5, 2025
a12f295
policy: move flattenTags and flattenTagOwners to policy.go
kradalby Dec 6, 2025
b991080
integration: replace time.Sleep with EventuallyWithT in tags tests
kradalby Dec 7, 2025
fe0934c
golangci-lint: use forbidigo to block time.Sleep
kradalby Dec 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions .github/workflows/test-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ jobs:
- TestPreAuthKeyCommandReusableEphemeral
- TestPreAuthKeyCorrectUserLoggedInCommand
- TestApiKeyCommand
- TestNodeTagCommand
- TestTaggedNodeRegistration
- TestTagPersistenceAcrossRestart
- TestNodeAdvertiseTagCommand
- TestNodeCommand
- TestNodeExpireCommand
- TestNodeRenameCommand
Expand Down Expand Up @@ -97,6 +93,33 @@ jobs:
- TestSSHIsBlockedInACL
- TestSSHUserOnlyIsolation
- TestSSHAutogroupSelf
- TestTagsAuthKeyWithTagRequestDifferentTag
- TestTagsAuthKeyWithTagNoAdvertiseFlag
- TestTagsAuthKeyWithTagCannotAddViaCLI
- TestTagsAuthKeyWithTagCannotChangeViaCLI
- TestTagsAuthKeyWithTagAdminOverrideReauthPreserves
- TestTagsAuthKeyWithTagCLICannotModifyAdminTags
- TestTagsAuthKeyWithoutTagCannotRequestTags
- TestTagsAuthKeyWithoutTagRegisterNoTags
- TestTagsAuthKeyWithoutTagCannotAddViaCLI
- TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset
- TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise
- TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag
- TestTagsUserLoginOwnedTagAtRegistration
- TestTagsUserLoginNonExistentTagAtRegistration
- TestTagsUserLoginUnownedTagAtRegistration
- TestTagsUserLoginAddTagViaCLIReauth
- TestTagsUserLoginRemoveTagViaCLIReauth
- TestTagsUserLoginCLINoOpAfterAdminAssignment
- TestTagsUserLoginCLICannotRemoveAdminTags
- TestTagsAuthKeyWithTagRequestNonExistentTag
- TestTagsAuthKeyWithTagRequestUnownedTag
- TestTagsAuthKeyWithoutTagRequestNonExistentTag
- TestTagsAuthKeyWithoutTagRequestUnownedTag
- TestTagsAdminAPICannotSetNonExistentTag
- TestTagsAdminAPICanSetUnownedTag
- TestTagsAdminAPICannotRemoveAllTags
- TestTagsAdminAPICannotSetInvalidFormat
uses: ./.github/workflows/integration-test-template.yml
with:
test: ${{ matrix.test }}
Expand Down
10 changes: 10 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ linters:
- depguard
- dupl
- exhaustruct
- funcorder
- funlen
- gochecknoglobals
- gochecknoinits
Expand All @@ -28,6 +29,15 @@ linters:
- wrapcheck
- wsl
settings:
forbidigo:
forbid:
# Forbid time.Sleep everywhere with context-appropriate alternatives
- pattern: 'time\.Sleep'
msg: >-
time.Sleep is forbidden.
In tests: use assert.EventuallyWithT for polling/waiting patterns.
In production code: use a backoff strategy (e.g., cenkalti/backoff) or proper synchronization primitives.
Comment on lines +36 to +39
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The forbidigo rule to ban time.Sleep everywhere is overly restrictive. While the intent to use assert.EventuallyWithT in tests is good, there are legitimate uses of time.Sleep in production code (e.g., rate limiting, deliberate delays, exponential backoff implementations). Consider:

  1. Exempting specific patterns where time.Sleep is intentional
  2. Only applying this rule to test files (_test.go)
  3. Or using a more nuanced message that acknowledges legitimate uses

The current rule may force developers to work around it in ways that make code less clear.

Suggested change
msg: >-
time.Sleep is forbidden.
In tests: use assert.EventuallyWithT for polling/waiting patterns.
In production code: use a backoff strategy (e.g., cenkalti/backoff) or proper synchronization primitives.
files: "*_test.go"
msg: >-
time.Sleep is forbidden in tests.
Use assert.EventuallyWithT for polling/waiting patterns.

Copilot uses AI. Check for mistakes.
analyze-types: true
gocritic:
disabled-checks:
- appendAssign
Expand Down
41 changes: 40 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,46 @@ go run ./cmd/hi run "TestPattern*"

- Only ONE test can run at a time (Docker port conflicts)
- Tests generate ~100MB of logs per run in `control_logs/`
- Clean environment before each test: `rm -rf control_logs/202507* && docker system prune -f`
- Clean environment before each test: `sudo rm -rf control_logs/202* && docker system prune -f`

### Full Matrix Testing

Some integration tests support **full matrix mode** that tests all combinations of test dimensions. This is critical for comprehensive validation but can take up to 2 hours to complete.

**Example: TestAutoApproveMultiNetwork Full Matrix**

```bash
# Set GOPATH to avoid environment issues
export GOPATH=$HOME/go

# Enable full matrix mode and run with generous timeout
HEADSCALE_INTEGRATION_FULL_MATRIX=1 go run ./cmd/hi run "TestAutoApproveMultiNetwork" --timeout=7200s
```

**Full Matrix Dimensions:**
- **Base scenarios (6):** All combinations of:
- Auth methods: `authkey`, `webauth`
- Approver types: `tag`, `user`, `group`
- **Policy modes (2):** `database`, `file`
- **Advertisement timing (2):** `advertiseduringup-true`, `advertiseduringup-false`
- **Total combinations:** 6 × 2 × 2 = **24 tests**

**Default (minimal) mode:** Runs only 3 representative tests covering all dimensions:
- `authkey-tag-advertiseduringup-false-pol-database`
- `webauth-user-advertiseduringup-true-pol-file`
- `authkey-group-advertiseduringup-false-pol-file`

**Full Matrix Requirements:**
- **Time:** Up to 2 hours for complete execution
- **Disk space:** ~2-3GB for all test artifacts
- **Environment:** Clean Docker state before starting
- **Timeout:** Use `--timeout=7200s` (2 hours) minimum

**When to use full matrix:**
- Before major releases or merges to main
- After changes to route management, ACL evaluation, or policy engine
- When debugging flaky tests or cross-scenario issues
- For comprehensive validation of tags-as-identity changes

### Test Artifacts Location

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ backwards compatibility.

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.

User-owned nodes can now request tags during registration using `--advertise-tags`. Tags are validated against the `tagOwners` policy and applied at registration time. Tags can be managed via the CLI or API after registration.

### Database migration support removed for pre-0.25.0 databases

Headscale no longer supports direct upgrades from databases created before
Expand All @@ -36,6 +38,12 @@ release.

- **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.

- **Tags**: Tags are now resolved from the node's stored Tags field only [#2931](https://github.com/juanfont/headscale/pull/2931)
- `--advertise-tags` is processed during registration, not on every policy evaluation
- PreAuthKey tagged devices ignore `--advertise-tags` from clients
- User-owned nodes can use `--advertise-tags` if authorized by `tagOwners` policy
- Tags can be managed via CLI (`headscale nodes tag`) or the SetTags API after registration

- Database migration support removed for pre-0.25.0 databases [#2883](https://github.com/juanfont/headscale/pull/2883)
- If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
- 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
Expand Down
6 changes: 0 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ github.com/creachadair/command v0.2.0 h1:qTA9cMMhZePAxFoNdnk6F6nn94s1qPndIg9hJbq
github.com/creachadair/command v0.2.0/go.mod h1:j+Ar+uYnFsHpkMeV9kGj6lJ45y9u2xqtg8FYy6cm+0o=
github.com/creachadair/flax v0.0.5 h1:zt+CRuXQASxwQ68e9GHAOnEgAU29nF0zYMHOCrL5wzE=
github.com/creachadair/flax v0.0.5/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8=
github.com/creachadair/mds v0.25.2 h1:xc0S0AfDq5GX9KUR5sLvi5XjA61/P6S5e0xFs1vA18Q=
github.com/creachadair/mds v0.25.2/go.mod h1:+s4CFteFRj4eq2KcGHW8Wei3u9NyzSPzNV32EvjyK/Q=
github.com/creachadair/mds v0.25.10 h1:9k9JB35D1xhOCFl0liBhagBBp8fWWkKZrA7UXsfoHtA=
github.com/creachadair/mds v0.25.10/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
Expand Down Expand Up @@ -278,8 +276,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE=
github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
Expand Down Expand Up @@ -463,8 +459,6 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/setec v0.0.0-20250305161714-445cadbbca3d h1:mnqtPWYyvNiPU9l9tzO2YbHXU/xV664XthZYA26lOiE=
github.com/tailscale/setec v0.0.0-20250305161714-445cadbbca3d/go.mod h1:9BzmlFc3OLqLzLTF/5AY+BMs+clxMqyhSGzgXIm8mNI=
github.com/tailscale/squibble v0.0.0-20250108170732-a4ca58afa694 h1:95eIP97c88cqAFU/8nURjgI9xxPbD+Ci6mY/a79BI/w=
github.com/tailscale/squibble v0.0.0-20250108170732-a4ca58afa694/go.mod h1:veguaG8tVg1H/JG5RfpoUW41I+O8ClPElo/fTYr8mMk=
github.com/tailscale/squibble v0.0.0-20251030164342-4d5df9caa993 h1:FyiiAvDAxpB0DrW2GW3KOVfi3YFOtsQUEeFWbf55JJU=
github.com/tailscale/squibble v0.0.0-20251030164342-4d5df9caa993/go.mod h1:xJkMmR3t+thnUQhA3Q4m2VSlS5pcOq+CIjmU/xfKKx4=
github.com/tailscale/tailsql v0.0.0-20250421235516-02f85f087b97 h1:JJkDnrAhHvOCttk8z9xeZzcDlzzkRA7+Duxj9cwOyxk=
Expand Down
135 changes: 130 additions & 5 deletions hscontrol/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,82 @@ func TestAuthenticationFlows(t *testing.T) {
},
},

// === ADVERTISE-TAGS (RequestTags) SCENARIOS ===
// Tests for client-provided tags via --advertise-tags flag

// TEST: PreAuthKey registration rejects client-provided RequestTags
// WHAT: Tests that PreAuthKey registrations cannot use client-provided tags
// INPUT: PreAuthKey registration with RequestTags in Hostinfo
// EXPECTED: Registration fails with "requested tags [...] are invalid or not permitted" error
// WHY: PreAuthKey nodes get their tags from the key itself, not from client requests
{
name: "preauth_key_rejects_request_tags",
setupFunc: func(t *testing.T, app *Headscale) (string, error) {
t.Helper()

user := app.state.CreateUserForTest("pak-requesttags-user")

pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, nil)
if err != nil {
return "", err
}

return pak.Key, nil
},
request: func(authKey string) tailcfg.RegisterRequest {
return tailcfg.RegisterRequest{
Auth: &tailcfg.RegisterResponseAuth{
AuthKey: authKey,
},
NodeKey: nodeKey1.Public(),
Hostinfo: &tailcfg.Hostinfo{
Hostname: "pak-requesttags-node",
RequestTags: []string{"tag:unauthorized"},
},
Expiry: time.Now().Add(24 * time.Hour),
}
},
machineKey: machineKey1.Public,
wantError: true,
},

// TEST: Tagged PreAuthKey ignores client-provided RequestTags
// WHAT: Tests that tagged PreAuthKey uses key tags, not client RequestTags
// INPUT: Tagged PreAuthKey registration with different RequestTags
// EXPECTED: Registration fails because RequestTags are rejected for PreAuthKey
// WHY: Tags-as-identity: PreAuthKey tags are authoritative, client cannot override
{
name: "tagged_preauth_key_rejects_client_request_tags",
setupFunc: func(t *testing.T, app *Headscale) (string, error) {
t.Helper()

user := app.state.CreateUserForTest("tagged-pak-clienttags-user")
keyTags := []string{"tag:authorized"}

pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, keyTags)
if err != nil {
return "", err
}

return pak.Key, nil
},
request: func(authKey string) tailcfg.RegisterRequest {
return tailcfg.RegisterRequest{
Auth: &tailcfg.RegisterResponseAuth{
AuthKey: authKey,
},
NodeKey: nodeKey1.Public(),
Hostinfo: &tailcfg.Hostinfo{
Hostname: "tagged-pak-clienttags-node",
RequestTags: []string{"tag:client-wants-this"}, // Should be rejected
},
Expiry: time.Now().Add(24 * time.Hour),
}
},
machineKey: machineKey1.Public,
wantError: true, // RequestTags rejected for PreAuthKey registrations
},

// === RE-AUTHENTICATION SCENARIOS ===
// TEST: Existing node re-authenticates with new pre-auth key
// WHAT: Tests that existing node can re-authenticate using new pre-auth key
Expand Down Expand Up @@ -1202,8 +1278,9 @@ func TestAuthenticationFlows(t *testing.T) {
OS: "unknown-os",
OSVersion: "999.999.999",
DeviceModel: "test-device-model",
RequestTags: []string{"invalid:tag", "another!tag"},
Services: []tailcfg.Service{{Proto: "tcp", Port: 65535}},
// Note: RequestTags are not included for PreAuthKey registrations
// since tags come from the key itself, not client requests.
Services: []tailcfg.Service{{Proto: "tcp", Port: 65535}},
},
Expiry: time.Now().Add(24 * time.Hour),
}
Expand Down Expand Up @@ -1315,9 +1392,13 @@ func TestAuthenticationFlows(t *testing.T) {
// === AUTH PROVIDER EDGE CASES ===
// TEST: Interactive workflow preserves custom hostinfo
// WHAT: Tests that custom hostinfo fields are preserved through interactive flow
// INPUT: Interactive registration with detailed hostinfo (OS, version, model, etc.)
// INPUT: Interactive registration with detailed hostinfo (OS, version, model)
// EXPECTED: Node registers with all hostinfo fields preserved
// WHY: Ensures interactive flow doesn't lose custom hostinfo data
// NOTE: RequestTags are NOT tested here because tag authorization via
// advertise-tags requires the user to have existing nodes (for IP-based
// ownership verification). New users registering their first node cannot
// claim tags via RequestTags - they must use a tagged PreAuthKey instead.
{
name: "interactive_workflow_with_custom_hostinfo",
setupFunc: func(t *testing.T, app *Headscale) (string, error) {
Expand All @@ -1331,7 +1412,6 @@ func TestAuthenticationFlows(t *testing.T) {
OS: "linux",
OSVersion: "20.04",
DeviceModel: "server",
RequestTags: []string{"tag:server"},
},
Expiry: time.Now().Add(24 * time.Hour),
}
Expand All @@ -1353,7 +1433,6 @@ func TestAuthenticationFlows(t *testing.T) {
assert.Equal(t, "linux", node.Hostinfo().OS())
assert.Equal(t, "20.04", node.Hostinfo().OSVersion())
assert.Equal(t, "server", node.Hostinfo().DeviceModel())
assert.Contains(t, node.Hostinfo().RequestTags().AsSlice(), "tag:server")
}
},
},
Expand Down Expand Up @@ -3423,3 +3502,49 @@ func TestGitHubIssue2830_ExistingNodeCanReregisterWithUsedPreAuthKey(t *testing.
nodesAfterAttack := app.state.ListNodesByUser(types.UserID(user.ID))
require.Equal(t, 1, nodesAfterAttack.Len(), "Should still have exactly one node (attack prevented)")
}

// TestWebAuthRejectsUnauthorizedRequestTags tests that web auth registrations
// validate RequestTags against policy and reject unauthorized tags.
func TestWebAuthRejectsUnauthorizedRequestTags(t *testing.T) {
t.Parallel()

app := createTestApp(t)

// Create a user that will authenticate via web auth
user := app.state.CreateUserForTest("webauth-tags-user")

machineKey := key.NewMachine()
nodeKey := key.NewNode()

// Simulate a registration cache entry (as would be created during web auth)
registrationID := types.MustRegistrationID()
regEntry := types.NewRegisterNode(types.Node{
MachineKey: machineKey.Public(),
NodeKey: nodeKey.Public(),
Hostname: "webauth-tags-node",
Hostinfo: &tailcfg.Hostinfo{
Hostname: "webauth-tags-node",
RequestTags: []string{"tag:unauthorized"}, // This tag is not in policy
},
})
app.state.SetRegistrationCacheEntry(registrationID, regEntry)

// Complete the web auth - should fail because tag is unauthorized
_, _, err := app.state.HandleNodeFromAuthPath(
registrationID,
types.UserID(user.ID),
nil, // no expiry
"webauth",
)

// Expect error due to unauthorized tags
require.Error(t, err, "HandleNodeFromAuthPath should reject unauthorized RequestTags")
require.Contains(t, err.Error(), "requested tags",
"Error should indicate requested tags are invalid or not permitted")
require.Contains(t, err.Error(), "tag:unauthorized",
"Error should mention the rejected tag")

// Verify no node was created
_, found := app.state.GetNodeByNodeKey(nodeKey.Public())
require.False(t, found, "Node should not be created when tags are unauthorized")
}
9 changes: 1 addition & 8 deletions hscontrol/grpcv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"time"

"github.com/rs/zerolog/log"
"github.com/samber/lo"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
Expand Down Expand Up @@ -556,13 +555,7 @@ func nodesToProto(state *state.State, nodes views.Slice[types.NodeView]) []*v1.N
resp.User = types.TaggedDevices.Proto()
}

var tags []string
for _, tag := range node.RequestTags() {
if state.NodeCanHaveTag(node, tag) {
tags = append(tags, tag)
}
}
resp.ValidTags = lo.Uniq(append(tags, node.Tags().AsSlice()...))
resp.ValidTags = node.Tags().AsSlice()

resp.SubnetRoutes = util.PrefixesToString(append(state.GetNodePrimaryRoutes(node.ID()), node.ExitRoutes()...))
response[index] = resp
Expand Down
4 changes: 2 additions & 2 deletions hscontrol/grpcv1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestSetTags_Conversion(t *testing.T) {
tags: []string{"tag:server"},
wantErr: true,
wantCode: codes.InvalidArgument,
wantErrMessage: "invalid or unauthorized tags",
wantErrMessage: "requested tags",
},
{
// Conversion is allowed, but tag authorization fails without tagOwners
Expand All @@ -114,7 +114,7 @@ func TestSetTags_Conversion(t *testing.T) {
tags: []string{"tag:server", "tag:database"},
wantErr: true,
wantCode: codes.InvalidArgument,
wantErrMessage: "invalid or unauthorized tags",
wantErrMessage: "requested tags",
},
{
name: "reject non-existent node",
Expand Down
Loading
Loading