mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
* 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 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 --------- Co-authored-by: Tim Ross <[email protected]> Co-authored-by: Tiago Silva <[email protected]>
207 lines
5.3 KiB
Go
207 lines
5.3 KiB
Go
/*
|
|
Copyright 2021 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"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"
|
|
|
|
apidefaults "github.com/gravitational/teleport/api/defaults"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/integration/helpers"
|
|
"github.com/gravitational/teleport/lib/auth"
|
|
"github.com/gravitational/teleport/lib/client"
|
|
)
|
|
|
|
func extractPort(svr *httptest.Server) (int, error) {
|
|
u, err := url.Parse(svr.URL)
|
|
if err != nil {
|
|
return 0, trace.Wrap(err)
|
|
}
|
|
n, err := strconv.Atoi(u.Port())
|
|
if err != nil {
|
|
return 0, trace.Wrap(err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func waitForSessionToBeEstablished(ctx context.Context, namespace string, site auth.ClientI) ([]types.SessionTracker, error) {
|
|
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
|
|
case <-ticker.C:
|
|
ss, err := site.GetActiveSessionTrackers(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if len(ss) > 0 {
|
|
return ss, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
// Given a running teleport instance with port forwarding
|
|
// permissions set per the test case
|
|
|
|
recCfg, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{
|
|
Mode: types.RecordOff,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cfg := suite.defaultServiceConfig()
|
|
cfg.Auth.Enabled = true
|
|
cfg.Auth.SessionRecordingConfig = recCfg
|
|
cfg.Proxy.Enabled = true
|
|
cfg.Proxy.DisableWebService = false
|
|
cfg.Proxy.DisableWebInterface = true
|
|
cfg.SSH.Enabled = true
|
|
cfg.SSH.AllowTCPForwarding = tt.portForwardingAllowed
|
|
|
|
teleport := suite.NewTeleportWithConfig(t, logins, nil, cfg)
|
|
defer teleport.StopAll()
|
|
|
|
site := teleport.GetSiteAPI(helpers.Site)
|
|
|
|
// ...and a running dummy server
|
|
remoteSvr := httptest.NewServer(http.HandlerFunc(
|
|
func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("Hello, World"))
|
|
}))
|
|
defer remoteSvr.Close()
|
|
|
|
// ... and a client connection that was launched with port
|
|
// forwarding enabled to that dummy server
|
|
localPort := newPortValue()
|
|
remotePort, err := extractPort(remoteSvr)
|
|
require.NoError(t, err)
|
|
|
|
nodeSSHPort := helpers.Port(t, teleport.SSH)
|
|
cl, err := teleport.NewClient(helpers.ClientConfig{
|
|
Login: tt.login,
|
|
Cluster: helpers.Site,
|
|
Host: Host,
|
|
Port: nodeSSHPort,
|
|
})
|
|
require.NoError(t, err)
|
|
cl.Config.LocalForwardPorts = []client.ForwardedPort{
|
|
{
|
|
SrcIP: "127.0.0.1",
|
|
SrcPort: localPort,
|
|
DestHost: "localhost",
|
|
DestPort: remotePort,
|
|
},
|
|
}
|
|
term := NewTerminal(250)
|
|
cl.Stdout = term
|
|
cl.Stdin = term
|
|
|
|
sshSessionCtx, sshSessionCancel := context.WithCancel(context.Background())
|
|
go cl.SSH(sshSessionCtx, []string{}, false)
|
|
defer sshSessionCancel()
|
|
|
|
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
_, err = waitForSessionToBeEstablished(timeout, apidefaults.Namespace, site)
|
|
require.NoError(t, err)
|
|
|
|
// When everything is *finally* set up, and I attempt to use the
|
|
// forwarded connection
|
|
localURL := fmt.Sprintf("http://%s:%d/", "localhost", localPort)
|
|
r, err := http.Get(localURL)
|
|
|
|
if r != nil {
|
|
r.Body.Close()
|
|
}
|
|
|
|
if tt.expectSuccess {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, r)
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|