grim/hgkeeper

Deny invalid path

13 months ago, aklitzing
5a19892df841
Deny invalid path

If an authenticated user calls `hg init hg.host.com/dummy/../../../etc`
it will create the repository in another root directory if the process of
hgkeeper has permissions for this.
This could be an attack to the server.

Also hgkeeper admin repository can be overriden like this.
`hg init ssh://hg.host.com/dummy/../hgkeeper/keys`

Reviewed at https://reviews.imfreedom.org/r/2422/
package ssh
import (
"fmt"
"github.com/gliderlabs/ssh"
"go.uber.org/zap"
gossh "golang.org/x/crypto/ssh"
"keep.imfreedom.org/grim/hgkeeper/access"
"keep.imfreedom.org/grim/hgkeeper/ssh/commands"
)
type Server struct {
reposPath string
server *ssh.Server
}
func NewServer(hostKeysPath, reposPath, adminRepo string) (*Server, error) {
s := &Server{
reposPath: reposPath,
}
s.server = &ssh.Server{
PublicKeyHandler: s.publicKeyHandler,
Handler: s.sessionHandler,
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {
return false
},
}
if err := s.setHostKeysPath(hostKeysPath); err != nil {
return nil, err
}
return s, nil
}
func (s *Server) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
username, err := access.UsernameFromPubkey(key)
if err != nil {
zap.S().Warnf("authentication failure, unknown key %s", gossh.FingerprintSHA256(key))
return false
}
ctx.SetValue("username", username)
zap.S().Infof(
"%q authenticated with %s",
username,
gossh.FingerprintSHA256(key),
)
return true
}
func (s *Server) sessionHandler(session ssh.Session) {
username := session.Context().Value("username").(string)
// per the docs, session.Command is empty if the user is requesting a shell.
// we only support execs, which means command should be non-empty.
if len(session.Command()) == 0 {
fmt.Fprintf(session, "logged in as %s\n\nShell access is disabled\n", username)
return
}
zap.S().Infof(
"%s@%s requested command %q",
username,
session.RemoteAddr(),
session.RawCommand(),
)
cmd, err := commands.Find(session.RawCommand(), s.reposPath)
if err != nil {
zap.S().Warnf("failed to find command for %q, %v", session.RawCommand(), err)
return
}
if err := cmd.Run(session, username); err != nil {
zap.S().Warnf(
"%s@%s command %q failed: %v",
username,
session.RemoteAddr(),
session.RawCommand(),
err,
)
if err := session.Exit(255); err != nil {
zap.S().Errorf("session failed to exit: %v", err)
}
} else {
zap.S().Infof(
"%s@%s command %q succeed",
username,
session.RemoteAddr(),
session.RawCommand(),
)
if err := session.Exit(0); err != nil {
zap.S().Errorf("session failed to exit: %v", err)
}
}
}
func (s *Server) Listen(addr string) error {
s.server.Addr = addr
zap.S().Infof("ssh listening on %s", s.server.Addr)
if err := s.server.ListenAndServe(); err != nil {
if err != ssh.ErrServerClosed {
return err
}
}
return nil
}
func (s *Server) Close() error {
if s.server != nil {
if err := s.server.Close(); err != nil {
s.server = nil
}
}
return nil
}