mirror of
https://github.com/containers/gvisor-tap-vsock.git
synced 2025-10-19 23:26:09 +08:00
Add tests for win-sshproxy
Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
19
.github/workflows/go.yml
vendored
19
.github/workflows/go.yml
vendored
@@ -52,3 +52,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: qcon
|
name: qcon
|
||||||
path: test/qcon.log
|
path: test/qcon.log
|
||||||
|
|
||||||
|
win-sshproxy-tests:
|
||||||
|
runs-on: windows-latest # Only builds/runs on windows
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.17
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -ldflags -H=windowsgui -o bin/win-sshproxy.exe ./cmd/win-sshproxy
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v .\test-win-sshproxy
|
||||||
|
|
||||||
|
|
||||||
|
89
test-win-sshproxy/basic_test.go
Normal file
89
test-win-sshproxy/basic_test.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
winio "github.com/Microsoft/go-winio"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var timeout = 1 * time.Minute
|
||||||
|
|
||||||
|
var _ = Describe("connectivity", func() {
|
||||||
|
It("proxy exits as requested, without a kill", func() {
|
||||||
|
err := startProxy()
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
var pid uint32
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
pid, _, err = readTid()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
proc, err := os.FindProcess(int(pid))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(proc).ShouldNot(BeNil())
|
||||||
|
err = stopProxy(true)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("proxies over a windows pipe", func() {
|
||||||
|
err := startProxy()
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
defer stopProxy(false)
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return winio.DialPipe(`\\.\pipe\fake_docker_engine`, &timeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
resp, err := httpClient.Get("http://host/ping")
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
g.Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||||
|
g.Expect(resp.ContentLength).To(Equal(int64(4)))
|
||||||
|
|
||||||
|
reply := make([]byte, resp.ContentLength)
|
||||||
|
_, err = io.ReadAtLeast(resp.Body, reply, len(reply))
|
||||||
|
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(string(reply)).To(Equal("pong"))
|
||||||
|
|
||||||
|
}).Should(Succeed())
|
||||||
|
|
||||||
|
err = stopProxy(true)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("windows event logs were created", func() {
|
||||||
|
cmd := exec.Command("powershell", "-Command", "&{Get-WinEvent -ProviderName \".NET Runtime\" -MaxEvents 10 | Where-Object -Property Message -Match \"test:\"}")
|
||||||
|
reader, err := cmd.StdoutPipe()
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
cmd.Start()
|
||||||
|
output, err := ioutil.ReadAll(reader)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
cmd.Wait()
|
||||||
|
Expect(strings.Contains(string(output), `[info ] test: Listening on: \\.\pipe\fake_docker_engine`)).Should(BeTrue())
|
||||||
|
Expect(strings.Contains(string(output),`[info ] test: Socket forward established`)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
130
test-win-sshproxy/mock_sshserver.go
Normal file
130
test-win-sshproxy/mock_sshserver.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fakeHostKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACAkXGLzDNnY5+xdAgnt8FlBIZtoFOZEdTUkNxkdSM05PgAAAJg9WMAvPVjA
|
||||||
|
LwAAAAtzc2gtZWQyNTUxOQAAACAkXGLzDNnY5+xdAgnt8FlBIZtoFOZEdTUkNxkdSM05Pg
|
||||||
|
AAAEAFvLprhpMPdNsxSwo1Cs5VP5joCh9XLicRqKE0JJzdxCRcYvMM2djn7F0CCe3wWUEh
|
||||||
|
m2gU5kR1NSQ3GR1IzTk+AAAAEmphc29uQFRyaXBlbC5sb2NhbAECAw==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----`
|
||||||
|
|
||||||
|
type streamLocalDirect struct {
|
||||||
|
SocketPath string
|
||||||
|
Reserved0 string
|
||||||
|
Reserved1 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
|
||||||
|
func startMockServer() {
|
||||||
|
sshConfig := &ssh.ServerConfig{
|
||||||
|
NoClientAuth: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := ssh.ParsePrivateKey([]byte(fakeHostKey))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Could not parse key: %s", err)
|
||||||
|
}
|
||||||
|
sshConfig.AddHostKey(key)
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", ":2134")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
// proceed
|
||||||
|
}
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From a standard TCP connection to an encrypted SSH connection
|
||||||
|
_, chans, reqs, err := ssh.NewServerConn(conn, sshConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go handleRequests(reqs)
|
||||||
|
// Accept all channels
|
||||||
|
go handleChannels(chans)
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopMockServer() {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequests(reqs <-chan *ssh.Request) {
|
||||||
|
for _ = range reqs {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleChannels(chans <-chan ssh.NewChannel) {
|
||||||
|
directMsg := streamLocalDirect{}
|
||||||
|
for newChannel := range chans {
|
||||||
|
if t := newChannel.ChannelType(); t != "direct-streamlocal@openssh.com" {
|
||||||
|
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ssh.Unmarshal(newChannel.ExtraData(), &directMsg); err != nil {
|
||||||
|
logrus.Errorf("could not direct-streamlocal data: %s", err)
|
||||||
|
|
||||||
|
newChannel.Reject(ssh.Prohibited, "invalid format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, _, err := newChannel.Accept()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("could not accept channel: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.ReadRequest(bufio.NewReader(channel))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("could not process http request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := http.Response{}
|
||||||
|
resp.Close = true
|
||||||
|
switch req.RequestURI {
|
||||||
|
case "/ping":
|
||||||
|
resp.StatusCode = 200
|
||||||
|
resp.ContentLength = 4
|
||||||
|
resp.Body = io.NopCloser(strings.NewReader("pong"))
|
||||||
|
default:
|
||||||
|
resp.StatusCode = 404
|
||||||
|
resp.ContentLength = 0
|
||||||
|
}
|
||||||
|
resp.Write(channel)
|
||||||
|
channel.CloseWrite()
|
||||||
|
}
|
||||||
|
}
|
124
test-win-sshproxy/suite_test.go
Normal file
124
test-win-sshproxy/suite_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WM_QUIT = 0x12
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpDir string
|
||||||
|
binDir string
|
||||||
|
keyFile string
|
||||||
|
winSshProxy string
|
||||||
|
tidFile string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSuite(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "win-sshproxy suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&tmpDir, "tmpDir", "../tmp", "temporary working directory")
|
||||||
|
flag.StringVar(&binDir, "bin", "../bin", "directory with compiled binaries")
|
||||||
|
_ = os.MkdirAll(tmpDir, 0755)
|
||||||
|
keyFile = filepath.Join(tmpDir, "id.key")
|
||||||
|
_ = ioutil.WriteFile(keyFile, []byte(fakeHostKey), 0600)
|
||||||
|
winSshProxy = filepath.Join(binDir, "win-sshproxy.exe")
|
||||||
|
tidFile = filepath.Join(tmpDir, "win-sshproxy.tid")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
startMockServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
stopMockServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
func startProxy() error {
|
||||||
|
os.Remove(tidFile)
|
||||||
|
cmd := exec.Command(winSshProxy, "-debug", "test", tmpDir, "npipe:////./pipe/fake_docker_engine", "ssh://localhost:2134/run/podman/podman.sock", keyFile)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTid() (uint32, uint32, error) {
|
||||||
|
contents, err := ioutil.ReadFile(tidFile)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pid, tid uint32
|
||||||
|
fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
|
||||||
|
return pid, tid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendQuit(tid uint32) {
|
||||||
|
user32 := syscall.NewLazyDLL("user32.dll")
|
||||||
|
postMessage := user32.NewProc("PostThreadMessageW")
|
||||||
|
postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopProxy(noKill bool) error {
|
||||||
|
pid, tid, err := readTid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, err := os.FindProcess(int(pid))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sendQuit(tid)
|
||||||
|
state := waitTimeout(proc, 20*time.Second)
|
||||||
|
if state == nil || !state.Exited() {
|
||||||
|
if noKill {
|
||||||
|
return fmt.Errorf("proxy did not exit on request")
|
||||||
|
}
|
||||||
|
_ = proc.Kill()
|
||||||
|
state = waitTimeout(proc, 20*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == nil || !state.Exited() {
|
||||||
|
return fmt.Errorf("Stop proxy failed: %d", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = os.Remove(tidFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitTimeout(proc *os.Process, timeout time.Duration) *os.ProcessState {
|
||||||
|
return doTimeout(func(complete chan *os.ProcessState) {
|
||||||
|
state, _ := proc.Wait()
|
||||||
|
complete <- state
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTimeout(action func(complete chan *os.ProcessState), timeout time.Duration) *os.ProcessState {
|
||||||
|
complete := make(chan *os.ProcessState)
|
||||||
|
|
||||||
|
go action(complete)
|
||||||
|
select {
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case state := <-complete:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user