mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 01:34:01 +00:00
Moved tests from lib/srv and lib/utils into integrations.
This commit is contained in:
parent
e5d6faf482
commit
5f670ef7d9
|
@ -6,11 +6,12 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gravitational/teleport"
|
"github.com/gravitational/teleport"
|
||||||
"github.com/gravitational/teleport/lib/auth"
|
"github.com/gravitational/teleport/lib/auth"
|
||||||
"github.com/gravitational/teleport/lib/auth/native"
|
"github.com/gravitational/teleport/lib/auth/native"
|
||||||
|
@ -23,7 +24,10 @@ import (
|
||||||
"github.com/gravitational/teleport/lib/service"
|
"github.com/gravitational/teleport/lib/service"
|
||||||
"github.com/gravitational/teleport/lib/services"
|
"github.com/gravitational/teleport/lib/services"
|
||||||
"github.com/gravitational/teleport/lib/utils"
|
"github.com/gravitational/teleport/lib/utils"
|
||||||
|
|
||||||
"github.com/gravitational/trace"
|
"github.com/gravitational/trace"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetTestTimeouts affects global timeouts inside Teleport, making connections
|
// SetTestTimeouts affects global timeouts inside Teleport, making connections
|
||||||
|
@ -558,6 +562,88 @@ func (i *TeleInstance) Stop(removeData bool) error {
|
||||||
return i.Process.Wait()
|
return i.Process.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type proxyServer struct {
|
||||||
|
sync.Mutex
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP only accepts the CONNECT verb and will tunnel your connection to
|
||||||
|
// the specified host. Also tracks the number of connections that it proxies for
|
||||||
|
// debugging purposes.
|
||||||
|
func (p *proxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// validate http connect parameters
|
||||||
|
if r.Method != http.MethodConnect {
|
||||||
|
trace.WriteError(w, trace.BadParameter("%v not supported", r.Method))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Host == "" {
|
||||||
|
trace.WriteError(w, trace.BadParameter("host not set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// hijack request so we can get underlying connection
|
||||||
|
hj, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
trace.WriteError(w, trace.AccessDenied("unable to hijack connection"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sconn, _, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
trace.WriteError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sconn.Close()
|
||||||
|
|
||||||
|
// dial to host we want to proxy connection to
|
||||||
|
dconn, err := net.Dial("tcp", r.Host)
|
||||||
|
if err != nil {
|
||||||
|
trace.WriteError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dconn.Close()
|
||||||
|
|
||||||
|
// write 200 OK to the source, but don't close the connection
|
||||||
|
resp := &http.Response{
|
||||||
|
Status: "OK",
|
||||||
|
StatusCode: 200,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 0,
|
||||||
|
}
|
||||||
|
err = resp.Write(sconn)
|
||||||
|
if err != nil {
|
||||||
|
trace.WriteError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// success, we're proxying data now
|
||||||
|
p.Lock()
|
||||||
|
p.count = p.count + 1
|
||||||
|
p.Unlock()
|
||||||
|
|
||||||
|
// copy from src to dst and dst to src
|
||||||
|
errc := make(chan error, 2)
|
||||||
|
replicate := func(dst io.Writer, src io.Reader) {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
go replicate(sconn, dconn)
|
||||||
|
go replicate(dconn, sconn)
|
||||||
|
|
||||||
|
// wait until done, error, or 10 second
|
||||||
|
select {
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
case <-errc:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of connections that have been proxied.
|
||||||
|
func (p *proxyServer) Count() int {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
return p.count
|
||||||
|
}
|
||||||
|
|
||||||
func fatalIf(err error) {
|
func fatalIf(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("", err)
|
log.Fatal("", err)
|
||||||
|
|
|
@ -21,8 +21,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -275,6 +279,86 @@ func (s *IntSuite) TestAudit(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestInteroperability checks if Teleport and OpenSSH behave in the same way
|
||||||
|
// when executing commands.
|
||||||
|
func (s *IntSuite) TestInteroperability(c *check.C) {
|
||||||
|
tempdir, err := ioutil.TempDir("", "teleport-")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer os.RemoveAll(tempdir)
|
||||||
|
tempfile := filepath.Join(tempdir, "file.txt")
|
||||||
|
|
||||||
|
// create new teleport server that will be used by all tests
|
||||||
|
t := s.newTeleport(c, nil, true)
|
||||||
|
defer t.Stop(true)
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
inCommand string
|
||||||
|
inStdin string
|
||||||
|
outContains string
|
||||||
|
outFile bool
|
||||||
|
}{
|
||||||
|
// 0 - echo "1\n2\n" | ssh localhost "cat -"
|
||||||
|
// this command can be used to copy files by piping stdout to stdin over ssh.
|
||||||
|
{
|
||||||
|
"cat -",
|
||||||
|
"1\n2\n",
|
||||||
|
"1\n2\n",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// 1 - ssh -tt locahost '/bin/sh -c "mkdir -p /tmp && echo a > /tmp/file.txt"'
|
||||||
|
// programs like ansible execute commands like this
|
||||||
|
{
|
||||||
|
fmt.Sprintf(`/bin/sh -c "mkdir -p /tmp && echo a > %v"`, tempfile),
|
||||||
|
"",
|
||||||
|
"a",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
// 2 - ssh localhost tty
|
||||||
|
// should print "not a tty"
|
||||||
|
{
|
||||||
|
"tty",
|
||||||
|
"",
|
||||||
|
"not a tty",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
// create new teleport client
|
||||||
|
cl, err := t.NewClient(s.me.Username, Site, Host, t.GetPortSSHInt())
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
// hook up stdin and stdout to a buffer for reading and writing
|
||||||
|
inbuf := bytes.NewReader([]byte(tt.inStdin))
|
||||||
|
outbuf := &bytes.Buffer{}
|
||||||
|
cl.Stdin = inbuf
|
||||||
|
cl.Stdout = outbuf
|
||||||
|
cl.Stderr = outbuf
|
||||||
|
|
||||||
|
// run command and wait a maximum of 10 seconds for it to complete
|
||||||
|
sessionEndC := make(chan interface{}, 0)
|
||||||
|
go func() {
|
||||||
|
// don't check for err, because sometimes this process should fail
|
||||||
|
// with an error and that's what the test is checking for.
|
||||||
|
cl.SSH(context.TODO(), []string{tt.inCommand}, false)
|
||||||
|
sessionEndC <- true
|
||||||
|
}()
|
||||||
|
waitFor(sessionEndC, time.Second*10)
|
||||||
|
|
||||||
|
// if we are looking for the output in a file, look in the file
|
||||||
|
// otherwise check stdout and stderr for the expected output
|
||||||
|
if tt.outFile {
|
||||||
|
bytes, err := ioutil.ReadFile(tempfile)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
comment := check.Commentf("Test %v: %q does not contain: %q", i, string(bytes), tt.outContains)
|
||||||
|
c.Assert(strings.Contains(string(bytes), tt.outContains), check.Equals, true, comment)
|
||||||
|
} else {
|
||||||
|
comment := check.Commentf("Test %v: %q does not contain: %q", i, outbuf.String(), tt.outContains)
|
||||||
|
c.Assert(strings.Contains(outbuf.String(), tt.outContains), check.Equals, true, comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestInteractive covers SSH into shell and joining the same session from another client
|
// TestInteractive covers SSH into shell and joining the same session from another client
|
||||||
func (s *IntSuite) TestInteractive(c *check.C) {
|
func (s *IntSuite) TestInteractive(c *check.C) {
|
||||||
t := s.newTeleport(c, nil, true)
|
t := s.newTeleport(c, nil, true)
|
||||||
|
@ -382,6 +466,16 @@ func (s *IntSuite) TestInvalidLogins(c *check.C) {
|
||||||
// Then it executes an SSH command on A by connecting directly
|
// Then it executes an SSH command on A by connecting directly
|
||||||
// to A and by connecting to B via B<->A tunnel
|
// to A and by connecting to B via B<->A tunnel
|
||||||
func (s *IntSuite) TestTwoClusters(c *check.C) {
|
func (s *IntSuite) TestTwoClusters(c *check.C) {
|
||||||
|
// start the http proxy, we need to make sure this was not used
|
||||||
|
ps := &proxyServer{}
|
||||||
|
ts := httptest.NewServer(ps)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// clear out any proxy environment variables
|
||||||
|
for _, v := range []string{"http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"} {
|
||||||
|
os.Setenv(v, "")
|
||||||
|
}
|
||||||
|
|
||||||
username := s.me.Username
|
username := s.me.Username
|
||||||
|
|
||||||
a := NewInstance("site-A", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
a := NewInstance("site-A", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||||
|
@ -410,6 +504,9 @@ func (s *IntSuite) TestTwoClusters(c *check.C) {
|
||||||
outputB bytes.Buffer
|
outputB bytes.Buffer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// make sure the direct dialer was used and not the proxy dialer
|
||||||
|
c.Assert(ps.Count(), check.Equals, 0)
|
||||||
|
|
||||||
// if we got here, it means two sites are cross-connected. lets execute SSH commands
|
// if we got here, it means two sites are cross-connected. lets execute SSH commands
|
||||||
sshPort := a.GetPortSSHInt()
|
sshPort := a.GetPortSSHInt()
|
||||||
cmd := []string{"echo", "hello world"}
|
cmd := []string{"echo", "hello world"}
|
||||||
|
@ -457,6 +554,51 @@ func (s *IntSuite) TestTwoClusters(c *check.C) {
|
||||||
c.Assert(a.Stop(true), check.IsNil)
|
c.Assert(a.Stop(true), check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTwoClustersProxy checks if the reverse tunnel uses a HTTP PROXY to
|
||||||
|
// establish a connection.
|
||||||
|
func (s *IntSuite) TestTwoClustersProxy(c *check.C) {
|
||||||
|
// start the http proxy
|
||||||
|
ps := &proxyServer{}
|
||||||
|
ts := httptest.NewServer(ps)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// set the http_proxy environment variable
|
||||||
|
u, err := url.Parse(ts.URL)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
os.Setenv("http_proxy", u.Host)
|
||||||
|
defer os.Setenv("http_proxy", "")
|
||||||
|
|
||||||
|
username := s.me.Username
|
||||||
|
|
||||||
|
a := NewInstance("site-A", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||||
|
b := NewInstance("site-B", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||||
|
|
||||||
|
a.AddUser(username, []string{username})
|
||||||
|
b.AddUser(username, []string{username})
|
||||||
|
|
||||||
|
c.Assert(b.Create(a.Secrets.AsSlice(), false, nil), check.IsNil)
|
||||||
|
c.Assert(a.Create(b.Secrets.AsSlice(), true, nil), check.IsNil)
|
||||||
|
|
||||||
|
c.Assert(b.Start(), check.IsNil)
|
||||||
|
c.Assert(a.Start(), check.IsNil)
|
||||||
|
|
||||||
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
||||||
|
abortTime := time.Now().Add(time.Second * 10)
|
||||||
|
for len(b.Tunnel.GetSites()) < 2 && len(b.Tunnel.GetSites()) < 2 {
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
if time.Now().After(abortTime) {
|
||||||
|
c.Fatalf("two sites do not see each other: tunnels are not working")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the reverse tunnel went through the proxy
|
||||||
|
c.Assert(ps.Count() > 0, check.Equals, true, check.Commentf("proxy did not intercept any connection"))
|
||||||
|
|
||||||
|
// stop both sites for real
|
||||||
|
c.Assert(b.Stop(true), check.IsNil)
|
||||||
|
c.Assert(a.Stop(true), check.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
// TestHA tests scenario when auth server for the cluster goes down
|
// TestHA tests scenario when auth server for the cluster goes down
|
||||||
// and we switch to local persistent caches
|
// and we switch to local persistent caches
|
||||||
func (s *IntSuite) TestHA(c *check.C) {
|
func (s *IntSuite) TestHA(c *check.C) {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
@ -291,254 +290,6 @@ func (s *SrvSuite) TestAgentForward(c *C) {
|
||||||
c.Fatalf("expected socket to be closed, still could dial after 150 ms")
|
c.Fatalf("expected socket to be closed, still could dial after 150 ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestExecCommandLocalPtyAndRemotePty tests executing a command where you have
|
|
||||||
// a local and remote PTY.
|
|
||||||
func (s *SrvSuite) TestExecCommandLocalPtyAndRemotePty(c *C) {
|
|
||||||
// expected output: success. top has a remote and local pty.
|
|
||||||
term, err := newTerminal()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session, err := s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session.Stdin = term.tty
|
|
||||||
session.Stdout = term.tty
|
|
||||||
session.Stderr = term.tty
|
|
||||||
|
|
||||||
modes := ssh.TerminalModes{
|
|
||||||
ssh.ECHO: 0,
|
|
||||||
ssh.TTY_OP_ISPEED: 14400,
|
|
||||||
ssh.TTY_OP_OSPEED: 14400,
|
|
||||||
}
|
|
||||||
err = session.RequestPty("term", 40, 80, modes)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
// run top, wait 500 ms for top to start, send ^C
|
|
||||||
go func() {
|
|
||||||
err := session.Run("top")
|
|
||||||
c.Assert(err, NotNil)
|
|
||||||
}()
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
_, err = term.tty.Write([]byte("\x03"))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
output := make([]byte, 2048)
|
|
||||||
_, err = term.pty.Read(output)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
outputContains := strings.Contains(string(output), "%CPU")
|
|
||||||
c.Assert(outputContains, Equals, true, Commentf("Output does not contain %CPU expected from top"))
|
|
||||||
|
|
||||||
// 2 - ssh -tt locahost '/bin/sh -c "mkdir -p /tmp && echo a > /tmp/file.txt"'
|
|
||||||
// expected output: success. this mirrors how ansible runs commands.
|
|
||||||
term, err = newTerminal()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session, err = s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session.Stdin = term.tty
|
|
||||||
session.Stdout = term.tty
|
|
||||||
session.Stderr = term.tty
|
|
||||||
|
|
||||||
modes = ssh.TerminalModes{
|
|
||||||
ssh.ECHO: 0,
|
|
||||||
ssh.TTY_OP_ISPEED: 14400,
|
|
||||||
ssh.TTY_OP_OSPEED: 14400,
|
|
||||||
}
|
|
||||||
err = session.RequestPty("term", 40, 80, modes)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
tempdir, err := ioutil.TempDir("", "teleport-")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
os.RemoveAll(tempdir)
|
|
||||||
tempfile := tempdir + "/file.txt"
|
|
||||||
|
|
||||||
command := fmt.Sprintf(`/bin/sh -c "mkdir -p %v && echo a > %v"`, tempdir, tempfile)
|
|
||||||
err = session.Run(command)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadFile(tempfile)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(string(bytes), Equals, "a\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestExecCommandLocalPtyAndNoRemotePty tests executing a command where you a
|
|
||||||
// local PTY but no remote PTY.
|
|
||||||
func (s *SrvSuite) TestExecCommandLocalPtyAndNoRemotePty(c *C) {
|
|
||||||
// 1 - command: ssh localhost top
|
|
||||||
// expected output: failure. no remote pty exists so top will not start.
|
|
||||||
term, err := newTerminal()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session, err := s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session.Stdin = term.tty
|
|
||||||
session.Stdout = term.tty
|
|
||||||
session.Stderr = term.tty
|
|
||||||
|
|
||||||
err = session.Run("top")
|
|
||||||
c.Assert(err, NotNil)
|
|
||||||
|
|
||||||
output := make([]byte, 21)
|
|
||||||
_, err = term.pty.Read(output)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(string(output), Equals, "top: failed tty get\r\n")
|
|
||||||
|
|
||||||
// 2 - command: ssh localhost seq 2
|
|
||||||
// expected output: success. because we have a local pty attached
|
|
||||||
// to stdout, the pty transforms 1\n2\n to 1\r\n2\r\n.
|
|
||||||
term, err = newTerminal()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session, err = s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
session.Stdin = term.tty
|
|
||||||
session.Stdout = term.tty
|
|
||||||
session.Stderr = term.tty
|
|
||||||
|
|
||||||
err = session.Run("seq 2")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
output = make([]byte, 6)
|
|
||||||
_, err = term.pty.Read(output)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(string(output), Equals, "1\r\n2\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestExecCommandNoLocalAndPtyRemotePty tests executing a command where you
|
|
||||||
// have no local PTY but have a remote PTY.
|
|
||||||
func (s *SrvSuite) TestExecCommandNoLocalPtyAndRemotePty(c *C) {
|
|
||||||
c.Skip("Temporarily remove test until we track down why it's flaky. Output of [0x0a 0x31 0x0a 0x32 0x0a] does not match expected output of [0x31 0x0d 0x0a 0x032 0x0d 0x0a 0x0a]")
|
|
||||||
|
|
||||||
// 1 - command: seq 2 | ssh -tt localhost 'stty -opost; echo'
|
|
||||||
// expected output: pipe stdin to ssh and turn off post-processing
|
|
||||||
// and echo stdin. we should see "1\r\n2\r\n\n"
|
|
||||||
session, err := s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdinPipe, err := session.StdinPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdoutPipe, err := session.StdoutPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
modes := ssh.TerminalModes{
|
|
||||||
ssh.ECHO: 0,
|
|
||||||
ssh.TTY_OP_ISPEED: 14400,
|
|
||||||
ssh.TTY_OP_OSPEED: 14400,
|
|
||||||
}
|
|
||||||
err = session.RequestPty("term", 40, 80, modes)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
_, err = stdinPipe.Write([]byte("1\n2\n"))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdinPipe.Close()
|
|
||||||
|
|
||||||
err = session.Run("stty -opost; echo")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdout, err := ioutil.ReadAll(stdoutPipe)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
outputContains := strings.Contains(string(stdout), "1\r\n2\r\n\n")
|
|
||||||
c.Assert(outputContains, Equals, true, Commentf("Output [%# x] does not match expected output: [%# x]", string(stdout), "1\r\n2\r\n\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestExecCommandNoLocalPtyAndNoRemotePty tests executing a command where
|
|
||||||
// you have no local PTY and no remote PTY. This is like running ssh
|
|
||||||
// with the -T flag.
|
|
||||||
func (s *SrvSuite) TestExecCommandNoLocalPtyAndNoRemotePty(c *C) {
|
|
||||||
// 1 - command: echo "1\n2\n" | ssh -T localhost "cat -"
|
|
||||||
// expected output: since no pty exists, cat takes stdin
|
|
||||||
// from the pipe and prints it out as-is: "1\n2\n"
|
|
||||||
session, err := s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdinPipe, err := session.StdinPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdoutPipe, err := session.StdoutPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
_, err = stdinPipe.Write([]byte("1\n2\n"))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdinPipe.Close()
|
|
||||||
|
|
||||||
err = session.Run("cat -")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdout, err := ioutil.ReadAll(stdoutPipe)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(string(stdout), Equals, "1\n2\n")
|
|
||||||
|
|
||||||
// 2 - command: echo "1\n2\n" | ssh -T localhost "cat - > /tmp/file"
|
|
||||||
// expected output: this is like using ssh to copy a file from one
|
|
||||||
// machine to another. since no pty exists, cat takes stdin from the pipe
|
|
||||||
// and writes it to disk as-is: "1\n2\n"
|
|
||||||
session, err = s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdinPipe, err = session.StdinPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdoutPipe, err = session.StdoutPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "teleport-")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
_, err = stdinPipe.Write([]byte("1\n2\n"))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
stdinPipe.Close()
|
|
||||||
|
|
||||||
cmd := fmt.Sprintf("cat - > %v", tmpfile.Name())
|
|
||||||
err = session.Run(cmd)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadFile(tmpfile.Name())
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(string(bytes), Equals, "1\n2\n")
|
|
||||||
|
|
||||||
c.Assert(os.Remove(tmpfile.Name()), IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestShell launches interactive shell session and executes a command
|
|
||||||
func (s *SrvSuite) TestShell(c *C) {
|
|
||||||
c.Skip("disabled")
|
|
||||||
se, err := s.clt.NewSession()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
writer, err := se.StdinPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
stdoutPipe, err := se.StdoutPipe()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
reader := bufio.NewReader(stdoutPipe)
|
|
||||||
|
|
||||||
c.Assert(se.Shell(), IsNil)
|
|
||||||
|
|
||||||
buf := make([]byte, 256)
|
|
||||||
_, err = reader.Read(buf)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(len(buf) > 0, Equals, true)
|
|
||||||
|
|
||||||
// send a few "keyboard inputs" into the session:
|
|
||||||
_, err = io.WriteString(writer, "echo $((50+100))\n\r")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
// read the output and make sure that "150" (output of $((50+100)) is there
|
|
||||||
// NOTE: this test may fail if you have errors in your .bashrc or .profile
|
|
||||||
// leading to tons of output when opening new bash session
|
|
||||||
foundOutput := false
|
|
||||||
for i := 0; i < 50 && !foundOutput; i++ {
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
_, err = reader.Read(buf)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
foundOutput = strings.Contains(string(buf), "150")
|
|
||||||
}
|
|
||||||
c.Assert(foundOutput, Equals, true)
|
|
||||||
c.Assert(se.Close(), IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SrvSuite) TestAllowedUsers(c *C) {
|
func (s *SrvSuite) TestAllowedUsers(c *C) {
|
||||||
up, err := newUpack(s.user, []string{s.user}, s.a)
|
up, err := newUpack(s.user, []string{s.user}, s.a)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
BIN
lib/utils/proxy/.proxy_test.go.swp
Normal file
BIN
lib/utils/proxy/.proxy_test.go.swp
Normal file
Binary file not shown.
|
@ -16,28 +16,12 @@ limitations under the License.
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
|
|
||||||
"github.com/gravitational/teleport/lib/utils"
|
"github.com/gravitational/teleport/lib/utils"
|
||||||
"github.com/gravitational/trace"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,58 +39,6 @@ func (s *ProxySuite) TearDownSuite(c *check.C) {}
|
||||||
func (s *ProxySuite) SetUpTest(c *check.C) {}
|
func (s *ProxySuite) SetUpTest(c *check.C) {}
|
||||||
func (s *ProxySuite) TearDownTest(c *check.C) {}
|
func (s *ProxySuite) TearDownTest(c *check.C) {}
|
||||||
|
|
||||||
func (s *ProxySuite) TestDirectDial(c *check.C) {
|
|
||||||
os.Unsetenv("https_proxy")
|
|
||||||
os.Unsetenv("http_proxy")
|
|
||||||
|
|
||||||
d := debugServer{}
|
|
||||||
err := d.Start()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
dialer := DialerFromEnvironment()
|
|
||||||
client, err := dialer.Dial("tcp", d.Address(), &ssh.ClientConfig{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
session, err := client.NewSession()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
session.Run("date")
|
|
||||||
session.Close()
|
|
||||||
client.Close()
|
|
||||||
|
|
||||||
c.Assert(d.Commands(), check.DeepEquals, []string{"date"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProxySuite) TestProxyDial(c *check.C) {
|
|
||||||
dh := &debugHandler{}
|
|
||||||
ts := httptest.NewServer(dh)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
u, err := url.Parse(ts.URL)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
os.Setenv("http_proxy", u.Host)
|
|
||||||
|
|
||||||
ds := debugServer{}
|
|
||||||
err = ds.Start()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
dialer := DialerFromEnvironment()
|
|
||||||
client, err := dialer.Dial("tcp", ds.Address(), &ssh.ClientConfig{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
session, err := client.NewSession()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
session.Run("date")
|
|
||||||
session.Close()
|
|
||||||
client.Close()
|
|
||||||
|
|
||||||
c.Assert(ds.Commands(), check.DeepEquals, []string{"date"})
|
|
||||||
c.Assert(dh.Count(), check.Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProxySuite) TestGetProxyAddress(c *check.C) {
|
func (s *ProxySuite) TestGetProxyAddress(c *check.C) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
inEnvName string
|
inEnvName string
|
||||||
|
@ -151,222 +83,8 @@ func (s *ProxySuite) TestGetProxyAddress(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type debugServer struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
addr string
|
|
||||||
commands []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) Start() error {
|
|
||||||
hostkey, err := d.generateHostKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
freePorts, err := utils.GetFreeTCPPorts(10)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
srvPort := freePorts[len(freePorts)-1]
|
|
||||||
d.addr = "127.0.0.1:" + srvPort
|
|
||||||
|
|
||||||
config := &ssh.ServerConfig{
|
|
||||||
NoClientAuth: true,
|
|
||||||
}
|
|
||||||
config.AddHostKey(hostkey)
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", d.addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Unable to accept: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go d.handleConnection(conn, config)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) handleConnection(conn net.Conn, config *ssh.ServerConfig) error {
|
|
||||||
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
|
|
||||||
newchan := <-chans
|
|
||||||
channel, requests, err := newchan.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := <-requests
|
|
||||||
err = d.handleRequest(channel, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.Close()
|
|
||||||
sconn.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) handleRequest(channel ssh.Channel, req *ssh.Request) error {
|
|
||||||
if req.Type != "exec" {
|
|
||||||
req.Reply(false, nil)
|
|
||||||
return trace.BadParameter("only exec type supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
type execRequest struct {
|
|
||||||
Command string
|
|
||||||
}
|
|
||||||
|
|
||||||
var e execRequest
|
|
||||||
if err := ssh.Unmarshal(req.Payload, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := exec.Command(e.Command).Output()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
io.Copy(channel, bytes.NewReader(out))
|
|
||||||
channel.Close()
|
|
||||||
|
|
||||||
d.Lock()
|
|
||||||
d.commands = append(d.commands, e.Command)
|
|
||||||
d.Unlock()
|
|
||||||
|
|
||||||
if req.WantReply {
|
|
||||||
req.Reply(true, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) generateHostKey() (ssh.Signer, error) {
|
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKeyPEM := &pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
||||||
}
|
|
||||||
var privateKeyBuffer bytes.Buffer
|
|
||||||
err = pem.Encode(&privateKeyBuffer, privateKeyPEM)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostkey, err := ssh.ParsePrivateKey(privateKeyBuffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostkey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) Commands() []string {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
return d.commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugServer) Address() string {
|
|
||||||
return d.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type debugHandler struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// validate http connect parameters
|
|
||||||
if r.Method != http.MethodConnect {
|
|
||||||
http.Error(w, fmt.Sprintf("%v not supported", r.Method), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Host == "" {
|
|
||||||
http.Error(w, fmt.Sprintf("host not set"), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// hijack request so we can get underlying connection
|
|
||||||
hj, ok := w.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "unable to hijack connection", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sconn, _, err := hj.Hijack()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// dial to host we want to proxy connection to
|
|
||||||
dconn, err := net.Dial("tcp", r.Host)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// write 200 OK to the source, but don't close the connection
|
|
||||||
resp := &http.Response{
|
|
||||||
Status: "OK",
|
|
||||||
StatusCode: 200,
|
|
||||||
Proto: "HTTP/1.1",
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 0,
|
|
||||||
}
|
|
||||||
resp.Write(sconn)
|
|
||||||
|
|
||||||
// copy from src to dst and dst to src
|
|
||||||
done := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
io.Copy(sconn, dconn)
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
io.Copy(dconn, sconn)
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
d.Lock()
|
|
||||||
d.count = d.count + 1
|
|
||||||
d.Unlock()
|
|
||||||
|
|
||||||
// wait until done
|
|
||||||
<-done
|
|
||||||
<-done
|
|
||||||
|
|
||||||
// close the connections
|
|
||||||
sconn.Close()
|
|
||||||
dconn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *debugHandler) Count() int {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
return d.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsetEnv() {
|
func unsetEnv() {
|
||||||
os.Unsetenv("http_proxy")
|
for _, envname := range []string{"http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"} {
|
||||||
os.Unsetenv("HTTP_PROXY")
|
os.Unsetenv(envname)
|
||||||
os.Unsetenv("https_proxy")
|
}
|
||||||
os.Unsetenv("HTTPS_PROXY")
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue