From b98921dff8cd005f34837ed61e7de405169d9ea9 Mon Sep 17 00:00:00 2001 From: Edoardo Spadolini Date: Fri, 24 Apr 2026 12:07:18 +0200 Subject: [PATCH] session-helper: Build changes (#66003) * Build and test changes for sessionhelper * Always print text in check-session-helper * Fix TwoClustersTunnel --- .github/workflows/bloat.yaml | 11 +++--- Makefile | 35 +++++++++++++++---- .../smoke_tests/02_sessionhelper/test.sh | 5 +++ integration/helpers/testmain.go | 8 ++++- integration/integration_test.go | 21 ++++++----- session/reexec/.gitignore | 2 +- session/reexec/embed_embed_linux_386.go | 2 +- session/reexec/embed_embed_linux_amd64.go | 2 +- session/reexec/embed_embed_linux_arm.go | 2 +- session/reexec/embed_embed_linux_arm64.go | 2 +- tool/teleport/common/teleport.go | 19 ++++++++++ 11 files changed, 79 insertions(+), 30 deletions(-) create mode 100755 build.assets/charts/smoke_tests/02_sessionhelper/test.sh diff --git a/.github/workflows/bloat.yaml b/.github/workflows/bloat.yaml index edaa3783ed1..d2aae17c7bc 100644 --- a/.github/workflows/bloat.yaml +++ b/.github/workflows/bloat.yaml @@ -36,7 +36,7 @@ jobs: - name: Checkout base uses: actions/checkout@v6 with: - ref: ${{ github.event.before }} + ref: ${{ github.event.pull_request.base.sha || github.event.before }} - name: Prepare workspace uses: ./.github/actions/prepare-workspace @@ -54,7 +54,7 @@ jobs: with: path: | ~/teleport_base_build_stats - key: ${{ github.job }}-${{ runner.os }}-${{ github.event.before }} + key: ${{ github.job }}-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.before }} - name: Generate GitHub Token id: generate_token @@ -68,7 +68,7 @@ jobs: id: build_base run: | make WEBASSETS_SKIP_BUILD=1 BUILDDIR=base_build binaries - cd .github/shared-workflows/bot && go run main.go -workflow=binary-sizes --artifacts="tbot,tctl,teleport,tsh,teleport-update,fdpass-teleport" --builddir="../../../base_build" -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" >> ~/teleport_base_build_stats + cd .github/shared-workflows/bot && go run main.go -workflow=binary-sizes --artifacts="tbot,tctl,teleport,tsh,teleport-update,fdpass-teleport,sessionhelper" --builddir="../../../base_build" -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" > ~/teleport_base_build_stats echo "base_stats_file=~/teleport_base_build_stats" >> $GITHUB_OUTPUT echo "base_stats=$(cat ~/teleport_base_build_stats)" >> $GITHUB_ENV @@ -79,7 +79,7 @@ jobs: with: path: | ${{ steps.build_base.outputs.base_stats_file }} - key: ${{ github.job }}-${{ runner.os }}-${{ github.event.before }} + key: ${{ github.job }}-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.before }} - if: ${{ steps.cache-build-restore.outputs.cache-hit == 'true' }} name: Restore base stats @@ -91,7 +91,6 @@ jobs: uses: actions/checkout@v6 with: clean: false - ref: ${{ github.event.after }} - name: Checkout shared-workflow uses: actions/checkout@v6 @@ -119,4 +118,4 @@ jobs: id: check_branch_bloat run: | current=$(pwd)/build - cd .github/shared-workflows/bot && go run main.go -workflow=bloat --artifacts="tbot,tctl,teleport,tsh,teleport-update,fdpass-teleport" --base="${base_stats}" --builddir="${current}" -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" > $GITHUB_STEP_SUMMARY + cd .github/shared-workflows/bot && go run main.go -workflow=bloat --artifacts="tbot,tctl,teleport,tsh,teleport-update,fdpass-teleport,sessionhelper" --base="${base_stats}" --builddir="${current}" -token="${{ steps.generate_token.outputs.token }}" -reviewers="${{ secrets.reviewers }}" > $GITHUB_STEP_SUMMARY diff --git a/Makefile b/Makefile index d5309756fbb..42fc003303c 100644 --- a/Makefile +++ b/Makefile @@ -242,6 +242,13 @@ STATIC_LIBS_TSH += -lpcsclite endif endif +SESSIONHELPER_EMBED_TAG := +SESSIONHELPER_MESSAGE := without-session-helper +ifeq ($(OS),linux) +SESSIONHELPER_EMBED_TAG = sessionhelper_embed +SESSIONHELPER_MESSAGE := with-session-helper +endif + # Reproducible builds are only available on select targets, and only when OS=linux. REPRODUCIBLE ?= ifneq ("$(OS)","linux") @@ -266,7 +273,7 @@ join-with = $(subst $(SPACE),$1,$(strip $2)) # Separate TAG messages into comma-separated WITH and WITHOUT lists for readability. COMMA := , -MESSAGES := $(PAM_MESSAGE) $(FIPS_MESSAGE) $(BPF_MESSAGE) $(RDPCLIENT_MESSAGE) $(LIBFIDO2_MESSAGE) $(TOUCHID_MESSAGE) $(PIV_MESSAGE) $(VNETDAEMON_MESSAGE) +MESSAGES := $(PAM_MESSAGE) $(FIPS_MESSAGE) $(BPF_MESSAGE) $(RDPCLIENT_MESSAGE) $(LIBFIDO2_MESSAGE) $(TOUCHID_MESSAGE) $(PIV_MESSAGE) $(VNETDAEMON_MESSAGE) $(SESSIONHELPER_MESSAGE) WITH := $(subst -," ",$(call join-with,$(COMMA) ,$(subst with-,,$(filter with-%,$(MESSAGES))))) WITHOUT := $(subst -," ",$(call join-with,$(COMMA) ,$(subst without-,,$(filter without-%,$(MESSAGES))))) RELEASE_MESSAGE := "Building with GOOS=$(OS) GOARCH=$(ARCH) REPRODUCIBLE=$(REPRODUCIBLE) and with $(WITH) and without $(WITHOUT)." @@ -388,8 +395,21 @@ $(BUILDDIR)/tctl: GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "grpcnotrace $(PAM_TAG) $(FIPS_TAG) $(LIBFIDO2_BUILD_TAG) $(TOUCHID_TAG) $(PIV_BUILD_TAG) $(KUSTOMIZE_NO_DYNAMIC_PLUGIN)" -o $(BUILDDIR)/tctl $(BUILDFLAGS) $(TOOLS_LDFLAGS) ./tool/tctl .PHONY: $(BUILDDIR)/teleport -$(BUILDDIR)/teleport: ensure-webassets rdpclient - GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "grpcnotrace webassets_embed $(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(WEBASSETS_TAG) $(RDPCLIENT_TAG) $(PIV_BUILD_TAG) $(KUSTOMIZE_NO_DYNAMIC_PLUGIN)" -o $(BUILDDIR)/teleport $(BUILDFLAGS) $(TELEPORT_LDFLAGS) ./tool/teleport +$(BUILDDIR)/teleport: ensure-webassets rdpclient session/reexec/embed/sessionhelper + GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "grpcnotrace webassets_embed $(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(SESSIONHELPER_EMBED_TAG) $(WEBASSETS_TAG) $(RDPCLIENT_TAG) $(PIV_BUILD_TAG) $(KUSTOMIZE_NO_DYNAMIC_PLUGIN)" -o $(BUILDDIR)/teleport $(BUILDFLAGS) $(TELEPORT_LDFLAGS) ./tool/teleport + +.PHONY: $(BUILDDIR)/sessionhelper +$(BUILDDIR)/sessionhelper: +ifneq ($(SESSIONHELPER_EMBED_TAG),) + GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go -C session build -buildvcs=false -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) gravitational_trace.nocrypto" -o '$(abspath $(BUILDDIR)/sessionhelper)' $(BUILDFLAGS) ./cmd/sessionhelper +endif + +.PHONY: session/reexec/embed/sessionhelper +session/reexec/embed/sessionhelper: $(BUILDDIR)/sessionhelper +ifneq ($(SESSIONHELPER_EMBED_TAG),) + mkdir -p session/reexec/embed + gzip -9 -n < '$(BUILDDIR)/sessionhelper' > 'session/reexec/embed/sessionhelper_$(OS)_$(ARCH).gz' +endif # NOTE: Any changes to the `tsh` build here must be copied to `build.assets/windows/build.ps1` # until we can use this Makefile for native Windows builds. @@ -580,6 +600,7 @@ clean: clean-ui clean-build clean-build: @echo "---> Cleaning up OSS build artifacts." rm -rf $(BUILDDIR) + rm -rf ./session/reexec/embed -cargo clean -go clean -cache rm -f *.gz @@ -1205,9 +1226,9 @@ run-etcd: .PHONY: integration integration: FLAGS ?= -v -race integration: PACKAGES = $(shell go list ./... | grep 'integration\([^s]\|$$\)' | grep -v integrations/lib/testing/integration ) -integration: | $(TEST_LOG_DIR) +integration: session/reexec/embed/sessionhelper | $(TEST_LOG_DIR) @echo KUBECONFIG is: $(KUBECONFIG), TEST_KUBE: $(TEST_KUBE) - $(CGOFLAG) go test -timeout 30m -json -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG)" $(PACKAGES) $(FLAGS) \ + $(CGOFLAG) go test -timeout 30m -json -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(SESSIONHELPER_EMBED_TAG)" $(PACKAGES) $(FLAGS) \ | $(GOTESTSUM) --junitfile $(TEST_LOG_DIR)/unit-tests-integration.xml --jsonfile $(TEST_LOG_DIR)/unit-tests-integration.json --raw-command -- cat # @@ -1231,8 +1252,8 @@ INTEGRATION_ROOT_REGEX := ^TestRoot .PHONY: integration-root integration-root: FLAGS ?= -v -race integration-root: PACKAGES = $(shell go list ./... | grep 'integration\([^s]\|$$\)') -integration-root: | $(TEST_LOG_DIR) - $(CGOFLAG) go test -json -run "$(INTEGRATION_ROOT_REGEX)" $(PACKAGES) $(FLAGS) \ +integration-root: session/reexec/embed/sessionhelper | $(TEST_LOG_DIR) + $(CGOFLAG) go test -json -tags '$(SESSIONHELPER_EMBED_TAG)' -run "$(INTEGRATION_ROOT_REGEX)" $(PACKAGES) $(FLAGS) \ | $(GOTESTSUM) --junitfile $(TEST_LOG_DIR)/unit-tests-integration-root.xml --jsonfile $(TEST_LOG_DIR)/unit-tests-integration-root.json --raw-command -- cat diff --git a/build.assets/charts/smoke_tests/02_sessionhelper/test.sh b/build.assets/charts/smoke_tests/02_sessionhelper/test.sh new file mode 100755 index 00000000000..2ba0578bd21 --- /dev/null +++ b/build.assets/charts/smoke_tests/02_sessionhelper/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# we only make OCI images for linux, and on all linux platforms the embedded +# session helper should be available and working +docker run --rm --entrypoint /usr/local/bin/teleport --platform $1 $2 -- debug require-session-helper diff --git a/integration/helpers/testmain.go b/integration/helpers/testmain.go index e93506b92a3..c264cf5c9d2 100644 --- a/integration/helpers/testmain.go +++ b/integration/helpers/testmain.go @@ -33,7 +33,13 @@ import ( // TestMainImplementation will re-execute Teleport to run a command if "exec" is passed to // it as an argument. Otherwise, it will run tests as normal. func TestMainImplementation(m *testing.M) { - reexec.MaybeReexec() + if ok, err := reexec.InitEmbeddedReexec(); err != nil { + panic(err) + } else if !ok { + reexec.MaybeReexec() + } else if reexec.IsReexec() { + panic("reexec attempted when embedded reexec was supposed to be available") + } logtest.InitLogger(testing.Verbose) diff --git a/integration/integration_test.go b/integration/integration_test.go index 4849acb41ec..8624b8a7da9 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -2351,11 +2351,6 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time, require.Eventually(t, helpers.WaitForClusters(b.Tunnel, 1), 10*time.Second, 1*time.Second, "Two clusters do not see each other: tunnels are not working.") - var ( - outputA bytes.Buffer - outputB bytes.Buffer - ) - // make sure the direct dialer was used and not the proxy dialer require.Zero(t, ph.Count()) @@ -2372,10 +2367,11 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time, ForwardAgent: true, }) require.NoError(t, err) - tc.Stdout = &outputA + stdout := new(bytes.Buffer) + tc.Stdout = stdout err = tc.SSH(ctx, cmd) require.NoError(t, err) - require.Equal(t, "hello world\n", outputA.String()) + require.Equal(t, "hello world\n", stdout.String()) // Update trusted CAs. err = tc.UpdateTrustedCA(ctx, a.GetSiteAPI(a.Secrets.SiteName)) @@ -2397,11 +2393,14 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time, ForwardAgent: true, }) require.NoError(t, err) - tc.Stdout = &outputB - err = tc.SSH(ctx, cmd) - require.NoError(t, err) - require.Equal(t, outputA.String(), outputB.String()) + require.EventuallyWithT(t, func(t *assert.CollectT) { + stdout = new(bytes.Buffer) + tc.Stdout = stdout + err = tc.SSH(ctx, cmd) + require.NoError(t, err) + }, 10*time.Second, 250*time.Millisecond) + require.Equal(t, "hello world\n", stdout.String()) clientHasEvents := func(site authclient.ClientI, count int) func() bool { // only look for exec events diff --git a/session/reexec/.gitignore b/session/reexec/.gitignore index ca0c0a68c6c..aa3a734a57f 100644 --- a/session/reexec/.gitignore +++ b/session/reexec/.gitignore @@ -1 +1 @@ -/sessionhelper_*.gz +/embed diff --git a/session/reexec/embed_embed_linux_386.go b/session/reexec/embed_embed_linux_386.go index bc8582e99ae..056169c7778 100644 --- a/session/reexec/embed_embed_linux_386.go +++ b/session/reexec/embed_embed_linux_386.go @@ -20,5 +20,5 @@ package reexec import _ "embed" -//go:embed sessionhelper_linux_386.gz +//go:embed embed/sessionhelper_linux_386.gz var sessionHelperGZ string diff --git a/session/reexec/embed_embed_linux_amd64.go b/session/reexec/embed_embed_linux_amd64.go index 7668b177376..dc576e75183 100644 --- a/session/reexec/embed_embed_linux_amd64.go +++ b/session/reexec/embed_embed_linux_amd64.go @@ -20,5 +20,5 @@ package reexec import _ "embed" -//go:embed sessionhelper_linux_amd64.gz +//go:embed embed/sessionhelper_linux_amd64.gz var sessionHelperGZ string diff --git a/session/reexec/embed_embed_linux_arm.go b/session/reexec/embed_embed_linux_arm.go index 57a0126c855..522c3a6b80e 100644 --- a/session/reexec/embed_embed_linux_arm.go +++ b/session/reexec/embed_embed_linux_arm.go @@ -20,5 +20,5 @@ package reexec import _ "embed" -//go:embed sessionhelper_linux_arm.gz +//go:embed embed/sessionhelper_linux_arm.gz var sessionHelperGZ string diff --git a/session/reexec/embed_embed_linux_arm64.go b/session/reexec/embed_embed_linux_arm64.go index 1c655cd9195..db8577df588 100644 --- a/session/reexec/embed_embed_linux_arm64.go +++ b/session/reexec/embed_embed_linux_arm64.go @@ -20,5 +20,5 @@ package reexec import _ "embed" -//go:embed sessionhelper_linux_arm64.gz +//go:embed embed/sessionhelper_linux_arm64.gz var sessionHelperGZ string diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index b7a184fa0dd..ef050cc2f21 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -60,6 +60,7 @@ import ( "github.com/gravitational/teleport/lib/utils" logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/versioncontrol" + "github.com/gravitational/teleport/session/reexec" "github.com/gravitational/teleport/session/reexec/reexecconstants" "github.com/gravitational/teleport/session/selinux" ) @@ -634,6 +635,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con collectProfilesCmd.Flag("seconds", "For CPU and trace profiles, profile for the given duration (if set to 0, it returns a profile snapshot). For other profiles, return a delta profile. Default: 0").Short('s').Default("0").IntVar(&ccf.ProfileSeconds) readyzCmd := debugCmd.Command("readyz", "Checks if the instance is ready to serve requests.") metricsCmd := debugCmd.Command("metrics", "Fetches the cluster's Prometheus metrics.") + checkSessionHelperCmd := debugCmd.Command("check-session-helper", "Checks if the embedded session helper is working, if available in this build.") + requireSessionHelperCmd := debugCmd.Command("require-session-helper", "Checks if the embedded session helper is working, failing if not available in this build.") selinuxCmd := app.Command("selinux-ssh", "Commands related to SSH SELinux module.").Hidden() selinuxCmd.Flag("config", fmt.Sprintf("Path to a configuration file [%v].", defaults.ConfigFilePath)).Short('c').ExistingFileVar(&ccf.ConfigFile) @@ -838,6 +841,22 @@ Examples: err = onReadyz(ctx, ccf.ConfigFile) case metricsCmd.FullCommand(): err = onMetrics(ctx, ccf.ConfigFile) + case checkSessionHelperCmd.FullCommand(): + var ok bool + ok, err = reexec.InitEmbeddedReexec() + if err == nil { + if ok { + fmt.Println("The embedded session helper is available in this build.") + } else { + fmt.Println("The embedded session helper is not available in this build.") + } + } + case requireSessionHelperCmd.FullCommand(): + var ok bool + ok, err = reexec.InitEmbeddedReexec() + if err == nil && !ok { + err = errors.New("the embedded session helper is not available in this build") + } case moduleSourceCmd.FullCommand(): if runtime.GOOS != "linux" { break