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