From 76f10bc72fab7a85728f190d0a49eaad0167ea82 Mon Sep 17 00:00:00 2001 From: Roman Tkachenko Date: Mon, 3 Apr 2023 09:03:01 -0700 Subject: [PATCH] Release 10.3.15 (#23849) * Prevent tunneling if the os login doesn't exist A user.Lookup was added to srv.RunForward to prevent dialing and forwarding any data if the os login is not found. The check alone only terminates the direct-tcpip ssh channel and not the underlying ssh connection. In order for the parent process to determine if the ssh connection should be terminated it needs to know why the child exited. That was not possible by looking at the exit code and any data written to standard error of the child process was forwarded to standard error on the parent; which was used to simply log the error and move on. To pass more detailed errors to the parent, the child process spawned by srv.RunForward now json marshals the trace.Error to standard error which is then decoded by the parent process. If the parent detects the error was due to a missing user it terminates the ssh connection. tsh ssh -N was also modified to terminate if the command context of tsh OR the ssh connection to the node is closes. Prior, it only terminated if the user cancelled the process by blocking on ctx.Done(). While this was necessary to end session if the os login does not exit, it also forces tsh to exit if the node goes offline. Note: This does not include any propagation of error messages to the user, so there won't be any indication from tsh about why the connection was closed. The session also will not be terminated until the first attempt to forward data and NOT when the session is created due to the way -N is implemented. Fixes #217 * Prevent unauthorized access to kube clusters by upserting kube_servers (#470) This PR changes the behavior of the kubernetes_service when validating access to kubernetes clusters. Previously, the kubernetes_service would use the first kubernetes cluster it found in the Auth server backend to validate access. This was problematic because if the first kubernetes cluster was upserted with a the same name as a kubernetes cluster the user was trying to access but with different labels, the user would be able to access the cluster even though they shouldn't be able to. This PR changes the behavior of the kubernetes_service to use the in memory kubernetes cluster representation used for heartbeats instead of relying on the information received from the auth server. This would block the user from accessing the cluster if the cluster was upserted with a different set of labels since the kubernetes_service would not have the updated labels in memory and would deny access. Fixes #469 * Release 10.3.15 --------- Co-authored-by: Tim Ross Co-authored-by: Tiago Silva --- CHANGELOG.md | 87 ++++++++++++++++ Makefile | 2 +- api/version.go | 2 +- examples/chart/teleport-cluster/Chart.yaml | 2 +- .../charts/teleport-operator/Chart.yaml | 2 +- .../__snapshot__/deployment_test.yaml.snap | 98 +++++++++---------- examples/chart/teleport-kube-agent/Chart.yaml | 2 +- .../__snapshot__/deployment_test.yaml.snap | 58 +++++------ .../__snapshot__/statefulset_test.yaml.snap | 58 +++++------ integration/port_forwarding_test.go | 38 ++++++- lib/client/api.go | 27 +++-- lib/client/client.go | 81 ++++++--------- lib/kube/proxy/forwarder.go | 17 +++- lib/kube/proxy/forwarder_test.go | 11 +++ lib/kube/proxy/server.go | 62 ++++++++++++ lib/srv/ctx.go | 63 ++++++++++++ lib/srv/ctx_test.go | 16 +++ lib/srv/reexec.go | 94 ++++++++---------- lib/srv/regular/sshserver.go | 96 ++++++++---------- lib/utils/proxyconn.go | 30 ++++++ version.go | 2 +- 21 files changed, 559 insertions(+), 289 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f35f17f1076..15b24f9e64ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,92 @@ # Changelog +## 10.3.15 (03/30/23) + +This release of Teleport contains 2 security fixes as well as multiple improvements and bug fixes. + +### [High] OS authorization bypass in SSH tunneling + +When establishing an SSH port forwarding connection, Teleport did not +sufficiently validate the specified OS principal. + +This could allow an attacker in possession of valid cluster credentials to +establish a TCP tunnel to a node using a non-existent Linux user. + +The connection attempt would show up in the audit log as a "port" audit event +(code T3003I) and include a Teleport username in the "user" field. + +### [High] Teleport authorization bypass in Kubernetes Access + +When authorizing a Kubernetes Access request, Teleport did not adequately +validate the target Kubernetes cluster. + +This could allow an attacker in possession of valid Kubernetes agent credentials +or a join token to trick Teleport into forwarding requests to a different +Kubernetes cluster. + +Every Kubernetes request would show up in the audit log as a "kube.request" +audit event (code T3009I) and include the Kubernetes cluster metadata. + +### [Medium] Moderated sessions leave behavior + +Fixed issue with moderated session being terminated after a short delay instead +of being immediately paused when moderator leaves. + +[#21972](https://github.com/gravitational/teleport/pull/21972) + +### Other improvements and fixes + +* AMIs + * Added support for configuring TLS routing mode in AMIs. [#23676](https://github.com/gravitational/teleport/pull/23676) +* Application Access + * Fixed app access requests being redirected to leaf's public address in some cases. [#23222](https://github.com/gravitational/teleport/pull/23222) + * Reduced log noise. [#23367](https://github.com/gravitational/teleport/pull/23367) +* Access Management + * Added per-session MFA support to connection testers. [#22922](https://github.com/gravitational/teleport/pull/22922) +* Performance & scalability + * Improved idle connection handling. [#22916](https://github.com/gravitational/teleport/pull/22916) + * Removed unnecessary resource updates. [#22573](https://github.com/gravitational/teleport/pull/22573) + * Fixed proxy peering issues when running behind a load balancer. [#23508](https://github.com/gravitational/teleport/pull/23508) + * Improved `tsh ls -R` performance in large clusters. [#23606](https://github.com/gravitational/teleport/pull/23606) + * Improved performance when setting environment for user session. [#23832](https://github.com/gravitational/teleport/pull/23832) +* Database Access + * Fixed `tsh db config` returning incorrect port in TLS routing mode. [#22891](https://github.com/gravitational/teleport/pull/22891) + * Fixed issue with query audit events always having `success: false` status. [#23276](https://github.com/gravitational/teleport/pull/23276) + * Fixed issue with Redis protocol not handling nil response [#22230](https://github.com/gravitational/teleport/pull/22230) +* Server Access + * Fixed issue with OS group check leading to session failures in some cases. [#22803](https://github.com/gravitational/teleport/pull/22803) + * Fixed issue with PuTTY `winadj` channel requests not being correctly handled. [#22421](https://github.com/gravitational/teleport/pull/22421) + * Improved handling of child processes upon session termination. [#22231](https://github.com/gravitational/teleport/pull/22231) +* Desktop Access + * Fixed panics on systems using large numbers of file descriptors. [#22800](https://github.com/gravitational/teleport/pull/22800) + * Fixed incorrect login options for Windows desktops. [#22344](https://github.com/gravitational/teleport/pull/22344) + * Updated setup script to be idempotent. [#23174](https://github.com/gravitational/teleport/pull/23174) +* Kubernetes Access + * Improved label validation for Kubernetes service. [#22780](https://github.com/gravitational/teleport/pull/22780) + * Fixed issue with Kubernetes impersonation header overwrite for leaf clusters. [#22247](https://github.com/gravitational/teleport/pull/22247) + * Fixed issue with `tsh kube credentials` failing on remote clusters. [#23352](https://github.com/gravitational/teleport/pull/23352) + * Fixed issue with `tsh kube credentials` loading incorrect profile. [#23717](https://github.com/gravitational/teleport/pull/23717) +* Auto-discovery + * Fixed issue with open-source package being installed for enterprise clusters. [#22768](https://github.com/gravitational/teleport/pull/22768) +* Trusted Clusters + * Added ability to update role map without having to recreate the trusted cluster resource. [#23645](https://github.com/gravitational/teleport/pull/23645) +* Tooling + * Updated Go to `1.19.7`. [#22729](https://github.com/gravitational/teleport/pull/22729) + * Updated Rust to `1.68.0`. [#23103](https://github.com/gravitational/teleport/pull/23103) +* CLI + * Fixed issue with `tsh` not respecting `HTTPS_PROXY` in some cases. [#22490](https://github.com/gravitational/teleport/pull/22490) + * Added flag to `tsh` to only display the binary version. [#22169](https://github.com/gravitational/teleport/pull/22169) + * Added `app_server` support to `tctl` resource commands. [#23138](https://github.com/gravitational/teleport/pull/23138) + * Display year in `tctl` commands output. [#23373](https://github.com/gravitational/teleport/pull/23373) + * Added `--cluster` flag to `tsh kube sessions` command. [#23827](https://github.com/gravitational/teleport/pull/23827) +* Resource Joining + * Fixed issue when joining leaf cluster over tunnel port with enabled proxy protocol. [#23485](https://github.com/gravitational/teleport/pull/23485) + * Added support for IAM joining in `ap-southeast-4` region. [#22488](https://github.com/gravitational/teleport/pull/22488) +* FIPS + * Fixed startup issue in FIPS mode when `local_auth` isn't explicitly set. [#22242](https://github.com/gravitational/teleport/pull/22242) +* Web UI + * Fixed intermittent "client connection is closing" errors in web UI after logging in. [#23736](https://github.com/gravitational/teleport/pull/23736) + ## 10.3.13 This release of Teleport contains two security fixes as well as multiple improvements and bug fixes. diff --git a/Makefile b/Makefile index 35e5631895071..e3ccf7dd08362 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ # Stable releases: "1.0.0" # Pre-releases: "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-rc.3" # Master/dev branch: "1.0.0-dev" -VERSION=10.3.13 +VERSION=10.3.15 DOCKER_IMAGE ?= teleport diff --git a/api/version.go b/api/version.go index 58ce7dd45d094..254028a8902fe 100644 --- a/api/version.go +++ b/api/version.go @@ -3,7 +3,7 @@ package api const ( - Version = "10.3.13" + Version = "10.3.15" ) // Gitref variable is automatically set to the output of git-describe diff --git a/examples/chart/teleport-cluster/Chart.yaml b/examples/chart/teleport-cluster/Chart.yaml index 55ee2132efbd9..531e2e6e145a8 100644 --- a/examples/chart/teleport-cluster/Chart.yaml +++ b/examples/chart/teleport-cluster/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "10.3.13" +.version: &version "10.3.15" name: teleport-cluster apiVersion: v2 diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml index 36ade5202544b..665aa130b5bd0 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "10.3.13" +.version: &version "10.3.15" name: teleport-operator apiVersion: v2 diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap index 78312a223a214..e5aeac26e101a 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap @@ -3,7 +3,7 @@ sets Deployment annotations when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -45,7 +45,7 @@ sets Pod annotations when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -87,7 +87,7 @@ should add PersistentVolumeClaim as volume when in custom mode and persistence.e containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -129,7 +129,7 @@ should add PersistentVolumeClaim as volume when in standalone mode and persisten containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -168,7 +168,7 @@ should add PersistentVolumeClaim as volume when in standalone mode and persisten claimName: RELEASE-NAME should add an operator side-car when operator is enabled: 1: | - image: quay.io/gravitational/teleport-operator:10.3.13 + image: quay.io/gravitational/teleport-operator:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -206,7 +206,7 @@ should add emptyDir for data in AWS mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -259,7 +259,7 @@ should add emptyDir for data in GCP mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -307,7 +307,7 @@ should add insecureSkipProxyTLSVerify to args when set in values: - args: - --diag-addr=0.0.0.0:3000 - --insecure - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -349,7 +349,7 @@ should add named PersistentVolumeClaim as volume when in custom mode and persist containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -392,7 +392,7 @@ should add named PersistentVolumeClaim as volume when in custom mode and persist containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -482,7 +482,7 @@ should expose diag port: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -524,7 +524,7 @@ should have Recreate strategy in standalone mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -578,7 +578,7 @@ should have multiple replicas when replicaCount is set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -632,7 +632,7 @@ should mount ConfigMap for config in AWS mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -685,7 +685,7 @@ should mount ConfigMap for config in GCP mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -732,7 +732,7 @@ should mount ConfigMap for config in custom mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -774,7 +774,7 @@ should mount ConfigMap for config in standalone mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -828,7 +828,7 @@ should mount GCP credentials for initContainer in GCP mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -901,7 +901,7 @@ should mount GCP credentials in GCP mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -960,7 +960,7 @@ should mount TLS certs for initContainer when cert-manager is enabled: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1042,7 +1042,7 @@ should mount TLS certs when cert-manager is enabled: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1107,7 +1107,7 @@ should mount cert-manager TLS secret when highAvailability.certManager.enabled i containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1155,7 +1155,7 @@ should mount extraVolumes and extraVolumeMounts: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1205,7 +1205,7 @@ should mount tls.existingCASecretName and set environment when set in values: env: - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1264,7 +1264,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: some-value - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1318,7 +1318,7 @@ should mount tls.existingSecretName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1366,7 +1366,7 @@ should not add PersistentVolumeClaim as volume when in custom mode and persisten containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1407,7 +1407,7 @@ should not add PersistentVolumeClaim as volume when in standalone mode and persi containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1449,7 +1449,7 @@ should not add PersistentVolumeClaim as volume when in standalone mode and persi containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1491,7 +1491,7 @@ should not add PersistentVolumeClaim as volume when in standalone mode and persi containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1574,7 +1574,7 @@ should not have more than one replica in standalone mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1628,7 +1628,7 @@ should not have strategy in AWS mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1681,7 +1681,7 @@ should not have strategy in GCP mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1728,7 +1728,7 @@ should not have strategy in custom mode: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1770,7 +1770,7 @@ should not mount TLS secrets when when highAvailability.certManager.enabled is f containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1824,7 +1824,7 @@ should not mount secret when credentialSecretName is blank in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1865,7 +1865,7 @@ should not set securityContext when is empty object (default value): containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1910,7 +1910,7 @@ should provision initContainer correctly when set in values: env: - name: SOME_ENVIRONMENT_VARIABLE value: some-value - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1986,7 +1986,7 @@ should set affinity when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2031,7 +2031,7 @@ should set environment when extraEnv set in values: env: - name: SOME_ENVIRONMENT_VARIABLE value: some-value - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2073,7 +2073,7 @@ should set imagePullPolicy when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -2115,7 +2115,7 @@ should set nodeSelector when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2160,7 +2160,7 @@ should set postStart command if set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent lifecycle: postStart: @@ -2208,7 +2208,7 @@ should set priorityClassName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2251,7 +2251,7 @@ should set probeTimeoutSeconds when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2303,7 +2303,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2344,7 +2344,7 @@ should set resources when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2393,7 +2393,7 @@ should set securityContext when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2454,7 +2454,7 @@ should set tolerations when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/Chart.yaml b/examples/chart/teleport-kube-agent/Chart.yaml index 272befcbddbd7..3c994151238f3 100644 --- a/examples/chart/teleport-kube-agent/Chart.yaml +++ b/examples/chart/teleport-kube-agent/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "10.3.13" +.version: &version "10.3.15" name: teleport-kube-agent apiVersion: v2 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap index a1c3fb873ce30..d8667b35de2af 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap @@ -27,7 +27,7 @@ sets Deployment annotations when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -95,7 +95,7 @@ sets Deployment labels when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -150,7 +150,7 @@ sets Pod annotations when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -205,7 +205,7 @@ sets Pod labels when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -260,7 +260,7 @@ should add emptyDir for data when existingDataVolume is not set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -316,7 +316,7 @@ should add insecureSkipProxyTLSVerify to args when set in values: - args: - --diag-addr=0.0.0.0:3000 - --insecure - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -371,7 +371,7 @@ should correctly configure existingDataVolume when set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -424,7 +424,7 @@ should expose diag port: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -491,7 +491,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -558,7 +558,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -613,7 +613,7 @@ should have one replica when replicaCount is not set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -668,7 +668,7 @@ should mount extraVolumes and extraVolumeMounts: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -731,7 +731,7 @@ should mount tls.existingCASecretName and set environment when set in values: env: - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -797,7 +797,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: http://username:password@my.proxy.host:3128 - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -858,7 +858,7 @@ should provision initContainer correctly when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -949,7 +949,7 @@ should set SecurityContext: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1024,7 +1024,7 @@ should set affinity when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1079,7 +1079,7 @@ should set default serviceAccountName when not set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1148,7 +1148,7 @@ should set environment when extraEnv set in values: env: - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1258,7 +1258,7 @@ should set imagePullPolicy when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -1313,7 +1313,7 @@ should set nodeSelector if set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1370,7 +1370,7 @@ should set not set priorityClassName when not set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1437,7 +1437,7 @@ should set preferred affinity when more than one replica is used: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1492,7 +1492,7 @@ should set priorityClassName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1548,7 +1548,7 @@ should set probeTimeoutSeconds when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1613,7 +1613,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1668,7 +1668,7 @@ should set resources when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1730,7 +1730,7 @@ should set serviceAccountName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1785,7 +1785,7 @@ should set tolerations when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap index 61e00dae4f48c..bc3b9e3994361 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap @@ -3,7 +3,7 @@ sets Pod annotations when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -58,7 +58,7 @@ sets Pod labels when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -137,7 +137,7 @@ sets StatefulSet labels when specified: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -203,7 +203,7 @@ should add insecureSkipProxyTLSVerify to args when set in values: - args: - --diag-addr=0.0.0.0:3000 - --insecure - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -258,7 +258,7 @@ should add volumeClaimTemplate for data volume when using StatefulSet: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -313,7 +313,7 @@ should add volumeMount for data volume when using StatefulSet: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -368,7 +368,7 @@ should expose diag port: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -435,7 +435,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -502,7 +502,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -557,7 +557,7 @@ should have one replica when replicaCount is not set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -612,7 +612,7 @@ should mount extraVolumes and extraVolumeMounts: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -675,7 +675,7 @@ should mount tls.existingCASecretName and set environment when set in values: env: - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -741,7 +741,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: http://username:password@my.proxy.host:3128 - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -802,7 +802,7 @@ should not add emptyDir for data when using StatefulSet: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -857,7 +857,7 @@ should provision initContainer correctly when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -948,7 +948,7 @@ should set SecurityContext: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1023,7 +1023,7 @@ should set affinity when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1078,7 +1078,7 @@ should set default serviceAccountName when not set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1147,7 +1147,7 @@ should set environment when extraEnv set in values: env: - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1257,7 +1257,7 @@ should set imagePullPolicy when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -1312,7 +1312,7 @@ should set nodeSelector if set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1381,7 +1381,7 @@ should set preferred affinity when more than one replica is used: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1436,7 +1436,7 @@ should set probeTimeoutSeconds when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1501,7 +1501,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1556,7 +1556,7 @@ should set resources when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1618,7 +1618,7 @@ should set serviceAccountName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1673,7 +1673,7 @@ should set storage.requests when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1728,7 +1728,7 @@ should set storage.storageClassName when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1783,7 +1783,7 @@ should set tolerations when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: quay.io/gravitational/teleport:10.3.13 + image: quay.io/gravitational/teleport:10.3.15 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/integration/port_forwarding_test.go b/integration/port_forwarding_test.go index 71f1360c9229f..67195c7af3d03 100644 --- a/integration/port_forwarding_test.go +++ b/integration/port_forwarding_test.go @@ -22,10 +22,12 @@ import ( "net/http" "net/http/httptest" "net/url" + "os/user" "strconv" "testing" "time" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/require" @@ -71,19 +73,49 @@ func waitForSessionToBeEstablished(ctx context.Context, namespace string, site a } func testPortForwarding(t *testing.T, suite *integrationTestSuite) { + invalidOSLogin := uuid.NewString()[:12] + notFound := false + for i := 0; i < 10; i++ { + if _, err := user.Lookup(invalidOSLogin); err == nil { + invalidOSLogin = uuid.NewString()[:12] + continue + } + notFound = true + break + } + require.True(t, notFound, "unable to locate invalid os user") + + // Providing our own logins to Teleport so we can verify that a user + // that exists within Teleport but does not exist on the local node + // cannot port forward. + logins := []string{ + invalidOSLogin, + suite.me.Username, + } + testCases := []struct { desc string portForwardingAllowed bool expectSuccess bool + login string }{ { desc: "Enabled", portForwardingAllowed: true, expectSuccess: true, - }, { + login: suite.me.Username, + }, + { desc: "Disabled", portForwardingAllowed: false, expectSuccess: false, + login: suite.me.Username, + }, + { + desc: "Enabled with invalid user", + portForwardingAllowed: true, + expectSuccess: false, + login: invalidOSLogin, }, } @@ -106,7 +138,7 @@ func testPortForwarding(t *testing.T, suite *integrationTestSuite) { cfg.SSH.Enabled = true cfg.SSH.AllowTCPForwarding = tt.portForwardingAllowed - teleport := suite.newTeleportWithConfig(t, nil, nil, cfg) + teleport := suite.newTeleportWithConfig(t, logins, nil, cfg) defer teleport.StopAll() site := teleport.GetSiteAPI(Site) @@ -127,7 +159,7 @@ func testPortForwarding(t *testing.T, suite *integrationTestSuite) { nodeSSHPort := teleport.GetPortSSHInt() cl, err := teleport.NewClient(ClientConfig{ - Login: suite.me.Username, + Login: tt.login, Cluster: Site, Host: Host, Port: nodeSSHPort, diff --git a/lib/client/api.go b/lib/client/api.go index 36c015c20374c..8edc2c6395b4c 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2036,16 +2036,27 @@ func (tc *TeleportClient) runShellOrCommandOnSingleNode(ctx context.Context, nod return trace.Wrap(err) } - // If no remote command execution was requested, block on the context which - // will unblock upon error or SIGINT. + // If no remote command execution was requested block on which ever comes first: + // 1) the context which will unblock upon error or user terminating the process + // 2) ssh.Client.Wait which will unblock when the connection has shut down if tc.NoRemoteExec { - log.Debugf("Connected to node, no remote command execution was requested, blocking until context closes.") - <-ctx.Done() - - // Only return an error if the context was canceled by something other than SIGINT. - if ctx.Err() != context.Canceled { - return ctx.Err() + connClosed := make(chan error, 1) + go func() { + connClosed <- nodeClient.Client.Wait() + }() + log.Debugf("Connected to node, no remote command execution was requested, blocking indefinitely.") + select { + case <-ctx.Done(): + // Only return an error if the context was canceled by something other than SIGINT. + if err := ctx.Err(); !errors.Is(err, context.Canceled) { + return trace.Wrap(err) + } + case err := <-connClosed: + if !errors.Is(err, io.EOF) { + return trace.Wrap(err) + } } + return nil } diff --git a/lib/client/client.go b/lib/client/client.go index 983f41a28c8f5..f7b184b704db3 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -1958,75 +1958,52 @@ func (c *NodeClient) ExecuteSCP(ctx context.Context, cmd scp.Command) error { } type netDialer interface { - Dial(string, string) (net.Conn, error) + DialContext(context.Context, string, string) (net.Conn, error) } func proxyConnection(ctx context.Context, conn net.Conn, remoteAddr string, dialer netDialer) error { defer conn.Close() defer log.Debugf("Finished proxy from %v to %v.", conn.RemoteAddr(), remoteAddr) - var ( - remoteConn net.Conn - err error - ) - + var remoteConn net.Conn log.Debugf("Attempting to connect proxy from %v to %v.", conn.RemoteAddr(), remoteAddr) - for attempt := 1; attempt <= 5; attempt++ { - remoteConn, err = dialer.Dial("tcp", remoteAddr) - if err != nil { - log.Debugf("Proxy connection attempt %v: %v.", attempt, err) - - timer := time.NewTimer(time.Duration(100*attempt) * time.Millisecond) - defer timer.Stop() - - // Wait and attempt to connect again, if the context has closed, exit - // right away. - select { - case <-ctx.Done(): - return trace.Wrap(ctx.Err()) - case <-timer.C: - continue - } - } - // Connection established, break out of the loop. - break - } + + retry, err := utils.NewLinear(utils.LinearConfig{ + First: 100 * time.Millisecond, + Step: 100 * time.Millisecond, + Max: time.Second, + Jitter: utils.NewHalfJitter(), + }) if err != nil { - return trace.BadParameter("failed to connect to node: %v", remoteAddr) + return trace.Wrap(err) } - defer remoteConn.Close() - - // Start proxying, close the connection if a problem occurs on either leg. - errCh := make(chan error, 2) - go func() { - defer conn.Close() - defer remoteConn.Close() - _, err := io.Copy(conn, remoteConn) - errCh <- err - }() - go func() { - defer conn.Close() - defer remoteConn.Close() - - _, err := io.Copy(remoteConn, conn) - errCh <- err - }() + for attempt := 1; attempt <= 5; attempt++ { + conn, err := dialer.DialContext(ctx, "tcp", remoteAddr) + if err == nil { + // Connection established, break out of the loop. + remoteConn = conn + break + } - var errs []error - for i := 0; i < 2; i++ { + log.Debugf("Proxy connection attempt %v: %v.", attempt, err) + // Wait and attempt to connect again, if the context has closed, exit + // right away. select { - case err := <-errCh: - if err != nil && err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") { - log.Warnf("Failed to proxy connection: %v.", err) - errs = append(errs, err) - } case <-ctx.Done(): return trace.Wrap(ctx.Err()) + case <-retry.After(): + retry.Inc() + continue } } + if remoteConn == nil { + return trace.BadParameter("failed to connect to node: %v", remoteAddr) + } + defer remoteConn.Close() - return trace.NewAggregate(errs...) + // Start proxying, close the connection if a problem occurs on either leg. + return trace.Wrap(utils.ProxyConn(ctx, remoteConn, conn)) } // acceptWithContext calls "Accept" on the listener but will unblock when the diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index 35a645ca229c4..d5361018416df 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -305,8 +305,19 @@ type Forwarder struct { sessions map[uuid.UUID]*session // upgrades connections to websockets upgrader websocket.Upgrader + // getKubernetesServersForKubeCluster is a function that returns a list of + // kubernetes services for a given kube cluster but uses different methods + // depending on the service type. + // For example, if the service type is KubeService, it will use the + // local kubernetes clusters. If the service type is Proxy, it will + // use the heartbeat clusters. + getKubernetesServersForKubeCluster getKubeServicesByNameFunc } +// getKubeServicesByNameFunc is a function that returns a list of +// kubernetes services that might contain the given kube cluster. +type getKubeServicesByNameFunc = func(ctx context.Context, name string) ([]types.Server, error) + // Close signals close to all outstanding or background operations // to complete func (f *Forwarder) Close() error { @@ -714,7 +725,7 @@ func (f *Forwarder) getKubeAccessDetails( kubeClusterName string, sessionTTL time.Duration, ) (kubeAccessDetails, error) { - kubeServices, err := f.cfg.CachingAuthClient.GetKubeServices(f.ctx) + kubeServices, err := f.getKubernetesServersForKubeCluster(f.ctx, kubeClusterName) if err != nil { return kubeAccessDetails{}, trace.Wrap(err) } @@ -761,7 +772,7 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error { f.log.WithField("auth_context", actx.String()).Debug("Skipping authorization due to unknown kubernetes cluster name") return nil } - servers, err := f.cfg.CachingAuthClient.GetKubeServices(ctx) + servers, err := f.getKubernetesServersForKubeCluster(f.ctx, actx.kubeCluster) if err != nil { return trace.Wrap(err) } @@ -1818,7 +1829,7 @@ func (f *Forwarder) newClusterSessionSameCluster(ctx authContext) (*clusterSessi return sess, nil } - kubeServices, err := f.cfg.CachingAuthClient.GetKubeServices(f.ctx) + kubeServices, err := f.getKubernetesServersForKubeCluster(f.ctx, ctx.kubeCluster) if err != nil && !trace.IsNotFound(err) { return nil, trace.Wrap(err) } diff --git a/lib/kube/proxy/forwarder_test.go b/lib/kube/proxy/forwarder_test.go index 3bb70b283dbe8..10c6a6ea400e6 100644 --- a/lib/kube/proxy/forwarder_test.go +++ b/lib/kube/proxy/forwarder_test.go @@ -166,6 +166,10 @@ func TestAuthenticate(t *testing.T) { }, } + f.getKubernetesServersForKubeCluster = func(ctx context.Context, kubeCluster string) ([]types.Server, error) { + return f.cfg.CachingAuthClient.GetKubeServices(ctx) + } + const remoteAddr = "user.example.com" activeAccessRequests := []string{uuid.NewString(), uuid.NewString()} tests := []struct { @@ -805,6 +809,10 @@ func TestNewClusterSessionLocal(t *testing.T) { }, } + f.getKubernetesServersForKubeCluster = func(ctx context.Context, kubeCluster string) ([]types.Server, error) { + return f.cfg.CachingAuthClient.GetKubeServices(ctx) + } + // Fail when kubeCluster is not specified authCtx.kubeCluster = "" _, err := f.newClusterSession(authCtx) @@ -863,6 +871,9 @@ func TestNewClusterSessionRemote(t *testing.T) { func TestNewClusterSessionDirect(t *testing.T) { ctx := context.Background() f := newMockForwader(ctx, t) + f.getKubernetesServersForKubeCluster = func(ctx context.Context, kubeCluster string) ([]types.Server, error) { + return f.cfg.CachingAuthClient.GetKubeServices(ctx) + } authCtx := mockAuthCtx(ctx, t, "kube-cluster", false) // helper function to create kube services diff --git a/lib/kube/proxy/server.go b/lib/kube/proxy/server.go index 220657012a586..7e13a8dcc5bf7 100644 --- a/lib/kube/proxy/server.go +++ b/lib/kube/proxy/server.go @@ -17,6 +17,7 @@ limitations under the License. package proxy import ( + "context" "crypto/tls" "net" "net/http" @@ -184,6 +185,10 @@ func NewTLSServer(cfg TLSServerConfig) (*TLSServer, error) { log.Debug("No local kube credentials on proxy, will not start kubernetes_service heartbeats") } + fwd.getKubernetesServersForKubeCluster, err = server.getKubernetesServiceFunc() + if err != nil { + return nil, trace.Wrap(err) + } return server, nil } @@ -280,3 +285,60 @@ func (t *TLSServer) GetServerInfo() (types.Resource, error) { return srv, nil } + +// getKubernetesServiceFunc returns a function that returns the kubernetes services +func (t *TLSServer) getKubernetesServiceFunc() (getKubeServicesByNameFunc, error) { + switch t.KubeServiceType { + case KubeService: + return func(_ context.Context, name string) ([]types.Server, error) { + resource, err := t.GetServerInfo() + if err != nil { + return nil, trace.Wrap(err) + } + srv, ok := resource.(types.Server) + if !ok { + return nil, trace.BadParameter("unexpected type %T", resource) + } + return []types.Server{srv}, nil + }, nil + case ProxyService: + return t.getAuthKubeServices, nil + case LegacyProxyService: + return func(ctx context.Context, name string) ([]types.Server, error) { + servers, err := t.getLocalKubeServiceForCluster(name) + if err != nil { + servers, err := t.getAuthKubeServices(ctx, name) + return servers, trace.Wrap(err) + } + return servers, nil + }, nil + default: + return nil, trace.BadParameter("unknown kubernetes service type %q", t.KubeServiceType) + } +} + +// getAuthKubeServers returns the kubernetes servers for a given kube cluster +// using the Auth server client. +func (t *TLSServer) getAuthKubeServices(ctx context.Context, name string) ([]types.Server, error) { + servers, err := t.CachingAuthClient.GetKubeServices(ctx) + return servers, trace.Wrap(err) +} + +// getLocalKubeServiceForCluster returns the local kubernetes service if it +// includes the given cluster. +func (t *TLSServer) getLocalKubeServiceForCluster(clusterName string) ([]types.Server, error) { + resource, err := t.GetServerInfo() + if err != nil { + return nil, trace.Wrap(err) + } + srv, ok := resource.(types.Server) + if !ok { + return nil, trace.BadParameter("unexpected type %T", resource) + } + for _, cluster := range srv.GetKubernetesClusters() { + if cluster.Name == clusterName { + return []types.Server{srv}, nil + } + } + return nil, trace.NotFound("kubernetes cluster %q not found", clusterName) +} diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index 24bcdd6df553a..6c6fc3ef1db96 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -18,6 +18,7 @@ package srv import ( "context" + "encoding/json" "fmt" "io" "net" @@ -185,6 +186,48 @@ type Server interface { TargetMetadata() apievents.ServerMetadata } +// childProcessError is used to provide an underlying error +// from a re-executed Teleport child process to its parent. +type childProcessError struct { + Code int `json:"code"` + RawError []byte `json:"rawError"` +} + +// writeChildError encodes the provided error +// as json and writes it to w. Special care +// is taken to preserve the error type by +// including the error code and raw message +// so that [DecodeChildError] will return +// the matching error type and message. +func writeChildError(w io.Writer, err error) { + if w == nil || err == nil { + return + } + + data, jerr := json.Marshal(err) + if jerr != nil { + return + } + + _ = json.NewEncoder(w).Encode(childProcessError{ + Code: trace.ErrorToCode(err), + RawError: data, + }) + +} + +// DecodeChildError consumes the output from a child +// process decoding it from its raw form back into +// a concrete error. +func DecodeChildError(r io.Reader) error { + var c childProcessError + if err := json.NewDecoder(r).Decode(&c); err != nil { + return nil + } + + return trace.ReadError(c.Code, c.RawError) +} + // IdentityContext holds all identity information associated with the user // logged on the connection. type IdentityContext struct { @@ -377,6 +420,12 @@ type ServerContext struct { x11rdyr *os.File x11rdyw *os.File + // err{r,w} is used to propagate errors from the child process to the + // parent process so the parent can get more information about why the child + // process failed and act accordingly. + errr *os.File + errw *os.File + // x11Config holds the xauth and XServer listener config for this session. x11Config *X11Config @@ -522,6 +571,15 @@ func NewServerContext(ctx context.Context, parent *sshutils.ConnectionContext, s child.AddCloser(child.x11rdyr) child.AddCloser(child.x11rdyw) + // Create pipe used to get errors from the child process. + child.errr, child.errw, err = os.Pipe() + if err != nil { + childErr := child.Close() + return nil, nil, trace.NewAggregate(err, childErr) + } + child.AddCloser(child.errr) + child.AddCloser(child.errw) + return ctx, child, nil } @@ -832,6 +890,11 @@ func (c *ServerContext) x11Ready() (bool, error) { return true, nil } +// GetChildError returns the error from the child process +func (c *ServerContext) GetChildError() error { + return DecodeChildError(c.errr) +} + // takeClosers returns all resources that should be closed and sets the properties to null // we do this to avoid calling Close() under lock to avoid potential deadlocks func (c *ServerContext) takeClosers() []io.Closer { diff --git a/lib/srv/ctx_test.go b/lib/srv/ctx_test.go index 36e38d369e712..92dba07023e5d 100644 --- a/lib/srv/ctx_test.go +++ b/lib/srv/ctx_test.go @@ -17,14 +17,30 @@ limitations under the License. package srv import ( + "bytes" + "os/user" "testing" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/services" ) +// TestDecodeChildError ensures that child error message marshaling +// and unmarshaling returns the original values. +func TestDecodeChildError(t *testing.T) { + var buf bytes.Buffer + require.NoError(t, DecodeChildError(&buf)) + + targetErr := trace.NotFound(user.UnknownUserError("test").Error()) + + writeChildError(&buf, targetErr) + + require.ErrorIs(t, DecodeChildError(&buf), targetErr) +} + func TestCheckSFTPAllowed(t *testing.T) { srv := newMockServer(t) ctx := newTestServerContext(t, srv, nil) diff --git a/lib/srv/reexec.go b/lib/srv/reexec.go index 349311785d3af..2343f6ccf2254 100644 --- a/lib/srv/reexec.go +++ b/lib/srv/reexec.go @@ -67,6 +67,9 @@ const ( // X11File is used to communicate to the parent process that the child // process has set up X11 forwarding. X11File + // ErrorFile is used to communicate any errors terminating the child process + // to the parent process + ErrorFile // PTYFile is a PTY the parent process passes to the child process. PTYFile // TTYFile is a TTY the parent process passes to the child process. @@ -74,9 +77,13 @@ const ( // FirstExtraFile is the first file descriptor that will be valid when // extra files are passed to child processes without a terminal. - FirstExtraFile = X11File + 1 + FirstExtraFile FileFD = ErrorFile + 1 ) +func fdName(f FileFD) string { + return fmt.Sprintf("/proc/self/fd/%d", f) +} + // ExecCommand contains the payload to "teleport exec" which will be used to // construct and execute a shell. type ExecCommand struct { @@ -190,29 +197,23 @@ func RunCommand() (errw io.Writer, code int, err error) { errorWriter := os.Stdout // Parent sends the command payload in the third file descriptor. - cmdfd := os.NewFile(CommandFile, fmt.Sprintf("/proc/self/fd/%d", CommandFile)) + cmdfd := os.NewFile(CommandFile, fdName(CommandFile)) if cmdfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("command pipe not found") } - contfd := os.NewFile(ContinueFile, fmt.Sprintf("/proc/self/fd/%d", ContinueFile)) + contfd := os.NewFile(ContinueFile, fdName(ContinueFile)) if contfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("continue pipe not found") } - termiantefd := os.NewFile(TerminateFile, fmt.Sprintf("/proc/self/fd/%d", TerminateFile)) + termiantefd := os.NewFile(TerminateFile, fdName(TerminateFile)) if termiantefd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("terminate pipe not found") } // Read in the command payload. - var b bytes.Buffer - _, err = b.ReadFrom(cmdfd) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) - } var c ExecCommand - err = json.Unmarshal(b.Bytes(), &c) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) + if err := json.NewDecoder(cmdfd).Decode(&c); err != nil { + return io.Discard, teleport.RemoteCommandFailure, trace.Wrap(err) } auditdMsg := auditd.Message{ @@ -250,8 +251,8 @@ func RunCommand() (errw io.Writer, code int, err error) { // PTY and TTY. Extract them and set the controlling TTY. Otherwise, connect // std{in,out,err} directly. if c.Terminal { - pty = os.NewFile(PTYFile, fmt.Sprintf("/proc/self/fd/%d", PTYFile)) - tty = os.NewFile(TTYFile, fmt.Sprintf("/proc/self/fd/%d", TTYFile)) + pty = os.NewFile(PTYFile, fdName(PTYFile)) + tty = os.NewFile(TTYFile, fdName(TTYFile)) if pty == nil || tty == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("pty and tty not found") } @@ -397,7 +398,7 @@ func RunCommand() (errw io.Writer, code int, err error) { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", x11.DisplayEnv, c.X11Config.XAuthEntry.Display.String())) // Open x11rdy fd to signal parent process once X11 forwarding is set up. - x11rdyfd := os.NewFile(X11File, fmt.Sprintf("/proc/self/fd/%d", X11File)) + x11rdyfd := os.NewFile(X11File, fdName(X11File)) if x11rdyfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("continue pipe not found") } @@ -574,20 +575,24 @@ func RunForward() (errw io.Writer, code int, err error) { errorWriter := os.Stderr // Parent sends the command payload in the third file descriptor. - cmdfd := os.NewFile(CommandFile, fmt.Sprintf("/proc/self/fd/%d", CommandFile)) + cmdfd := os.NewFile(CommandFile, fdName(CommandFile)) if cmdfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("command pipe not found") } - // Read in the command payload. - var b bytes.Buffer - _, err = b.ReadFrom(cmdfd) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) + // Parent receives any errors on the sixth file descriptor. + errfd := os.NewFile(ErrorFile, fdName(ErrorFile)) + if errfd == nil { + return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("error pipe not found") } + + defer func() { + writeChildError(errfd, err) + }() + + // Read in the command payload. var c ExecCommand - err = json.Unmarshal(b.Bytes(), &c) - if err != nil { + if err := json.NewDecoder(cmdfd).Decode(&c); err != nil { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) } @@ -613,6 +618,10 @@ func RunForward() (errw io.Writer, code int, err error) { defer pamContext.Close() } + if _, err := user.Lookup(c.Login); err != nil { + return errorWriter, teleport.RemoteCommandFailure, trace.NotFound(err.Error()) + } + // Connect to the target host. conn, err := net.Dial("tcp", c.DestinationAddress) if err != nil { @@ -620,33 +629,12 @@ func RunForward() (errw io.Writer, code int, err error) { } defer conn.Close() - // Start copy routines that copy from channel to stdin pipe and from stdout - // pipe to channel. - errorCh := make(chan error, 2) - go func() { - defer conn.Close() - defer os.Stdout.Close() - defer os.Stdin.Close() - - _, err := io.Copy(os.Stdout, conn) - errorCh <- err - }() - go func() { - defer conn.Close() - defer os.Stdout.Close() - defer os.Stdin.Close() - - _, err := io.Copy(conn, os.Stdin) - errorCh <- err - }() - - // Block until copy is complete in either direction. The other direction - // will get cleaned up automatically. - if err = <-errorCh; err != nil && err != io.EOF { + err = utils.ProxyConn(context.Background(), utils.CombineReadWriteCloser(os.Stdin, os.Stdout), conn) + if err != nil && !errors.Is(err, io.EOF) { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) } - return io.Discard, teleport.RemoteCommandSuccess, nil + return errorWriter, teleport.RemoteCommandSuccess, nil } // runCheckHomeDir check's if the active user's $HOME dir exists. @@ -881,11 +869,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er cmdmsg.ExtraFilesLen = len(extraFiles) } - cmdbytes, err := json.Marshal(cmdmsg) - if err != nil { - return nil, trace.Wrap(err) - } - go copyCommand(ctx, cmdbytes) + go copyCommand(ctx, cmdmsg) // Find the Teleport executable and its directory on disk. executable, err := os.Executable() @@ -915,6 +899,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er ctx.contr, ctx.killShellr, ctx.x11rdyw, + ctx.errw, }, } // Add extra files if applicable. @@ -930,7 +915,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er // copyCommand will copy the provided command to the child process over the // pipe attached to the context. -func copyCommand(ctx *ServerContext, cmdbytes []byte) { +func copyCommand(ctx *ServerContext, cmdmsg *ExecCommand) { defer func() { err := ctx.cmdw.Close() if err != nil { @@ -943,8 +928,7 @@ func copyCommand(ctx *ServerContext, cmdbytes []byte) { // Write command bytes to pipe. The child process will read the command // to execute from this pipe. - _, err := io.Copy(ctx.cmdw, bytes.NewReader(cmdbytes)) - if err != nil { + if err := json.NewEncoder(ctx.cmdw).Encode(cmdmsg); err != nil { log.Errorf("Failed to copy command over pipe: %v.", err) return } diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index 52782cac02abd..9e5dac67311bb 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -21,6 +21,7 @@ package regular import ( "context" "encoding/json" + "errors" "fmt" "io" "net" @@ -1342,8 +1343,8 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ccx *sshutils.Con defer scx.Debugf("Closing direct-tcpip channel from %v to %v.", scx.SrcAddr, scx.DstAddr) // Create command to re-exec Teleport which will perform a net.Dial. The - // reason it's not done directly is because the PAM stack needs to be called - // from another process. + // reason it's not done directly because the PAM stack needs to be called + // from the child process. cmd, err := srv.ConfigureCommand(scx) if err != nil { writeStderr(channel, err.Error()) @@ -1375,63 +1376,48 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ccx *sshutils.Con return } - // Start copy routines that copy from channel to stdin pipe and from stdout - // pipe to channel. - errorCh := make(chan error, 2) - go func() { - defer channel.Close() - defer pw.Close() - defer pr.Close() - - _, err := io.Copy(pw, channel) - errorCh <- err - }() - go func() { - defer channel.Close() - defer pw.Close() - defer pr.Close() - - _, err := io.Copy(channel, pr) - errorCh <- err - }() - - // Block until copy is complete and the child process is done executing. -Loop: - for i := 0; i < 2; i++ { - select { - case err := <-errorCh: - if err != nil && err != io.EOF { - s.Logger.Warnf("Connection problem in \"direct-tcpip\" channel: %v %T.", trace.DebugReport(err), err) - } - case <-ctx.Done(): - break Loop - case <-s.ctx.Done(): - break Loop + if err := utils.ProxyConn(ctx, utils.CombineReadWriteCloser(pr, pw), channel); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, os.ErrClosed) { + s.Logger.Warnf("Connection problem in direct-tcpip channel: %v %T.", trace.DebugReport(err), err) + } + + // Emit a port forwarding event if the command exited successfully. + if err := cmd.Wait(); err == nil { + if err := s.EmitAuditEvent(s.ctx, &apievents.PortForward{ + Metadata: apievents.Metadata{ + Type: events.PortForwardEvent, + Code: events.PortForwardCode, + }, + UserMetadata: scx.Identity.GetUserMetadata(), + ConnectionMetadata: apievents.ConnectionMetadata{ + LocalAddr: scx.ServerConn.LocalAddr().String(), + RemoteAddr: scx.ServerConn.RemoteAddr().String(), + }, + Addr: scx.DstAddr, + Status: apievents.Status{ + Success: true, + }, + }); err != nil { + s.Logger.WithError(err).Warn("Failed to emit port forward event.") } - } - err = cmd.Wait() - if err != nil { - writeStderr(channel, err.Error()) return } - // Emit a port forwarding event. - if err := s.EmitAuditEvent(s.ctx, &apievents.PortForward{ - Metadata: apievents.Metadata{ - Type: events.PortForwardEvent, - Code: events.PortForwardCode, - }, - UserMetadata: scx.Identity.GetUserMetadata(), - ConnectionMetadata: apievents.ConnectionMetadata{ - LocalAddr: scx.ServerConn.LocalAddr().String(), - RemoteAddr: scx.ServerConn.RemoteAddr().String(), - }, - Addr: scx.DstAddr, - Status: apievents.Status{ - Success: true, - }, - }); err != nil { - s.Logger.WithError(err).Warn("Failed to emit port forward event.") + // Get the error to see why the child process failed and + // determine the correct course of action. + err = scx.GetChildError() + switch { + case err == nil: + s.Logger.Warn("Forwarding data via direct-tcpip channel failed for unknown reason") + return + // The user does not exist for the provided login. Terminate the connection. + case errors.Is(err, trace.NotFound(user.UnknownUserError(scx.Identity.Login).Error())), + errors.Is(err, trace.BadParameter("unknown user")): + s.Logger.Warnf("Forwarding data via direct-tcpip channel failed. Terminating connection because user %q does not exist", scx.Identity.Login) + if err := ccx.ServerConn.Close(); err != nil { + s.Logger.Warnf("Unable to terminate connection: %v", err) + } + default: + s.Logger.WithError(err).Error("Forwarding data via direct-tcpip channel failed") } } diff --git a/lib/utils/proxyconn.go b/lib/utils/proxyconn.go index 3856f9d998ce7..2493ad222edef 100644 --- a/lib/utils/proxyconn.go +++ b/lib/utils/proxyconn.go @@ -23,6 +23,36 @@ import ( "github.com/gravitational/trace" ) +// CombinedReadWriteCloser wraps an [io.ReadCloser] and an [io.WriteCloser] to +// implement [io.ReadWriteCloser]. Reads are performed on the [io.ReadCloser] and +// writes are performed on the [io.WriteCloser]. Closing will return the +// aggregated errors of both. +type CombinedReadWriteCloser struct { + r io.ReadCloser + w io.WriteCloser +} + +func (o CombinedReadWriteCloser) Read(p []byte) (int, error) { + return o.r.Read(p) +} + +func (o CombinedReadWriteCloser) Write(p []byte) (int, error) { + return o.w.Write(p) +} + +func (o CombinedReadWriteCloser) Close() error { + return trace.NewAggregate(o.r.Close(), o.w.Close()) +} + +// CombineReadWriteCloser creates a CombinedReadWriteCloser from the provided +// [io.ReadCloser] and [io.WriteCloser] that implements [io.ReadWriteCloser] +func CombineReadWriteCloser(r io.ReadCloser, w io.WriteCloser) CombinedReadWriteCloser { + return CombinedReadWriteCloser{ + r: r, + w: w, + } +} + // ProxyConn launches a double-copy loop that proxies traffic between the // provided client and server connections. // diff --git a/version.go b/version.go index e51e3e0a57850..dc776c3349c9a 100644 --- a/version.go +++ b/version.go @@ -3,7 +3,7 @@ package teleport const ( - Version = "10.3.13" + Version = "10.3.15" ) // Gitref variable is automatically set to the output of git-describe