Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
430 changes: 0 additions & 430 deletions assets/install-scripts/install.sh

This file was deleted.

1 change: 1 addition & 0 deletions assets/install-scripts/install.sh
2 changes: 1 addition & 1 deletion lib/srv/server/installer/defaultinstallers.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func oneoffScriptToDefaultInstaller() *types.InstallerV1 {
}

script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Teleport is installed and running.",
TeleportCommandPrefix: oneoff.PrefixSUDO,
})
Expand Down
5 changes: 5 additions & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,11 @@ func (h *Handler) bindDefaultEndpoints() {
h.GET("/webapi/tokens", h.WithAuth(h.getTokens))
h.DELETE("/webapi/tokens", h.WithAuth(h.deleteToken))

// install script, the ':token' wildcard is a hack to make the router happy and support
// the token-less route "/scripts/install.sh".
// h.installScriptHandle Will reject any unknown sub-route.
h.GET("/scripts/:token", h.WithHighLimiter(h.installScriptHandle))

// join scripts
h.GET("/scripts/:token/install-node.sh", h.WithLimiter(h.getNodeJoinScriptHandle))
h.GET("/scripts/:token/install-app.sh", h.WithLimiter(h.getAppJoinScriptHandle))
Expand Down
2 changes: 1 addition & 1 deletion lib/web/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8538,9 +8538,9 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula
},
)
handler.handler.cfg.ProxyKubeAddr = utils.FromAddr(kubeProxyAddr)
handler.handler.cfg.PublicProxyAddr = webServer.Listener.Addr().String()
Copy link
Copy Markdown
Contributor Author

@hugoShaka hugoShaka Feb 14, 2025

Choose a reason for hiding this comment

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

About this change: this was a nasty bug in the web test pack.
The public proxy address is supposed to be "hostname:port" but the test pack was putting a URL in there. The value was correctly set in real-world builds.

In teleport's codebase, Addr should always mean hostname:port.

url, err := url.Parse("https://" + webServer.Listener.Addr().String())
require.NoError(t, err)
handler.handler.cfg.PublicProxyAddr = url.String()

