Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logtunnel #1

Merged
merged 7 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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