mirror of
https://github.com/juanfont/headscale.git
synced 2025-09-25 17:51:11 +02:00
attempt to replicate #2731
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
4de56c40d8
commit
56dac59538
@ -451,3 +451,83 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDuplicateNodeKeysSpoofing2731(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
spec := ScenarioSpec{
|
||||||
|
NodesPerUser: 0,
|
||||||
|
Users: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario, err := NewScenario(spec)
|
||||||
|
assertNoErr(t, err)
|
||||||
|
// defer scenario.ShutdownAssertNoPanics(t)
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnvWithLoginURL(
|
||||||
|
nil,
|
||||||
|
hsic.WithTestName("weblogout"),
|
||||||
|
hsic.WithTLS(),
|
||||||
|
)
|
||||||
|
assertNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
hs, err := scenario.Headscale()
|
||||||
|
assertNoErrGetHeadscale(t, err)
|
||||||
|
|
||||||
|
adminUser, err := scenario.CreateUser("admin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attackerUser, err := scenario.CreateUser("attacker")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
adminNode, err := scenario.CreateTailscaleNode("unstable", tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork]))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attackerNode, err := scenario.CreateTailscaleNode("unstable", tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork]))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
adminPAK, err := scenario.CreatePreAuthKey(adminUser.Id, true, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attackerPAK, err := scenario.CreatePreAuthKey(attackerUser.Id, true, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = adminNode.Login(hs.GetEndpoint(), adminPAK.GetKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = attackerNode.Login(hs.GetEndpoint(), attackerPAK.GetKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.EventuallyWithT(t, func(ct *assert.CollectT) {
|
||||||
|
nodes, err := hs.ListNodes()
|
||||||
|
assert.NoError(ct, err)
|
||||||
|
assert.Len(ct, nodes, 2, "there should be two nodes registered")
|
||||||
|
|
||||||
|
users, err := hs.ListUsers()
|
||||||
|
assert.NoError(ct, err)
|
||||||
|
assert.Len(ct, users, 2, "there should be two users created")
|
||||||
|
}, 30*time.Second, 1*time.Second, "ensuring nodes and users are created")
|
||||||
|
|
||||||
|
nodePriv, err := adminNode.GetNodePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = attackerNode.SetNodePrivateKey(*nodePriv)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = attackerNode.ReloadTailscaled()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nodePriv2, err := attackerNode.GetNodePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, nodePriv.Public().String(), nodePriv2.Public().String(), "node private keys should be equal")
|
||||||
|
|
||||||
|
// require.NoError(t, attackerNode.Logout())
|
||||||
|
// require.NoError(t, attackerNode.WaitForNeedsLogin())
|
||||||
|
|
||||||
|
err = attackerNode.Login(hs.GetEndpoint(), attackerPAK.GetKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
@ -41,6 +41,8 @@ type TailscaleClient interface {
|
|||||||
Netmap() (*netmap.NetworkMap, error)
|
Netmap() (*netmap.NetworkMap, error)
|
||||||
DebugDERPRegion(region string) (*ipnstate.DebugDERPRegionReport, error)
|
DebugDERPRegion(region string) (*ipnstate.DebugDERPRegionReport, error)
|
||||||
GetNodePrivateKey() (*key.NodePrivate, error)
|
GetNodePrivateKey() (*key.NodePrivate, error)
|
||||||
|
SetNodePrivateKey(privateKey key.NodePrivate) error
|
||||||
|
ReloadTailscaled() error
|
||||||
Netcheck() (*netcheck.Report, error)
|
Netcheck() (*netcheck.Report, error)
|
||||||
WaitForNeedsLogin(timeout time.Duration) error
|
WaitForNeedsLogin(timeout time.Duration) error
|
||||||
WaitForRunning(timeout time.Duration) error
|
WaitForRunning(timeout time.Duration) error
|
||||||
|
@ -1333,3 +1333,69 @@ func (t *TailscaleInContainer) GetNodePrivateKey() (*key.NodePrivate, error) {
|
|||||||
|
|
||||||
return &p.Persist.PrivateNodeKey, nil
|
return &p.Persist.PrivateNodeKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TailscaleInContainer) SetNodePrivateKey(privateKey key.NodePrivate) error {
|
||||||
|
state, err := t.ReadFile(paths.DefaultTailscaledStateFile())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read state file: %w", err)
|
||||||
|
}
|
||||||
|
store := &mem.Store{}
|
||||||
|
if err = store.LoadFromJSON(state); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal state file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentProfileKey, err := store.ReadState(ipn.CurrentProfileStateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read current profile state key: %w", err)
|
||||||
|
}
|
||||||
|
currentProfile, err := store.ReadState(ipn.StateKey(currentProfileKey))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read current profile state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &ipn.Prefs{}
|
||||||
|
if err = json.Unmarshal(currentProfile, &p); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal current profile state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the private node key
|
||||||
|
p.Persist.PrivateNodeKey = privateKey
|
||||||
|
|
||||||
|
// Marshal the updated preferences back to JSON
|
||||||
|
updatedProfile, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal updated profile state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated profile back to the store
|
||||||
|
if err = store.WriteState(ipn.StateKey(currentProfileKey), updatedProfile); err != nil {
|
||||||
|
return fmt.Errorf("failed to write updated profile state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated store back to the state file
|
||||||
|
updatedState, err := store.ExportToJSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to export updated state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.WriteFile(paths.DefaultTailscaledStateFile(), updatedState); err != nil {
|
||||||
|
return fmt.Errorf("failed to write updated state file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TailscaleInContainer) ReloadTailscaled() error {
|
||||||
|
command := []string{
|
||||||
|
"kill",
|
||||||
|
"-HUP",
|
||||||
|
"1",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := t.Execute(command)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reload tailscaled: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user