return &testProxy{
clock: clock,
Expand Down
4 changes: 2 additions & 2 deletions lib/web/autoupdate_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (h *Handler) autoUpdateAgentVersion(ctx context.Context, group, updaterUUID
rollout, err := h.cfg.AccessPoint.GetAutoUpdateAgentRollout(ctx)
if err != nil {
// Fallback to channels if there is no autoupdate_agent_rollout.
if trace.IsNotFound(err) {
if trace.IsNotFound(err) || trace.IsNotImplemented(err) {
return getVersionFromChannel(ctx, h.cfg.AutomaticUpgradesChannels, group)
}
// Something is broken, we don't want to fallback to channels, this would be harmful.
Expand Down Expand Up @@ -77,7 +77,7 @@ func (h *Handler) autoUpdateAgentShouldUpdate(ctx context.Context, group, update
rollout, err := h.cfg.AccessPoint.GetAutoUpdateAgentRollout(ctx)
if err != nil {
// Fallback to channels if there is no autoupdate_agent_rollout.
if trace.IsNotFound(err) {
if trace.IsNotFound(err) || trace.IsNotImplemented(err) {
// Updaters using the RFD184 API are not aware of maintenance windows
// like RFD109 updaters are. To have both updaters adopt the same behavior
// we must do the CMC window lookup for them.
Expand Down
14 changes: 7 additions & 7 deletions lib/web/integrations_awsoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ func (h *Handler) awsOIDCConfigureDeployServiceIAM(w http.ResponseWriter, r *htt
fmt.Sprintf("--aws-account-id=%s", shsprintf.EscapeDefaultContext(awsAccountID)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete the database enrollment.",
})
if err != nil {
Expand Down Expand Up @@ -633,7 +633,7 @@ func (h *Handler) awsOIDCConfigureAWSAppAccessIAM(w http.ResponseWriter, r *http
fmt.Sprintf("--role=%s", shsprintf.EscapeDefaultContext(role)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to use AWS App Access.",
})
if err != nil {
Expand Down Expand Up @@ -704,7 +704,7 @@ func (h *Handler) awsOIDCConfigureEC2SSMIAM(w http.ResponseWriter, r *http.Reque
fmt.Sprintf("--aws-account-id=%s", shsprintf.EscapeDefaultContext(awsAccountID)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to finish the EC2 auto discover set up.",
})
if err != nil {
Expand Down Expand Up @@ -745,7 +745,7 @@ func (h *Handler) awsOIDCConfigureEKSIAM(w http.ResponseWriter, r *http.Request,
fmt.Sprintf("--aws-account-id=%s", shsprintf.EscapeDefaultContext(awsAccountID)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete the EKS enrollment.",
})
if err != nil {
Expand Down Expand Up @@ -1252,7 +1252,7 @@ func (h *Handler) awsOIDCConfigureIdP(w http.ResponseWriter, r *http.Request, p
}

script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to use the integration with AWS.",
})
if err != nil {
Expand Down Expand Up @@ -1293,7 +1293,7 @@ func (h *Handler) awsOIDCConfigureListDatabasesIAM(w http.ResponseWriter, r *htt
fmt.Sprintf("--aws-account-id=%s", shsprintf.EscapeDefaultContext(awsAccountID)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete the Database enrollment.",
})
if err != nil {
Expand Down Expand Up @@ -1339,7 +1339,7 @@ func (h *Handler) awsAccessGraphOIDCSync(w http.ResponseWriter, r *http.Request,
fmt.Sprintf("--aws-account-id=%s", shsprintf.EscapeDefaultContext(awsAccountID)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete the Access Graph AWS Sync enrollment.",
})
if err != nil {
Expand Down
12 changes: 6 additions & 6 deletions lib/web/integrations_awsoidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestBuildDeployServiceConfigureIAMScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down Expand Up @@ -304,7 +304,7 @@ func TestBuildEC2SSMIAMScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down Expand Up @@ -379,7 +379,7 @@ func TestBuildAWSAppAccessConfigureIAMScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down Expand Up @@ -482,7 +482,7 @@ func TestBuildEKSConfigureIAMScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down Expand Up @@ -614,7 +614,7 @@ func TestBuildAWSOIDCIdPConfigureScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down Expand Up @@ -717,7 +717,7 @@ func TestBuildListDatabasesConfigureIAMScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down
2 changes: 1 addition & 1 deletion lib/web/integrations_azureoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (h *Handler) azureOIDCConfigure(w http.ResponseWriter, r *http.Request, p h
}

script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to use the integration with Azure.",
})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion lib/web/integrations_azureoidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestAzureOIDCConfigureScript(t *testing.T) {
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("teleportArgs='%s'\n", tc.expectedTeleportArgs),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
Expand Down
2 changes: 1 addition & 1 deletion lib/web/integrations_samlidp.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (h *Handler) gcpWorkforceConfigScript(w http.ResponseWriter, r *http.Reques
fmt.Sprintf("--idp-metadata-url=%s", shsprintf.EscapeDefaultContext(samlIdPMetadataURL)),
}
script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
TeleportArgs: strings.Join(argsList, " "),
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete enrolling this workforce pool to Teleport SAML Identity Provider.",
})
if err != nil {
Expand Down
154 changes: 154 additions & 0 deletions lib/web/scripts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Teleport
* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
*/

package web

import (
"context"
"fmt"
"net/http"
"os"

"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/utils/teleportassets"
"github.com/gravitational/teleport/lib/web/scripts"
)

// installScriptHandle handles calls for "/scripts/install.sh" and responds with a bash script installing Teleport
// by downloading and running `teleport-update`. This installation script does not start the agent, join it,
// or configure its services. This is handled by the "/scripts/:token/install-*.sh" scripts.
func (h *Handler) installScriptHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params) (any, error) {
// This is a hack because the router is not allowing us to register "/scripts/install.sh", so we use
// the parameter ":token" to match the script name.
// Currently, only "install.sh" is supported.
if params.ByName("token") != "install.sh" {
return nil, trace.NotFound(`Route not found, query "/scripts/install.sh" for the install-only script, or "/scripts/:token/install-node.sh" for the install + join script.`)
}

// TODO(hugoShaka): cache function
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This TODO will be addressed in a future PR

opts, err := h.installScriptOptions(r.Context())
if err != nil {
return nil, trace.Wrap(err, "Failed to build install script options")
}

script, err := scripts.GetInstallScript(r.Context(), opts)
if err != nil {
h.logger.WarnContext(r.Context(), "Failed to get install script", "error", err)
return nil, trace.Wrap(err, "getting script")
}

w.WriteHeader(http.StatusOK)
if _, err := fmt.Fprintln(w, script); err != nil {
h.logger.WarnContext(r.Context(), "Failed to write install script", "error", err)
}

return nil, nil
}

// installScriptOptions computes the agent installation options based on the proxy configuration and the cluster status.
// This includes:
// - the type of automatic updates
// - the desired version
// - the proxy address (used for updates).
// - the Teleport artifact name and CDN
func (h *Handler) installScriptOptions(ctx context.Context) (scripts.InstallScriptOptions, error) {
const defaultGroup, defaultUpdater = "", ""

version, err := h.autoUpdateAgentVersion(ctx, defaultGroup, defaultUpdater)
if err != nil {
h.logger.WarnContext(ctx, "Failed to get intended agent version", "error", err)
version = teleport.Version
}

// if there's a rollout, we do new autoupdates
_, rolloutErr := h.cfg.AccessPoint.GetAutoUpdateAgentRollout(ctx)
if rolloutErr != nil && !(trace.IsNotFound(rolloutErr) || trace.IsNotImplemented(rolloutErr)) {
h.logger.WarnContext(ctx, "Failed to get rollout", "error", rolloutErr)
return scripts.InstallScriptOptions{}, trace.Wrap(err, "failed to check the autoupdate agent rollout state")
}

var autoupdateStyle scripts.AutoupdateStyle
switch {
case rolloutErr == nil:
autoupdateStyle = scripts.UpdaterBinaryAutoupdate
case automaticUpgrades(h.clusterFeatures):
autoupdateStyle = scripts.PackageManagerAutoupdate
default:
autoupdateStyle = scripts.NoAutoupdate
}

var teleportFlavor string
switch modules.GetModules().BuildType() {
case modules.BuildEnterprise:
teleportFlavor = types.PackageNameEnt
case modules.BuildOSS, modules.BuildCommunity:
teleportFlavor = types.PackageNameOSS
default:
h.logger.WarnContext(ctx, "Unknown built type, defaulting to the 'teleport' package.", "type", modules.GetModules().BuildType())
teleportFlavor = types.PackageNameOSS
}

cdnBaseURL, err := getCDNBaseURL()
if err != nil {
h.logger.WarnContext(ctx, "Failed to get CDN base URL", "error", err)
return scripts.InstallScriptOptions{}, trace.Wrap(err)
}

return scripts.InstallScriptOptions{
AutoupdateStyle: autoupdateStyle,
TeleportVersion: version,
CDNBaseURL: cdnBaseURL,
ProxyAddr: h.PublicProxyAddr(),
TeleportFlavor: teleportFlavor,
FIPS: modules.IsBoringBinary(),
}, nil

}

// EnvVarCDNBaseURL is the environment variable that allows users to override the Teleport base CDN url used in the installation script.
// Setting this value is required for testing (make production builds install from the dev CDN, and vice versa).
// As we (the Teleport company) don't distribute AGPL binaries, this must be set when using a Teleport OSS build.
// Example values:
// - "https://cdn.teleport.dev" (prod)
// - "https://cdn.cloud.gravitational.io" (dev builds/staging)
const EnvVarCDNBaseURL = "TELEPORT_CDN_BASE_URL"

func getCDNBaseURL() (string, error) {
// If the user explicitly overrides the CDN base URL, we use it.
if override := os.Getenv(EnvVarCDNBaseURL); override != "" {
return override, nil
}

// If this is an AGPL build, we don't want to automatically install binaries distributed under a more restrictive
// license so we error and ask the user set the CDN URL, either to:
// - the official Teleport CDN if they agree with the community license and meet its requirements
// - a custom CDN where they can store their own AGPL binaries
if modules.GetModules().BuildType() == modules.BuildOSS {
return "", trace.BadParameter(
"This proxy is licensed under AGPL but CDN binaries are licensed under the more restrictive Community license. "+
"You can set TELEPORT_CDN_BASE_URL to a custom CDN, or to %q if you are OK with using the Community Edition license.",
teleportassets.CDNBaseURL())
}

return teleportassets.CDNBaseURL(), nil
}
Loading