From f45b81dbe2157fc7561862a471d140fc3519312e Mon Sep 17 00:00:00 2001
From: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
Date: Tue, 5 Nov 2024 09:00:13 -0500
Subject: [PATCH] Prevent overwriting existing host_uuid file (#48012)
In some circumstances, multiple Teleport processes may be trying
to write the host_uuid file in the same data directory simultaneously.
The last of the writers would win, and any process using a host
UUID that did not match what ended up on disk could get into a perpertual
state of being unable to connect to the cluster.
To avoid the raciness, the host_uuid file writing process is no
longer a blind upsert. Instead, special care is taken to ensure
that there can only be a single writer, and that any subsequent
updates to the file are aborted and the first value written is
used instead.
---
lib/service/service.go | 15 +--
lib/service/service_test.go | 3 +-
lib/srv/regular/sshserver.go | 3 +-
.../connectmycomputer/connectmycomputer.go | 7 +-
.../connectmycomputer_test.go | 4 +-
lib/utils/hostid/hostid.go | 61 +++++++++
lib/utils/hostid/hostid_test.go | 116 ++++++++++++++++++
lib/utils/hostid/hostid_unix.go | 105 ++++++++++++++++
lib/utils/hostid/hostid_windows.go | 30 +++++
lib/utils/utils.go | 72 -----------
lib/utils/utils_test.go | 50 --------
tool/tctl/common/admin_action_test.go | 3 +-
tool/tctl/common/tctl.go | 7 +-
tool/teleport/testenv/test_server.go | 3 +-
14 files changed, 338 insertions(+), 141 deletions(-)
create mode 100644 lib/utils/hostid/hostid.go
create mode 100644 lib/utils/hostid/hostid_test.go
create mode 100644 lib/utils/hostid/hostid_unix.go
create mode 100644 lib/utils/hostid/hostid_windows.go
diff --git a/lib/service/service.go b/lib/service/service.go
index 7f0b89e1bc3b1..83c11173898ba 100644
--- a/lib/service/service.go
+++ b/lib/service/service.go
@@ -160,6 +160,7 @@ import (
"github.com/gravitational/teleport/lib/utils"
awsutils "github.com/gravitational/teleport/lib/utils/aws"
"github.com/gravitational/teleport/lib/utils/cert"
+ "github.com/gravitational/teleport/lib/utils/hostid"
logutils "github.com/gravitational/teleport/lib/utils/log"
vc "github.com/gravitational/teleport/lib/versioncontrol"
"github.com/gravitational/teleport/lib/versioncontrol/endpoint"
@@ -2830,7 +2831,7 @@ func (process *TeleportProcess) initSSH() error {
storagePresence := local.NewPresenceService(process.storage.BackendStorage)
// read the host UUID:
- serverID, err := utils.ReadOrMakeHostUUID(cfg.DataDir)
+ serverID, err := hostid.ReadOrCreateFile(cfg.DataDir)
if err != nil {
return trace.Wrap(err)
}
@@ -4307,7 +4308,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
}
// read the host UUID:
- serverID, err := utils.ReadOrMakeHostUUID(cfg.DataDir)
+ serverID, err := hostid.ReadOrCreateFile(cfg.DataDir)
if err != nil {
return trace.Wrap(err)
}
@@ -6307,7 +6308,7 @@ func readOrGenerateHostID(ctx context.Context, cfg *servicecfg.Config, kubeBacke
if err := persistHostIDToStorages(ctx, cfg, kubeBackend); err != nil {
return trace.Wrap(err)
}
- } else if kubeBackend != nil && utils.HostUUIDExistsLocally(cfg.DataDir) {
+ } else if kubeBackend != nil && hostid.ExistsLocally(cfg.DataDir) {
// This case is used when loading a Teleport pre-11 agent with storage attached.
// In this case, we have to copy the "host_uuid" from the agent to the secret
// in case storage is removed later.
@@ -6346,14 +6347,14 @@ func readHostIDFromStorages(ctx context.Context, dataDir string, kubeBackend kub
}
// Even if running in Kubernetes fallback to local storage if `host_uuid` was
// not found in secret.
- hostID, err := utils.ReadHostUUID(dataDir)
+ hostID, err := hostid.ReadFile(dataDir)
return hostID, trace.Wrap(err)
}
// persistHostIDToStorages writes the cfg.HostUUID to local data and to
// Kubernetes Secret if this process is running on a Kubernetes Cluster.
func persistHostIDToStorages(ctx context.Context, cfg *servicecfg.Config, kubeBackend kubernetesBackend) error {
- if err := utils.WriteHostUUID(cfg.DataDir, cfg.HostUUID); err != nil {
+ if err := hostid.WriteFile(cfg.DataDir, cfg.HostUUID); err != nil {
if errors.Is(err, fs.ErrPermission) {
cfg.Logger.ErrorContext(ctx, "Teleport does not have permission to write to the data directory. Ensure that you are running as a user with appropriate permissions.", "data_dir", cfg.DataDir)
}
@@ -6372,7 +6373,7 @@ func persistHostIDToStorages(ctx context.Context, cfg *servicecfg.Config, kubeBa
// loadHostIDFromKubeSecret reads the host_uuid from the Kubernetes secret with
// the expected key: `/host_uuid`.
func loadHostIDFromKubeSecret(ctx context.Context, kubeBackend kubernetesBackend) (string, error) {
- item, err := kubeBackend.Get(ctx, backend.NewKey(utils.HostUUIDFile))
+ item, err := kubeBackend.Get(ctx, backend.NewKey(hostid.FileName))
if err != nil {
return "", trace.Wrap(err)
}
@@ -6385,7 +6386,7 @@ func writeHostIDToKubeSecret(ctx context.Context, kubeBackend kubernetesBackend,
_, err := kubeBackend.Put(
ctx,
backend.Item{
- Key: backend.NewKey(utils.HostUUIDFile),
+ Key: backend.NewKey(hostid.FileName),
Value: []byte(id),
},
)
diff --git a/lib/service/service_test.go b/lib/service/service_test.go
index b94d776627999..6c5cb7b606e4e 100644
--- a/lib/service/service_test.go
+++ b/lib/service/service_test.go
@@ -69,6 +69,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
)
func TestMain(m *testing.M) {
@@ -1177,7 +1178,7 @@ func Test_readOrGenerateHostID(t *testing.T) {
dataDir := t.TempDir()
// write host_uuid file to temp dir.
if len(tt.args.hostIDContent) > 0 {
- err := utils.WriteHostUUID(dataDir, tt.args.hostIDContent)
+ err := hostid.WriteFile(dataDir, tt.args.hostIDContent)
require.NoError(t, err)
}
diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go
index df0e87f4abb48..6425e1dab27b7 100644
--- a/lib/srv/regular/sshserver.go
+++ b/lib/srv/regular/sshserver.go
@@ -72,6 +72,7 @@ import (
"github.com/gravitational/teleport/lib/sshutils/x11"
"github.com/gravitational/teleport/lib/teleagent"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
"github.com/gravitational/teleport/lib/utils/uds"
)
@@ -726,7 +727,7 @@ func New(
options ...ServerOption,
) (*Server, error) {
// read the host UUID:
- uuid, err := utils.ReadOrMakeHostUUID(dataDir)
+ uuid, err := hostid.ReadOrCreateFile(dataDir)
if err != nil {
return nil, trace.Wrap(err)
}
diff --git a/lib/teleterm/services/connectmycomputer/connectmycomputer.go b/lib/teleterm/services/connectmycomputer/connectmycomputer.go
index 1cc0f8914a052..26ecc8aafe8d9 100644
--- a/lib/teleterm/services/connectmycomputer/connectmycomputer.go
+++ b/lib/teleterm/services/connectmycomputer/connectmycomputer.go
@@ -41,6 +41,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/teleterm/clusters"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
)
type RoleSetup struct {
@@ -395,7 +396,7 @@ func (n *NodeJoinWait) getNodeNameFromHostUUIDFile(ctx context.Context, cluster
// the file is empty.
//
// Here we need to be able to distinguish between both of those two cases.
- out, err := utils.ReadPath(utils.GetHostUUIDPath(dataDir))
+ out, err := utils.ReadPath(hostid.GetPath(dataDir))
if err != nil {
if trace.IsNotFound(err) {
continue
@@ -536,7 +537,7 @@ type NodeDelete struct {
// Run grabs the host UUID of an agent from a disk and deletes the node with that name.
func (n *NodeDelete) Run(ctx context.Context, presence Presence, cluster *clusters.Cluster) error {
- hostUUID, err := utils.ReadHostUUID(getAgentDataDir(n.cfg.AgentsDir, cluster.ProfileName))
+ hostUUID, err := hostid.ReadFile(getAgentDataDir(n.cfg.AgentsDir, cluster.ProfileName))
if trace.IsNotFound(err) {
return nil
}
@@ -585,7 +586,7 @@ type NodeName struct {
// Get returns the host UUID of the agent from a disk.
func (n *NodeName) Get(cluster *clusters.Cluster) (string, error) {
- hostUUID, err := utils.ReadHostUUID(getAgentDataDir(n.cfg.AgentsDir, cluster.ProfileName))
+ hostUUID, err := hostid.ReadFile(getAgentDataDir(n.cfg.AgentsDir, cluster.ProfileName))
return hostUUID, trace.Wrap(err)
}
diff --git a/lib/teleterm/services/connectmycomputer/connectmycomputer_test.go b/lib/teleterm/services/connectmycomputer/connectmycomputer_test.go
index 9a0af0b749edf..e7b453b94b2bc 100644
--- a/lib/teleterm/services/connectmycomputer/connectmycomputer_test.go
+++ b/lib/teleterm/services/connectmycomputer/connectmycomputer_test.go
@@ -35,7 +35,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/teleterm/api/uri"
"github.com/gravitational/teleport/lib/teleterm/clusters"
- "github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
)
func TestRoleSetupRun_WithNonLocalUser(t *testing.T) {
@@ -472,7 +472,7 @@ func mustMakeHostUUIDFile(t *testing.T, agentsDir string, profileName string) st
err = os.MkdirAll(dataDir, agentsDirStat.Mode())
require.NoError(t, err)
- hostUUID, err := utils.ReadOrMakeHostUUID(dataDir)
+ hostUUID, err := hostid.ReadOrCreateFile(dataDir)
require.NoError(t, err)
return hostUUID
diff --git a/lib/utils/hostid/hostid.go b/lib/utils/hostid/hostid.go
new file mode 100644
index 0000000000000..094e4cf9547ae
--- /dev/null
+++ b/lib/utils/hostid/hostid.go
@@ -0,0 +1,61 @@
+// Teleport
+// Copyright (C) 2024 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package hostid
+
+import (
+ "errors"
+ "io/fs"
+ "path/filepath"
+ "strings"
+
+ "github.com/gravitational/trace"
+
+ "github.com/gravitational/teleport/lib/utils"
+)
+
+const (
+ // FileName is the file name where the host UUID file is stored
+ FileName = "host_uuid"
+)
+
+// GetPath returns the path to the host UUID file given the data directory.
+func GetPath(dataDir string) string {
+ return filepath.Join(dataDir, FileName)
+}
+
+// ExistsLocally checks if dataDir/host_uuid file exists in local storage.
+func ExistsLocally(dataDir string) bool {
+ _, err := ReadFile(dataDir)
+ return err == nil
+}
+
+// ReadFile reads host UUID from the file in the data dir
+func ReadFile(dataDir string) (string, error) {
+ out, err := utils.ReadPath(GetPath(dataDir))
+ if err != nil {
+ if errors.Is(err, fs.ErrPermission) {
+ //do not convert to system error as this loses the ability to compare that it is a permission error
+ return "", trace.Wrap(err)
+ }
+ return "", trace.ConvertSystemError(err)
+ }
+ id := strings.TrimSpace(string(out))
+ if id == "" {
+ return "", trace.NotFound("host uuid is empty")
+ }
+ return id, nil
+}
diff --git a/lib/utils/hostid/hostid_test.go b/lib/utils/hostid/hostid_test.go
new file mode 100644
index 0000000000000..2ea22c4e71e7f
--- /dev/null
+++ b/lib/utils/hostid/hostid_test.go
@@ -0,0 +1,116 @@
+//go:build !windows
+
+// Teleport
+// Copyright (C) 2024 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package hostid_test
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
+)
+
+func TestMain(m *testing.M) {
+ utils.InitLoggerForTests()
+ os.Exit(m.Run())
+}
+
+func TestReadOrCreate(t *testing.T) {
+ t.Parallel()
+
+ dir := t.TempDir()
+
+ var wg errgroup.Group
+ concurrency := 10
+ ids := make([]string, concurrency)
+ barrier := make(chan struct{})
+
+ for i := 0; i < concurrency; i++ {
+ i := i
+ wg.Go(func() error {
+ <-barrier
+ id, err := hostid.ReadOrCreateFile(dir)
+ ids[i] = id
+ return err
+ })
+ }
+
+ close(barrier)
+
+ require.NoError(t, wg.Wait())
+ for _, id := range ids {
+ assert.Equal(t, ids[0], id)
+ }
+}
+
+func TestIdempotence(t *testing.T) {
+ t.Parallel()
+
+ // call twice, get same result
+ dir := t.TempDir()
+ id, err := hostid.ReadOrCreateFile(dir)
+ require.Len(t, id, 36)
+ require.NoError(t, err)
+ uuidCopy, err := hostid.ReadOrCreateFile(dir)
+ require.NoError(t, err)
+ require.Equal(t, id, uuidCopy)
+}
+
+func TestBadLocation(t *testing.T) {
+ t.Parallel()
+
+ // call with a read-only dir, make sure to get an error
+ id, err := hostid.ReadOrCreateFile("/bad-location")
+ require.Empty(t, id)
+ require.Error(t, err)
+ require.Regexp(t, "^.*no such file or directory.*$", err.Error())
+}
+
+func TestIgnoreWhitespace(t *testing.T) {
+ t.Parallel()
+
+ // newlines are getting ignored
+ dir := t.TempDir()
+ id := fmt.Sprintf("%s\n", uuid.NewString())
+ err := os.WriteFile(filepath.Join(dir, hostid.FileName), []byte(id), 0666)
+ require.NoError(t, err)
+ out, err := hostid.ReadFile(dir)
+ require.NoError(t, err)
+ require.Equal(t, strings.TrimSpace(id), out)
+}
+
+func TestRegenerateEmpty(t *testing.T) {
+ t.Parallel()
+
+ // empty UUID in file is regenerated
+ dir := t.TempDir()
+ err := os.WriteFile(filepath.Join(dir, hostid.FileName), nil, 0666)
+ require.NoError(t, err)
+ out, err := hostid.ReadOrCreateFile(dir)
+ require.NoError(t, err)
+ require.Len(t, out, 36)
+}
diff --git a/lib/utils/hostid/hostid_unix.go b/lib/utils/hostid/hostid_unix.go
new file mode 100644
index 0000000000000..b5334e641c232
--- /dev/null
+++ b/lib/utils/hostid/hostid_unix.go
@@ -0,0 +1,105 @@
+//go:build !windows
+
+// Teleport
+// Copyright (C) 2024 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package hostid
+
+import (
+ "errors"
+ "io/fs"
+ "time"
+
+ "github.com/google/renameio/v2"
+ "github.com/google/uuid"
+ "github.com/gravitational/trace"
+
+ "github.com/gravitational/teleport/lib/utils"
+)
+
+// WriteFile writes host UUID into a file
+func WriteFile(dataDir string, id string) error {
+ err := renameio.WriteFile(GetPath(dataDir), []byte(id), 0o400)
+ if err != nil {
+ if errors.Is(err, fs.ErrPermission) {
+ //do not convert to system error as this loses the ability to compare that it is a permission error
+ return trace.Wrap(err)
+ }
+ return trace.ConvertSystemError(err)
+ }
+ return nil
+}
+
+// ReadOrCreateFile looks for a hostid file in the data dir. If present,
+// returns the UUID from it, otherwise generates one
+func ReadOrCreateFile(dataDir string) (string, error) {
+ hostUUIDFileLock := GetPath(dataDir) + ".lock"
+ const iterationLimit = 3
+
+ for i := 0; i < iterationLimit; i++ {
+ if read, err := ReadFile(dataDir); err == nil {
+ return read, nil
+ } else if !trace.IsNotFound(err) {
+ return "", trace.Wrap(err)
+ }
+
+ // Checking error instead of the usual uuid.New() in case uuid generation
+ // fails due to not enough randomness. It's been known to happen happen when
+ // Teleport starts very early in the node initialization cycle and /dev/urandom
+ // isn't ready yet.
+ rawID, err := uuid.NewRandom()
+ if err != nil {
+ return "", trace.BadParameter("" +
+ "Teleport failed to generate host UUID. " +
+ "This may happen if randomness source is not fully initialized when the node is starting up. " +
+ "Please try restarting Teleport again.")
+ }
+
+ writeFile := func(potentialID string) (string, error) {
+ unlock, err := utils.FSTryWriteLock(hostUUIDFileLock)
+ if err != nil {
+ return "", trace.Wrap(err)
+ }
+ defer unlock()
+
+ if read, err := ReadFile(dataDir); err == nil {
+ return read, nil
+ } else if !trace.IsNotFound(err) {
+ return "", trace.Wrap(err)
+ }
+
+ if err := WriteFile(dataDir, potentialID); err != nil {
+ return "", trace.Wrap(err)
+ }
+
+ return potentialID, nil
+ }
+
+ id, err := writeFile(rawID.String())
+ if err != nil {
+ if errors.Is(err, utils.ErrUnsuccessfulLockTry) {
+ time.Sleep(100 * time.Millisecond)
+ continue
+ }
+
+ return "", trace.Wrap(err)
+ }
+
+ return id, nil
+ }
+
+ return "", trace.LimitExceeded("failed to obtain host uuid")
+}
diff --git a/lib/utils/hostid/hostid_windows.go b/lib/utils/hostid/hostid_windows.go
new file mode 100644
index 0000000000000..ab2a5a55e56d7
--- /dev/null
+++ b/lib/utils/hostid/hostid_windows.go
@@ -0,0 +1,30 @@
+// Teleport
+// Copyright (C) 2024 Gravitational, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package hostid
+
+import "github.com/gravitational/trace"
+
+// WriteFile writes host UUID into a file
+func WriteFile(dataDir string, id string) error {
+ return trace.NotImplemented("host id writing is not supported on windows")
+}
+
+// ReadOrCreateFile looks for a hostid file in the data dir. If present,
+// returns the UUID from it, otherwise generates one
+func ReadOrCreateFile(dataDir string) (string, error) {
+ return "", trace.NotImplemented("host id writing is not supported on windows")
+}
diff --git a/lib/utils/utils.go b/lib/utils/utils.go
index b1931e2ae8cf4..5da5b39d05685 100644
--- a/lib/utils/utils.go
+++ b/lib/utils/utils.go
@@ -37,7 +37,6 @@ import (
"time"
"unicode"
- "github.com/google/uuid"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/validation"
@@ -468,75 +467,6 @@ func GetFreeTCPPorts(n int, offset ...int) (PortList, error) {
return PortList{ports: list}, nil
}
-// GetHostUUIDPath returns the path to the host UUID file given the data directory.
-func GetHostUUIDPath(dataDir string) string {
- return filepath.Join(dataDir, HostUUIDFile)
-}
-
-// HostUUIDExistsLocally checks if dataDir/host_uuid file exists in local storage.
-func HostUUIDExistsLocally(dataDir string) bool {
- _, err := ReadHostUUID(dataDir)
- return err == nil
-}
-
-// ReadHostUUID reads host UUID from the file in the data dir
-func ReadHostUUID(dataDir string) (string, error) {
- out, err := ReadPath(GetHostUUIDPath(dataDir))
- if err != nil {
- if errors.Is(err, fs.ErrPermission) {
- //do not convert to system error as this loses the ability to compare that it is a permission error
- return "", err
- }
- return "", trace.ConvertSystemError(err)
- }
- id := strings.TrimSpace(string(out))
- if id == "" {
- return "", trace.NotFound("host uuid is empty")
- }
- return id, nil
-}
-
-// WriteHostUUID writes host UUID into a file
-func WriteHostUUID(dataDir string, id string) error {
- err := os.WriteFile(GetHostUUIDPath(dataDir), []byte(id), os.ModeExclusive|0400)
- if err != nil {
- if errors.Is(err, fs.ErrPermission) {
- //do not convert to system error as this loses the ability to compare that it is a permission error
- return err
- }
- return trace.ConvertSystemError(err)
- }
- return nil
-}
-
-// ReadOrMakeHostUUID looks for a hostid file in the data dir. If present,
-// returns the UUID from it, otherwise generates one
-func ReadOrMakeHostUUID(dataDir string) (string, error) {
- id, err := ReadHostUUID(dataDir)
- if err == nil {
- return id, nil
- }
- if !trace.IsNotFound(err) {
- return "", trace.Wrap(err)
- }
- // Checking error instead of the usual uuid.New() in case uuid generation
- // fails due to not enough randomness. It's been known to happen happen when
- // Teleport starts very early in the node initialization cycle and /dev/urandom
- // isn't ready yet.
- rawID, err := uuid.NewRandom()
- if err != nil {
- return "", trace.BadParameter("" +
- "Teleport failed to generate host UUID. " +
- "This may happen if randomness source is not fully initialized when the node is starting up. " +
- "Please try restarting Teleport again.")
- }
- id = rawID.String()
- if err = WriteHostUUID(dataDir, id); err != nil {
- return "", trace.Wrap(err)
- }
- return id, nil
-}
-
// StringSliceSubset returns true if b is a subset of a.
func StringSliceSubset(a []string, b []string) error {
aset := make(map[string]bool)
@@ -712,8 +642,6 @@ const (
// CertExtensionAuthority specifies teleport authority's name
// that signed this domain
CertExtensionAuthority = "x-teleport-authority"
- // HostUUIDFile is the file name where the host UUID file is stored
- HostUUIDFile = "host_uuid"
// CertTeleportClusterName is a name of the teleport cluster
CertTeleportClusterName = "x-teleport-cluster-name"
// CertTeleportUserCertificate is the certificate of the authenticated in user.
diff --git a/lib/utils/utils_test.go b/lib/utils/utils_test.go
index 42ca172f35b78..e1625915bb204 100644
--- a/lib/utils/utils_test.go
+++ b/lib/utils/utils_test.go
@@ -20,14 +20,12 @@ package utils
import (
"bytes"
- "fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
- "github.com/google/uuid"
"github.com/gravitational/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -41,54 +39,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
-func TestHostUUIDIdempotent(t *testing.T) {
- t.Parallel()
-
- // call twice, get same result
- dir := t.TempDir()
- id, err := ReadOrMakeHostUUID(dir)
- require.Len(t, id, 36)
- require.NoError(t, err)
- uuidCopy, err := ReadOrMakeHostUUID(dir)
- require.NoError(t, err)
- require.Equal(t, id, uuidCopy)
-}
-
-func TestHostUUIDBadLocation(t *testing.T) {
- t.Parallel()
-
- // call with a read-only dir, make sure to get an error
- id, err := ReadOrMakeHostUUID("/bad-location")
- require.Empty(t, id)
- require.Error(t, err)
- require.Regexp(t, "^.*no such file or directory.*$", err.Error())
-}
-
-func TestHostUUIDIgnoreWhitespace(t *testing.T) {
- t.Parallel()
-
- // newlines are getting ignored
- dir := t.TempDir()
- id := fmt.Sprintf("%s\n", uuid.NewString())
- err := os.WriteFile(filepath.Join(dir, HostUUIDFile), []byte(id), 0666)
- require.NoError(t, err)
- out, err := ReadHostUUID(dir)
- require.NoError(t, err)
- require.Equal(t, strings.TrimSpace(id), out)
-}
-
-func TestHostUUIDRegenerateEmpty(t *testing.T) {
- t.Parallel()
-
- // empty UUID in file is regenerated
- dir := t.TempDir()
- err := os.WriteFile(filepath.Join(dir, HostUUIDFile), nil, 0666)
- require.NoError(t, err)
- out, err := ReadOrMakeHostUUID(dir)
- require.NoError(t, err)
- require.Len(t, out, 36)
-}
-
func TestSelfSignedCert(t *testing.T) {
t.Parallel()
diff --git a/tool/tctl/common/admin_action_test.go b/tool/tctl/common/admin_action_test.go
index 7e9ff52ceae3a..b910cf239b5ab 100644
--- a/tool/tctl/common/admin_action_test.go
+++ b/tool/tctl/common/admin_action_test.go
@@ -56,6 +56,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
tctl "github.com/gravitational/teleport/tool/tctl/common"
testserver "github.com/gravitational/teleport/tool/teleport/testenv"
tsh "github.com/gravitational/teleport/tool/tsh/common"
@@ -1076,7 +1077,7 @@ func newAdminActionTestSuite(t *testing.T) *adminActionTestSuite {
})
require.NoError(t, err)
- hostUUID, err := utils.ReadHostUUID(process.Config.DataDir)
+ hostUUID, err := hostid.ReadFile(process.Config.DataDir)
require.NoError(t, err)
localAdmin, err := storage.ReadLocalIdentity(
filepath.Join(process.Config.DataDir, teleport.ComponentProcess),
diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go
index b4d0883c8464b..6ee12979b73bc 100644
--- a/tool/tctl/common/tctl.go
+++ b/tool/tctl/common/tctl.go
@@ -52,6 +52,7 @@ import (
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
"github.com/gravitational/teleport/tool/common"
)
@@ -380,16 +381,16 @@ func ApplyConfig(ccf *GlobalCLIFlags, cfg *servicecfg.Config) (*authclient.Confi
authConfig := new(authclient.Config)
// read the host UUID only in case the identity was not provided,
// because it will be used for reading local auth server identity
- cfg.HostUUID, err = utils.ReadHostUUID(cfg.DataDir)
+ cfg.HostUUID, err = hostid.ReadFile(cfg.DataDir)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, trace.Wrap(err, "Could not load Teleport host UUID file at %s. "+
"Please make sure that a Teleport Auth Service instance is running on this host prior to using tctl or provide credentials by logging in with tsh first.",
- filepath.Join(cfg.DataDir, utils.HostUUIDFile))
+ filepath.Join(cfg.DataDir, hostid.FileName))
} else if errors.Is(err, fs.ErrPermission) {
return nil, trace.Wrap(err, "Teleport does not have permission to read Teleport host UUID file at %s. "+
"Ensure that you are running as a user with appropriate permissions or provide credentials by logging in with tsh first.",
- filepath.Join(cfg.DataDir, utils.HostUUIDFile))
+ filepath.Join(cfg.DataDir, hostid.FileName))
}
return nil, trace.Wrap(err)
}
diff --git a/tool/teleport/testenv/test_server.go b/tool/teleport/testenv/test_server.go
index 759abe0d56a4e..234ad8296792b 100644
--- a/tool/teleport/testenv/test_server.go
+++ b/tool/teleport/testenv/test_server.go
@@ -62,6 +62,7 @@ import (
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
+ "github.com/gravitational/teleport/lib/utils/hostid"
"github.com/gravitational/teleport/tool/teleport/common"
)
@@ -695,7 +696,7 @@ func MakeDefaultAuthClient(t *testing.T, process *service.TeleportProcess) *auth
t.Helper()
cfg := process.Config
- hostUUID, err := utils.ReadHostUUID(process.Config.DataDir)
+ hostUUID, err := hostid.ReadFile(process.Config.DataDir)
require.NoError(t, err)
identity, err := storage.ReadLocalIdentity(