Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v8) Merge 'config-proxy' and 'proxy ssh' commands logic #8958

Merged
merged 4 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 3 additions & 31 deletions lib/srv/alpnproxy/local_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ import (
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
appaws "github.com/gravitational/teleport/lib/srv/app/aws"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/agentconn"
)

// LocalProxy allows upgrading incoming connection to TLS where custom TLS values are set SNI ALPN and
Expand Down Expand Up @@ -108,7 +106,7 @@ func NewLocalProxy(cfg LocalProxyConfig) (*LocalProxy, error) {

// SSHProxy is equivalent of `ssh -o 'ForwardAgent yes' -p port %r@host -s proxy:%h:%p` but established SSH
// connection to RemoteProxyAddr is wrapped with TLS protocol.
func (l *LocalProxy) SSHProxy() error {
func (l *LocalProxy) SSHProxy(localAgent *client.LocalKeyAgent) error {
if l.cfg.ClientTLSConfig == nil {
return trace.BadParameter("client TLS config is missing")
}
Expand All @@ -124,14 +122,10 @@ func (l *LocalProxy) SSHProxy() error {
}
defer upstreamConn.Close()

sshAgent, err := getAgent()
if err != nil {
return trace.Wrap(err)
}
client, err := makeSSHClient(upstreamConn, l.cfg.RemoteProxyAddr, &ssh.ClientConfig{
User: l.cfg.SSHUser,
Auth: []ssh.AuthMethod{
ssh.PublicKeysCallback(sshAgent.Signers),
ssh.PublicKeysCallback(localAgent.Signers),
},
HostKeyCallback: l.cfg.SSHHostKeyCallback,
})
Expand All @@ -146,15 +140,6 @@ func (l *LocalProxy) SSHProxy() error {
}
defer sess.Close()

err = agent.ForwardToAgent(client, sshAgent)
if err != nil {
return trace.Wrap(err)
}
err = agent.RequestAgentForwarding(sess)
if err != nil {
return trace.Wrap(err)
}

if err = sess.RequestSubsystem(proxySubsystemName(l.cfg.SSHUserHost, l.cfg.SSHTrustedCluster)); err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -300,19 +285,6 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC
return trace.NewAggregate(errs...)
}

func getAgent() (agent.ExtendedAgent, error) {
agentSocket := os.Getenv(teleport.SSHAuthSock)
if agentSocket == "" {
return nil, trace.NotFound("failed to connect to SSH agent, %s env var not set", teleport.SSHAuthSock)
}

conn, err := agentconn.Dial(agentSocket)
if err != nil {
return nil, trace.Wrap(err)
}
return agent.NewClient(conn), nil
}

func (l *LocalProxy) Close() error {
l.cancel()
if l.cfg.Listener != nil {
Expand Down
10 changes: 4 additions & 6 deletions tool/tsh/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import (
"strings"
"text/template"

"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/profile"
"github.com/gravitational/teleport/api/utils/keypaths"
"github.com/gravitational/trace"
)

const sshConfigTemplate = `
Expand All @@ -41,7 +42,7 @@ Host *.{{ .ClusterName }} {{ .ProxyHost }}
# Flags for all {{ .ClusterName }} hosts except the proxy
Host *.{{ .ClusterName }} !{{ .ProxyHost }}
Port 3022
ProxyCommand "{{ .TSHPath }}" config-proxy --login="%r" --proxy={{ .ProxyHost }}:{{ .ProxyPort }} %h:%p "{{ .ClusterName }}"
ProxyCommand "{{ .TSHPath }}" proxy ssh --cluster={{ .ClusterName }} --proxy={{ .ProxyHost }} %r@%h:%p
`

type hostConfigParameters struct {
Expand All @@ -50,7 +51,6 @@ type hostConfigParameters struct {
IdentityFilePath string
CertificateFilePath string
ProxyHost string
ProxyPort string
TSHPath string
}

Expand Down Expand Up @@ -88,7 +88,7 @@ func onConfig(cf *CLIConf) error {

// Note: TeleportClient.connectToProxy() overrides the proxy address when
// JumpHosts are in use, which this does not currently implement.
proxyHost, proxyPort, err := net.SplitHostPort(tc.Config.SSHProxyAddr)
proxyHost, _, err := net.SplitHostPort(tc.Config.SSHProxyAddr)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -127,7 +127,6 @@ func onConfig(cf *CLIConf) error {
IdentityFilePath: identityFilePath,
CertificateFilePath: keypaths.SSHCertPath(keysDir, proxyHost, tc.Config.Username, rootClusterName),
ProxyHost: proxyHost,
ProxyPort: proxyPort,
TSHPath: cf.executablePath,
})
if err != nil {
Expand All @@ -141,7 +140,6 @@ func onConfig(cf *CLIConf) error {
IdentityFilePath: identityFilePath,
CertificateFilePath: keypaths.SSHCertPath(keysDir, proxyHost, tc.Config.Username, rootClusterName),
ProxyHost: proxyHost,
ProxyPort: proxyPort,
TSHPath: cf.executablePath,
})
if err != nil {
Expand Down
52 changes: 52 additions & 0 deletions tool/tsh/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
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 main

import (
"strings"
"testing"

"github.com/stretchr/testify/require"
)

// TestWriteSSHConfig tests the writeSSHConfig template output.
func TestWriteSSHConfig(t *testing.T) {
want := `
# Common flags for all test-cluster hosts
Host *.test-cluster localhost
UserKnownHostsFile "/tmp/know_host"
IdentityFile "/tmp/alice"
CertificateFile "/tmp/localhost-cert.pub"

# Flags for all test-cluster hosts except the proxy
Host *.test-cluster !localhost
Port 3022
ProxyCommand "/bin/tsh" proxy ssh --cluster=test-cluster --proxy=localhost %r@%h:%p
`

var sb strings.Builder
err := writeSSHConfig(&sb, hostConfigParameters{
ClusterName: "test-cluster",
KnownHostsPath: "/tmp/know_host",
IdentityFilePath: "/tmp/alice",
CertificateFilePath: "/tmp/localhost-cert.pub",
ProxyHost: "localhost",
TSHPath: "/bin/tsh",
})
require.NoError(t, err)
require.Equal(t, want, sb.String())
}
2 changes: 1 addition & 1 deletion tool/tsh/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestDatabaseLogin(t *testing.T) {
require.NoError(t, err)
alice.SetRoles([]string{"access"})

authProcess, proxyProcess := makeTestServers(t, connector, alice)
authProcess, proxyProcess := makeTestServers(t, withBootstrap(connector, alice))
makeTestDatabaseServer(t, authProcess, proxyProcess, service.Database{
Name: "postgres",
Protocol: defaults.ProtocolPostgres,
Expand Down
64 changes: 62 additions & 2 deletions tool/tsh/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"fmt"
"net"
"os"
"os/exec"
"strconv"
"strings"
"text/template"

"github.com/gravitational/trace"
Expand All @@ -32,12 +35,43 @@ import (
"github.com/gravitational/teleport/lib/utils"
)

// onProxyCommandSSH creates a local ssh proxy.
// In cases of TLS Routing the connection is established to the WebProxy with teleport-proxy-ssh ALPN protocol.
// and all ssh traffic is forwarded through the local ssh proxy.
//
// If proxy doesn't support TLS Routing the onProxyCommandSSH is used as ProxyCommand to remove proxy/site prefixes
// from destination node address to support multiple platform where 'cut -d' command is not provided.
// For more details please look at: Generate Windows-compatible OpenSSH config https://github.com/gravitational/teleport/pull/7848
func onProxyCommandSSH(cf *CLIConf) error {
tc, err := makeClient(cf, false)
if err != nil {
return trace.Wrap(err)
}

targetHost, targetPort, err := net.SplitHostPort(tc.Host)
if err != nil {
return trace.Wrap(err)
}
targetHost = cleanTargetHost(targetHost, tc.WebProxyHost(), tc.SiteName)

if tc.TLSRoutingEnabled {
return trace.Wrap(sshProxyWithTLSRouting(cf, tc, targetHost, targetPort))
}

return trace.Wrap(sshProxy(cf, tc, targetHost, targetPort))
}

// cleanTargetHost cleans the targetHost and remote site and proxy suffixes.
// Before the `cut -d` command was used for this purpose but to support multi-platform OpenSSH clients the logic
// it was moved tsh proxy ssh command.
// For more details please look at: Generate Windows-compatible OpenSSH config https://github.com/gravitational/teleport/pull/7848
func cleanTargetHost(targetHost, proxyHost, siteName string) string {
targetHost = strings.TrimSuffix(targetHost, "."+proxyHost)
targetHost = strings.TrimSuffix(targetHost, "."+siteName)
return targetHost
}

func sshProxyWithTLSRouting(cf *CLIConf, tc *libclient.TeleportClient, targetHost, targetPort string) error {
address, err := utils.ParseAddr(tc.WebProxyAddr)
if err != nil {
return trace.Wrap(err)
Expand All @@ -58,7 +92,7 @@ func onProxyCommandSSH(cf *CLIConf) error {
ParentContext: cf.Context,
SNI: address.Host(),
SSHUser: tc.HostLogin,
SSHUserHost: cf.UserHost,
SSHUserHost: fmt.Sprintf("%s:%s", targetHost, targetPort),
SSHHostKeyCallback: tc.HostKeyCallback,
SSHTrustedCluster: cf.SiteName,
ClientTLSConfig: tlsConfig,
Expand All @@ -67,12 +101,38 @@ func onProxyCommandSSH(cf *CLIConf) error {
return trace.Wrap(err)
}
defer lp.Close()
if err := lp.SSHProxy(); err != nil {
if err := lp.SSHProxy(tc.LocalAgent()); err != nil {
return trace.Wrap(err)
}
return nil
}

func sshProxy(cf *CLIConf, tc *libclient.TeleportClient, targetHost, targetPort string) error {
sshPath, err := getSSHPath()
if err != nil {
return trace.Wrap(err)
}

sshHost, sshPort := tc.SSHProxyHostPort()
args := []string{
"-p",
strconv.Itoa(sshPort),
sshHost,
"-s",
fmt.Sprintf("proxy:%s:%s@%s", targetHost, targetPort, tc.SiteName),
}

if cf.NodeLogin != "" {
args = append([]string{"-l", cf.NodeLogin}, args...)
}

child := exec.Command(sshPath, args...)
child.Stdin = os.Stdin
child.Stdout = os.Stdout
child.Stderr = os.Stderr
return trace.Wrap(child.Run())
}

func onProxyCommandDB(cf *CLIConf) error {
client, err := makeClient(cf, false)
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion tool/tsh/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

"github.com/gravitational/teleport/api/profile"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/teleagent"
)

Expand All @@ -55,7 +56,12 @@ func TestProxySSHDial(t *testing.T) {
require.NoError(t, err)
alice.SetRoles([]string{"access", "ssh-login"})

authProcess, proxyProcess := makeTestServers(t, connector, alice, sshLoginRole)
authProcess, proxyProcess := makeTestServers(t,
withBootstrap(connector, alice, sshLoginRole),
withAuthConfig(func(cfg *service.AuthConfig) {
cfg.NetworkingConfig.SetProxyListenerMode(types.ProxyListenerMode_Multiplex)
}),
)

authServer := authProcess.GetAuthServer()
require.NotNil(t, authServer)
Expand Down
1 change: 1 addition & 0 deletions tool/tsh/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ func Run(args []string, opts ...cliOption) error {
// config-proxy is a wrapper to ensure Windows clients can properly use
// `tsh config`. As it's not intended to run by users directly and may
// not have a stable CLI interface, hide it.
// DELETE IN 9.0: The config-proxy logic was moved to the tsh proxy ssh command.
configProxy := app.Command("config-proxy", "ProxyCommand wrapper for SSH config as generated by `config`").Hidden()
configProxy.Arg("target", "Target node host:port").Required().StringVar(&cf.ConfigProxyTarget)
configProxy.Arg("cluster-name", "Target cluster name").Required().StringVar(&cf.SiteName)
Expand Down
Loading