mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-10 19:17:25 +02:00
state: allow untagging nodes via reauth with empty RequestTags
When a node re-authenticates via OIDC/web auth with empty RequestTags (from `tailscale up --advertise-tags= --force-reauth`), remove all tags and return ownership to the authenticating user. This allows nodes to transition from any tagged state (including nodes originally registered with a tagged pre-auth key) back to user-owned. Fixes #2979
This commit is contained in:
@@ -12,7 +12,8 @@ tags rather than users, making them suitable for servers and infrastructure. App
|
|||||||
ownership. See the [Tailscale tags documentation](https://tailscale.com/kb/1068/tags) for details on how tags work.
|
ownership. 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
|
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.
|
and applied at registration time. Tags can be managed via the CLI or API after registration. Tagged nodes can return to user-owned
|
||||||
|
by re-authenticating with `tailscale up --advertise-tags= --force-reauth`.
|
||||||
|
|
||||||
### Smarter map updates
|
### Smarter map updates
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ sequentially through each stable release, selecting the latest patch version ava
|
|||||||
- **API**: The Node message in the gRPC/REST API has been simplified - the `ForcedTags`, `InvalidTags`, and `ValidTags` fields have been removed and replaced with a single `Tags` field that contains the node's applied tags [#2993](https://github.com/juanfont/headscale/pull/2993)
|
- **API**: The Node message in the gRPC/REST API has been simplified - the `ForcedTags`, `InvalidTags`, and `ValidTags` fields have been removed and replaced with a single `Tags` field that contains the node's applied tags [#2993](https://github.com/juanfont/headscale/pull/2993)
|
||||||
- API clients should use the `Tags` field instead of `ValidTags`
|
- API clients should use the `Tags` field instead of `ValidTags`
|
||||||
- The `headscale nodes list` CLI command now always shows a Tags column and the `--tags` flag has been removed
|
- The `headscale nodes list` CLI command now always shows a Tags column and the `--tags` flag has been removed
|
||||||
- **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. [#2885](https://github.com/juanfont/headscale/pull/2885)
|
- **Tags**: The gRPC `SetTags` endpoint now allows converting user-owned nodes to tagged nodes by setting tags. [#2885](https://github.com/juanfont/headscale/pull/2885)
|
||||||
- **Tags**: Tags are now resolved from the node's stored Tags field only [#2931](https://github.com/juanfont/headscale/pull/2931)
|
- **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
|
- `--advertise-tags` is processed during registration, not on every policy evaluation
|
||||||
- PreAuthKey tagged devices ignore `--advertise-tags` from clients
|
- PreAuthKey tagged devices ignore `--advertise-tags` from clients
|
||||||
|
|||||||
@@ -3541,3 +3541,187 @@ func TestWebAuthRejectsUnauthorizedRequestTags(t *testing.T) {
|
|||||||
_, found := app.state.GetNodeByNodeKey(nodeKey.Public())
|
_, found := app.state.GetNodeByNodeKey(nodeKey.Public())
|
||||||
require.False(t, found, "Node should not be created when tags are unauthorized")
|
require.False(t, found, "Node should not be created when tags are unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWebAuthReauthWithEmptyTagsRemovesAllTags tests that when an existing tagged node
|
||||||
|
// reauths with empty RequestTags, all tags are removed and ownership returns to user.
|
||||||
|
// This is the fix for issue #2979.
|
||||||
|
func TestWebAuthReauthWithEmptyTagsRemovesAllTags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := createTestApp(t)
|
||||||
|
|
||||||
|
// Create a user
|
||||||
|
user := app.state.CreateUserForTest("reauth-untag-user")
|
||||||
|
|
||||||
|
// Update policy manager to recognize the new user
|
||||||
|
// This is necessary because CreateUserForTest doesn't update the policy manager
|
||||||
|
err := app.state.UpdatePolicyManagerUsersForTest()
|
||||||
|
require.NoError(t, err, "Failed to update policy manager users")
|
||||||
|
|
||||||
|
// Set up policy that allows the user to own these tags
|
||||||
|
policy := `{
|
||||||
|
"tagOwners": {
|
||||||
|
"tag:valid-owned": ["reauth-untag-user@"],
|
||||||
|
"tag:second": ["reauth-untag-user@"]
|
||||||
|
},
|
||||||
|
"acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}]
|
||||||
|
}`
|
||||||
|
_, err = app.state.SetPolicy([]byte(policy))
|
||||||
|
require.NoError(t, err, "Failed to set policy")
|
||||||
|
|
||||||
|
machineKey := key.NewMachine()
|
||||||
|
nodeKey1 := key.NewNode()
|
||||||
|
|
||||||
|
// Step 1: Initial registration with tags
|
||||||
|
registrationID1 := types.MustRegistrationID()
|
||||||
|
regEntry1 := types.NewRegisterNode(types.Node{
|
||||||
|
MachineKey: machineKey.Public(),
|
||||||
|
NodeKey: nodeKey1.Public(),
|
||||||
|
Hostname: "reauth-untag-node",
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "reauth-untag-node",
|
||||||
|
RequestTags: []string{"tag:valid-owned", "tag:second"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
app.state.SetRegistrationCacheEntry(registrationID1, regEntry1)
|
||||||
|
|
||||||
|
// Complete initial registration with tags
|
||||||
|
node, _, err := app.state.HandleNodeFromAuthPath(
|
||||||
|
registrationID1,
|
||||||
|
types.UserID(user.ID),
|
||||||
|
nil,
|
||||||
|
"webauth",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Initial registration should succeed")
|
||||||
|
require.True(t, node.IsTagged(), "Node should be tagged after initial registration")
|
||||||
|
require.ElementsMatch(t, []string{"tag:valid-owned", "tag:second"}, node.Tags().AsSlice())
|
||||||
|
t.Logf("Initial registration complete - Node ID: %d, Tags: %v, IsTagged: %t",
|
||||||
|
node.ID().Uint64(), node.Tags().AsSlice(), node.IsTagged())
|
||||||
|
|
||||||
|
// Step 2: Reauth with EMPTY tags to untag
|
||||||
|
nodeKey2 := key.NewNode() // New node key for reauth
|
||||||
|
registrationID2 := types.MustRegistrationID()
|
||||||
|
regEntry2 := types.NewRegisterNode(types.Node{
|
||||||
|
MachineKey: machineKey.Public(), // Same machine key
|
||||||
|
NodeKey: nodeKey2.Public(), // Different node key (rotation)
|
||||||
|
Hostname: "reauth-untag-node",
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "reauth-untag-node",
|
||||||
|
RequestTags: []string{}, // EMPTY - should untag
|
||||||
|
},
|
||||||
|
})
|
||||||
|
app.state.SetRegistrationCacheEntry(registrationID2, regEntry2)
|
||||||
|
|
||||||
|
// Complete reauth with empty tags
|
||||||
|
nodeAfterReauth, _, err := app.state.HandleNodeFromAuthPath(
|
||||||
|
registrationID2,
|
||||||
|
types.UserID(user.ID),
|
||||||
|
nil,
|
||||||
|
"webauth",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Reauth should succeed")
|
||||||
|
|
||||||
|
// Verify tags were removed
|
||||||
|
require.False(t, nodeAfterReauth.IsTagged(), "Node should NOT be tagged after reauth with empty tags")
|
||||||
|
require.Empty(t, nodeAfterReauth.Tags().AsSlice(), "Node should have no tags")
|
||||||
|
|
||||||
|
// Verify ownership returned to user
|
||||||
|
require.True(t, nodeAfterReauth.UserID().Valid(), "Node should have a user ID")
|
||||||
|
require.Equal(t, user.ID, nodeAfterReauth.UserID().Get(), "Node should be owned by the user again")
|
||||||
|
|
||||||
|
// Verify it's the same node (not a new one)
|
||||||
|
require.Equal(t, node.ID(), nodeAfterReauth.ID(), "Should be the same node after reauth")
|
||||||
|
|
||||||
|
t.Logf("Reauth complete - Node ID: %d, Tags: %v, IsTagged: %t, UserID: %d",
|
||||||
|
nodeAfterReauth.ID().Uint64(), nodeAfterReauth.Tags().AsSlice(),
|
||||||
|
nodeAfterReauth.IsTagged(), nodeAfterReauth.UserID().Get())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAuthKeyTaggedToUserOwnedViaReauth tests that a node originally registered
|
||||||
|
// with a tagged pre-auth key can transition to user-owned by re-authenticating
|
||||||
|
// via web auth with empty RequestTags. This ensures authkey-tagged nodes are
|
||||||
|
// not permanently locked to being tagged.
|
||||||
|
func TestAuthKeyTaggedToUserOwnedViaReauth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := createTestApp(t)
|
||||||
|
|
||||||
|
// Create a user
|
||||||
|
user := app.state.CreateUserForTest("authkey-to-user")
|
||||||
|
|
||||||
|
// Create a tagged pre-auth key
|
||||||
|
authKeyTags := []string{"tag:server", "tag:prod"}
|
||||||
|
pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, authKeyTags)
|
||||||
|
require.NoError(t, err, "Failed to create tagged pre-auth key")
|
||||||
|
|
||||||
|
machineKey := key.NewMachine()
|
||||||
|
nodeKey1 := key.NewNode()
|
||||||
|
|
||||||
|
// Step 1: Initial registration with tagged pre-auth key
|
||||||
|
regReq := tailcfg.RegisterRequest{
|
||||||
|
Auth: &tailcfg.RegisterResponseAuth{
|
||||||
|
AuthKey: pak.Key,
|
||||||
|
},
|
||||||
|
NodeKey: nodeKey1.Public(),
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "authkey-tagged-node",
|
||||||
|
},
|
||||||
|
Expiry: time.Now().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := app.handleRegisterWithAuthKey(regReq, machineKey.Public())
|
||||||
|
require.NoError(t, err, "Initial registration should succeed")
|
||||||
|
require.True(t, resp.MachineAuthorized, "Node should be authorized")
|
||||||
|
|
||||||
|
// Verify initial state: node is tagged via authkey
|
||||||
|
node, found := app.state.GetNodeByNodeKey(nodeKey1.Public())
|
||||||
|
require.True(t, found, "Node should be found")
|
||||||
|
require.True(t, node.IsTagged(), "Node should be tagged after authkey registration")
|
||||||
|
require.ElementsMatch(t, authKeyTags, node.Tags().AsSlice(), "Node should have authkey tags")
|
||||||
|
require.NotNil(t, node.AuthKey(), "Node should have AuthKey reference")
|
||||||
|
require.Positive(t, node.AuthKey().Tags().Len(), "AuthKey should have tags")
|
||||||
|
|
||||||
|
t.Logf("Initial registration complete - Node ID: %d, Tags: %v, IsTagged: %t, AuthKey.Tags.Len: %d",
|
||||||
|
node.ID().Uint64(), node.Tags().AsSlice(), node.IsTagged(), node.AuthKey().Tags().Len())
|
||||||
|
|
||||||
|
// Step 2: Reauth via web auth with EMPTY tags to transition to user-owned
|
||||||
|
nodeKey2 := key.NewNode() // New node key for reauth
|
||||||
|
registrationID := types.MustRegistrationID()
|
||||||
|
regEntry := types.NewRegisterNode(types.Node{
|
||||||
|
MachineKey: machineKey.Public(), // Same machine key
|
||||||
|
NodeKey: nodeKey2.Public(), // Different node key (rotation)
|
||||||
|
Hostname: "authkey-tagged-node",
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "authkey-tagged-node",
|
||||||
|
RequestTags: []string{}, // EMPTY - should untag
|
||||||
|
},
|
||||||
|
})
|
||||||
|
app.state.SetRegistrationCacheEntry(registrationID, regEntry)
|
||||||
|
|
||||||
|
// Complete reauth with empty tags
|
||||||
|
nodeAfterReauth, _, err := app.state.HandleNodeFromAuthPath(
|
||||||
|
registrationID,
|
||||||
|
types.UserID(user.ID),
|
||||||
|
nil,
|
||||||
|
"webauth",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Reauth should succeed")
|
||||||
|
|
||||||
|
// Verify tags were removed (authkey-tagged → user-owned transition)
|
||||||
|
require.False(t, nodeAfterReauth.IsTagged(), "Node should NOT be tagged after reauth with empty tags")
|
||||||
|
require.Empty(t, nodeAfterReauth.Tags().AsSlice(), "Node should have no tags")
|
||||||
|
|
||||||
|
// Verify ownership returned to user
|
||||||
|
require.True(t, nodeAfterReauth.UserID().Valid(), "Node should have a user ID")
|
||||||
|
require.Equal(t, user.ID, nodeAfterReauth.UserID().Get(), "Node should be owned by the user")
|
||||||
|
|
||||||
|
// Verify it's the same node (not a new one)
|
||||||
|
require.Equal(t, node.ID(), nodeAfterReauth.ID(), "Should be the same node after reauth")
|
||||||
|
|
||||||
|
// AuthKey reference should still exist (for audit purposes)
|
||||||
|
require.NotNil(t, nodeAfterReauth.AuthKey(), "AuthKey reference should be preserved")
|
||||||
|
|
||||||
|
t.Logf("Reauth complete - Node ID: %d, Tags: %v, IsTagged: %t, UserID: %d",
|
||||||
|
nodeAfterReauth.ID().Uint64(), nodeAfterReauth.Tags().AsSlice(),
|
||||||
|
nodeAfterReauth.IsTagged(), nodeAfterReauth.UserID().Get())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1246,6 +1246,93 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro
|
|||||||
return s.nodeStore.PutNode(*savedNode), nil
|
return s.nodeStore.PutNode(*savedNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processReauthTags handles tag changes during node re-authentication.
|
||||||
|
// It processes RequestTags from the client and updates node tags accordingly.
|
||||||
|
// Returns rejected tags (if any) for post-validation error handling.
|
||||||
|
func (s *State) processReauthTags(
|
||||||
|
node *types.Node,
|
||||||
|
requestTags []string,
|
||||||
|
user *types.User,
|
||||||
|
oldTags []string,
|
||||||
|
) []string {
|
||||||
|
wasAuthKeyTagged := node.AuthKey != nil && node.AuthKey.IsTagged()
|
||||||
|
|
||||||
|
logEvent := log.Debug().
|
||||||
|
Uint64("node.id", uint64(node.ID)).
|
||||||
|
Str("node.name", node.Hostname).
|
||||||
|
Strs("request.tags", requestTags).
|
||||||
|
Strs("current.tags", node.Tags).
|
||||||
|
Bool("is.tagged", node.IsTagged()).
|
||||||
|
Bool("was.authkey.tagged", wasAuthKeyTagged)
|
||||||
|
logEvent.Msg("Processing RequestTags during reauth")
|
||||||
|
|
||||||
|
// Empty RequestTags means untag node (transition to user-owned)
|
||||||
|
if len(requestTags) == 0 {
|
||||||
|
if node.IsTagged() {
|
||||||
|
log.Info().
|
||||||
|
Uint64("node.id", uint64(node.ID)).
|
||||||
|
Str("node.name", node.Hostname).
|
||||||
|
Strs("removed.tags", node.Tags).
|
||||||
|
Str("user.name", user.Name).
|
||||||
|
Bool("was.authkey.tagged", wasAuthKeyTagged).
|
||||||
|
Msg("Reauth: removing all tags, returning node ownership to user")
|
||||||
|
|
||||||
|
node.Tags = []string{}
|
||||||
|
node.UserID = &user.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-empty RequestTags: validate and apply
|
||||||
|
var approvedTags, rejectedTags []string
|
||||||
|
|
||||||
|
for _, tag := range requestTags {
|
||||||
|
if s.polMan.NodeCanHaveTag(node.View(), tag) {
|
||||||
|
approvedTags = append(approvedTags, tag)
|
||||||
|
} else {
|
||||||
|
rejectedTags = append(rejectedTags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rejectedTags) > 0 {
|
||||||
|
log.Warn().
|
||||||
|
Uint64("node.id", uint64(node.ID)).
|
||||||
|
Str("node.name", node.Hostname).
|
||||||
|
Strs("rejected.tags", rejectedTags).
|
||||||
|
Msg("Reauth: requested tags are not permitted")
|
||||||
|
|
||||||
|
return rejectedTags
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(approvedTags) > 0 {
|
||||||
|
slices.Sort(approvedTags)
|
||||||
|
approvedTags = slices.Compact(approvedTags)
|
||||||
|
|
||||||
|
wasTagged := node.IsTagged()
|
||||||
|
node.Tags = approvedTags
|
||||||
|
|
||||||
|
// Note: UserID is preserved as "created by" tracking, consistent with SetNodeTags
|
||||||
|
if !wasTagged {
|
||||||
|
log.Info().
|
||||||
|
Uint64("node.id", uint64(node.ID)).
|
||||||
|
Str("node.name", node.Hostname).
|
||||||
|
Strs("new.tags", approvedTags).
|
||||||
|
Str("old.user", user.Name).
|
||||||
|
Msg("Reauth: applying tags, transferring node to tagged-devices")
|
||||||
|
} else {
|
||||||
|
log.Info().
|
||||||
|
Uint64("node.id", uint64(node.ID)).
|
||||||
|
Str("node.name", node.Hostname).
|
||||||
|
Strs("old.tags", oldTags).
|
||||||
|
Strs("new.tags", approvedTags).
|
||||||
|
Msg("Reauth: updating tags on already-tagged node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HandleNodeFromAuthPath handles node registration through authentication flow (like OIDC).
|
// HandleNodeFromAuthPath handles node registration through authentication flow (like OIDC).
|
||||||
func (s *State) HandleNodeFromAuthPath(
|
func (s *State) HandleNodeFromAuthPath(
|
||||||
registrationID types.RegistrationID,
|
registrationID types.RegistrationID,
|
||||||
@@ -1291,14 +1378,26 @@ func (s *State) HandleNodeFromAuthPath(
|
|||||||
|
|
||||||
// If this node exists for this user, update the node in place.
|
// If this node exists for this user, update the node in place.
|
||||||
if existsSameUser && existingNodeSameUser.Valid() {
|
if existsSameUser && existingNodeSameUser.Valid() {
|
||||||
log.Debug().
|
log.Info().
|
||||||
Caller().
|
Caller().
|
||||||
Str("registration_id", registrationID.String()).
|
Str("registration_id", registrationID.String()).
|
||||||
Str("user.name", user.Name).
|
Str("user.name", user.Name).
|
||||||
Str("registrationMethod", registrationMethod).
|
Str("registrationMethod", registrationMethod).
|
||||||
Str("node.name", existingNodeSameUser.Hostname()).
|
Str("node.name", existingNodeSameUser.Hostname()).
|
||||||
Uint64("node.id", existingNodeSameUser.ID().Uint64()).
|
Uint64("node.id", existingNodeSameUser.ID().Uint64()).
|
||||||
Msg("Updating existing node registration")
|
Interface("hostinfo", regEntry.Node.Hostinfo).
|
||||||
|
Msg("Updating existing node registration via reauth")
|
||||||
|
|
||||||
|
// Process RequestTags during reauth (#2979)
|
||||||
|
// Due to json:",omitempty", we treat empty/nil as "clear tags"
|
||||||
|
var requestTags []string
|
||||||
|
if regEntry.Node.Hostinfo != nil {
|
||||||
|
requestTags = regEntry.Node.Hostinfo.RequestTags
|
||||||
|
}
|
||||||
|
|
||||||
|
oldTags := existingNodeSameUser.Tags().AsSlice()
|
||||||
|
|
||||||
|
var rejectedTags []string
|
||||||
|
|
||||||
// Update existing node - NodeStore first, then database
|
// Update existing node - NodeStore first, then database
|
||||||
updatedNodeView, ok := s.nodeStore.UpdateNode(existingNodeSameUser.ID(), func(node *types.Node) {
|
updatedNodeView, ok := s.nodeStore.UpdateNode(existingNodeSameUser.ID(), func(node *types.Node) {
|
||||||
@@ -1327,12 +1426,18 @@ func (s *State) HandleNodeFromAuthPath(
|
|||||||
node.Expiry = regEntry.Node.Expiry
|
node.Expiry = regEntry.Node.Expiry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rejectedTags = s.processReauthTags(node, requestTags, user, oldTags)
|
||||||
})
|
})
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return types.NodeView{}, change.Change{}, fmt.Errorf("%w: %d", ErrNodeNotInNodeStore, existingNodeSameUser.ID())
|
return types.NodeView{}, change.Change{}, fmt.Errorf("%w: %d", ErrNodeNotInNodeStore, existingNodeSameUser.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(rejectedTags) > 0 {
|
||||||
|
return types.NodeView{}, change.Change{}, fmt.Errorf("%w %v are invalid or not permitted", ErrRequestedTagsInvalidOrNotPermitted, rejectedTags)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
_, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||||
// Use Updates() to preserve fields not modified by UpdateNode.
|
// Use Updates() to preserve fields not modified by UpdateNode.
|
||||||
err := tx.Updates(updatedNodeView.AsStruct()).Error
|
err := tx.Updates(updatedNodeView.AsStruct()).Error
|
||||||
@@ -1686,6 +1791,14 @@ func (s *State) updatePolicyManagerUsers() (change.Change, error) {
|
|||||||
return change.Change{}, nil
|
return change.Change{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePolicyManagerUsersForTest updates the policy manager's user cache.
|
||||||
|
// This is exposed for testing purposes to sync the policy manager after
|
||||||
|
// creating test users via CreateUserForTest().
|
||||||
|
func (s *State) UpdatePolicyManagerUsersForTest() error {
|
||||||
|
_, err := s.updatePolicyManagerUsers()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// updatePolicyManagerNodes updates the policy manager with current nodes.
|
// updatePolicyManagerNodes updates the policy manager with current nodes.
|
||||||
// Returns true if the policy changed and notifications should be sent.
|
// Returns true if the policy changed and notifications should be sent.
|
||||||
// TODO(kradalby): This is a temporary stepping stone, ultimately we should
|
// TODO(kradalby): This is a temporary stepping stone, ultimately we should
|
||||||
|
|||||||
Reference in New Issue
Block a user