From 922cebe94000fab1b41f9da237dd3c6af28a40e5 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Wed, 22 Jan 2025 12:01:09 +0000 Subject: [PATCH] Server Auto Discovery: allow pre-releases --- lib/srv/server/installer/autodiscover.go | 46 +++- lib/srv/server/installer/autodiscover_test.go | 252 +++++++++++++++--- lib/utils/packagemanager/apt.go | 32 ++- lib/utils/packagemanager/distro_info.go | 16 +- lib/utils/packagemanager/package_metadata.go | 35 +++ lib/utils/packagemanager/yum.go | 9 +- lib/utils/packagemanager/zypper.go | 18 +- 7 files changed, 333 insertions(+), 75 deletions(-) diff --git a/lib/srv/server/installer/autodiscover.go b/lib/srv/server/installer/autodiscover.go index 744acbd737ec4..aaa6e3c0fbc53 100644 --- a/lib/srv/server/installer/autodiscover.go +++ b/lib/srv/server/installer/autodiscover.go @@ -33,6 +33,7 @@ import ( "sort" "strings" + "github.com/coreos/go-semver/semver" "github.com/google/safetext/shsprintf" "github.com/gravitational/trace" @@ -87,9 +88,14 @@ type AutoDiscoverNodeInstallerConfig struct { // TokenName is the token name to be used by the instance to join the cluster. TokenName string - // aptPublicKeyEndpoint contains the URL for the APT public key. - // Defaults to: https://apt.releases.teleport.dev/gpg - aptPublicKeyEndpoint string + // defaultVersion is the version used to compute whether the production or development repositories should be used. + // If auto upgrades are enabled, then the defaultVersion is ignored. + // Defaults to api.SemVersion. + defaultVersion *semver.Version + + // aptRepoKeyEndpointOverride contains the URL for the APT public key. + // Used for testing. + aptRepoKeyEndpointOverride string // fsRootPrefix is the prefix to use when reading operating system information and when installing teleport. // Used for testing. @@ -109,6 +115,10 @@ func (c *AutoDiscoverNodeInstallerConfig) checkAndSetDefaults() error { return trace.BadParameter("install teleport config is required") } + if c.defaultVersion == nil { + c.defaultVersion = api.SemVersion + } + if c.fsRootPrefix == "" { c.fsRootPrefix = "/" } @@ -401,7 +411,7 @@ func (ani *AutoDiscoverNodeInstaller) installTeleportFromRepo(ctx context.Contex "version_id", linuxInfo.VersionID, ) - packageManager, err := packagemanager.PackageManagerForSystem(linuxInfo, ani.fsRootPrefix, ani.binariesLocation, ani.aptPublicKeyEndpoint) + packageManager, err := packagemanager.PackageManagerForSystem(linuxInfo, ani.fsRootPrefix, ani.binariesLocation, ani.aptRepoKeyEndpointOverride) if err != nil { return trace.Wrap(err) } @@ -423,7 +433,8 @@ func (ani *AutoDiscoverNodeInstaller) installTeleportFromRepo(ctx context.Contex } packagesToInstall = append(packagesToInstall, packagemanager.PackageVersion{Name: ani.TeleportPackage, Version: targetVersion}) - if err := packageManager.AddTeleportRepository(ctx, linuxInfo, ani.RepositoryChannel); err != nil { + productionRepo := useProductionRepo(packagesToInstall, ani.defaultVersion) + if err := packageManager.AddTeleportRepository(ctx, linuxInfo, ani.RepositoryChannel, productionRepo); err != nil { return trace.BadParameter("failed to add teleport repository to system: %v", err) } if err := packageManager.InstallPackages(ctx, packagesToInstall); err != nil { @@ -433,6 +444,31 @@ func (ani *AutoDiscoverNodeInstaller) installTeleportFromRepo(ctx context.Contex return nil } +// useProductionRepo returns whether this is a production installation. +// In case of an error, it returns true to default to ensure only production binaries are available. +func useProductionRepo(packagesToInstall []packagemanager.PackageVersion, defaultVersion *semver.Version) bool { + for _, p := range packagesToInstall { + if p.Version == "" { + continue + } + + ver, err := semver.NewVersion(p.Version) + if err != nil { + return true + } + + if ver.PreRelease != "" { + return false + } + } + + if defaultVersion.PreRelease != "" { + return false + } + + return true +} + func (ani *AutoDiscoverNodeInstaller) getIMDSClient(ctx context.Context) (imds.Client, error) { // detect and fetch cloud provider metadata imdsClient, err := cloud.DiscoverInstanceMetadata(ctx, ani.imdsProviders) diff --git a/lib/srv/server/installer/autodiscover_test.go b/lib/srv/server/installer/autodiscover_test.go index 6c70173b91514..8673de3533922 100644 --- a/lib/srv/server/installer/autodiscover_test.go +++ b/lib/srv/server/installer/autodiscover_test.go @@ -29,6 +29,7 @@ import ( "testing" "github.com/buildkite/bintest/v3" + "github.com/coreos/go-semver/semver" "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -101,10 +102,26 @@ func (m *mockGCPInstanceGetter) GetInstanceTags(ctx context.Context, req *gcp.In func TestAutoDiscoverNode(t *testing.T) { ctx := context.Background() + productionVersion := &semver.Version{ + Major: 18, + Minor: 0, + Patch: 0, + } + developmentVersion := &semver.Version{ + Major: 18, + Minor: 0, + Patch: 0, + PreRelease: "alpha-1", + } - mockRepoKeys := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("my-public-key")) + mockRepo := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "gpg" { + w.WriteHeader(http.StatusOK) + w.Write([]byte("my-public-key")) + return + } + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("bad request")) })) mockBins, binariesLocation, releaseMockedBinsFN := buildMockBins(t) @@ -190,10 +207,11 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -223,7 +241,7 @@ func TestAutoDiscoverNode(t *testing.T) { c.Exit(0) }) case "sles": - mockBins["rpm"].Expect("--import", packagemanager.ZypperPublicKeyEndpoint) + mockBins["rpm"].Expect("--import", "https://zypper.releases.teleport.dev/gpg") mockBins["rpm"].Expect("--eval", bintest.MatchAny()) mockBins["zypper"].Expect("--non-interactive", "addrepo", bintest.MatchAny()) mockBins["zypper"].Expect("--gpg-auto-import-keys", "refresh") @@ -257,6 +275,12 @@ func TestAutoDiscoverNode(t *testing.T) { } require.FileExists(t, testTempDir+"/etc/teleport.yaml") require.FileExists(t, testTempDir+"/etc/teleport.yaml.discover") + + if distroName == "ubuntu" || distroName == "debian" { + teleportRepoFile, err := os.ReadFile(testTempDir + "/etc/apt/sources.list.d/teleport.list") + require.NoError(t, err) + require.Contains(t, string(teleportRepoFile), "https://apt.releases.teleport.dev/") + } }) } } @@ -287,11 +311,12 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, - autoUpgradesChannelURL: proxyServer.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + autoUpgradesChannelURL: proxyServer.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -335,6 +360,157 @@ func TestAutoDiscoverNode(t *testing.T) { } require.FileExists(t, testTempDir+"/etc/teleport.yaml") require.FileExists(t, testTempDir+"/etc/teleport.yaml.discover") + teleportRepoFile, err := os.ReadFile(testTempDir + "/etc/apt/sources.list.d/teleport.list") + require.NoError(t, err) + require.Contains(t, string(teleportRepoFile), "https://apt.releases.teleport.dev/ubuntu") + }) + + t.Run("with automatic upgrades using a development version, installs the development repositories", func(t *testing.T) { + distroConfig := wellKnownOS["ubuntu"]["24.04"] + + proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // We only expect calls to the automatic upgrade default channel's version endpoint. + w.WriteHeader(http.StatusOK) + w.Write([]byte("v15.4.0-alpha-2\n")) + })) + t.Cleanup(func() { + proxyServer.Close() + }) + proxyPublicAddr := proxyServer.Listener.Addr().String() + + testTempDir := t.TempDir() + + setupDirsForTest(t, testTempDir, distroConfig) + + installerConfig := &AutoDiscoverNodeInstallerConfig{ + RepositoryChannel: "stable/rolling", + AutoUpgrades: true, + ProxyPublicAddr: proxyPublicAddr, + TeleportPackage: "teleport-ent", + TokenName: "my-token", + AzureClientID: "azure-client-id", + + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + autoUpgradesChannelURL: proxyServer.URL, + defaultVersion: developmentVersion, + } + + teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) + require.NoError(t, err) + + // One of the first things the install command does is to check if teleport is already installed. + // If so, it stops the installation with success. + // Given that we are mocking the binary, it means it already exists and as such, the installation will stop. + // To prevent that, we must rename the file, call ` install teleport` and rename it back. + teleportInitialPath := mockBins["teleport"].Path + teleportHiddenPath := teleportInitialPath + "-hidden" + require.NoError(t, os.Rename(teleportInitialPath, teleportHiddenPath)) + + mockBins["apt-get"].Expect("update") + mockBins["apt-get"].Expect("install", "-y", "teleport-ent-updater=15.4.0-alpha-2", "teleport-ent=15.4.0-alpha-2").AndCallFunc(func(c *bintest.Call) { + assert.NoError(t, os.Rename(teleportHiddenPath, teleportInitialPath)) + c.Exit(0) + }) + + mockBins["teleport"].Expect("node", + "configure", + "--output=file://"+testTempDir+"/etc/teleport.yaml.new", + "--proxy="+proxyPublicAddr, + "--join-method=azure", + "--token=my-token", + "--labels=teleport.internal/region=eastus,teleport.internal/resource-group=TestGroup,teleport.internal/subscription-id=5187AF11-3581-4AB6-A654-59405CD40C44,teleport.internal/vm-id=ED7DAC09-6E73-447F-BD18-AF4D1196C1E4", + "--azure-client-id=azure-client-id", + ).AndCallFunc(func(c *bintest.Call) { + // create a teleport.yaml configuration file + require.NoError(t, os.WriteFile(testTempDir+"/etc/teleport.yaml.new", []byte("teleport.yaml configuration bytes"), 0o644)) + c.Exit(0) + }) + + mockBins["systemctl"].Expect("enable", "teleport") + mockBins["systemctl"].Expect("restart", "teleport") + + require.NoError(t, teleportInstaller.Install(ctx)) + + for binName, mockBin := range mockBins { + require.True(t, mockBin.Check(t), "mismatch between expected invocations and actual calls for %q", binName) + } + require.FileExists(t, testTempDir+"/etc/teleport.yaml") + require.FileExists(t, testTempDir+"/etc/teleport.yaml.discover") + + teleportRepoFile, err := os.ReadFile(testTempDir + "/etc/apt/sources.list.d/teleport.list") + require.NoError(t, err) + require.Contains(t, string(teleportRepoFile), "https://apt.releases.development.teleport.dev/ubuntu") + }) + + t.Run("installs the development repositories when the current version is a dev build", func(t *testing.T) { + testTempDir := t.TempDir() + distroConfig := wellKnownOS["ubuntu"]["24.04"] + // Common folders to all distros + setupDirsForTest(t, testTempDir, distroConfig) + + installerConfig := &AutoDiscoverNodeInstallerConfig{ + RepositoryChannel: "stable/rolling", + AutoUpgrades: false, + ProxyPublicAddr: "proxy.example.com", + TeleportPackage: "teleport", + TokenName: "my-token", + AzureClientID: "azure-client-id", + + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, + } + + teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) + require.NoError(t, err) + + // One of the first things the install command does is to check if teleport is already installed. + // If so, it stops the installation with success. + // Given that we are mocking the binary, it means it already exists and as such, the installation will stop. + // To prevent that, we must rename the file, call ` install teleport` and rename it back. + teleportInitialPath := mockBins["teleport"].Path + teleportHiddenPath := teleportInitialPath + "-hidden" + require.NoError(t, os.Rename(teleportInitialPath, teleportHiddenPath)) + + mockBins["apt-get"].Expect("update") + mockBins["apt-get"].Expect("install", "-y", "teleport").AndCallFunc(func(c *bintest.Call) { + assert.NoError(t, os.Rename(teleportHiddenPath, teleportInitialPath)) + c.Exit(0) + }) + + mockBins["teleport"].Expect("node", + "configure", + "--output=file://"+testTempDir+"/etc/teleport.yaml.new", + "--proxy=proxy.example.com", + "--join-method=azure", + "--token=my-token", + "--labels=teleport.internal/region=eastus,teleport.internal/resource-group=TestGroup,teleport.internal/subscription-id=5187AF11-3581-4AB6-A654-59405CD40C44,teleport.internal/vm-id=ED7DAC09-6E73-447F-BD18-AF4D1196C1E4", + "--azure-client-id=azure-client-id", + ).AndCallFunc(func(c *bintest.Call) { + // create a teleport.yaml configuration file + require.NoError(t, os.WriteFile(testTempDir+"/etc/teleport.yaml.new", []byte("teleport.yaml configuration bytes"), 0o644)) + c.Exit(0) + }) + + mockBins["systemctl"].Expect("enable", "teleport") + mockBins["systemctl"].Expect("restart", "teleport") + + require.NoError(t, teleportInstaller.Install(ctx)) + + for binName, mockBin := range mockBins { + require.True(t, mockBin.Check(t), "mismatch between expected invocations and actual calls for %q", binName) + } + require.FileExists(t, testTempDir+"/etc/teleport.yaml") + require.FileExists(t, testTempDir+"/etc/teleport.yaml.discover") + + teleportRepoFile, err := os.ReadFile(testTempDir + "/etc/apt/sources.list.d/teleport.list") + require.NoError(t, err) + require.Contains(t, string(teleportRepoFile), "https://apt.releases.teleport.dev/") }) t.Run("gcp adds a label with the project id", func(t *testing.T) { @@ -372,10 +548,11 @@ func TestAutoDiscoverNode(t *testing.T) { TeleportPackage: "teleport", TokenName: "my-token", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -444,8 +621,9 @@ func TestAutoDiscoverNode(t *testing.T) { return &imds.DisabledClient{}, nil }, }, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -476,10 +654,11 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -542,10 +721,11 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -604,10 +784,11 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) @@ -666,10 +847,11 @@ func TestAutoDiscoverNode(t *testing.T) { TokenName: "my-token", AzureClientID: "azure-client-id", - fsRootPrefix: testTempDir, - imdsProviders: mockIMDSProviders, - binariesLocation: binariesLocation, - aptPublicKeyEndpoint: mockRepoKeys.URL, + fsRootPrefix: testTempDir, + imdsProviders: mockIMDSProviders, + binariesLocation: binariesLocation, + aptRepoKeyEndpointOverride: mockRepo.URL, + defaultVersion: productionVersion, } teleportInstaller, err := NewAutoDiscoverNodeInstaller(installerConfig) diff --git a/lib/utils/packagemanager/apt.go b/lib/utils/packagemanager/apt.go index 591971e2774a2..607d821443c0b 100644 --- a/lib/utils/packagemanager/apt.go +++ b/lib/utils/packagemanager/apt.go @@ -36,9 +36,6 @@ import ( ) const ( - productionAPTPublicKeyEndpoint = "https://apt.releases.teleport.dev/gpg" - aptRepoEndpoint = "https://apt.releases.teleport.dev/" - aptTeleportSourceListFileRelative = "/etc/apt/sources.list.d/teleport.list" aptKeyringsLocation = "/etc/apt/keyrings" @@ -62,10 +59,13 @@ type APT struct { // APTConfig contains the configurable fields for setting up the APT package manager. type APTConfig struct { - logger *slog.Logger - aptPublicKeyEndpoint string - fsRootPrefix string - bins BinariesLocation + logger *slog.Logger + fsRootPrefix string + bins BinariesLocation + + // aptRepoKeyEndpointOverride is used to override the endpoint used to fetch the repository public key. + // Only for tests. + aptRepoKeyEndpointOverride string } // CheckAndSetDefaults checks and sets default config values. @@ -74,10 +74,6 @@ func (p *APTConfig) CheckAndSetDefaults() error { return trace.BadParameter("config is required") } - if p.aptPublicKeyEndpoint == "" { - p.aptPublicKeyEndpoint = productionAPTPublicKeyEndpoint - } - p.bins.CheckAndSetDefaults() if p.fsRootPrefix == "" { @@ -115,10 +111,18 @@ func NewAPTLegacy(cfg *APTConfig) (*APT, error) { } // AddTeleportRepository adds the Teleport repository to the current system. -func (pm *APT) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string) error { - pm.logger.InfoContext(ctx, "Fetching Teleport repository key", "endpoint", pm.aptPublicKeyEndpoint) +func (pm *APT) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string, productionRepo bool) error { + aptRepoEndpoint, aptRepoKeyEndpoint, err := repositoryEndpoint(productionRepo, apt) + if err != nil { + return trace.Wrap(err) + } + if pm.aptRepoKeyEndpointOverride != "" { + aptRepoKeyEndpoint = pm.aptRepoKeyEndpointOverride + } + + pm.logger.InfoContext(ctx, "Fetching Teleport repository key", "endpoint", aptRepoKeyEndpoint) - resp, err := pm.httpClient.Get(pm.aptPublicKeyEndpoint) + resp, err := pm.httpClient.Get(aptRepoKeyEndpoint) if err != nil { return trace.Wrap(err) } diff --git a/lib/utils/packagemanager/distro_info.go b/lib/utils/packagemanager/distro_info.go index 3cffb750e985b..684324702ed93 100644 --- a/lib/utils/packagemanager/distro_info.go +++ b/lib/utils/packagemanager/distro_info.go @@ -26,7 +26,7 @@ import ( ) // PackageManagerForSystem returns the PackageManager for the current detected linux distro. -func PackageManagerForSystem(osRelease *linux.OSRelease, fsRootPrefix string, binariesLocation BinariesLocation, aptPubKeyEndpoint string) (PackageManager, error) { +func PackageManagerForSystem(osRelease *linux.OSRelease, fsRootPrefix string, binariesLocation BinariesLocation, aptRepoEndpointOverride string) (PackageManager, error) { aptWellKnownIDs := []string{"debian", "ubuntu"} legacyAPT := []string{"xenial", "trusty"} @@ -38,9 +38,9 @@ func PackageManagerForSystem(osRelease *linux.OSRelease, fsRootPrefix string, bi case slices.Contains(aptWellKnownIDs, osRelease.ID): if slices.Contains(legacyAPT, osRelease.VersionCodename) { pm, err := NewAPTLegacy(&APTConfig{ - fsRootPrefix: fsRootPrefix, - bins: binariesLocation, - aptPublicKeyEndpoint: aptPubKeyEndpoint, + fsRootPrefix: fsRootPrefix, + bins: binariesLocation, + aptRepoKeyEndpointOverride: aptRepoEndpointOverride, }) if err != nil { return nil, trace.Wrap(err) @@ -49,9 +49,9 @@ func PackageManagerForSystem(osRelease *linux.OSRelease, fsRootPrefix string, bi } pm, err := NewAPT(&APTConfig{ - fsRootPrefix: fsRootPrefix, - bins: binariesLocation, - aptPublicKeyEndpoint: aptPubKeyEndpoint, + fsRootPrefix: fsRootPrefix, + bins: binariesLocation, + aptRepoKeyEndpointOverride: aptRepoEndpointOverride, }) if err != nil { return nil, trace.Wrap(err) @@ -86,7 +86,7 @@ func PackageManagerForSystem(osRelease *linux.OSRelease, fsRootPrefix string, bi // PackageManager describes the required methods to implement a package manager. type PackageManager interface { // AddTeleportRepository adds the Teleport repository using a specific channel. - AddTeleportRepository(ctx context.Context, ldi *linux.OSRelease, repoChannel string) error + AddTeleportRepository(ctx context.Context, ldi *linux.OSRelease, repoChannel string, productionRepo bool) error // InstallPackages installs a list of packages. // If a PackageVersion does not contain the version, then it will install the latest available. InstallPackages(context.Context, []PackageVersion) error diff --git a/lib/utils/packagemanager/package_metadata.go b/lib/utils/packagemanager/package_metadata.go index 5d7e320a9fc08..a35d283c5bdd0 100644 --- a/lib/utils/packagemanager/package_metadata.go +++ b/lib/utils/packagemanager/package_metadata.go @@ -16,9 +16,44 @@ package packagemanager +import ( + "fmt" + "slices" + + "github.com/gravitational/trace" +) + // PackageVersion contains the package name and its version. // Version can be empty. type PackageVersion struct { Name string Version string } + +const ( + apt = "apt" + yum = "yum" + zypper = "zypper" +) + +var ( + supportedPackageManagers = []string{apt, yum, zypper} +) + +// repositoryEndpoint returns the Teleport repository and public key endpoints for the package manager. +// The URL has a trailing slash. +// An error is returned when the package manager is not supported. +func repositoryEndpoint(productionRepo bool, packageManager string) (string, string, error) { + if !slices.Contains(supportedPackageManagers, packageManager) { + return "", "", trace.BadParameter("invalid package manager, only %v are supported", supportedPackageManagers) + } + + releasesPath := "releases" + if !productionRepo { + releasesPath = "releases.development" + } + + repositoryEndpoint := fmt.Sprintf("https://%s.%s.teleport.dev/", packageManager, releasesPath) + + return repositoryEndpoint, repositoryEndpoint + "gpg", nil +} diff --git a/lib/utils/packagemanager/yum.go b/lib/utils/packagemanager/yum.go index 591921ff83044..114059609207d 100644 --- a/lib/utils/packagemanager/yum.go +++ b/lib/utils/packagemanager/yum.go @@ -29,8 +29,6 @@ import ( "github.com/gravitational/teleport/lib/linux" ) -const yumRepoEndpoint = "https://yum.releases.teleport.dev/" - var ( // yumDistroMap maps distro IDs that teleport doesn't officially support but are known to work. // The key is the not-officially-supported distro ID and the value is the most similar distro. @@ -81,7 +79,12 @@ func NewYUM(cfg *YUMConfig) (*YUM, error) { } // AddTeleportRepository adds the Teleport repository to the current system. -func (pm *YUM) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string) error { +func (pm *YUM) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string, productionRepo bool) error { + yumRepoEndpoint, _, err := repositoryEndpoint(productionRepo, yum) + if err != nil { + return trace.Wrap(err) + } + distroID := cmp.Or(yumDistroMap[linuxInfo.ID], linuxInfo.ID) // Teleport repo only targets the major version of the target distros. diff --git a/lib/utils/packagemanager/zypper.go b/lib/utils/packagemanager/zypper.go index 45b2197d29343..be498aa3bda07 100644 --- a/lib/utils/packagemanager/zypper.go +++ b/lib/utils/packagemanager/zypper.go @@ -28,13 +28,6 @@ import ( "github.com/gravitational/teleport/lib/linux" ) -const ( - // ZypperPublicKeyEndpoint is the endpoint that contains the Teleport's GPG production Key. - ZypperPublicKeyEndpoint = "https://zypper.releases.teleport.dev/gpg" - // zypperRepoEndpoint is the repo endpoint for Zypper based distros. - zypperRepoEndpoint = "https://zypper.releases.teleport.dev/" -) - // Zypper is a wrapper for apt package manager. // This package manager is used in OpenSUSE/SLES and distros based on this distribution. type Zypper struct { @@ -76,7 +69,12 @@ func NewZypper(cfg *ZypperConfig) (*Zypper, error) { } // AddTeleportRepository adds the Teleport repository to the current system. -func (pm *Zypper) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string) error { +func (pm *Zypper) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OSRelease, repoChannel string, productionRepo bool) error { + zypperRepoEndpoint, zypperRepoKeyEndpoint, err := repositoryEndpoint(productionRepo, zypper) + if err != nil { + return trace.Wrap(err) + } + // Teleport repo only targets the major version of the target distros. versionID := strings.Split(linuxInfo.VersionID, ".")[0] @@ -84,8 +82,8 @@ func (pm *Zypper) AddTeleportRepository(ctx context.Context, linuxInfo *linux.OS versionID = "15" // tumbleweed uses dated VERSION_IDs like 20230702 } - pm.logger.InfoContext(ctx, "Trusting Teleport repository key", "command", "rpm --import "+ZypperPublicKeyEndpoint) - importPublicKeyCMD := exec.CommandContext(ctx, pm.bins.Rpm, "--import", ZypperPublicKeyEndpoint) + pm.logger.InfoContext(ctx, "Trusting Teleport repository key", "command", "rpm --import "+zypperRepoKeyEndpoint) + importPublicKeyCMD := exec.CommandContext(ctx, pm.bins.Rpm, "--import", zypperRepoKeyEndpoint) importPublicKeyCMDOutput, err := importPublicKeyCMD.CombinedOutput() if err != nil { return trace.Wrap(err, string(importPublicKeyCMDOutput))