Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework hostname handling #560

Merged
merged 44 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
663e838
Nickname support
bravechamp Mar 13, 2022
9901d6b
Ability to clear nickname
bravechamp Mar 13, 2022
60ee046
Normalize nickname before saving to database
bravechamp Mar 13, 2022
c8aa653
Merge branch 'main' into main
kradalby Mar 19, 2022
8845938
Merge branch 'main' into main
kradalby Apr 24, 2022
6e27680
Rename name -> hostname, nickname -> givenname
kradalby Apr 24, 2022
caf79f6
Change nickname to givenname in proto
kradalby Apr 24, 2022
6b79679
Generate from proto
kradalby Apr 24, 2022
14994cb
Use new logic and fields for dns
kradalby Apr 24, 2022
62808cb
Bubble error up to user for rename
kradalby Apr 24, 2022
06c928b
Migrate name and nickname fields
kradalby Apr 24, 2022
79704dc
Update command with new fields
kradalby Apr 24, 2022
7ef8cd8
Fix comment
kradalby Apr 24, 2022
8504d0d
Move todo to correct file
kradalby Apr 24, 2022
f4873d9
Fix rename cli error
kradalby May 16, 2022
177c21b
Add helper function to create a unique givenname
kradalby May 16, 2022
03cccd6
Reword FQDN normalize errors to not _only_ cover namespaces
kradalby May 16, 2022
5fa3016
Generate unique givennames for hosts joining (and debug added)
kradalby May 16, 2022
9b393eb
Add integration cli tests for rename command
kradalby May 16, 2022
4aae917
Require GivenName to be unique
kradalby May 16, 2022
163e5c2
fix trace log message
kradalby May 16, 2022
e631c6f
Merge master
kradalby May 16, 2022
9ebeb3d
Retreive hostnames from headscale, now that they are random
kradalby May 17, 2022
802eb93
Make sure givenname is set for preauthkeys
kradalby May 17, 2022
4a9d3be
Use new names to resolve magic dns
kradalby May 18, 2022
77ceeaf
Test magic dns with the correct urls
kradalby May 18, 2022
5fa9875
move populate to after when given_name exist
kradalby May 23, 2022
4f3f054
Fix some issues in testing with new hostname handling
juanfont May 28, 2022
ef497ca
Merge pull request #2 from juanfont/fix-rename-integration-tests
kradalby May 28, 2022
6eac504
Merge branch 'main' into rename-fixess
kradalby May 29, 2022
fc502e1
Update golines and fix go mod checksum
kradalby May 30, 2022
59a1a85
Change to a go generics set implementation, no more casting :tada:
kradalby May 30, 2022
5316dd9
Use new nix stable (22.05)
kradalby May 30, 2022
3a3fc0a
Update headscale checksum
kradalby May 30, 2022
9993f51
docs(README): update contributors
github-actions[bot] May 29, 2022
dbc1d98
Revert golines
kradalby May 30, 2022
a992840
Give UpdateMachine a more meaningful name
kradalby May 30, 2022
a443255
Validate isOutdated against all namespaces
kradalby May 30, 2022
4ffd3ea
Override golangci-lint to use go 1.17
kradalby May 30, 2022
266aac9
Update CHANGELOG
kradalby May 30, 2022
d11279e
Merge branch 'main' into rename-fixess
kradalby May 30, 2022
9175aca
Merge branch 'main' into rename-fixess
kradalby May 30, 2022
f7edea5
Merge branch 'main' into rename-fixess
kradalby May 31, 2022
7f7cd73
Merge branch 'main' into rename-fixess
kradalby May 31, 2022
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
- Add command to set tags on a node [#525](https://github.com/juanfont/headscale/issues/525)
- Add command to view tags of nodes [#356](https://github.com/juanfont/headscale/issues/356)
- Add --all (-a) flag to enable routes command [#360](https://github.com/juanfont/headscale/issues/360)
- Fix issue where nodes was not updated across namespaces [#560](https://github.com/juanfont/headscale/pull/560)
- Add the ability to rename a nodes name [#560](https://github.com/juanfont/headscale/pull/560)
- Node DNS names are now unique, a random suffix will be added when a node joins
- This change contains database changes, remember to **backup** your database before upgrading
- Add option to enable/disable logtail (Tailscale's logging infrastructure) [#596](https://github.com/juanfont/headscale/pull/596)
Copy link
Owner

Choose a reason for hiding this comment

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

👀

- This change disables the logs by default

Expand Down
14 changes: 7 additions & 7 deletions acls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
MachineKey: "foo",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
Hostname: "testmachine",
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
Expand Down Expand Up @@ -164,7 +164,7 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
MachineKey: "12345",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
Hostname: "testmachine",
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
Expand Down Expand Up @@ -210,7 +210,7 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
MachineKey: "12345",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
Hostname: "testmachine",
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
Expand Down Expand Up @@ -255,7 +255,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
MachineKey: "12345",
NodeKey: "bar",
DiscoKey: "faa",
Name: "webserver",
Hostname: "webserver",
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
Expand All @@ -274,7 +274,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
MachineKey: "56789",
NodeKey: "bar2",
DiscoKey: "faab",
Name: "user",
Hostname: "user",
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
Expand Down Expand Up @@ -368,7 +368,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
MachineKey: "12345",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
Hostname: "testmachine",
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
IPAddresses: ips,
Expand Down Expand Up @@ -410,7 +410,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
MachineKey: "foo",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
Hostname: "testmachine",
NamespaceID: namespace.ID,
RegisterMethod: RegisterMethodAuthKey,
IPAddresses: ips,
Expand Down
34 changes: 23 additions & 11 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,8 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {

return
}
hname, err := NormalizeToFQDNRules(
req.Hostinfo.Hostname,
h.cfg.OIDC.StripEmaildomain,
)

givenName, err := h.GenerateGivenName(req.Hostinfo.Hostname)
if err != nil {
log.Error().
Caller().
Expand All @@ -153,7 +151,8 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
// happens
newMachine := Machine{
MachineKey: machineKeyStr,
Name: hname,
Hostname: req.Hostinfo.Hostname,
GivenName: givenName,
NodeKey: NodePublicKeyStripPrefix(req.NodeKey),
LastSeen: &now,
Expiry: &time.Time{},
Expand Down Expand Up @@ -364,7 +363,7 @@ func (h *Headscale) handleMachineLogOut(
resp := tailcfg.RegisterResponse{}

log.Info().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("Client requested logout")

h.ExpireMachine(&machine)
Expand Down Expand Up @@ -394,7 +393,7 @@ func (h *Headscale) handleMachineValidRegistration(

// The machine registration is valid, respond with redirect to /map
log.Debug().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("Client is registered and we have the current NodeKey. All clear to /map")

resp.AuthURL = ""
Expand Down Expand Up @@ -429,7 +428,7 @@ func (h *Headscale) handleMachineExpired(

// The client has registered before, but has expired
log.Debug().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("Machine registration has expired. Sending a authurl to register")

if registerRequest.Auth.AuthKey != "" {
Expand Down Expand Up @@ -472,7 +471,7 @@ func (h *Headscale) handleMachineRefreshKey(
resp := tailcfg.RegisterResponse{}

log.Debug().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("We have the OldNodeKey in the database. This is a key refresh")
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
h.db.Save(&machine)
Expand Down Expand Up @@ -597,16 +596,29 @@ func (h *Headscale) handleAuthKey(
if machine != nil {
log.Trace().
Caller().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("machine already registered, refreshing with new auth key")

machine.NodeKey = nodeKey
machine.AuthKeyID = uint(pak.ID)
h.RefreshMachine(machine, registerRequest.Expiry)
} else {
now := time.Now().UTC()

givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname)
if err != nil {
log.Error().
Caller().
Str("func", "RegistrationHandler").
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
Err(err)

return
}

machineToRegister := Machine{
Name: registerRequest.Hostinfo.Hostname,
Hostname: registerRequest.Hostinfo.Hostname,
GivenName: givenName,
NamespaceID: pak.Namespace.ID,
MachineKey: machineKeyStr,
RegisterMethod: RegisterMethodAuthKey,
Expand Down
28 changes: 20 additions & 8 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/patrickmn/go-cache"
zerolog "github.com/philip-bui/grpc-zerolog"
"github.com/puzpuzpuz/xsync"
zl "github.com/rs/zerolog"
"github.com/rs/zerolog/log"
ginprometheus "github.com/zsais/go-gin-prometheus"
Expand Down Expand Up @@ -166,7 +167,7 @@ type Headscale struct {
aclPolicy *ACLPolicy
aclRules []tailcfg.FilterRule

lastStateChange sync.Map
lastStateChange *xsync.MapOf[time.Time]

oidcProvider *oidc.Provider
oauth2Config *oauth2.Config
Expand Down Expand Up @@ -310,14 +311,14 @@ func (h *Headscale) expireEphemeralNodesWorker() {
After(machine.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
expiredFound = true
log.Info().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("Ephemeral client removed from database")

err = h.db.Unscoped().Delete(machine).Error
if err != nil {
log.Error().
Err(err).
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Msg("🤮 Cannot delete ephemeral machine from the database")
}
}
Expand Down Expand Up @@ -799,18 +800,29 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) {
func (h *Headscale) setLastStateChangeToNow(namespace string) {
now := time.Now().UTC()
lastStateUpdate.WithLabelValues("", "headscale").Set(float64(now.Unix()))
if h.lastStateChange == nil {
h.lastStateChange = xsync.NewMapOf[time.Time]()
}
h.lastStateChange.Store(namespace, now)
}

func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
times := []time.Time{}

for _, namespace := range namespaces {
if wrapped, ok := h.lastStateChange.Load(namespace); ok {
lastChange, _ := wrapped.(time.Time)

times = append(times, lastChange)
// getLastStateChange takes a list of namespaces as a "filter", if no namespaces
// are past, then use the entier list of namespaces and look for the last update
if len(namespaces) > 0 {
for _, namespace := range namespaces {
if lastChange, ok := h.lastStateChange.Load(namespace); ok {
times = append(times, lastChange)
}
}
} else {
h.lastStateChange.Range(func(key string, value time.Time) bool {
times = append(times, value)

return true
})
}

sort.Slice(times, func(i, j int) bool {
Expand Down
55 changes: 55 additions & 0 deletions cmd/headscale/cli/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ func init() {
}
nodeCmd.AddCommand(expireNodeCmd)

renameNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = renameNodeCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
}
nodeCmd.AddCommand(renameNodeCmd)

deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = deleteNodeCmd.MarkFlagRequired("identifier")
if err != nil {
Expand Down Expand Up @@ -240,6 +247,54 @@ var expireNodeCmd = &cobra.Command{
},
}

var renameNodeCmd = &cobra.Command{
Use: "rename NEW_NAME",
Short: "Renames a machine in your network",
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")

identifier, err := cmd.Flags().GetUint64("identifier")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)

return
}

ctx, client, conn, cancel := getHeadscaleCLIClient()
defer cancel()
defer conn.Close()

newName := ""
if len(args) > 0 {
newName = args[0]
}
request := &v1.RenameMachineRequest{
MachineId: identifier,
NewName: newName,
}

response, err := client.RenameMachine(ctx, request)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
"Cannot rename machine: %s\n",
status.Convert(err).Message(),
),
output,
)

return
}

SuccessOutput(response.Machine, "Machine renamed", output)
},
}

var deleteNodeCmd = &cobra.Command{
Use: "delete",
Short: "Delete a node",
Expand Down
43 changes: 41 additions & 2 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func (h *Headscale) initDB() error {
}

_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
_ = db.Migrator().RenameColumn(&Machine{}, "name", "hostname")

// GivenName is used as the primary source of DNS names, make sure
// the field is populated and normalized if it was not when the
// machine was registered.
_ = db.Migrator().RenameColumn(&Machine{}, "nickname", "given_name")

// If the Machine table has a column for registered,
// find all occourences of "false" and drop them. Then
Expand All @@ -54,13 +60,13 @@ func (h *Headscale) initDB() error {

for _, machine := range machines {
log.Info().
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Str("machine_key", machine.MachineKey).
Msg("Deleting unregistered machine")
if err := h.db.Delete(&Machine{}, machine.ID).Error; err != nil {
log.Error().
Err(err).
Str("machine", machine.Name).
Str("machine", machine.Hostname).
Str("machine_key", machine.MachineKey).
Msg("Error deleting unregistered machine")
}
Expand All @@ -77,6 +83,39 @@ func (h *Headscale) initDB() error {
return err
}

if db.Migrator().HasColumn(&Machine{}, "given_name") {
machines := Machines{}
if err := h.db.Find(&machines).Error; err != nil {
log.Error().Err(err).Msg("Error accessing db")
}

for _, machine := range machines {
if machine.GivenName == "" {
normalizedHostname, err := NormalizeToFQDNRules(
machine.Hostname,
h.cfg.OIDC.StripEmaildomain,
)
if err != nil {
log.Error().
Caller().
Str("hostname", machine.Hostname).
Err(err).
Msg("Failed to normalize machine hostname in DB migration")
}

err = h.RenameMachine(&machine, normalizedHostname)
if err != nil {
log.Error().
Caller().
Str("hostname", machine.Hostname).
Err(err).
Msg("Failed to save normalized machine name in DB migration")
}

}
}
}

err = db.AutoMigrate(&KV{})
if err != nil {
return err
Expand Down
Loading