grim/hgkeeper
Clone
Summary
Browse
Changes
Graph
rework the ssh commands so there have a setup and teardown functions
2019-09-10, Gary Kramlich
f59237b3e3f2
rework the ssh commands so there have a setup and teardown functions
package
ssh
import
(
"fmt"
"net"
log
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"bitbucket.org/rw_grim/hgkeeper/access"
"bitbucket.org/rw_grim/hgkeeper/ssh/commands"
)
type
Server
struct
{
reposPath
string
cfg
*
ssh
.
ServerConfig
listener
net
.
Listener
}
func
NewServer
(
hostKeysPath
,
reposPath
,
adminRepo
string
)
(
*
Server
,
error
)
{
s
:=
&
Server
{
reposPath
:
reposPath
,
}
s
.
cfg
=
&
ssh
.
ServerConfig
{
MaxAuthTries
:
1
,
PublicKeyCallback
:
s
.
publicKeyCallback
,
}
var
err
error
if
err
=
s
.
setHostKeysPath
(
hostKeysPath
);
err
!=
nil
{
return
nil
,
err
}
return
s
,
nil
}
func
(
s
*
Server
)
Listen
(
addr
string
)
error
{
listener
,
err
:=
net
.
Listen
(
"tcp"
,
addr
)
if
err
!=
nil
{
return
err
}
s
.
listener
=
listener
log
.
Infof
(
"listening for ssh connections on %s"
,
addr
)
for
{
tcpConn
,
err
:=
s
.
listener
.
Accept
()
if
err
!=
nil
{
log
.
Errorf
(
"failed to accept ssh connection: %v"
,
err
)
continue
}
sshConn
,
chans
,
reqs
,
err
:=
ssh
.
NewServerConn
(
tcpConn
,
s
.
cfg
)
if
err
!=
nil
{
log
.
Errorf
(
"ssh handshake failed: %v"
,
err
)
continue
}
log
.
Infof
(
"new ssh connection from %s(%s)"
,
sshConn
.
RemoteAddr
(),
sshConn
.
ClientVersion
())
go
ssh
.
DiscardRequests
(
reqs
)
go
s
.
processSSHChannels
(
chans
,
sshConn
)
}
}
func
(
s
*
Server
)
publicKeyCallback
(
meta
ssh
.
ConnMetadata
,
key
ssh
.
PublicKey
)
(
*
ssh
.
Permissions
,
error
)
{
username
,
err
:=
access
.
UsernameFromPubkey
(
key
)
log
.
Infof
(
"username: %q; err %v"
,
username
,
err
)
if
err
!=
nil
{
return
nil
,
err
}
log
.
Infof
(
"returning username: %q"
,
username
)
return
&
ssh
.
Permissions
{
Extensions
:
map
[
string
]
string
{
"username"
:
username
},
},
nil
}
func
(
s
*
Server
)
processSSHChannels
(
chans
<-
chan
ssh
.
NewChannel
,
serverConn
*
ssh
.
ServerConn
)
{
for
ch
:=
range
chans
{
// we process each channel in a go routine as there's network traffic involved
go
s
.
processSSHChannel
(
ch
,
serverConn
)
}
}
func
(
s
*
Server
)
processSSHChannel
(
ch
ssh
.
NewChannel
,
serverConn
*
ssh
.
ServerConn
)
{
if
t
:=
ch
.
ChannelType
();
t
!=
"session"
{
ch
.
Reject
(
ssh
.
UnknownChannelType
,
fmt
.
Sprintf
(
"unsupported channel type %s"
,
t
))
return
}
conn
,
requests
,
err
:=
ch
.
Accept
()
if
err
!=
nil
{
log
.
Warnf
(
"failed to accept connection: %v"
,
err
)
return
}
// now run through all of the requests but only handle shell requests
go
func
()
{
for
req
:=
range
requests
{
switch
req
.
Type
{
case
"exec"
:
// this is garbage, but payload is a pascal string where the
// first 4 bytes are the length of the string followed by the
// data. the string() method converts it, but includes the
// length, so we just strip it off ahead of time and everything
// works fine.
rawCmd
:=
string
(
req
.
Payload
[
4
:])
log
.
Infof
(
"%s requested command %q"
,
serverConn
.
RemoteAddr
(),
rawCmd
)
cmd
,
err
:=
commands
.
Find
(
rawCmd
,
s
.
reposPath
)
if
err
!=
nil
{
log
.
Warnf
(
"failed to find command for %q, %v"
,
rawCmd
,
err
)
req
.
Reply
(
false
,
nil
)
continue
}
username
:=
serverConn
.
Permissions
.
Extensions
[
"username"
]
log
.
Infof
(
"username in exec: %q"
,
username
)
if
err
:=
cmd
.
Run
(
conn
,
serverConn
,
username
,
req
);
err
!=
nil
{
log
.
Warnf
(
"%s command %q failed: %v"
,
serverConn
.
RemoteAddr
(),
rawCmd
,
err
)
req
.
Reply
(
false
,
nil
)
}
default
:
log
.
Debugf
(
"%s unsupported request: %q"
,
serverConn
.
RemoteAddr
(),
req
.
Type
)
if
req
.
WantReply
{
req
.
Reply
(
false
,
nil
)
}
}
}
}()
}