Skip to content

Instantly share code, notes, and snippets.

@moul
Created August 24, 2015 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moul/a6cb3fc8685ca3aa2f87 to your computer and use it in GitHub Desktop.
Save moul/a6cb3fc8685ca3aa2f87 to your computer and use it in GitHub Desktop.
ssh2docker tmp
package main
import (
"net"
"github.com/Sirupsen/logrus"
"github.com/moul/ssh2docker"
)
func main() {
server, err := ssh2docker.NewServer()
if err != nil {
logrus.Fatalf("Cannot create server: %v", err)
}
err = server.AddHostKeyFile("/Users/moul/Git/moul/ssh2docker/host_rsa")
if err != nil {
logrus.Fatalf("Cannot add host key file: %v", err)
}
listener, err := net.Listen("tcp", ":2222")
if err != nil {
logrus.Fatalf("Failed to start listener: %v", err)
}
logrus.Infof("Listening on port 2222")
for {
conn, err := listener.Accept()
if err != nil {
logrus.Error("Accept failed: %v", err)
continue
}
go server.Handle(conn)
}
}
package ssh2docker
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"sync"
"syscall"
"time"
"unsafe"
"github.com/Sirupsen/logrus"
"github.com/kr/pty"
"golang.org/x/crypto/ssh"
)
type sessionInfo struct {
User string
}
type Server struct {
sshConfig *ssh.ServerConfig
mu sync.RWMutex
sessions map[string]sessionInfo
}
type logEntry struct {
Timestamp string
Username string
Password string
ChannelTypes []string
RequestTypes []string
Error string
KeysOffered []string
ClientVersion string
}
func NewServer() (*Server, error) {
server := Server{
sessions: make(map[string]sessionInfo),
}
server.sshConfig = &ssh.ServerConfig{
PasswordCallback: server.PasswordCallback,
}
return &server, nil
}
func (s *Server) AddHostKeyFile(keypath string) error {
keystring, err := ioutil.ReadFile("host_rsa")
if err != nil {
return err
}
hostkey, err := ssh.ParsePrivateKey(keystring)
if err != nil {
return err
}
s.sshConfig.AddHostKey(hostkey)
return nil
}
func (s *Server) PasswordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
s.mu.Lock()
session := s.sessions[string(conn.SessionID())]
session.User = conn.User()
// si.Keys
s.sessions[string(conn.SessionID())] = session
s.mu.Unlock()
return nil, nil
}
func (s *Server) Handle(netConn net.Conn) error {
le := &logEntry{Timestamp: time.Now().Format(time.RFC3339)}
defer json.NewEncoder(os.Stdout).Encode(le)
conn, chans, reqs, err := ssh.NewServerConn(netConn, s.sshConfig)
if err != nil {
le.Error = "Handshake failed: " + err.Error()
return err
}
defer func() {
s.mu.Lock()
delete(s.sessions, string(conn.SessionID()))
s.mu.Unlock()
conn.Close()
}()
go func(in <-chan *ssh.Request) {
for req := range in {
le.RequestTypes = append(le.RequestTypes, req.Type)
if req.WantReply {
req.Reply(false, nil)
}
}
}(reqs)
//s.mu.RLock()
//session := s.sessions[string(conn.ClientVersion())]
//s.mu.RUnlock()
le.Username = conn.User()
le.ClientVersion = fmt.Sprintf("%x", conn.ClientVersion())
for newChannel := range chans {
le.ChannelTypes = append(le.ChannelTypes, newChannel.ChannelType())
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
le.Error = "Channel accept failed: " + err.Error()
return err
}
reqLock := &sync.Mutex{}
reqLock.Lock()
timeout := time.AfterFunc(30*time.Second, func() { reqLock.Unlock() })
logrus.Infof("Creating pty...")
f, tty, err := pty.Open()
if err != nil {
le.Error = fmt.Sprintf("Could not start pty: %v", err)
continue
}
go func(in <-chan *ssh.Request) {
for req := range in {
le.RequestTypes = append(le.RequestTypes, req.Type)
ok := false
switch req.Type {
case "shell":
if len(req.Payload) != 0 {
break
}
ok = true
cmd := exec.Command("docker", "run", "-it", "--rm", conn.User(), "/bin/sh")
cmd.Env = []string{
"TERM=xterm",
"DOCKER_HOST=" + os.Getenv("DOCKER_HOST"),
"DOCKER_CERT_PATH=" + os.Getenv("DOCKER_CERT_PATH"),
"DOCKER_TLS_VERIFY=" + os.Getenv("DOCKER_TLS_VERIFY"),
}
defer tty.Close()
cmd.Stdout = tty
cmd.Stdin = tty
cmd.Stderr = tty
cmd.SysProcAttr = &syscall.SysProcAttr{
Setctty: true,
Setsid: true,
}
err := cmd.Start()
if err != nil {
le.Error = fmt.Sprintf("Could not start command: %v", err)
continue
}
var once sync.Once
close := func() {
channel.Close()
logrus.Infof("session closed")
}
go func() {
io.Copy(channel, f)
once.Do(close)
}()
go func() {
io.Copy(f, channel)
once.Do(close)
}()
case "pty-req":
ok = true
termLen := req.Payload[3]
termEnv := string(req.Payload[4 : termLen+4])
w, h := parseDims(req.Payload[termLen+4:])
SetWinsize(f.Fd(), w, h)
logrus.Infof("pty-req: %s", termEnv)
if timeout.Stop() {
reqLock.Unlock()
}
}
if req.WantReply {
req.Reply(ok, nil)
}
}
}(requests)
reqLock.Lock()
}
return nil
}
func parseDims(b []byte) (uint32, uint32) {
w := binary.BigEndian.Uint32(b)
h := binary.BigEndian.Uint32(b[4:])
return w, h
}
type Winsize struct {
Height uint16
Width uint16
x uint16 // unused
y uint16 // unused
}
func SetWinsize(fd uintptr, w, h uint32) {
log.Printf("window resize %dx%d", w, h)
ws := &Winsize{Width: uint16(w), Height: uint16(h)}
syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment