mirror of
https://github.com/containers/gvisor-tap-vsock.git
synced 2025-10-24 17:20:28 +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:
|
||||
name: qcon
|
||||
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