Implement command-based echo app and HTTP handler

This commit is contained in:
Barak Michener 2020-09-24 14:19:31 -07:00
parent 07036afafa
commit b6b58fe2b3
6 changed files with 190 additions and 7 deletions

View file

@ -1,7 +1,40 @@
package main
import "fmt"
import (
"fmt"
"log"
"net/http"
"github.com/barakmich/ussher"
)
func main() {
fmt.Println("vim-go")
config := &ussher.Config{
Port: 2222,
HTTPPort: 8080,
BindAddress: "0.0.0.0",
HostKeyPath: "id_ed25519",
Keystore: ussher.AcceptAllKeys(),
HTTPMux: http.NewServeMux(),
SSHApps: make(map[string]ussher.SSHApp),
}
err := ussher.RegisterEchoApp(config)
if err != nil {
fmt.Println(err)
}
go runHTTPServer(config)
err = ussher.RunSSHServer(config)
if err != nil {
fmt.Printf("%#v", err)
}
}
func runHTTPServer(config *ussher.Config) {
httpAddr := fmt.Sprintf("%s:%d", config.BindAddress, config.HTTPPort)
log.Printf("Listening HTTP: %s\n", httpAddr)
srv := &http.Server{
Addr: httpAddr,
Handler: config.HTTPMux,
}
log.Fatal(srv.ListenAndServe())
}

View file

@ -2,6 +2,7 @@ package ussher
import (
"io/ioutil"
"net/http"
"golang.org/x/crypto/ssh"
)
@ -9,6 +10,11 @@ import (
type Config struct {
HostKeyPath string
Keystore Keystore
BindAddress string
Port int
HTTPPort int
SSHApps map[string]SSHApp
HTTPMux *http.ServeMux
}
func (c *Config) GetPrivateKey() (ssh.Signer, error) {
@ -18,7 +24,3 @@ func (c *Config) GetPrivateKey() (ssh.Signer, error) {
}
return ssh.ParsePrivateKey(bytes)
}
type Keystore interface {
CheckPublicKey(user string, key ssh.PublicKey) (*ssh.Permissions, error)
}

30
echo_app.go Normal file
View file

@ -0,0 +1,30 @@
package ussher
import (
"fmt"
"io"
"net/http"
"golang.org/x/crypto/ssh"
)
type echoHandler struct {
count int
}
func (e *echoHandler) HandleExec(conn *ssh.ServerConn, channel ssh.Channel) {
e.count += 1
io.Copy(channel, channel)
}
func (e *echoHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
msg := fmt.Sprintf("Hello Echo World! Calls: %d", e.count)
w.Write([]byte(msg))
}
func RegisterEchoApp(conf *Config) error {
e := &echoHandler{}
conf.SSHApps["echo"] = e
conf.HTTPMux.Handle("/echo/", e)
return nil
}

17
keystore.go Normal file
View file

@ -0,0 +1,17 @@
package ussher
import "golang.org/x/crypto/ssh"
type Keystore interface {
CheckPublicKey(user string, key ssh.PublicKey) (*ssh.Permissions, error)
}
func AcceptAllKeys() *allKeyAcceptor {
return &allKeyAcceptor{}
}
type allKeyAcceptor struct{}
func (all *allKeyAcceptor) CheckPublicKey(user string, key ssh.PublicKey) (*ssh.Permissions, error) {
return nil, nil
}

7
ssh_app.go Normal file
View file

@ -0,0 +1,7 @@
package ussher
import "golang.org/x/crypto/ssh"
type SSHApp interface {
HandleExec(conn *ssh.ServerConn, channel ssh.Channel)
}

View file

@ -1,6 +1,13 @@
package ussher
import "golang.org/x/crypto/ssh"
import (
"fmt"
"log"
"net"
"time"
"golang.org/x/crypto/ssh"
)
func RunSSHServer(config *Config) error {
sshConfig := &ssh.ServerConfig{
@ -14,4 +21,91 @@ func RunSSHServer(config *Config) error {
return err
}
sshConfig.AddHostKey(private)
// Once a ServerConfig has been configured, connections can be accepted.
listenAddr := fmt.Sprintf("%s:%d", config.BindAddress, config.Port)
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatalf("Failed to listen on %s (%s)", listenAddr, err)
}
// Accept all connections
log.Printf("Listening on %s...", listenAddr)
for {
tcpConn, err := listener.Accept()
if err != nil {
log.Printf("Failed to accept incoming connection (%s)", err)
continue
}
// Before use, a handshake must be performed on the incoming net.Conn.
sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, sshConfig)
if err != nil {
log.Printf("Failed to handshake (%s)", err)
continue
}
log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
// Discard all global out-of-band Requests
go ssh.DiscardRequests(reqs)
// Accept all channels
go handleChannels(chans, sshConn, config)
}
}
func handleChannels(chans <-chan ssh.NewChannel, conn *ssh.ServerConn, config *Config) {
for newChannel := range chans {
go handleChannel(newChannel, conn, config)
}
}
func handleChannel(newChannel ssh.NewChannel, conn *ssh.ServerConn, config *Config) {
// We're emulating a shell command; so this is a "session"
if t := newChannel.ChannelType(); t != "session" {
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
return
}
connection, requests, err := newChannel.Accept()
if err != nil {
log.Printf("Could not accept channel (%s)", err)
return
}
go func() {
var exit bool
for {
if exit {
break
}
select {
case req := <-requests:
switch req.Type {
case "shell":
log.Printf("Requested shell; none can be provided")
connection.Close()
exit = true
case "exec":
appname := string(req.Payload[4:]) // skip the first four bytes, which are length of string
for k, v := range config.SSHApps {
fmt.Println(k, " ", []byte(k))
fmt.Printf("%#v\n", v)
}
fmt.Println(appname, req.Payload)
v, ok := config.SSHApps[appname]
if ok {
v.HandleExec(conn, connection)
} else {
log.Printf("Can't find app %s", appname)
}
connection.Close()
exit = true
default:
// Ignore things like 'pty' and 'env' variables.
continue
}
case <-time.After(5 * time.Second):
fmt.Println("connection timeout")
connection.Close()
}
}
}()
}