opensource ssh audit
This commit is contained in:
parent
525e22306e
commit
ce84839c83
4 changed files with 145 additions and 5 deletions
|
|
@ -49,6 +49,9 @@ type pipedConn struct {
|
|||
downstream *downstream
|
||||
|
||||
processAuthMsg func(msg *userAuthRequestMsg) (*userAuthRequestMsg, error)
|
||||
|
||||
hookUpstreamMsg func(msg []byte) ([]byte, error)
|
||||
hookDownstreamMsg func(msg []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// SSHPiperConn is a piped SSH connection, linking upstream ssh server and
|
||||
|
|
@ -57,11 +60,32 @@ type pipedConn struct {
|
|||
// AdditionalChallenge from SSHPiper.
|
||||
type SSHPiperConn struct {
|
||||
*pipedConn
|
||||
|
||||
HookUpstreamMsg func(conn ConnMetadata, msg []byte) ([]byte, error)
|
||||
HookDownstreamMsg func(conn ConnMetadata, msg []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// Wait blocks until the piped connection has shut down, and returns the
|
||||
// error causing the shutdown.
|
||||
func (p *SSHPiperConn) Wait() error {
|
||||
|
||||
p.pipedConn.hookUpstreamMsg = func(msg []byte) ([]byte, error) {
|
||||
if p.HookUpstreamMsg != nil {
|
||||
// api always using p.downstream as conn meta
|
||||
return p.HookUpstreamMsg(p.downstream, msg)
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
p.pipedConn.hookDownstreamMsg = func(msg []byte) ([]byte, error) {
|
||||
if p.HookDownstreamMsg != nil {
|
||||
return p.HookDownstreamMsg(p.downstream, msg)
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
return p.pipedConn.loop()
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +94,14 @@ func (p *SSHPiperConn) Close() {
|
|||
p.pipedConn.Close()
|
||||
}
|
||||
|
||||
func (p *SSHPiperConn) UpstreamConnMeta() ConnMetadata {
|
||||
return p.pipedConn.upstream
|
||||
}
|
||||
|
||||
func (p *SSHPiperConn) DownstreamConnMeta() ConnMetadata {
|
||||
return p.pipedConn.downstream
|
||||
}
|
||||
|
||||
// AddHostKey adds a private key as a SSHPiper host key. If an existing host
|
||||
// key exists with the same algorithm, it is overwritten. Each SSHPiper
|
||||
// config must have at least one host key.
|
||||
|
|
@ -228,7 +260,7 @@ func NewSSHPiperConn(conn net.Conn, piper *SSHPiperConfig) (pipe *SSHPiperConn,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &SSHPiperConn{p}, nil
|
||||
return &SSHPiperConn{pipedConn: p}, nil
|
||||
}
|
||||
|
||||
func (pipe *pipedConn) validAndAck(user string, upKey, downKey PublicKey) (*userAuthRequestMsg, error) {
|
||||
|
|
@ -343,7 +375,7 @@ func parsePublicKeyMsg(userAuthReq *userAuthRequestMsg) (PublicKey, bool, *Signa
|
|||
return pubKey, isQuery, sig, nil
|
||||
}
|
||||
|
||||
func piping(dst, src packetConn) error {
|
||||
func piping(dst, src packetConn, hooker func(msg []byte) ([]byte, error)) error {
|
||||
for {
|
||||
p, err := src.readPacket()
|
||||
|
||||
|
|
@ -351,6 +383,12 @@ func piping(dst, src packetConn) error {
|
|||
return err
|
||||
}
|
||||
|
||||
p, err = hooker(p)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dst.writePacket(p)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -363,11 +401,11 @@ func (pipe *pipedConn) loop() error {
|
|||
c := make(chan error)
|
||||
|
||||
go func() {
|
||||
c <- piping(pipe.upstream.transport, pipe.downstream.transport)
|
||||
c <- piping(pipe.upstream.transport, pipe.downstream.transport, pipe.hookDownstreamMsg)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
c <- piping(pipe.downstream.transport, pipe.upstream.transport)
|
||||
c <- piping(pipe.downstream.transport, pipe.upstream.transport, pipe.hookUpstreamMsg)
|
||||
}()
|
||||
|
||||
defer pipe.Close()
|
||||
|
|
|
|||
85
sshpiperd/audit.go
Normal file
85
sshpiperd/audit.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
msgChannelData = 94
|
||||
)
|
||||
|
||||
type filePtyLogger struct {
|
||||
typescript *os.File
|
||||
timing *os.File
|
||||
|
||||
oldtime time.Time
|
||||
}
|
||||
|
||||
func newFilePtyLogger(user string) (*filePtyLogger, error) {
|
||||
|
||||
now := time.Now()
|
||||
|
||||
filename := fmt.Sprintf("%d", now.Unix())
|
||||
|
||||
typescript, err := os.OpenFile(userSpecFile(user, fmt.Sprintf("%v.typescript", filename)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = typescript.Write([]byte(fmt.Sprintf("Script started on %v\n", now.Format(time.ANSIC))))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timing, err := os.OpenFile(userSpecFile(user, fmt.Sprintf("%v.timing", filename)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &filePtyLogger{
|
||||
typescript: typescript,
|
||||
timing: timing,
|
||||
oldtime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *filePtyLogger) loggingTty(conn ssh.ConnMetadata, msg []byte) ([]byte, error) {
|
||||
|
||||
if msg[0] == msgChannelData {
|
||||
|
||||
buf := msg[9:]
|
||||
|
||||
now := time.Now()
|
||||
|
||||
delta := now.Sub(l.oldtime)
|
||||
|
||||
// see term-utils/script.c
|
||||
fmt.Fprintf(l.timing, "%v.%06v %v\n", int64(delta/time.Second), int64(delta/time.Microsecond), len(buf))
|
||||
|
||||
l.oldtime = now
|
||||
|
||||
_, err := l.typescript.Write(buf)
|
||||
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (l *filePtyLogger) Close() (err error) {
|
||||
_, err = l.typescript.Write([]byte(fmt.Sprintf("Script done on %v\n", time.Now().Format(time.ANSIC))))
|
||||
l.typescript.Close()
|
||||
l.timing.Close()
|
||||
|
||||
return nil // TODO
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ var (
|
|||
ShowVersion bool
|
||||
AllowBadUsername bool
|
||||
NoCheckPerm bool
|
||||
RecordTypescript bool
|
||||
}{}
|
||||
|
||||
out = os.Stdout
|
||||
|
|
@ -85,9 +86,12 @@ func initConfig() {
|
|||
pflag.StringVarP(&config.WorkingDir, "working_dir", "w", "/var/sshpiper", "Working Dir")
|
||||
pflag.StringVarP(&config.PiperKeyFile, "server_key", "i", "/etc/ssh/ssh_host_rsa_key", "Key file for SSH Piper")
|
||||
pflag.StringVarP(&config.Challenger, "challenger", "c", "", "Additional challenger name, e.g. pam, emtpy for no additional challenge")
|
||||
|
||||
pflag.StringVar(&config.Logfile, "log", "", "Logfile path. Leave emtpy or any error occurs will fall back to stdout")
|
||||
pflag.BoolVar(&config.AllowBadUsername, "allow_bad_username", false, "disable username check while search the working dir")
|
||||
pflag.BoolVar(&config.AllowBadUsername, "allow_bad_username", false, "Disable username check while search the working dir")
|
||||
pflag.BoolVar(&config.NoCheckPerm, "no_check_perm", false, "Disable 0400 checking when using files in the working dir")
|
||||
pflag.BoolVar(&config.RecordTypescript, "record_typescript", false, "record screen output into the working dir with typescript format")
|
||||
|
||||
pflag.BoolVarP(&config.ShowHelp, "help", "h", false, "Print help and exit")
|
||||
pflag.BoolVar(&config.ShowVersion, "version", false, "Print version and exit")
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,19 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
if config.RecordTypescript {
|
||||
auditor, err := newFilePtyLogger(p.DownstreamConnMeta().User())
|
||||
|
||||
if err != nil {
|
||||
logger.Printf("connection from %v failed to create auditor reason: %v", c.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
defer auditor.Close()
|
||||
|
||||
p.HookUpstreamMsg = auditor.loggingTty
|
||||
}
|
||||
|
||||
err = p.Wait()
|
||||
logger.Printf("connection from %v closed reason: %v", c.RemoteAddr(), err)
|
||||
}()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue