diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod
index 6309482e9588f..0265236f2ffb6 100644
--- a/integrations/event-handler/go.mod
+++ b/integrations/event-handler/go.mod
@@ -163,7 +163,6 @@ require (
github.com/google/go-tpm-tools v0.4.4 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
- github.com/google/renameio/v2 v2.0.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum
index 6cf606c8ad697..f7d9d02875a0a 100644
--- a/integrations/event-handler/go.sum
+++ b/integrations/event-handler/go.sum
@@ -1114,8 +1114,6 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
-github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f h1:o2yGZLlsOj5H5uvtQNEdi6DeA0GbUP3lm0gWW5RvY0s=
diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod
index 030b29ba08341..c9459a5381189 100644
--- a/integrations/terraform/go.mod
+++ b/integrations/terraform/go.mod
@@ -184,7 +184,6 @@ require (
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
- github.com/google/renameio/v2 v2.0.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum
index 95ef579188b65..257c076fd1e5d 100644
--- a/integrations/terraform/go.sum
+++ b/integrations/terraform/go.sum
@@ -1252,6 +1252,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
+github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
diff --git a/lib/service/service.go b/lib/service/service.go
index 215fdb0035f00..991d96ce587d4 100644
--- a/lib/service/service.go
+++ b/lib/service/service.go
@@ -164,6 +164,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"
@@ -2932,7 +2933,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)
}
@@ -4437,7 +4438,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)
}
@@ -6496,7 +6497,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.
@@ -6535,14 +6536,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)
}
@@ -6561,7 +6562,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)
}
@@ -6574,7 +6575,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 ec596200d1edc..e934e3160e02e 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) {
@@ -1167,7 +1168,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 5719a56060509..beade253ab3d2 100644
--- a/lib/srv/regular/sshserver.go
+++ b/lib/srv/regular/sshserver.go
@@ -71,6 +71,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"
)
var log = logrus.WithFields(logrus.Fields{
@@ -724,7 +725,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..d8ce46089b68a
--- /dev/null
+++ b/lib/utils/hostid/hostid.go
@@ -0,0 +1,144 @@
+// 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"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/google/renameio/v2"
+ "github.com/google/uuid"
+ "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 "", err
+ }
+ return "", trace.ConvertSystemError(err)
+ }
+ id := strings.TrimSpace(string(out))
+ if id == "" {
+ return "", trace.NotFound("host uuid is empty")
+ }
+ return id, nil
+}
+
+// WriteFile writes host UUID into a file
+func WriteFile(dataDir string, id string) error {
+ err := os.WriteFile(GetPath(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
+}
+
+// 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) {
+ hostUUIDFileName := GetPath(dataDir)
+ hostUUIDFileLock := hostUUIDFileName + ".lock"
+ iterationLimit := 3
+
+ for i := 0; i < iterationLimit; i++ {
+ id, err := ReadFile(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()
+
+ writeFile := func() (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 := renameio.WriteFile(hostUUIDFileName, []byte(id), 0o400); err != nil {
+ return "", trace.Wrap(err)
+ }
+
+ return id, nil
+ }
+
+ id, err = writeFile()
+ if err != nil {
+ if errors.Is(err, fs.ErrPermission) || errors.Is(err, utils.ErrUnsuccessfulLockTry) {
+ time.Sleep(10 * 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_test.go b/lib/utils/hostid/hostid_test.go
new file mode 100644
index 0000000000000..63216fdce509a
--- /dev/null
+++ b/lib/utils/hostid/hostid_test.go
@@ -0,0 +1,111 @@
+// 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"
+ "slices"
+ "strings"
+ "testing"
+
+ "github.com/google/uuid"
+ "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++ {
+ wg.Go(func() error {
+ <-barrier
+ id, err := hostid.ReadOrCreateFile(dir)
+ ids[i] = id
+ return err
+ })
+ }
+
+ close(barrier)
+
+ require.NoError(t, wg.Wait())
+ require.Equal(t, slices.Repeat([]string{ids[0]}, concurrency), ids)
+}
+
+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/utils.go b/lib/utils/utils.go
index 1aafb1daf418b..5da5b39d05685 100644
--- a/lib/utils/utils.go
+++ b/lib/utils/utils.go
@@ -37,8 +37,6 @@ import (
"time"
"unicode"
- "github.com/google/renameio/v2"
- "github.com/google/uuid"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/validation"
@@ -469,113 +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) {
- hostUUIDFileName := GetHostUUIDPath(dataDir)
- hostUUIDFileLock := hostUUIDFileName + ".lock"
- iterationLimit := 3
-
- for i := 0; i < iterationLimit; i++ {
- 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()
-
- writeFile := func() (string, error) {
- unlock, err := FSTryWriteLock(hostUUIDFileLock)
- if err != nil {
- return "", trace.Wrap(err)
- }
- defer unlock()
-
- if read, err := ReadHostUUID(dataDir); err == nil {
- return read, nil
- } else if !trace.IsNotFound(err) {
- return "", trace.Wrap(err)
- }
-
- if err := renameio.WriteFile(hostUUIDFileName, []byte(id), 0o400); err != nil {
- return "", trace.Wrap(err)
- }
-
- return id, nil
- }
-
- id, err = writeFile()
- if err != nil {
- if errors.Is(err, fs.ErrPermission) || errors.Is(err, ErrUnsuccessfulLockTry) {
- time.Sleep(10 * time.Millisecond)
- continue
- }
-
- return "", trace.Wrap(err)
- }
-
- return id, nil
- }
-
- return "", trace.LimitExceeded("failed to obtain host uuid")
-}
-
// StringSliceSubset returns true if b is a subset of a.
func StringSliceSubset(a []string, b []string) error {
aset := make(map[string]bool)
@@ -751,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 c2c897ed10613..cf636c4a65f8a 100644
--- a/lib/utils/utils_test.go
+++ b/lib/utils/utils_test.go
@@ -20,19 +20,15 @@ package utils
import (
"bytes"
- "fmt"
"os"
"path/filepath"
- "slices"
"strings"
"testing"
"time"
- "github.com/google/uuid"
"github.com/gravitational/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- errgroup "golang.org/x/sync/errgroup"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/fixtures"
@@ -44,79 +40,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
-func TestReadOrMakeHostUUID(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++ {
- wg.Go(func() error {
- <-barrier
- id, err := ReadOrMakeHostUUID(dir)
- ids[i] = id
- return err
- })
- }
-
- close(barrier)
-
- require.NoError(t, wg.Wait())
- require.Equal(t, slices.Repeat([]string{ids[0]}, concurrency), ids)
-}
-
-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 dcd3642774b5d..bc46a7886cbd2 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 48ad1f0b75b6d..651a8b13c2aec 100644
--- a/tool/tctl/common/tctl.go
+++ b/tool/tctl/common/tctl.go
@@ -53,6 +53,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"
)
@@ -382,16 +383,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 21430d058329b..f2564c8be64de 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"
)
@@ -703,7 +704,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(