Skip to content

Commit

Permalink
Logtunnel (#1)
Browse files Browse the repository at this point in the history
* * When a new channel is opened we got stuck in the select loop in
bastionsession.go, and we couldn't open a new channel. The fix is
easy it calls the bastionsession.ChannelHandler in a goroutine,
at the cost of some error management. I think this is ok because
we can allow a channel to fail on his own. This seems to be
* This add the tunnel feature, which use a new concurrent channel.
* This add some pcap logging for tunnel.
For now it is logged only one way, and the logged ip packet seems
buggy.

* Add logtunnuel as a package.
The logfile format is a tweaked version of ttyrec format file as it will be easy to review the use of human readable tunnel...

To get the ChannelHandler work as a go routine I had to deactivate lint errcheck for logcahnnel. I think this could be a problem. What is your thoughts about this ?
  • Loading branch information
Manuel Sabban authored Jan 18, 2018
1 parent 7c4aab3 commit 2c3de75
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 38 deletions.
132 changes: 95 additions & 37 deletions pkg/bastionsession/bastionsession.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,69 +10,127 @@ import (

"github.com/gliderlabs/ssh"
"github.com/arkan/bastion/pkg/logchannel"
"github.com/moul/sshportal/pkg/logtunnel"
gossh "golang.org/x/crypto/ssh"
)


type ForwardData struct {
DestinationHost string
DestinationPort uint32
SourceHost string
SourcePort uint32
}

type Config struct {
Addr string
Logs string
ClientConfig *gossh.ClientConfig
}

func ChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context, config Config) error {
if newChan.ChannelType() != "session" {
switch newChan.ChannelType() {
case "session" :
lch, lreqs, err := newChan.Accept()
// TODO: defer clean closer
if err != nil {
// TODO: trigger event callback
return nil
}

// open client channel
rconn, err := gossh.Dial("tcp", config.Addr, config.ClientConfig)
if err != nil {
return err
}
defer func() { _ = rconn.Close() }()
rch, rreqs, err := rconn.OpenChannel("session", []byte{})
if err != nil {
return err
}
user := conn.User()
// pipe everything
return pipe(lreqs, rreqs, lch, rch, config.Logs, user, newChan)
case "direct-tcpip":
lch, lreqs, err := newChan.Accept()
// TODO: defer clean closer
if err != nil {
// TODO: trigger event callback
return nil
}

// open client channel
rconn, err := gossh.Dial("tcp", config.Addr, config.ClientConfig)
if err != nil {
return err
}
defer func() { _ = rconn.Close() }()
d := logtunnel.ForwardData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
return err
}
rch, rreqs, err := rconn.OpenChannel("direct-tcpip", newChan.ExtraData())
if err != nil {
return err
}
user := conn.User()
// pipe everything
return pipe(lreqs, rreqs, lch, rch, config.Logs, user, newChan)
default:
newChan.Reject(gossh.UnknownChannelType, "unsupported channel type")
return nil
}
lch, lreqs, err := newChan.Accept()
// TODO: defer clean closer
if err != nil {
// TODO: trigger event callback
return nil
}

// open client channel
rconn, err := gossh.Dial("tcp", config.Addr, config.ClientConfig)
if err != nil {
return err
}
defer func() { _ = rconn.Close() }()
rch, rreqs, err := rconn.OpenChannel("session", []byte{})
if err != nil {
return err
}
user := conn.User()
// pipe everything
return pipe(lreqs, rreqs, lch, rch, config.Logs, user)
}

func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocation string, user string) error {
func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocation string, user string, newChan gossh.NewChannel) error {
defer func() {
_ = lch.Close()
_ = rch.Close()
}()

errch := make(chan error, 1)
file_name := strings.Join([]string{logsLocation, "/", user, "-", time.Now().Format(time.RFC3339)}, "") // get user
channeltype := newChan.ChannelType()

file_name := strings.Join([]string{logsLocation, "/", user, "-", channeltype, "-", time.Now().Format(time.RFC3339)}, "") // get user
f, err := os.OpenFile(file_name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
defer f.Close()

if err != nil {
log.Fatalf("error: %v", err)
}

log.Printf("Session is recorded in %v", file_name)
wrappedlch := logchannel.New(lch, f)
go func() {
_, _ = io.Copy(wrappedlch, rch)
errch <- errors.New("lch closed the connection")
}()

defer f.Close()

go func() {
_, _ = io.Copy(rch, lch)
errch <- errors.New("rch closed the connection")
}()

log.Printf("Session %v is recorded in %v", channeltype, file_name)
if channeltype == "session" {
wrappedlch := logchannel.New(lch, f)
go func() {
_, _ = io.Copy(wrappedlch, rch)
errch <- errors.New("lch closed the connection")
}()

go func() {
_, _ = io.Copy(rch, lch)
errch <- errors.New("rch closed the connection")
}()
}
if channeltype == "direct-tcpip" {
d := logtunnel.ForwardData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
return err
}
wrappedlch := logtunnel.New(lch, f, d.SourceHost)
wrappedrch := logtunnel.New(rch, f, d.DestinationHost)
go func() {
_, _ = io.Copy(wrappedlch, rch)
errch <- errors.New("lch closed the connection")
}()

go func() {
_, _ = io.Copy(wrappedrch, lch)
errch <- errors.New("rch closed the connection")
}()
}


for {
select {
case req := <-lreqs: // forward ssh requests from local to remote
Expand Down
59 changes: 59 additions & 0 deletions pkg/logtunnel/logtunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package logtunnel

import (
"encoding/binary"
"io"
"syscall"
"time"

"golang.org/x/crypto/ssh"
)

type logTunnel struct {
host string
channel ssh.Channel
writer io.WriteCloser
}

type ForwardData struct {
DestinationHost string
DestinationPort uint32
SourceHost string
SourcePort uint32
}

func writeHeader(fd io.Writer, length int) {
t := time.Now()

tv := syscall.NsecToTimeval(t.UnixNano())

binary.Write(fd, binary.LittleEndian, int32(tv.Sec))
binary.Write(fd, binary.LittleEndian, int32(tv.Usec))
binary.Write(fd, binary.LittleEndian, int32(length))
}

func New(channel ssh.Channel, writer io.WriteCloser, host string) *logTunnel {
return &logTunnel{
host: host,
channel: channel,
writer: writer,
}
}

func (l *logTunnel) Read(data []byte) (int, error) {
return l.Read(data)
}

func (l *logTunnel) Write(data []byte) (int, error) {
writeHeader(l.writer, len(data) + len(l.host + ": "))
l.writer.Write([]byte(l.host + ": "))
l.writer.Write(data)

return l.channel.Write(data)
}

func (l *logTunnel) Close() error {
l.writer.Close()

return l.channel.Close()
}
3 changes: 2 additions & 1 deletion ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func dynamicHostKey(db *gorm.DB, host *Host) gossh.HostKeyCallback {
func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
switch newChan.ChannelType() {
case "session":
case "direct-tcpip":
default:
// TODO: handle direct-tcp (only for ssh scheme)
if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil {
Expand Down Expand Up @@ -140,7 +141,7 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
return
}

err = bastionsession.ChannelHandler(srv, conn, newChan, ctx, bastionsession.Config{
go bastionsession.ChannelHandler(srv, conn, newChan, ctx, bastionsession.Config{ // nolint: errcheck
Addr: host.DialAddr(),
ClientConfig: clientConfig,
Logs: actx.config.logsLocation,
Expand Down

0 comments on commit 2c3de75

Please sign in to comment.