ssh: add openssh controlmaster socket support
Adds support for establishing SSH sessions over an existing "ControlMaster" [unix domain] socket in proxy mode. Details of the protocol can be found here: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.mux More details about ControlMaster sockets can be found here: https://linux.die.net/man/5/ssh_config Fixes golang/go#32958 Co-authored-by: Cyrus Katrak <ckatrak@slack-corp.com> Change-Id: Ia3ae8893262f5060ed3fadfcbe97619c9659145b Reviewed-on: https://go-review.googlesource.com/c/crypto/+/733040 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Nicola Murino <nicola.murino@gmail.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
committed by
Nicola Murino
co-authored by
Cyrus Katrak
parent
d59570d1a3
commit
d2fe592df1
@@ -7,11 +7,13 @@ package test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/internal/testenv"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -161,3 +163,87 @@ func TestSSHCLIKeyExchanges(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHCLIControlClientConn(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skipf("always fails on Windows, see #64403")
|
||||
}
|
||||
sshCLI := sshClient(t)
|
||||
keyFiles := map[string][]byte{
|
||||
"rsa": testdata.PEMBytes["rsa"],
|
||||
"rsa.pub": ssh.MarshalAuthorizedKey(testPublicKeys["rsa"]),
|
||||
}
|
||||
keyPrivPath := setupSSHCLIKeys(t, keyFiles, "rsa")
|
||||
|
||||
config := &ssh.ServerConfig{
|
||||
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if conn.User() == "testcontrolproxy" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
|
||||
},
|
||||
}
|
||||
config.AddHostKey(testSigners["rsa"])
|
||||
|
||||
server, err := newTestServer(config)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to start test server: %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
port, err := server.port()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get server port: %v", err)
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "controlSocket")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir for control socket: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
csPath := filepath.Join(dir, "c")
|
||||
cmd := testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no",
|
||||
"-p", port, "-o", "ControlPath="+csPath, "-o", "ControlMaster=yes", "-N", "testcontrolproxy@127.0.0.1")
|
||||
var output bytes.Buffer
|
||||
cmd.Stdout = &output
|
||||
cmd.Stderr = &output
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("control socket master start failed, error: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
if t.Failed() {
|
||||
t.Logf("OpenSSH output:\n\n%s", cmd.Stdout)
|
||||
}
|
||||
}()
|
||||
for i := range 10 {
|
||||
if _, err := os.Stat(csPath); err == nil {
|
||||
break
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Fatalf("unable to stat control socket: %v", err)
|
||||
}
|
||||
time.Sleep((1 << i) * 5 * time.Millisecond)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", csPath)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to open control socket: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
cc, chans, reqs, err := ssh.NewControlClientConn(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create client: %v", err)
|
||||
}
|
||||
client := ssh.NewClient(cc, chans, reqs)
|
||||
defer client.Close()
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
out, err := session.CombinedOutput("true")
|
||||
if err != nil {
|
||||
t.Fatalf("command execution failed, error: %v, command output %q", err, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user