additional challenge api support and pam challenger implement

This commit is contained in:
tgic 2014-12-12 15:09:09 +08:00 committed by Boshi Lian
parent b3d6855032
commit d2c05aed8c
4 changed files with 247 additions and 87 deletions

View file

@ -9,8 +9,9 @@ import (
type SSHPiper struct {
DownstreamConfig ServerConfig
FindUpstream func(conn ConnMetadata) (net.Conn, *ClientConfig, error)
MapPublicKey func(conn ConnMetadata, key PublicKey) (Signer, error)
AdditionalChallenge func(conn ConnMetadata, client KeyboardInteractiveChallenge) (bool, error)
FindUpstream func(conn ConnMetadata) (net.Conn, *ClientConfig, error)
MapPublicKey func(conn ConnMetadata, key PublicKey) (Signer, error)
}
type upstream struct{ *connection }
@ -39,6 +40,41 @@ func (piper *SSHPiper) Serve(conn net.Conn) error {
d.user = userAuthReq.User
// need additional challenge
if piper.AdditionalChallenge != nil {
for {
err := d.transport.writePacket(Marshal(&userAuthFailureMsg{
Methods: []string{"keyboard-interactive"},
}))
if err != nil {
return err
}
userAuthReq, err := d.nextAuthMsg()
if err != nil {
return err
}
if userAuthReq.Method == "keyboard-interactive" {
break
}
}
prompter := &sshClientKeyboardInteractive{d.connection}
ok, err := piper.AdditionalChallenge(d, prompter.Challenge)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("additional challenge failed")
}
}
upconn, upconfig, err := piper.FindUpstream(d)
if err != nil {
return err

View file

@ -0,0 +1,40 @@
package challenger
import (
"fmt"
"github.com/tg123/sshpiper/ssh"
"sort"
)
type Challenger func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (bool, error)
var challengers = make(map[string]Challenger)
// copied from database/sql
func Register(name string, challenger Challenger) {
if challenger == nil {
panic("challenger is nil")
}
if _, dup := challengers[name]; dup {
panic("Register twice for challenger" + name)
}
challengers[name] = challenger
}
func Challengers() []string {
var list []string
for name := range challengers {
list = append(list, name)
}
sort.Strings(list)
return list
}
func GetChallenger(name string) (Challenger, error) {
challenger, ok := challengers[name]
if !ok {
return nil, fmt.Errorf("no such challenger:" + name)
}
return challenger, nil
}

View file

@ -0,0 +1,70 @@
// +build pam
package challenger
import (
"fmt"
"github.com/tg123/sshpiper/ssh"
pam "github.com/vvanpo/golang-pam"
"os"
)
const (
SSHPIPER_PAM_SERVICE_FILE = "/etc/pam.d/sshpiperd"
)
func pamChallenger(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (bool, error) {
user := conn.User()
sendQuesttion := func(question string, echo bool) (string, bool) {
ans, err := client(user, "", []string{question}, []bool{echo})
// TODO lost err
if err != nil {
return "", false
}
return ans[0], true
}
sendInstruction := func(instruction string) (string, bool) {
_, err := client(user, instruction, nil, nil)
return "", err == nil
}
t, status := pam.Start("sshpiperd", user, pam.ResponseFunc(func(style int, msg string) (string, bool) {
switch style {
case pam.PROMPT_ECHO_OFF:
return sendQuesttion(msg, false)
case pam.PROMPT_ECHO_ON:
return sendQuesttion(msg, true)
case pam.ERROR_MSG:
return sendInstruction(fmt.Sprintf("Error: %s", msg))
case pam.TEXT_INFO:
return sendInstruction(msg)
}
return "", false
}))
if status != pam.SUCCESS {
return false, fmt.Errorf("pam.Start() failed: %s\n", t.Error(status))
}
defer func() { t.End(status) }()
status = t.Authenticate(0)
if status != pam.SUCCESS {
return false, fmt.Errorf("Auth failed: %s\n", t.Error(status))
}
return true, nil
}
func init() {
if _, err := os.Stat(SSHPIPER_PAM_SERVICE_FILE); os.IsNotExist(err) {
return
}
Register("pam", pamChallenger)
}

View file

@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"github.com/tg123/sshpiper/ssh"
"github.com/tg123/sshpiper/sshpiperd/challenger"
"io/ioutil"
"log"
"net"
@ -26,6 +27,7 @@ var (
WorkingDir string
PiperKeyFile string
ShowHelp bool
Challenger string
logger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
)
@ -35,6 +37,7 @@ func init() {
flag.UintVar(&Port, "p", 2222, "Listening Port")
flag.StringVar(&WorkingDir, "w", "/var/sshpiper", "Working Dir")
flag.StringVar(&PiperKeyFile, "i", "/etc/ssh/ssh_host_rsa_key", "Key file for SSH Piper")
flag.StringVar(&Challenger, "c", "", "Additional challenger name, e.g. pam, emtpy for no additional challenge")
flag.BoolVar(&ShowHelp, "h", false, "Print help and exit")
flag.Parse()
}
@ -72,6 +75,92 @@ func (file userFile) check400(user string) error {
return nil
}
func findUpstreamFromUserfile(conn ssh.ConnMetadata) (net.Conn, *ssh.ClientConfig, error) {
user := conn.User()
err := UserUpstreamFile.check400(user)
if err != nil {
return nil, nil, err
}
addr, err := UserUpstreamFile.read(user)
if err != nil {
return nil, nil, err
}
saddr := strings.TrimSpace(string(addr))
logger.Printf("mapping user [%s] to [%s]", user, saddr)
c, err := net.Dial("tcp", saddr)
if err != nil {
return nil, nil, err
}
return c, &ssh.ClientConfig{}, nil
}
func mapPublicKeyFromUserfile(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.Signer, error) {
user := conn.User()
var err error
defer func() { // print error when func exit
if err != nil {
logger.Printf("mapping private key error: %v, public key auth denied for [%v] from [%v]", err, user, conn.RemoteAddr())
}
}()
err = UserAuthorizedKeysFile.check400(user)
if err != nil {
return nil, err
}
keydata := key.Marshal()
var rest []byte
rest, err = UserAuthorizedKeysFile.read(user)
if err != nil {
return nil, err
}
var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
if err != nil {
return nil, err
}
if bytes.Equal(authedPubkey.Marshal(), keydata) {
err = UserKeyFile.check400(user)
if err != nil {
return nil, err
}
var privateBytes []byte
privateBytes, err = UserKeyFile.read(user)
if err != nil {
return nil, err
}
var private ssh.Signer
private, err = ssh.ParsePrivateKey(privateBytes)
if err != nil {
return nil, err
}
// in log may see this twice, one is for query the other is real sign again
logger.Printf("auth succ, using mapped private key [%v] for user [%v] from [%v]", UserKeyFile.realPath(user), user, conn.RemoteAddr())
return private, nil
}
}
logger.Printf("public key auth failed user [%v] from [%v]", conn.User(), conn.RemoteAddr())
return nil, nil
}
func main() {
if ShowHelp {
@ -80,93 +169,18 @@ func main() {
}
piper := &ssh.SSHPiper{
FindUpstream: func(conn ssh.ConnMetadata) (net.Conn, *ssh.ClientConfig, error) {
FindUpstream: findUpstreamFromUserfile,
MapPublicKey: mapPublicKeyFromUserfile,
}
user := conn.User()
if Challenger != "" {
ac, err := challenger.GetChallenger(Challenger)
if err != nil {
logger.Fatalln(err)
}
err := UserUpstreamFile.check400(user)
if err != nil {
return nil, nil, err
}
addr, err := UserUpstreamFile.read(user)
if err != nil {
return nil, nil, err
}
saddr := strings.TrimSpace(string(addr))
logger.Printf("mapping user [%s] to [%s]", user, saddr)
c, err := net.Dial("tcp", saddr)
if err != nil {
return nil, nil, err
}
return c, &ssh.ClientConfig{}, nil
},
MapPublicKey: func(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.Signer, error) {
user := conn.User()
var err error
defer func() { // print error when func exit
if err != nil {
logger.Printf("mapping private key error: %v, public key auth denied for [%v] from [%v]", err, user, conn.RemoteAddr())
}
}()
err = UserAuthorizedKeysFile.check400(user)
if err != nil {
return nil, err
}
keydata := key.Marshal()
var rest []byte
rest, err = UserAuthorizedKeysFile.read(user)
if err != nil {
return nil, err
}
var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
if err != nil {
return nil, err
}
if bytes.Equal(authedPubkey.Marshal(), keydata) {
err = UserKeyFile.check400(user)
if err != nil {
return nil, err
}
var privateBytes []byte
privateBytes, err = UserKeyFile.read(user)
if err != nil {
return nil, err
}
var private ssh.Signer
private, err = ssh.ParsePrivateKey(privateBytes)
if err != nil {
return nil, err
}
// in log may see this twice, one is for query the other is real sign again
logger.Printf("auth succ, using mapped private key [%v] for user [%v] from [%v]", UserKeyFile.realPath(user), user, conn.RemoteAddr())
return private, nil
}
}
logger.Printf("public key auth failed user [%v] from [%v]", conn.User(), conn.RemoteAddr())
return nil, nil
},
logger.Printf("using additional challenger %s", Challenger)
piper.AdditionalChallenge = ac
}
privateBytes, err := ioutil.ReadFile(PiperKeyFile)