remove old dir

This commit is contained in:
Boshi Lian 2022-07-04 16:42:28 +00:00
parent 19f851fc74
commit 17f558ae93
55 changed files with 0 additions and 5838 deletions

View file

@ -1,275 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
log "github.com/sirupsen/logrus"
"github.com/jessevdk/go-flags"
"github.com/tg123/sshkey"
"github.com/tg123/sshpiper/sshpiperd/auditor"
"github.com/tg123/sshpiper/sshpiperd/challenger"
"github.com/tg123/sshpiper/sshpiperd/registry"
"github.com/tg123/sshpiper/sshpiperd/upstream"
)
type subCommand struct{ callback func(args []string) error }
func (s *subCommand) Execute(args []string) error {
return s.callback(args)
}
func addSubCommand(command *flags.Command, name, desc string, callback interface{}) *flags.Command {
c, err := command.AddCommand(name, desc, "", callback)
if err != nil {
panic(err)
}
return c
}
func addOpt(group *flags.Group, name string, data interface{}) {
_, err := group.AddGroup(name, "", data)
if err != nil {
panic(err)
}
}
func addPlugins(group *flags.Group, name string, pluginNames []string, getter func(n string) registry.Plugin) {
for _, n := range pluginNames {
p := getter(n)
opt := p.GetOpts()
if opt == nil {
continue
}
_, err := group.AddGroup(name+"."+p.GetName(), "", opt)
if err != nil {
panic(err)
}
}
}
func populateFromConfig(ini *flags.IniParser, data interface{}, longopt string) error {
parser := flags.NewParser(data, flags.IgnoreUnknown)
_, _ = parser.Parse()
o := parser.FindOptionByLongName(longopt)
file := o.Value().(flags.Filename)
err := ini.ParseFile(string(file))
if err != nil {
// set by user
if !o.IsSetDefault() {
return err
}
}
return nil
}
func main() {
parser := flags.NewNamedParser("sshpiperd", flags.Default)
parser.LongDescription = "SSH Piper works as a proxy-like ware, and route connections by username, src ip , etc. Please see <https://github.com/tg123/sshpiper> for more information"
// public config
configFile := &struct {
ConfigFile flags.Filename `long:"config" description:"Config file path. Will be overwritten by arg options and environment variables" default:"/etc/sshpiperd.ini" env:"SSHPIPERD_CONFIG_FILE" no-ini:"true"`
}{}
addOpt(parser.Group, "sshpiperd", configFile)
loadFromConfigFile := func(c *flags.Command) {
parser := flags.NewNamedParser("sshpiperd", flags.IgnoreUnknown)
parser.Command = c
ini := flags.NewIniParser(parser)
ini.ParseAsDefaults = true
err := populateFromConfig(ini, configFile, "config")
if err != nil {
fmt.Printf("load config file failed %v", err)
os.Exit(1)
}
}
// version
{
addSubCommand(parser.Command, "version", "Show version", &subCommand{func(args []string) error {
showVersion()
return nil
}})
}
// manpage
addSubCommand(parser.Command, "manpage", "Write man page to stdout", &subCommand{func(args []string) error {
parser.WriteManPage(os.Stdout)
return nil
}})
// plugins
addSubCommand(parser.Command, "plugins", "List support plugins, e.g. sshpiperd plugins upstream", &subCommand{func(args []string) error {
output := func(all []string) {
for _, p := range all {
fmt.Println(p)
}
}
if len(args) == 0 {
args = []string{"upstream", "challenger", "auditor"}
}
for _, n := range args {
switch n {
case "upstream":
output(upstream.All())
case "challenger":
output(challenger.All())
case "auditor":
output(auditor.All())
}
}
return nil
}})
// generate key tools
{
addSubCommand(parser.Command, "genkey", "generate a 2048 rsa key to stdout", &subCommand{func(args []string) error {
key, err := sshkey.GenerateKey(sshkey.KEY_RSA, 2048)
if err != nil {
return err
}
out, err := sshkey.MarshalPrivate(key, "")
if err != nil {
return err
}
_, err = fmt.Fprint(os.Stdout, string(out))
return err
}})
}
// pipe management
{
config := &struct {
UpstreamDriver string `long:"upstream-driver" description:"Upstream provider driver" default:"workingdir" env:"SSHPIPERD_UPSTREAM_DRIVER" ini-name:"upstream-driver"`
}{}
var c *flags.Command
c = addSubCommand(parser.Command, "pipe", "manage pipe on current upstream driver", createPipeMgr(func() (upstream.Provider, error) {
loadFromConfigFile(c)
if config.UpstreamDriver == "" {
return nil, fmt.Errorf("must provider upstream driver")
}
provider := upstream.Get(config.UpstreamDriver)
logger := log.New()
logger.SetOutput(ioutil.Discard)
err := provider.Init(logger)
if err != nil {
return nil, err
}
return provider, nil
}))
addOpt(c.Group, "sshpiperd", config)
addPlugins(c.Group, "upstream", upstream.All(), func(n string) registry.Plugin { return upstream.Get(n) })
}
// daemon command
{
config := &struct {
piperdConfig
loggerConfig
}{}
var c *flags.Command
c = addSubCommand(parser.Command, "daemon", "run in daemon mode, serving traffic", &subCommand{func(args []string) error {
// populate by config
loadFromConfigFile(c)
showVersion()
// dump used configure only
{
fmt.Println()
for _, gk := range []string{"sshpiperd", "upstream." + config.UpstreamDriver, "challenger." + config.ChallengerDriver, "auditor." + config.AuditorDriver} {
g := c.Group.Find(gk)
if g == nil {
continue
}
fmt.Println("[" + g.ShortDescription + "]")
for _, o := range g.Options() {
if strings.HasSuffix(o.LongName, "password") {
fmt.Printf("%v = %v", o.LongName, "******")
} else {
fmt.Printf("%v = %v", o.LongName, o.Value())
}
fmt.Println()
}
fmt.Println()
}
}
return startPiper(&config.piperdConfig, config.createLogger())
}})
c.SubcommandsOptional = true
addOpt(c.Group, "sshpiperd", config)
addPlugins(c.Group, "upstream", upstream.All(), func(n string) registry.Plugin { return upstream.Get(n) })
addPlugins(c.Group, "challenger", challenger.All(), func(n string) registry.Plugin { return challenger.Get(n) })
addPlugins(c.Group, "auditor", auditor.All(), func(n string) registry.Plugin { return auditor.Get(n) })
// dumpini for daemon
addSubCommand(c, "dumpconfig", "dump current config for daemon ini to stdout", &subCommand{func(args []string) error {
loadFromConfigFile(c)
parser := flags.NewNamedParser("sshpiperd", flags.Default)
parser.Command = c
ini := flags.NewIniParser(parser)
ini.Write(os.Stdout, flags.IniIncludeDefaults)
return nil
}})
// options, for snap only at the moment
addSubCommand(c, "options", "list all options for daemon mode", &subCommand{func(args []string) error {
var printOpts func(*flags.Group)
printOpts = func(group *flags.Group) {
for _, o := range group.Options() {
fmt.Println(o.LongName)
}
for _, g := range group.Groups() {
printOpts(g)
}
}
printOpts(c.Group)
return nil
}})
}
_, err := parser.Parse()
if err != nil {
os.Exit(1)
}
}

View file

@ -1,8 +0,0 @@
FROM golang
ADD . /go/src/github.com/tg123/sshpiper/
WORKDIR /go/src/github.com/tg123/sshpiper/sshpiperd/upstream/grpcupstream/simpleserver
RUN go build -o /simpleserver

View file

@ -1,241 +0,0 @@
version: '3'
services:
host1:
image: rastasheep/ubuntu-sshd
volumes:
- host1ssh:/root/.ssh
- names:/names
host2:
image: rastasheep/ubuntu-sshd
volumes:
- names:/names
client:
image: valudio/docker-sshpass
volumes:
- workingdir:/workingdir
- localkey:/local
- host1ssh:/host1
- names:/names
- ./test.sh:/test.sh
- ./wait.sh:/wait.sh
command:
- "/bin/sh"
- "/test.sh"
depends_on:
- piper
- piper_yaml
# - piper_mysql
# - piper_pg
# - piper_mssql
# - piper_sqlite
# - piper_grpc_privatekey_host1
# - piper_grpc_remotesigner_host1
# - piper_grpc_host2
piper:
environment:
- "SSHPIPERD_UPSTREAM_WORKINGDIR_NOCHECKPERM=true"
- "SSHPIPERD_AUDITOR=typescript-logger"
- "SSHPIPERD_BANNERTEXT=hellopiper"
- "SSHPIPERD_LOG_LEVEL=trace"
build: ../..
volumes:
- workingdir:/var/sshpiper
- ./piper.sh:/piper.sh
- ./wait.sh:/wait.sh
command:
- "/bin/sh"
- "/piper.sh"
depends_on:
- host1
- host2
piper_yaml:
environment:
- "SSHPIPERD_UPSTREAM_DRIVER=yaml"
- "SSHPIPERD_UPSTREAM_YAML_NOCHECKPERM=true"
- "SSHPIPERD_UPSTREAM_YAML_FILE=/sshpiperd.yaml"
- "SSHPIPERD_AUDITOR=typescript-logger"
- "SSHPIPERD_BANNERTEXT=hellopiper"
- "SSHPIPERD_LOG_LEVEL=trace"
build: ../..
volumes:
- localkey:/local
- workingdir:/var/sshpiper
- ./sshpiperd.yaml:/sshpiperd.yaml
depends_on:
- host1
- host2
# piper_sqlite:
# environment:
# - "SSHPIPERD_UPSTREAM_DRIVER=sqlite"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# build: ../..
# volumes:
# - workingdir:/var/sshpiper
# - ./piper.sh:/piper.sh
# - ./wait.sh:/wait.sh
# command:
# - "/bin/sh"
# - "/piper.sh"
# depends_on:
# - host1
# - host2
# piper_mysql:
# environment:
# - "WAIT_HOST=mysql"
# - "WAIT_PORT=3306"
# - "SSHPIPERD_UPSTREAM_DRIVER=mysql"
# - "SSHPIPERD_UPSTREAM_MYSQL_HOST=mysql"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# volumes:
# - ./piper.sh:/piper.sh
# - workingdir:/var/sshpiper
# - ./wait.sh:/wait.sh
# command:
# - "/bin/sh"
# - "/piper.sh"
# depends_on:
# - mysql
# - host1
# - host2
# mysql:
# image: mysql
# environment:
# - "MYSQL_ALLOW_EMPTY_PASSWORD=true"
# - "MYSQL_DATABASE=sshpiper"
# tmpfs:
# - /var/lib/mysql
# piper_pg:
# environment:
# - "WAIT_HOST=postgres"
# - "WAIT_PORT=5432"
# - "SSHPIPERD_UPSTREAM_DRIVER=postgres"
# - "SSHPIPERD_UPSTREAM_POSTGRES_HOST=postgres"
# - "SSHPIPERD_UPSTREAM_POSTGRES_SSLMODE=disable"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# volumes:
# - ./piper.sh:/piper.sh
# - workingdir:/var/sshpiper
# - ./wait.sh:/wait.sh
# command:
# - "/bin/sh"
# - "/piper.sh"
# depends_on:
# - postgres
# - host1
# - host2
# postgres:
# image: postgres
# environment:
# - "POSTGRES_DB=sshpiper"
# - "POSTGRES_HOST_AUTH_METHOD=trust"
# tmpfs:
# - /var/lib/postgresql/data
# piper_mssql:
# environment:
# - "WAIT_HOST=mssql"
# - "WAIT_PORT=1433"
# - "EXTRA_WAIT=5"
# - "SSHPIPERD_UPSTREAM_DRIVER=mssql"
# - "SSHPIPERD_UPSTREAM_MSSQL_HOST=mssql"
# - "SSHPIPERD_UPSTREAM_MSSQL_PASSWORD=yourStrong(!)Password"
# - "SSHPIPERD_UPSTREAM_MSSQL_DBNAME=master"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# volumes:
# - ./piper.sh:/piper.sh
# - workingdir:/var/sshpiper
# - ./wait.sh:/wait.sh
# command:
# - "/bin/sh"
# - "/piper.sh"
# depends_on:
# - mssql
# - host1
# - host2
# mssql:
# image: mcr.microsoft.com/mssql/server
# environment:
# - "ACCEPT_EULA=y"
# - "SA_PASSWORD=yourStrong(!)Password"
# - "MSSQL_PID=Developer"
# grpc_remotesigner_host1:
# build:
# context: ../..
# dockerfile: sshpiperd/e2e/Dockerfile-grpcremote
# command: ["/simpleserver", "--toaddr", "host1", "--totype", "REMOTESIGNER", "--mappeduser", "root", "--privatekey", "/workingdir/host1/id_rsa"]
# volumes:
# - workingdir:/workingdir
# depends_on:
# - host1
# piper_grpc_remotesigner_host1:
# environment:
# - "SSHPIPERD_UPSTREAM_DRIVER=grpc"
# - "SSHPIPERD_UPSTREAM_GRPC_INSECURE=true"
# - "SSHPIPERD_UPSTREAM_GRPC_ENDPOINT=grpc_remotesigner_host1:2233"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# volumes:
# - workingdir:/var/sshpiper
# depends_on:
# - grpc_remotesigner_host1
# grpc_privatekey_host1:
# build:
# context: ../..
# dockerfile: sshpiperd/e2e/Dockerfile-grpcremote
# command: ["/simpleserver", "--toaddr", "host1", "--totype", "REMOTESIGNER", "--mappeduser", "root", "--privatekey", "/workingdir/host1/id_rsa"]
# volumes:
# - workingdir:/workingdir
# depends_on:
# - host1
# piper_grpc_privatekey_host1:
# environment:
# - "SSHPIPERD_UPSTREAM_DRIVER=grpc"
# - "SSHPIPERD_UPSTREAM_GRPC_INSECURE=true"
# - "SSHPIPERD_UPSTREAM_GRPC_ENDPOINT=grpc_privatekey_host1:2233"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# volumes:
# - workingdir:/var/sshpiper
# depends_on:
# - grpc_privatekey_host1
# grpc_remotepass_host2:
# build:
# context: ../..
# dockerfile: sshpiperd/e2e/Dockerfile-grpcremote
# command: ["/simpleserver", "--toaddr", "host2", "--totype", "PASSWORD", "--mappeduser", "root", "--password", "root"]
# depends_on:
# - host2
# piper_grpc_host2:
# environment:
# - "SSHPIPERD_UPSTREAM_DRIVER=grpc"
# - "SSHPIPERD_UPSTREAM_GRPC_INSECURE=true"
# - "SSHPIPERD_UPSTREAM_GRPC_ENDPOINT=grpc_remotepass_host2:2233"
# - "SSHPIPERD_AUDITOR=typescript-logger"
# - "SSHPIPERD_BANNERTEXT=hellopiper"
# - "SSHPIPERD_LOG_LEVEL=trace"
# build: ../..
# depends_on:
# - grpc_remotepass_host2
# volumes:
# - workingdir:/var/sshpiper
volumes:
workingdir:
localkey:
host1ssh:
names:

View file

@ -1,12 +0,0 @@
#!/bin/sh
if [ ! -z $WAIT_HOST ]; then
/wait.sh $WAIT_HOST $WAIT_PORT $EXTRA_WAIT
fi
/sshpiperd pipe add -n host1 -u host1 --upstream-username root
/sshpiperd pipe add -n host2 -u host2 --upstream-username root
/sshpiperd pipe list
/sshpiperd daemon

View file

@ -1,82 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/tg123/sshpiper/master/sshpiperd/upstream/yaml/schema.json
version: "0.1"
pipes:
- username: passthrough
upstream_host: host2
ignore_hostkey: true
authmap:
mapped_username: root
- username: mappasspass
upstream_host: host2
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: password
password: pass
to:
type: password
password: root
- username: mappasskey
upstream_host: host1
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: password
password: pass
to:
type: privatekey
private_key: /var/sshpiper/host1/id_rsa
- username: mapkeypass
upstream_host: host2
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: publickey
authorized_keys: /local/id_rsa2.pub
to:
type: password
password: root
- username: mapkeykey
upstream_host: host1
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: publickey
authorized_keys: /local/id_rsa2.pub
to:
type: privatekey
private_key: /var/sshpiper/host1/id_rsa
- username: mapkeykey2
upstream_host: host1
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: publickey
allow_any_public_key: true
to:
type: privatekey
key_map:
- authorized_keys: /local/id_rsa2.pub
private_key: /var/sshpiper/host1/id_rsa
- username: ^regex.*$
username_regex_match: true
upstream_host: host2
ignore_hostkey: true
authmap:
mapped_username: root
- username: ^host.*$
username_regex_match: true
upstream_host: host1
ignore_hostkey: true
authmap:
mapped_username: root
from:
- type: any
to:
type: privatekey
private_key: /var/sshpiper/$USER/id_rsa

View file

@ -1,85 +0,0 @@
#!/bin/sh
/wait.sh piper 2222
/wait.sh piper_yaml 2222
#/wait.sh piper_sqlite 2222
# /wait.sh piper_mysql 2222
# /wait.sh piper_pg 2222
# /wait.sh piper_mssql 2222
# /wait.sh piper_grpc_remotesigner_host1 2222
# /wait.sh piper_grpc_privatekey_host1 2222
# /wait.sh piper_grpc_host2 2222
# TODO to python
mkdir -p /local
mkdir -p /workingdir/host{1,2}
ssh-keygen -N '' -f /local/id_rsa
ssh-keygen -N '' -f /workingdir/host1/id_rsa #TODO pipe cmd
ssh-keygen -N '' -f /local/id_rsa2
/bin/cp /local/id_rsa.pub /workingdir/host1/authorized_keys
/bin/cp /workingdir/host1/id_rsa.pub /host1/authorized_keys
fail="\033[0;31mFAIL\033[0m"
succ="\033[0;32mSUCC\033[0m"
runtest(){
casename=$1
host=$2
user=$3
cmd=$4
rnd=`head -c 20 /dev/urandom | base64`
echo $rnd > /names/$host
rm -f /tmp/$host.stderr
t=$($cmd 2>/tmp/$host.stderr)
if [ "$t" != "$rnd" ];then
echo -e $casename $fail
exit 1
else
echo -e $casename $succ
fi
grep $rnd /workingdir/$user/*
if [ $? -ne 0 ];then
echo -e "grep typescript logger" $fail
exit 1
fi
grep "hellopiper" /tmp/$host.stderr
if [ $? -ne 0 ];then
echo -e "welcome text" $fail
exit 1
fi
}
runtest "host1 with public key:" "host1" "host1" "ssh -v host1@piper -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /local/id_rsa cat /names/host1"
runtest "host2 with password:" "host2" "host2" "sshpass -p root ssh -v host2@piper -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
#runtest "sqlite host2 with password:" "host2" "host2" "sshpass -p root ssh host2@piper_sqlite -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
# runtest "mysql host2 with password:" "host2" "host2" "sshpass -p root ssh host2@piper_mysql -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
# runtest "pg host2 with password:" "host2" "host2" "sshpass -p root ssh host2@piper_pg -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
# runtest "msql host2 with password:" "host2" "host2" "sshpass -p root ssh host2@piper_mssql -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
runtest "yaml host2 with password passthrough:" "host2" "passthrough" "sshpass -p root ssh passthrough@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
runtest "yaml host2 with password mappasspass:" "host2" "mappasspass" "sshpass -p pass ssh mappasspass@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
runtest "yaml host1 with password mappasskey:" "host1" "mappasskey" "sshpass -p pass ssh mappasskey@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host1"
runtest "yaml host2 with password mapkeypass:" "host2" "mapkeypass" "ssh mapkeypass@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /local/id_rsa2 cat /names/host2"
runtest "yaml host2 with key mapkeykey:" "host1" "mapkeykey" "ssh mapkeykey@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /local/id_rsa2 cat /names/host1"
runtest "yaml host2 with key mapkeykey2:" "host1" "mapkeykey2" "ssh mapkeykey2@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /local/id_rsa2 cat /names/host1"
runtest "yaml host2 with password regex:" "host2" "regex000" "sshpass -p root ssh regex000@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"
runtest "yaml host1 with none host1:" "host1" "host1" "ssh host1@piper_yaml -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host1"
# runtest "grpc host1 with remotesigner:" "host1" "host1" "ssh host1@piper_grpc_remotesigner_host1 -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PubkeyAuthentication=no -o PasswordAuthentication=no cat /names/host1"
# runtest "grpc host1 with privatekey:" "host1" "host1" "ssh host1@piper_grpc_privatekey_host1 -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PubkeyAuthentication=no -o PasswordAuthentication=no cat /names/host1"
# runtest "grpc host2 with password:" "host2" "host2" "sshpass -p wrongpassword ssh host2@piper_grpc_host2 -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cat /names/host2"

View file

@ -1,13 +0,0 @@
#!/bin/sh
for i in `seq 300`; do
nc -w 1 -z "$1" "$2"
if [ $? -eq 0 ]; then
break
fi
sleep 1
done
if [ -n "$3" ]; then
sleep $3;
fi

View file

@ -1,45 +0,0 @@
#!/bin/bash
set -e
CURRENT_DIR=$(dirname $(realpath $0))
SSHPIPERD_SRC=$CURRENT_DIR/..
BASEDIR=$(mktemp -d)
go build -o $BASEDIR/sshpiperd $SSHPIPERD_SRC
SSHPIPERD_BIN="$BASEDIR/sshpiperd"
if [ ! -f $BASEDIR/sshpiperd_key ];then
$SSHPIPERD_BIN genkey > $BASEDIR/sshpiperd_key
fi
$SSHPIPERD_BIN pipe --upstream-workingdir=$BASEDIR/workingdir add -n github -u github.com --upstream-username git
$SSHPIPERD_BIN pipe --upstream-workingdir=$BASEDIR/workingdir add -n gitlab -u gitlab.com --upstream-username git
$SSHPIPERD_BIN pipe --upstream-workingdir=$BASEDIR/workingdir add -n bitbucket -u bitbucket.org --upstream-username git
IFS="
"
echo "#### CURRENT PIPES"
echo "# "
echo "# test using ssh 127.0.0.1 -p 2222 -l username"
echo
for p in `$SSHPIPERD_BIN pipe --upstream-workingdir=$BASEDIR/workingdir list`; do
user=`echo $p | cut -f 1 -d ' '`
echo "$p # ssh 127.0.0.1 -p 2222 -l $user"
done
echo
echo "#### "
echo "#### git clone example"
echo "# cp ~/.ssh/id_rsa $BASEDIR/workingdir/github/"
echo "# ssh-keygen -y -f ~/.ssh/id_rsa > $BASEDIR/workingdir/github/authorized_keys"
echo "# chmod 400 $BASEDIR/workingdir/github/authorized_keys"
echo "git clone git clone ssh://github@127.0.0.1:2222/[youruser]/[yourproj]# e.g. ssh://github@127.0.0.1:2222/tg123/sshpiper"
echo "#### "
echo "Starting piper"
$SSHPIPERD_BIN daemon -i $BASEDIR/sshpiperd_key --upstream-workingdir=$BASEDIR/workingdir

View file

@ -1,15 +0,0 @@
package main
import (
// _ "github.com/tg123/sshpiper/sshpiperd/upstream/database"
// _ "github.com/tg123/sshpiper/sshpiperd/upstream/grpcupstream"
// _ "github.com/tg123/sshpiper/sshpiperd/upstream/kubernetes"
_ "github.com/tg123/sshpiper/sshpiperd/upstream/workingdir"
_ "github.com/tg123/sshpiper/sshpiperd/upstream/yaml"
// _ "github.com/tg123/sshpiper/sshpiperd/challenger/authy"
// _ "github.com/tg123/sshpiper/sshpiperd/challenger/azdevicecode"
// _ "github.com/tg123/sshpiper/sshpiperd/challenger/pome"
_ "github.com/tg123/sshpiper/sshpiperd/auditor/typescriptlogger"
)

View file

@ -1,42 +0,0 @@
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
type loggerConfig struct {
LogFile string `long:"log" description:"LogFile path. Leave empty or any error occurs will fall back to stdout" env:"SSHPIPERD_LOG_PATH" ini-name:"log-path"`
LogLevel string `long:"log-level" default:"info" description:"These are the different logging levels, see https://pkg.go.dev/github.com/sirupsen/logrus#Level" env:"SSHPIPERD_LOG_LEVEL" ini-name:"log-level"`
}
func (l loggerConfig) createLogger() (logger *log.Logger) {
logger = log.New()
if l.LogFile != "" {
f, err := os.OpenFile(l.LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
logger.Printf("cannot open log file %v", err)
return
}
logger.SetOutput(f)
}
if l.LogLevel == "" {
l.LogLevel = "info"
}
level, err := log.ParseLevel(l.LogLevel)
if err != nil {
log.Fatalf("parse log level %v error %v", l.LogLevel, err)
return
}
logger.SetLevel(level)
return
}

View file

@ -1,40 +0,0 @@
package main
import (
"io/ioutil"
"os"
"strings"
"testing"
)
func TestCreateLogger(t *testing.T) {
// flags
{
logger := loggerConfig{LogLevel: "trace"}.createLogger()
if logger.GetLevel().String() != "trace" {
t.Errorf("logger level not set")
}
}
// file
{
tmpfile, err := ioutil.TempFile("", "sshpiperlog")
if err != nil {
t.Errorf("failed to create tmp file %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
logger := loggerConfig{LogFile: tmpfile.Name()}.createLogger()
logger.Print("test123")
s, _ := ioutil.ReadFile(tmpfile.Name())
if !strings.Contains(string(s), "test123") {
t.Errorf("log failed")
}
}
}

View file

@ -1,89 +0,0 @@
package main
import (
"fmt"
"os"
"text/template"
"github.com/tg123/sshpiper/sshpiperd/upstream"
)
func createPipeMgr(load func() (upstream.Provider, error)) interface{} {
// pipe management
pipeMgrCmd := struct {
List struct {
subCommand
} `command:"list" description:"list all pipes"`
Add struct {
subCommand
PiperUserName string `short:"n" long:"piper-username" description:"" required:"true" no-ini:"true"`
// PiperAuthorizedKeysFile flags.Filename
UpstreamUserName string `long:"upstream-username" description:"mapped user name" no-ini:"true"`
UpstreamHost string `short:"u" long:"host" description:"upstream sshd host" required:"true" no-ini:"true"`
UpstreamPort int `short:"p" long:"port" description:"upstream sshd port" default:"22" no-ini:"true"`
// UpstreamKeyFile flags.Filename
// UpstreamHostKey
// MapType
} `command:"add" description:"add a pipe to current upstream"`
Remove struct {
subCommand
Name string `short:"n" long:"piper-username" required:"true" no-ini:"true"`
} `command:"remove" description:"remove a pipe from current upstream"`
}{}
pipeMgrCmd.List.callback = func(args []string) error {
p, err := load()
if err != nil {
return err
}
// opt := pipeMgrCmd.List
pipes, err := p.ListPipe()
if err != nil {
return err
}
t := template.Must(template.New("").Parse(`{{.Username}} -> {{.UpstreamUsername}}@{{.Host}}:{{.Port}}`))
for _, pipe := range pipes {
_ = t.Execute(os.Stdout, pipe)
fmt.Println()
}
return nil
}
pipeMgrCmd.Add.callback = func(args []string) error {
p, err := load()
if err != nil {
return err
}
opt := pipeMgrCmd.Add
return p.CreatePipe(upstream.CreatePipeOption{
Username: opt.PiperUserName,
UpstreamUsername: opt.UpstreamUserName,
Host: opt.UpstreamHost,
Port: opt.UpstreamPort,
})
}
pipeMgrCmd.Remove.callback = func(args []string) error {
p, err := load()
if err != nil {
return err
}
opt := pipeMgrCmd.Remove
return p.RemovePipe(opt.Name)
}
return &pipeMgrCmd
}

View file

@ -1,19 +0,0 @@
package registry
import (
log "github.com/sirupsen/logrus"
)
// Plugin is to be registered with sshpiper to provide additional functions
type Plugin interface {
// The name of the Plugin
GetName() string
// A ref to a struct which holds the options for the plugins
// will be populated by cmd or other plugin runners
GetOpts() interface{}
// Will be called before the Plugin is used to ensure the Plugin is ready
Init(logger *log.Logger) error
}

View file

@ -1,51 +0,0 @@
package registry
import (
"sort"
"sync"
)
// Registry is a place to hold all plugins
type Registry struct {
driversMu sync.RWMutex
drivers map[string]interface{}
}
// NewRegistry creates a new Registry
func NewRegistry() *Registry {
return &Registry{drivers: make(map[string]interface{})}
}
// Register adds a Plugin with given name to Registry
func (r *Registry) Register(name string, driver interface{}) {
// copy from database/sql
r.driversMu.Lock()
defer r.driversMu.Unlock()
if driver == nil {
panic("Register driver is nil")
}
if _, dup := r.drivers[name]; dup {
panic("Register called twice for driver " + name)
}
r.drivers[name] = driver
}
// Drivers return all registered Plugins
func (r *Registry) Drivers() []string {
r.driversMu.RLock()
defer r.driversMu.RUnlock()
var list []string
for name := range r.drivers {
list = append(list, name)
}
sort.Strings(list)
return list
}
// Get returns an Plugins by name, return nil if not found
func (r *Registry) Get(name string) interface{} {
r.driversMu.RLock()
defer r.driversMu.RUnlock()
return r.drivers[name]
}

View file

@ -1,71 +0,0 @@
package registry
import (
"reflect"
"testing"
log "github.com/sirupsen/logrus"
)
type testplugin struct {
name string
}
func (p *testplugin) GetName() string {
return p.name
}
func (p *testplugin) GetOpts() interface{} {
return nil
}
func (p *testplugin) Init(logger *log.Logger) error {
return nil
}
func TestRegistry_Register(t *testing.T) {
r := NewRegistry()
r.Register("x", &testplugin{"x"})
r.Register("y", &testplugin{"y"})
if r.Get("x") == nil {
t.Errorf("cannot find x in registry")
}
all := r.Drivers()
if !reflect.DeepEqual(all, []string{"x", "y"}) {
t.Errorf("should contain x y in registry")
}
if r.Get("z") != nil {
t.Errorf("should not find z in registry")
}
// duplicate name
func() {
defer func() {
if r := recover(); r == nil {
t.Errorf("should panic")
}
}()
r := NewRegistry()
r.Register("x", &testplugin{"x"})
r.Register("x", &testplugin{"x"})
}()
// nil driver
func() {
defer func() {
if r := recover(); r == nil {
t.Errorf("should panic")
}
}()
r := NewRegistry()
r.Register("nil", nil)
}()
}

View file

@ -1,32 +0,0 @@
version: '3'
services:
snap:
image: snapcore/snapcraft
volumes:
- build:/build
- stage:/stage
- ./snapcraft.yaml:/stage/snapcraft.yaml
- ./snap:/stage/snap
- .:/output
working_dir: /stage
depends_on:
- piper
command:
- "/bin/sh"
- "-c"
- "snapcraft clean && snapcraft && cp /stage/*.snap /output"
piper:
build:
context: ../..
args:
- VER=devel
volumes:
- build:/build
command:
- "/bin/sh"
- "-c"
- "/bin/cp /sshpiperd /build/sshpiperd && /build/sshpiperd version && sleep 1h"
volumes:
build:
stage:

View file

@ -1,37 +0,0 @@
#!/bin/sh
set -e
bin=$SNAP/sshpiperd
config=$SNAP_DATA/sshpiperd.ini
args=""
for opt in $($bin daemon options); do
v=$(snapctl get $opt)
if [ "$v" != "" ]; then
v=$(eval echo $v)
if [ "$v" = "true" ];then
args="$args --$opt"
else
args="$args --$opt=$v"
fi
# special default
elif [ "$opt" = "upstream-workingdir" ]; then
mkdir -p $SNAP_COMMON/workingdir
args="$args --$opt=$SNAP_COMMON/workingdir"
elif [ "$opt" = "upstream-sqlite-dbfile" ]; then
args="$args --$opt=$SNAP_COMMON/sshpiperd.sqlite3"
elif [ "$opt" = "auditor-typescriptlogger-outputdir" ]; then
mkdir -p $SNAP_COMMON/typescriptlog
args="$args --$opt=$SNAP_COMMON/typescriptlog"
elif [ "$opt" = "server-key" ]; then
args="$args --$opt=$SNAP_COMMON/host_rsa_key"
elif [ "$opt" = "log-flags" ]; then
args="$args --$opt=0"
fi
done
echo ";DO NOT MODIFY, USE snap set sshpiperd key=val, AUTO GENERATED" $(date) > $config
$bin daemon --config /dev/null $args dumpconfig >> $config

View file

@ -1,7 +0,0 @@
#!/bin/sh
set -e
bin=$SNAP/sshpiperd
key=$SNAP_COMMON/host_rsa_key
$bin genkey > $key

View file

@ -1,23 +0,0 @@
name: sshpiperd
version: devel
summary: The missing reverse proxy for ssh scp
description: |
SSH Piper works as a proxy-like ware, and route connections by username, src ip , etc.
grade: stable
confinement: strict
parts:
sshpiperd:
plugin: dump
source: /build
apps:
sshpiperd-daemon:
command: sshpiperd daemon --config $SNAP_DATA/sshpiperd.ini
plugs: [network-bind]
daemon: simple
sshpiperd:
command: sshpiperd --config $SNAP_DATA/sshpiperd.ini
hooks:
configure:
plugs: [home]
install:
plugs: [home]

View file

@ -1,272 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"net"
"path/filepath"
"time"
"golang.org/x/crypto/ssh"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/sshpiperd/auditor"
"github.com/tg123/sshpiper/sshpiperd/registry"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"github.com/tg123/sshpiper/sshpiperd/v0bridge"
)
type piperdConfig struct {
ListenAddr string `short:"l" long:"listen" description:"Listening Address" default:"0.0.0.0" env:"SSHPIPERD_LISTENADDR" ini-name:"listen-address"`
Port uint `short:"p" long:"port" description:"Listening Port" default:"2222" env:"SSHPIPERD_PORT" ini-name:"listen-port"`
PiperKeyFile string `short:"i" long:"server-key" description:"Server key file for SSH Piper" default:"/etc/ssh/ssh_host_rsa_key" env:"SSHPIPERD_SERVER_KEY" ini-name:"server-key"`
LoginGraceTime time.Duration `long:"login-grace-time" description:"Piper disconnects after this time if the pipe has not successfully established" default:"30s" env:"SSHPIPERD_LOGIN_GRACETIME" ini-name:"login-grace-time"`
UpstreamDriver string `short:"u" long:"upstream-driver" description:"Upstream provider driver" default:"workingdir" env:"SSHPIPERD_UPSTREAM_DRIVER" ini-name:"upstream-driver"`
ChallengerDriver string `short:"c" long:"challenger-driver" description:"Additional challenger name, e.g. pam, empty for no additional challenge" env:"SSHPIPERD_CHALLENGER" ini-name:"challenger-driver"`
AuditorDriver string `long:"auditor-driver" description:"Auditor for ssh connections piped by SSH Piper" env:"SSHPIPERD_AUDITOR" ini-name:"auditor-driver"`
BannerText string `long:"banner-text" description:"Display a banner before authentication, would be ignored if banner file was set" env:"SSHPIPERD_BANNERTEXT" ini-name:"banner-text" `
BannerFile string `long:"banner-file" description:"Display a banner from file before authentication" env:"SSHPIPERD_BANNERFILE" ini-name:"banner-file" `
}
func getAndInstall(reg, name string, get func(n string) registry.Plugin, install func(plugin registry.Plugin) error, logger *log.Logger) error {
if name == "" {
return nil
}
p := get(name)
if p == nil {
return fmt.Errorf("%v driver %v not found", reg, name)
}
err := p.Init(logger)
if err != nil {
return err
}
return install(p)
}
func installDrivers(piper *ssh.PiperConfig, config *piperdConfig, logger *log.Logger) (auditor.Provider, error) {
// install upstreamProvider driver
if config.UpstreamDriver == "" {
return nil, fmt.Errorf("must provider upstream driver")
}
var bigbro auditor.Provider
for _, d := range []struct {
reg string
name string
get func(n string) registry.Plugin
install func(plugin registry.Plugin) error
}{
// upstream driver
{
"Upstream",
config.UpstreamDriver,
func(n string) registry.Plugin {
return upstream.Get(n)
},
func(plugin registry.Plugin) error {
v1, ok := plugin.(upstream.V1Provider)
if ok {
return v1.InstallUpstream(piper)
}
handler := plugin.(upstream.Provider).GetHandler()
if handler == nil {
return fmt.Errorf("upstream driver return nil handler")
}
v0bridge.InstallUpstream(piper, func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (net.Conn, *v0bridge.AuthPipe, error) {
c, a, err := handler(conn, challengeCtx)
if err != nil {
logger.Errorf("upstream driver [%v] cannot find upstream due to [%v]", plugin.GetName(), err)
} else {
logger.Debugf("upstream driver [%v] found upstream connection [%v]", plugin.GetName(), c.RemoteAddr().String())
}
return c, a, err
})
return nil
},
},
// challenger driver
// {
// "Challenger",
// config.ChallengerDriver,
// func(n string) registry.Plugin {
// return challenger.Get(n)
// },
// func(plugin registry.Plugin) error {
// handler := plugin.(challenger.Provider).GetHandler()
// if handler == nil {
// return fmt.Errorf("challenger driver return nil handler")
// }
// piper.AdditionalChallenge = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
// c, err := handler(conn, client)
// if err != nil {
// logger.Errorf("challenger [%v] failed [%v]", plugin.GetName(), err)
// } else {
// if c != nil {
// logger.Infof("challenger [%v] success with challenged username [%v]", plugin.GetName(), c.ChallengedUsername())
// } else {
// logger.Debugf("challenger [%v] success with empty context", plugin.GetName())
// }
// }
// return c, err
// }
// return nil
// },
// },
// auditor driver
{
"Auditor",
config.AuditorDriver,
func(n string) registry.Plugin {
return auditor.Get(n)
},
func(plugin registry.Plugin) error {
bigbro = plugin.(auditor.Provider)
return nil
},
},
} {
err := getAndInstall(d.reg, d.name, d.get, d.install, logger)
if err != nil {
return nil, err
}
}
return bigbro, nil
}
func startPiper(config *piperdConfig, logger *log.Logger) error {
logger.Println("sshpiper is about to start")
piper := &ssh.PiperConfig{}
// // drivers
bigbro, err := installDrivers(piper, config, logger)
if err != nil {
return err
}
// listeners
privateKeys, err := filepath.Glob(config.PiperKeyFile)
if err != nil {
return err
}
logger.Println("Found host keys", privateKeys)
for _, privateKey := range privateKeys {
logger.Println("Loading host key", privateKey)
privateBytes, err := ioutil.ReadFile(privateKey)
if err != nil {
return err
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return err
}
piper.AddHostKey(private)
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddr, config.Port))
if err != nil {
return fmt.Errorf("failed to listen for connection: %v", err)
}
defer listener.Close()
// banner
if config.BannerFile != "" {
piper.BannerCallback = func(conn ssh.ConnMetadata, _ ssh.ChallengeContext) string {
msg, err := ioutil.ReadFile(config.BannerFile)
if err != nil {
logger.Printf("failed to read banner file: %v", err)
return ""
}
return string(msg)
}
} else if config.BannerText != "" {
piper.BannerCallback = func(conn ssh.ConnMetadata, _ ssh.ChallengeContext) string {
return config.BannerText + "\n"
}
}
logger.Printf("sshpiperd started")
for {
conn, err := listener.Accept()
if err != nil {
logger.Debugf("failed to accept connection: %v", err)
continue
}
logger.Debugf("connection accepted: %v", conn.RemoteAddr())
go func(c net.Conn) {
defer c.Close()
pipec := make(chan *ssh.PiperConn)
errorc := make(chan error)
go func() {
p, err := ssh.NewSSHPiperConn(c, piper)
if err != nil {
errorc <- err
return
}
pipec <- p
}()
var p *ssh.PiperConn
select {
case p = <-pipec:
case err := <-errorc:
logger.Debugf("connection from %v establishing failed reason: %v", c.RemoteAddr(), err)
return
case <-time.After(config.LoginGraceTime):
logger.Debugf("pipe establishing timeout, disconnected connection from %v", c.RemoteAddr())
return
}
defer p.Close()
logger.Infof("ssh connection accepted from %v", c.RemoteAddr())
if bigbro != nil {
a, err := bigbro.Create(p.DownstreamConnMeta())
if err != nil {
logger.Printf("connection from %v failed to create auditor reason: %v", c.RemoteAddr(), err)
return
}
defer a.Close()
err = p.WaitWithHook(a.GetUpstreamHook(), a.GetDownstreamHook())
} else {
err = p.Wait()
}
logger.Infof("connection from %v closed reason: %v", c.RemoteAddr(), err)
}(conn)
}
}

View file

@ -1,362 +0,0 @@
package main
import (
"testing"
log "github.com/sirupsen/logrus"
"fmt"
"net"
"time"
"github.com/tg123/sshpiper/sshpiperd/auditor"
"github.com/tg123/sshpiper/sshpiperd/challenger"
"github.com/tg123/sshpiper/sshpiperd/registry"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"golang.org/x/crypto/ssh"
)
type testplugin struct {
name string
init func(logger *log.Logger) error
}
func (p *testplugin) GetName() string {
return p.name
}
func (p *testplugin) GetOpts() interface{} {
return nil
}
func (p *testplugin) Init(logger *log.Logger) error {
if p.init == nil {
return nil
}
return p.init(logger)
}
func Test_getAndInstall(t *testing.T) {
var err error
// ignore empty
_ = getAndInstall("", "", func(n string) registry.Plugin {
t.Errorf("should not call get")
return nil
}, func(plugin registry.Plugin) error {
t.Errorf("should not call install")
return nil
}, log.StandardLogger())
// fail when not found
err = getAndInstall("", "test", func(n string) registry.Plugin {
if n != "test" {
t.Errorf("plugin name changed")
}
return nil
}, func(plugin registry.Plugin) error {
t.Errorf("should not call install")
return nil
}, log.StandardLogger())
if err == nil {
t.Errorf("should err when not found")
}
// init err
err = getAndInstall("", "test", func(n string) registry.Plugin {
return &testplugin{
init: func(logger *log.Logger) error {
return fmt.Errorf("init failed")
},
}
}, func(plugin registry.Plugin) error {
return fmt.Errorf("test")
}, log.StandardLogger())
if err == nil {
t.Errorf("should err when not found")
}
// call init
inited := false
installed := false
err = getAndInstall("", "test", func(n string) registry.Plugin {
if n != "test" {
t.Errorf("plugin name changed")
}
return &testplugin{
init: func(logger *log.Logger) error {
inited = true
return nil
},
}
}, func(plugin registry.Plugin) error {
if !inited {
t.Errorf("not inited")
}
installed = true
return nil
}, log.StandardLogger())
if !installed {
t.Errorf("not installed")
}
if err != nil {
t.Errorf("should err when not found")
}
}
type testupstream struct {
testplugin
h upstream.Handler
}
func (t *testupstream) ListPipe() ([]upstream.Pipe, error) {
return nil, nil
}
func (t *testupstream) CreatePipe(opt upstream.CreatePipeOption) error {
return nil
}
func (t *testupstream) RemovePipe(name string) error {
return nil
}
func (t *testupstream) GetHandler() upstream.Handler {
return t.h
}
type testchallenger struct {
testplugin
h challenger.Handler
}
func (t *testchallenger) GetHandler() challenger.Handler {
return t.h
}
type testauditorprovider struct {
testplugin
a auditor.Auditor
}
func (t *testauditorprovider) Create(ssh.ConnMetadata) (auditor.Auditor, error) {
return t.a, nil
}
type testauditor struct {
up auditor.Hook
down auditor.Hook
}
func (t *testauditor) GetUpstreamHook() auditor.Hook {
return t.up
}
func (t *testauditor) GetDownstreamHook() auditor.Hook {
return t.down
}
func (t *testauditor) Close() error {
return nil
}
func Test_installDriver(t *testing.T) {
var (
upstreamName = fmt.Sprintf("u_%v", time.Now().UTC().UnixNano())
challengerName = fmt.Sprintf("c_%v", time.Now().UTC().UnixNano())
auditorName = fmt.Sprintf("a_%v", time.Now().UTC().UnixNano())
upstreamErrName = fmt.Sprintf("ue_%v", time.Now().UTC().UnixNano())
upstreamNilName = fmt.Sprintf("un_%v", time.Now().UTC().UnixNano())
)
findUpstream := func(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
c, s := net.Pipe()
defer c.Close()
defer s.Close()
return c, nil, nil
}
upstream.Register(upstreamName, &testupstream{
testplugin: testplugin{
name: upstreamName,
},
h: findUpstream,
})
upstream.Register(upstreamErrName, &testupstream{
testplugin: testplugin{
name: upstreamErrName,
init: func(logger *log.Logger) error {
return fmt.Errorf("test err")
},
},
h: findUpstream,
})
upstream.Register(upstreamNilName, &testupstream{
testplugin: testplugin{
name: upstreamNilName,
},
h: nil,
})
challenger.Register(challengerName, &testchallenger{
testplugin: testplugin{
name: upstreamName,
},
h: func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
return nil, nil
},
})
auditor.Register(auditorName, &testauditorprovider{})
// empty driver name
{
piper := &ssh.PiperConfig{}
_, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: "",
}, log.StandardLogger())
if err == nil {
t.Errorf("should fail when empty driver")
}
}
// install upstream
{
piper := &ssh.PiperConfig{}
_, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: upstreamName,
}, log.StandardLogger())
if err != nil {
t.Errorf("install failed %v", err)
}
if _, _, err := piper.FindUpstream(nil, nil); err != nil {
t.Errorf("install wrong func")
}
if piper.AdditionalChallenge != nil {
t.Errorf("should not install challenger")
}
}
// install upstream with failed init
{
piper := &ssh.PiperConfig{}
_, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: upstreamErrName,
}, log.StandardLogger())
if err == nil {
t.Errorf("install should fail")
}
if piper.FindUpstream != nil {
t.Errorf("should not install upstream provider")
}
if piper.AdditionalChallenge != nil {
t.Errorf("should not install challenger")
}
}
// install upstream with nil handler
{
piper := &ssh.PiperConfig{}
_, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: upstreamNilName,
}, log.StandardLogger())
if err == nil {
t.Errorf("install should fail")
}
if piper.FindUpstream != nil {
t.Errorf("should not install upstream provider")
}
if piper.AdditionalChallenge != nil {
t.Errorf("should not install challenger")
}
}
// install challenger
{
piper := &ssh.PiperConfig{}
_, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: upstreamName,
ChallengerDriver: challengerName,
}, log.StandardLogger())
if err != nil {
t.Errorf("install failed %v", err)
}
if _, err := piper.AdditionalChallenge(nil, nil); err != nil {
t.Errorf("should install challenger")
}
}
// install auditor
{
piper := &ssh.PiperConfig{}
ap, err := installDrivers(piper, &piperdConfig{
UpstreamDriver: upstreamName,
AuditorDriver: auditorName,
}, log.StandardLogger())
if err != nil {
t.Errorf("install failed %v", err)
}
if ap == nil {
t.Errorf("nil auditor provider")
}
ap0 := ap.(*testauditorprovider)
ap0.a = &testauditor{
up: func(conn ssh.ConnMetadata, msg []byte) ([]byte, error) {
msg[0] = 42
return msg, nil
},
down: func(conn ssh.ConnMetadata, msg []byte) ([]byte, error) {
msg[0] = 100
return msg, nil
},
}
a, err := ap.Create(nil)
if err != nil {
t.Errorf("install failed %v", err)
}
m := []byte{0}
if _, err := a.GetUpstreamHook()(nil, m); err != nil {
t.Errorf("run upstream hook %v", err)
}
if m[0] != 42 {
t.Errorf("upstream not handled")
}
if _, err := a.GetDownstreamHook()(nil, m); err != nil {
t.Errorf("run downstream hook %v", err)
}
if m[0] != 100 {
t.Errorf("downstream not handled")
}
}
}

View file

@ -1,12 +0,0 @@
# Database upstream for SSHPiper
Database is a ideally place to store upstream and downstream relationship. sshpiperd provides 4 database support:
* MySQL
* PostgreSQL
* SQLite 3
* SQL Server
![Imgur](https://i.imgur.com/lxdIK3K.png)
You may want to use `sshpiperd pipe add ` to manage them.

View file

@ -1,256 +0,0 @@
package database
import (
"bytes"
"fmt"
"net"
"github.com/jinzhu/gorm"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
type pipeConfig struct {
Username string
UpstreamHost string
MappedUsername string
FromType authMapType
FromPassword string
FromPrivateKey keydata
FromAuthorizedKeys keydata
FromAllowAnyPublicKey bool
ToType authMapType
ToPassword string
ToPrivateKey keydata
ToAuthorizedKeys keydata
NoPassthrough bool
KnownHosts string
KnownHostsData string
IgnoreHostkey bool
}
func (p *plugin) loadPipeFromDB(conn ssh.ConnMetadata) (pipeConfig, error) {
user := conn.User()
d, err := lookupDownstreamWithFallback(p.db, user)
if err != nil {
return pipeConfig{}, err
}
pipe := pipeConfig{
Username: user,
UpstreamHost: d.Upstream.Server.Address,
MappedUsername: d.Upstream.Username,
FromType: d.AuthMapType,
FromPassword: d.Password,
FromAuthorizedKeys: d.AuthorizedKeys,
FromAllowAnyPublicKey: d.AllowAnyPublicKey,
ToType: d.Upstream.AuthMapType,
ToPassword: d.Upstream.Password,
ToPrivateKey: d.Upstream.PrivateKey,
NoPassthrough: d.NoPassthrough,
KnownHosts: d.Upstream.KnownHosts,
IgnoreHostkey: d.Upstream.Server.IgnoreHostKey,
}
return pipe, nil
}
func (p *plugin) createAuthPipe(pipe pipeConfig, conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (*ssh.AuthPipe, error) {
hostKeyCallback := ssh.InsecureIgnoreHostKey()
if !pipe.IgnoreHostkey {
var err error
data := []byte(pipe.KnownHosts)
if len(data) == 0 {
return nil, fmt.Errorf("no known hosts specified")
}
hostKeyCallback, err = knownhosts.NewFromReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
}
to := func() (ssh.AuthPipeType, ssh.AuthMethod, error) {
switch pipe.ToType {
case authMapTypeNone:
return ssh.AuthPipeTypeNone, nil, nil
case authMapTypePassword:
return ssh.AuthPipeTypeMap, ssh.Password(pipe.ToPassword), nil
case authMapTypePrivateKey:
privateBytes := []byte(pipe.ToPrivateKey.Data)
if len(privateBytes) == 0 {
return ssh.AuthPipeTypeDiscard, nil, fmt.Errorf("no private key found")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return ssh.AuthPipeTypeDiscard, nil, err
}
return ssh.AuthPipeTypeMap, ssh.PublicKeys(private), nil
default:
logger.Printf("unsupport type [%v] fallback to passthrough", pipe.ToType)
}
if pipe.NoPassthrough {
return ssh.AuthPipeTypeDiscard, nil, nil
}
return ssh.AuthPipeTypePassThrough, nil, nil
}
a := &ssh.AuthPipe{
User: pipe.MappedUsername,
UpstreamHostKeyCallback: hostKeyCallback,
}
noneauth := func(conn ssh.ConnMetadata) (ssh.AuthPipeType, ssh.AuthMethod, error) {
return to()
}
passauth := func(conn ssh.ConnMetadata, password []byte) (ssh.AuthPipeType, ssh.AuthMethod, error) {
if string(password) == pipe.FromPassword {
return to()
}
if pipe.NoPassthrough {
return ssh.AuthPipeTypeDiscard, nil, nil
}
return ssh.AuthPipeTypePassThrough, nil, nil
}
var allowPubKeys []ssh.PublicKey
allowAnyPubKey := pipe.FromAllowAnyPublicKey
if !allowAnyPubKey {
var err error
rest := []byte(pipe.FromAuthorizedKeys.Data)
var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
if err != nil {
return nil, err
}
allowPubKeys = append(allowPubKeys, authedPubkey)
}
}
keyuath := func(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.AuthPipeType, ssh.AuthMethod, error) {
if allowAnyPubKey {
return to()
}
keydata := key.Marshal()
for _, authedPubkey := range allowPubKeys {
if bytes.Equal(authedPubkey.Marshal(), keydata) {
return to()
}
}
if pipe.NoPassthrough {
return ssh.AuthPipeTypeDiscard, nil, nil
}
// will fail but discard will lead a timeout
return ssh.AuthPipeTypePassThrough, nil, nil
}
switch pipe.FromType {
case authMapTypeNone:
a.NoneAuthCallback = noneauth
case authMapTypePassword:
a.PasswordCallback = passauth
case authMapTypePrivateKey:
a.PublicKeyCallback = keyuath
case authMapTypeAny:
a.NoneAuthCallback = noneauth
a.PasswordCallback = passauth
a.PublicKeyCallback = keyuath
default:
return nil, fmt.Errorf("unsupported auth type [%v],", pipe.FromType)
}
return a, nil
}
func (p *plugin) findUpstream(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
pipe, err := p.loadPipeFromDB(conn)
if err != nil {
return nil, nil, err
}
if pipe.Username != "" {
logger.Printf("mapping [%v] to [%v@%v] from %v to %v", pipe.Username, pipe.MappedUsername, pipe.UpstreamHost, pipe.FromType, pipe.ToType)
c, err := upstreamprovider.DialForSSH(pipe.UpstreamHost)
if err != nil {
return nil, nil, err
}
a, err := p.createAuthPipe(pipe, conn, challengeContext)
if err != nil {
return nil, nil, err
}
return c, a, nil
}
return nil, nil, fmt.Errorf("username should not be empty")
}
func lookupDownstreamWithFallback(db *gorm.DB, user string) (*downstream, error) {
d, err := lookupDownstream(db, user)
if gorm.IsRecordNotFoundError(err) {
fallback, _ := lookupConfigValue(db, fallbackUserEntry)
if len(fallback) > 0 {
return lookupDownstream(db, fallback)
}
}
return d, err
}
func lookupDownstream(db *gorm.DB, user string) (*downstream, error) {
d := downstream{}
if err := db.Set("gorm:auto_preload", true).Where(&downstream{Username: user}).First(&d).Error; err != nil {
return nil, err
}
return &d, nil
}
func lookupConfigValue(db *gorm.DB, entry string) (string, error) {
c := config{}
if err := db.Where(&config{Entry: entry}).First(&c).Error; err != nil {
return "", err
}
return c.Value, nil
}

View file

@ -1,202 +0,0 @@
package database
import (
"net"
"testing"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshkey"
"golang.org/x/crypto/ssh"
"github.com/jinzhu/gorm"
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
func generateKeyPair() (string, string, error) {
priv, err := sshkey.GenerateKey(sshkey.KEY_RSA, 2048)
if err != nil {
return "", "", err
}
privb, err := sshkey.MarshalPrivate(priv, "")
if err != nil {
return "", "", err
}
pub := sshkey.NewPublic(priv, "")
return string(sshkey.MarshalPublic(pub)), string(privb), nil
}
func newTestPlugin(t *testing.T) *plugin {
p := upstreamprovider.Get("sqlite").(*sqliteplugin)
p.Config.File = "file::memory:?mode=memory&cache=shared"
err := p.Init(log.New())
if err != nil {
t.Fatal(err)
}
return &p.plugin
}
type testconn struct {
user string
}
func (c testconn) User() string {
return c.user
}
func (testconn) SessionID() []byte {
return nil
}
func (testconn) ClientVersion() []byte {
return nil
}
func (testconn) ServerVersion() []byte {
return nil
}
func (testconn) RemoteAddr() net.Addr {
return nil
}
func (testconn) LocalAddr() net.Addr {
return nil
}
func createEntry(t *testing.T, db *gorm.DB, downUser, upUser, serverAddr string, ignoreServerKey bool) (string, string) {
pub, priv, err := generateKeyPair()
if err != nil {
t.Fatal(err)
}
err = db.Create(&downstream{
Username: downUser,
AuthMapType: authMapTypeAny,
AuthorizedKeys: keydata{
Data: pub,
Type: "rsa",
},
Upstream: upstream{
Username: upUser,
AuthMapType: authMapTypePrivateKey,
PrivateKey: keydata{
Data: priv,
Type: "rsa",
},
Server: server{
Address: serverAddr,
IgnoreHostKey: ignoreServerKey,
HostKey: keydata{
Data: pub,
Type: "rsa",
},
},
},
}).Error
if err != nil {
t.Fatal(err)
}
return pub, priv
}
func TestFindUpstream(t *testing.T) {
p := newTestPlugin(t)
defer p.db.Close()
db := p.db
h := p.GetHandler()
listener, err := createListener(t)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
createEntry(t, db, "finddown0", "findup0", listener.Addr().String(), true)
createEntry(t, db, "finddown1", "findup1", listener.Addr().String(), true)
createEntry(t, db, "finddown2", "findup2", listener.Addr().String(), true)
_, auth, err := h(testconn{"finddown0"}, nil)
if err != nil {
t.Fatal(err)
}
if auth.User != "findup0" {
t.Error("auth pipe user name is not correct")
}
db.Delete(&config{Entry: fallbackUserEntry})
_, _, err = h(testconn{"not_exists"}, nil)
if err == nil {
t.Error("should not found any user")
}
db.NewRecord(&config{Entry: fallbackUserEntry, Value: "findup0"})
_, auth, err = h(testconn{"finddown0"}, nil)
if err != nil {
t.Fatal(err)
}
if auth.User != "findup0" {
t.Error("auth pipe user name is not correct (fallback)")
}
}
func TestPublicKeyCallback(t *testing.T) {
p := newTestPlugin(t)
defer p.db.Close()
db := p.db
h := p.GetHandler()
listener, err := createListener(t)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
pub, _ := createEntry(t, db, "pkdown", "pkdown", listener.Addr().String(), true)
_, auth, err := h(testconn{"pkdown"}, nil)
if err != nil {
t.Fatal(err)
}
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pub))
if err != nil {
t.Fatal(err)
}
authType, method, err := auth.PublicKeyCallback(nil, publicKey)
if err != nil {
t.Fatal(err)
}
if authType != ssh.AuthPipeTypeMap {
t.Error("auth type map should be AuthPipeTypeMap")
}
if method == nil {
t.Error("auth method is missing")
}
}
func createListener(t *testing.T) (net.Listener, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("cant create fake server: %v", err)
}
// go listener.Accept()
return listener, err
}

View file

@ -1,74 +0,0 @@
package database
import (
"github.com/jinzhu/gorm"
)
type authMapType int
const (
authMapTypeNone = iota
authMapTypePassword
authMapTypePrivateKey
authMapTypeAny
)
const fallbackUserEntry = "FALLBACK_USER"
type keydata struct {
gorm.Model
Name string `gorm:"type:varchar(45)"`
Data string `gorm:"type:text"`
Type string `gorm:"type:varchar(45)"`
}
type server struct {
gorm.Model
Name string `gorm:"type:varchar(45)"`
Address string `gorm:"type:varchar(100)"`
HostKeyID int
HostKey keydata
IgnoreHostKey bool
}
type upstream struct {
gorm.Model
Name string `gorm:"type:varchar(45)"`
ServerID int
Server server
Username string `gorm:"type:varchar(45)"`
Password string `gorm:"type:varchar(60)"`
PrivateKeyID int
PrivateKey keydata
AuthMapType authMapType
KnownHosts string `gorm:"type:varchar(100)"`
}
type downstream struct {
gorm.Model
Name string `gorm:"type:varchar(45)"`
Username string `gorm:"type:varchar(45);unique_index"`
Password string `gorm:"type:varchar(60)"`
AuthMapType authMapType
AllowAnyPublicKey bool
NoPassthrough bool
UpstreamID int
Upstream upstream
AuthorizedKeysID int
AuthorizedKeys keydata
}
type config struct {
gorm.Model
Entry string `gorm:"type:varchar(45);unique_index"`
Value string `gorm:"type:varchar(100)"`
}

View file

@ -1,58 +0,0 @@
package database
import (
"fmt"
"net/url"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mssql" // gorm dialect
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
type mssqlplugin struct {
plugin
Config struct {
Host string `long:"upstream-mssql-host" default:"127.0.0.1" description:"SQL Server host" env:"SSHPIPERD_UPSTREAM_MSSQL_HOST" ini-name:"upstream-mssql-host"`
User string `long:"upstream-mssql-user" default:"sa" description:"SQL Server user" env:"SSHPIPERD_UPSTREAM_MSSQL_USER" ini-name:"upstream-mssql-user"`
Password string `long:"upstream-mssql-password" default:"" description:"SQL Server password" env:"SSHPIPERD_UPSTREAM_MSSQL_PASSWORD" ini-name:"upstream-mssql-password"`
Port uint `long:"upstream-mssql-port" default:"1433" description:"SQL Server port" env:"SSHPIPERD_UPSTREAM_MSSQL_PORT" ini-name:"upstream-mssql-port"`
Dbname string `long:"upstream-mssql-dbname" default:"sshpiper" description:"SQL server database name" env:"SSHPIPERD_UPSTREAM_MSSQL_DBNAME" ini-name:"upstream-mssql-dbname"`
Instance string `long:"upstream-mssql-instance" description:"SQL Server database instance" env:"SSHPIPERD_UPSTREAM_MSSQL_INSTANCE" ini-name:"upstream-mssql-instance"`
}
}
func (p *mssqlplugin) create() (*gorm.DB, error) {
query := url.Values{}
query.Add("database", p.Config.Dbname)
u := &url.URL{
Scheme: "sqlserver",
User: url.UserPassword(p.Config.User, p.Config.Password),
Host: fmt.Sprintf("%s:%d", p.Config.Host, p.Config.Port),
Path: p.Config.Instance,
RawQuery: query.Encode(),
}
db, err := gorm.Open("mssql", u.String())
if err != nil {
return nil, err
}
return db, nil
}
func (mssqlplugin) GetName() string {
return "mssql"
}
func (p *mssqlplugin) GetOpts() interface{} {
return &p.Config
}
func init() {
p := &mssqlplugin{}
p.createdb = p
upstreamprovider.Register("mssql", p)
}

View file

@ -1,55 +0,0 @@
package database
import (
"fmt"
mysqldriver "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql" // gorm dialiect
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
type mysqlplugin struct {
plugin
Config struct {
Host string `long:"upstream-mysql-host" default:"127.0.0.1" description:"MySQL host" env:"SSHPIPERD_UPSTREAM_MYSQL_HOST" ini-name:"upstream-mysql-host"`
User string `long:"upstream-mysql-user" default:"root" description:"MySQL user" env:"SSHPIPERD_UPSTREAM_MYSQL_USER" ini-name:"upstream-mysql-user"`
Password string `long:"upstream-mysql-password" default:"" description:"MySQL password" env:"SSHPIPERD_UPSTREAM_MYSQL_PASSWORD" ini-name:"upstream-mysql-password"`
Port uint `long:"upstream-mysql-port" default:"3306" description:"MySQL port" env:"SSHPIPERD_UPSTREAM_MYSQL_PORT" ini-name:"upstream-mysql-port"`
Dbname string `long:"upstream-mysql-dbname" default:"sshpiper" description:"MySQL database name" env:"SSHPIPERD_UPSTREAM_MYSQL_DBNAME" ini-name:"upstream-mysql-dbname"`
}
}
func (p *mysqlplugin) create() (*gorm.DB, error) {
config := mysqldriver.NewConfig()
config.User = p.Config.User
config.Passwd = p.Config.Password
config.Net = "tcp"
config.Addr = fmt.Sprintf("%v:%v", p.Config.Host, p.Config.Port)
config.DBName = p.Config.Dbname
config.ParseTime = true
db, err := gorm.Open("mysql", config.FormatDSN())
if err != nil {
return nil, err
}
return db, nil
}
func (mysqlplugin) GetName() string {
return "mysql"
}
func (p *mysqlplugin) GetOpts() interface{} {
return &p.Config
}
func init() {
p := &mysqlplugin{}
p.createdb = p
upstreamprovider.Register("mysql", p)
}

View file

@ -1,76 +0,0 @@
package database
import (
"fmt"
"github.com/jinzhu/gorm"
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
"github.com/tg123/sshpiper/sshpiperd/utils"
)
func (p *plugin) ListPipe() ([]upstreamprovider.Pipe, error) {
db := p.db
downstreams := make([]downstream, 0)
err := db.Set("gorm:auto_preload", true).Find(&downstreams).Error
if err != nil {
return nil, err
}
pipes := make([]upstreamprovider.Pipe, 0, len(downstreams))
for _, d := range downstreams {
host, port, err := upstreamprovider.SplitHostPortForSSH(d.Upstream.Server.Address)
if err != nil {
continue
}
upuser := d.Upstream.Username
if upuser == "" {
upuser = d.Username
}
pipes = append(pipes, upstreamprovider.Pipe{
Host: utils.FormatIPAddress(host),
Port: port,
Username: d.Username,
UpstreamUsername: upuser,
})
}
return pipes, nil
}
func (p *plugin) CreatePipe(opt upstreamprovider.CreatePipeOption) error {
db := p.db
return db.Create(&downstream{
Username: opt.Username,
Upstream: upstream{
Username: opt.UpstreamUsername,
AuthMapType: authMapTypeNone,
Server: server{
Address: fmt.Sprintf("%v:%v", opt.Host, opt.Port),
IgnoreHostKey: true,
},
},
}).Error
}
func (p *plugin) RemovePipe(name string) error {
db := p.db
d, err := lookupDownstream(db, name)
if err != nil {
if gorm.IsRecordNotFoundError(err) {
return nil
}
return err
}
return db.Unscoped().Delete(d).Error
}

View file

@ -1,61 +0,0 @@
package database
import (
log "github.com/sirupsen/logrus"
"github.com/jinzhu/gorm"
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
var logger *log.Logger
type createdb interface {
create() (*gorm.DB, error)
}
type plugin struct {
createdb
db *gorm.DB
}
func (p *plugin) GetHandler() upstreamprovider.Handler {
return p.findUpstream
}
func (p *plugin) Init(glogger *log.Logger) error {
logger = glogger
db, err := p.create()
if err != nil {
return err
}
logger.Printf("upstream provider: Database driver [%v] initializing", db.Dialect().GetName())
err = db.AutoMigrate(
new(keydata),
new(server),
new(upstream),
new(downstream),
new(config),
).Error
if err != nil {
logger.Printf("AutoMigrate error: %v", err)
}
// TODO add config
// db.SetLogger(logger)
// db.LogMode(true)
p.db = db
// plugin is alive within program lifecycle, close when unload added
// defer db.Close()
return nil
}

View file

@ -1,62 +0,0 @@
package database
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres" // gorm dialect
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
type postgresplugin struct {
plugin
Config struct {
Host string `long:"upstream-postgres-host" default:"127.0.0.1" description:"PostgreSQL host" env:"SSHPIPERD_UPSTREAM_POSTGRES_HOST" ini-name:"upstream-postgres-host"`
User string `long:"upstream-postgres-user" default:"postgres" description:"PostgreSQL user" env:"SSHPIPERD_UPSTREAM_POSTGRES_USER" ini-name:"upstream-postgres-user"`
Password string `long:"upstream-postgres-password" description:"PostgreSQL password" env:"SSHPIPERD_UPSTREAM_POSTGRES_PASSWORD" ini-name:"upstream-postgres-password"`
Port uint `long:"upstream-postgres-port" default:"5432" description:"PostgreSQL port" env:"SSHPIPERD_UPSTREAM_POSTGRES_PORT" ini-name:"upstream-postgres-port"`
Dbname string `long:"upstream-postgres-dbname" default:"sshpiper" description:"PostgreSQL database name" env:"SSHPIPERD_UPSTREAM_POSTGRES_DBNAME" ini-name:"upstream-postgres-dbname"`
SslMode string `long:"upstream-postgres-sslmode" default:"require" description:"PostgreSQL ssl mode" env:"SSHPIPERD_UPSTREAM_POSTGRES_SSLMODE" ini-name:"upstream-postgres-sslmode"`
SslCert string `long:"upstream-postgres-sslcert" description:"PostgreSQL ssl cert path" env:"SSHPIPERD_UPSTREAM_POSTGRES_SSLCERT" ini-name:"upstream-postgres-sslcert"`
SslKey string `long:"upstream-postgres-sslkey" description:"PostgreSQL ssl key path" env:"SSHPIPERD_UPSTREAM_POSTGRES_SSLKEY" ini-name:"upstream-postgres-sslkey"`
SslRootCert string `long:"upstream-postgres-sslrootcert" description:"PostgreSQL ssl root cert path" env:"SSHPIPERD_UPSTREAM_POSTGRES_SSLROOTCERT" ini-name:"upstream-postgres-sslrootcert"`
}
}
func (p *postgresplugin) create() (*gorm.DB, error) {
conn := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=%v sslcert=%v sslkey=%v sslrootcert=%v",
p.Config.Host,
p.Config.Port,
p.Config.User,
p.Config.Password,
p.Config.Dbname,
p.Config.SslMode,
p.Config.SslCert,
p.Config.SslKey,
p.Config.SslRootCert,
)
db, err := gorm.Open("postgres", conn)
if err != nil {
return nil, err
}
return db, nil
}
func (postgresplugin) GetName() string {
return "postgres"
}
func (p *postgresplugin) GetOpts() interface{} {
return &p.Config
}
func init() {
p := &postgresplugin{}
p.createdb = p
upstreamprovider.Register("postgres", p)
}

View file

@ -1,40 +0,0 @@
package database
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite" // gorm dialect
upstreamprovider "github.com/tg123/sshpiper/sshpiperd/upstream"
)
type sqliteplugin struct {
plugin
Config struct {
File string `long:"upstream-sqlite-dbfile" default:"file:sshpiper.sqlite" description:"Database file path for SQLite 3" env:"SSHPIPERD_UPSTREAM_SQLITE_FILE" ini-name:"upstream-sqlite-file"`
}
}
func (p *sqliteplugin) create() (*gorm.DB, error) {
db, err := gorm.Open("sqlite3", p.Config.File)
if err != nil {
return nil, err
}
return db, nil
}
func (sqliteplugin) GetName() string {
return "sqlite"
}
func (p *sqliteplugin) GetOpts() interface{} {
return &p.Config
}
func init() {
p := &sqliteplugin{}
p.createdb = p
upstreamprovider.Register("sqlite", p)
}

View file

@ -1,124 +0,0 @@
package grpcupstream
import (
"context"
"fmt"
"net"
"time"
"github.com/tg123/remotesigner"
"github.com/tg123/remotesigner/grpcsigner"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"golang.org/x/crypto/ssh"
)
func (p *plugin) timeoutCtx() (context.Context, context.CancelFunc) {
d := time.Now().Add(time.Duration(p.Config.Timeout) * time.Second)
return context.WithDeadline(context.Background(), d)
}
func (p *plugin) mapauth(conn ssh.ConnMetadata, metadata string, typ MapAuthRequest_Authtype, param []byte) (ssh.AuthPipeType, ssh.AuthMethod, error) {
ctx, cancel := p.timeoutCtx()
defer cancel()
a, err := p.upstreamClient.MapAuth(ctx, &MapAuthRequest{
UserName: conn.User(),
FromAddr: conn.RemoteAddr().String(),
AuthType: typ,
AuthParam: param,
Metadata: metadata,
})
if err != nil {
return ssh.AuthPipeTypeDiscard, nil, err
}
switch a.MappedAuthType {
case MapAuthReply_PASSTHROUGH:
return ssh.AuthPipeTypePassThrough, nil, nil
case MapAuthReply_DISCARD:
return ssh.AuthPipeTypeDiscard, nil, nil
case MapAuthReply_NONE:
return ssh.AuthPipeTypeNone, nil, nil
case MapAuthReply_PASSWORD:
return ssh.AuthPipeTypeMap, ssh.Password(string(a.MappedAuthParam)), nil
case MapAuthReply_PRIVATEKEY:
private, err := ssh.ParsePrivateKey(a.MappedAuthParam)
if err != nil {
return ssh.AuthPipeTypeDiscard, nil, err
}
return ssh.AuthPipeTypeMap, ssh.PublicKeys(private), nil
case MapAuthReply_REMOTESIGNER:
rs := remotesigner.New(grpcsigner.New(p.remotesignerClient, string(a.MappedAuthParam)))
signer, err := ssh.NewSignerFromSigner(rs)
if err != nil {
return ssh.AuthPipeTypeDiscard, nil, err
}
return ssh.AuthPipeTypeMap, ssh.PublicKeys(signer), nil
default:
return ssh.AuthPipeTypeDiscard, nil, nil
}
}
func (p *plugin) verifyHostKey(hostname string, remote net.Addr, key ssh.PublicKey) error {
ctx, cancel := p.timeoutCtx()
defer cancel()
v, err := p.upstreamClient.VerifyHostKey(ctx, &VerifyHostKeyRequest{
Hostname: hostname,
Address: remote.String(),
Key: key.Marshal(),
})
if err != nil {
return err
}
if !v.Verified {
return fmt.Errorf("host key mismatch")
}
return nil
}
func (p *plugin) findUpstream(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
ctx, cancel := p.timeoutCtx()
defer cancel()
u, err := p.upstreamClient.FindUpstream(ctx, &FindUpstreamRequest{
UserName: conn.User(),
FromAddr: conn.RemoteAddr().String(),
})
if err != nil {
return nil, nil, err
}
c, err := upstream.DialForSSH(u.ToAddr)
if err != nil {
return nil, nil, err
}
a := &ssh.AuthPipe{
User: u.MappedUserName,
UpstreamHostKeyCallback: p.verifyHostKey,
}
a.NoneAuthCallback = func(conn ssh.ConnMetadata) (ssh.AuthPipeType, ssh.AuthMethod, error) {
return p.mapauth(conn, u.Metadata, MapAuthRequest_NONE, nil)
}
a.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (ssh.AuthPipeType, ssh.AuthMethod, error) {
return p.mapauth(conn, u.Metadata, MapAuthRequest_PASSWORD, password)
}
a.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.AuthPipeType, ssh.AuthMethod, error) {
return p.mapauth(conn, u.Metadata, MapAuthRequest_PUBLICKEY, key.Marshal())
}
return c, a, nil
}

View file

@ -1,20 +0,0 @@
package grpcupstream
import (
"github.com/tg123/sshpiper/sshpiperd/upstream"
)
// Return All pipes inside upstream
func (p *plugin) ListPipe() ([]upstream.Pipe, error) {
return nil, nil
}
// Create a pipe inside upstream
func (p *plugin) CreatePipe(opt upstream.CreatePipeOption) error {
return nil
}
// Remove a pipe from upstream
func (p *plugin) RemovePipe(name string) error {
return nil
}

View file

@ -1,92 +0,0 @@
package grpcupstream
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
log "github.com/sirupsen/logrus"
"github.com/tg123/remotesigner/grpcsigner"
"github.com/tg123/sshpiper/sshpiperd/upstream"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
type plugin struct {
Config struct {
Endpoint string `long:"upstream-grpc-endpoint" description:"grpc endpoint of the upstream" env:"SSHPIPERD_UPSTREAM_GRPC_ENDPOINT" ini-name:"upstream-grpc-endpoint"`
Insecure bool `long:"upstream-grpc-insecure" description:"disable secure grpc connection" env:"SSHPIPERD_UPSTREAM_GRPC_INSECURE" ini-name:"upstream-grpc-insecure"`
CA string `long:"upstream-grpc-ca" description:"ca file path" env:"SSHPIPERD_UPSTREAM_GRPC_CA" ini-name:"upstream-grpc-ca"`
Cert string `long:"upstream-grpc-cert" description:"certificate file path" env:"SSHPIPERD_UPSTREAM_GRPC_CERT" ini-name:"upstream-grpc-cert"`
Key string `long:"upstream-grpc-key" description:"key file path" env:"SSHPIPERD_UPSTREAM_GRPC_KEY" ini-name:"upstream-grpc-key"`
Timeout int `long:"upstream-grpc-timeout" description:"grpc call timeout in second" default:"10" env:"SSHPIPERD_UPSTREAM_GRPC_TIMEOUT" ini-name:"upstream-grpc-timeout"`
}
logger *log.Logger
upstreamClient UpstreamRegistryClient
remotesignerClient grpcsigner.SignerClient
}
// The name of the Plugin
func (p *plugin) GetName() string {
return "grpc"
}
func (p *plugin) GetOpts() interface{} {
return &p.Config
}
func (p *plugin) Init(logger *log.Logger) error {
p.logger = logger
var secopt grpc.DialOption
if p.Config.Insecure {
secopt = grpc.WithTransportCredentials(insecure.NewCredentials())
} else {
clientCert, err := tls.LoadX509KeyPair(p.Config.Cert, p.Config.Key)
if err != nil {
return err
}
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
}
if p.Config.CA != "" {
ca, err := ioutil.ReadFile(p.Config.CA)
if err != nil {
return err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(ca) {
return fmt.Errorf("failed to append ca")
}
config.RootCAs = certPool
}
secopt = grpc.WithTransportCredentials(credentials.NewTLS(config))
}
conn, err := grpc.Dial(p.Config.Endpoint, secopt, grpc.WithBlock())
if err != nil {
return err
}
p.upstreamClient = NewUpstreamRegistryClient(conn)
p.remotesignerClient = grpcsigner.NewSignerClient(conn)
return nil
}
func (p *plugin) GetHandler() upstream.Handler {
return p.findUpstream
}
func init() {
upstream.Register("grpc", &plugin{})
}

View file

@ -1,72 +0,0 @@
package main
import (
"context"
"fmt"
"io/ioutil"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/sshpiperd/upstream/grpcupstream"
)
type server struct {
grpcupstream.UnimplementedUpstreamRegistryServer
ToType string `long:"totype" default:"PASSTHROUGH"`
ToAddr string `long:"toaddr"`
MappedUserName string `long:"mappeduser"`
Password string `long:"password"`
PrivateKey string `long:"privatekey"`
}
func (s *server) FindUpstream(_ context.Context, request *grpcupstream.FindUpstreamRequest) (*grpcupstream.FindUpstreamReply, error) {
log.Printf("mapping [%v@%v] to [%v@%v]", request.UserName, request.FromAddr, s.MappedUserName, s.ToAddr)
return &grpcupstream.FindUpstreamReply{
ToAddr: s.ToAddr,
MappedUserName: s.MappedUserName,
}, nil
}
func (s *server) VerifyHostKey(context.Context, *grpcupstream.VerifyHostKeyRequest) (*grpcupstream.VerifyHostKeyReply, error) {
return &grpcupstream.VerifyHostKeyReply{
Verified: true,
}, nil
}
func (s *server) MapAuth(_ context.Context, request *grpcupstream.MapAuthRequest) (*grpcupstream.MapAuthReply, error) {
totype := grpcupstream.MapAuthReply_Authtype(grpcupstream.MapAuthReply_Authtype_value[s.ToType])
log.Printf("mapping auth [%v] to [%v]", request.AuthType, totype)
// add your own auth here
switch totype {
case grpcupstream.MapAuthReply_PASSTHROUGH, grpcupstream.MapAuthReply_DISCARD, grpcupstream.MapAuthReply_NONE:
return &grpcupstream.MapAuthReply{
MappedAuthType: totype,
}, nil
case grpcupstream.MapAuthReply_PASSWORD:
return &grpcupstream.MapAuthReply{
MappedAuthType: grpcupstream.MapAuthReply_PASSWORD,
MappedAuthParam: []byte(s.Password),
}, nil
case grpcupstream.MapAuthReply_PRIVATEKEY:
b, err := ioutil.ReadFile(s.PrivateKey)
if err != nil {
return nil, err
}
return &grpcupstream.MapAuthReply{
MappedAuthType: grpcupstream.MapAuthReply_PRIVATEKEY,
MappedAuthParam: b,
}, nil
case grpcupstream.MapAuthReply_REMOTESIGNER:
return &grpcupstream.MapAuthReply{
MappedAuthType: grpcupstream.MapAuthReply_REMOTESIGNER,
MappedAuthParam: []byte(s.PrivateKey),
}, nil
}
return nil, fmt.Errorf("unsupported to type")
}

View file

@ -1,73 +0,0 @@
package main
import (
"crypto"
"fmt"
"io/ioutil"
"net"
"reflect"
"unsafe"
log "github.com/sirupsen/logrus"
"github.com/jessevdk/go-flags"
"github.com/tg123/remotesigner/grpcsigner"
"github.com/tg123/sshpiper/sshpiperd/upstream/grpcupstream"
"golang.org/x/crypto/ssh"
"google.golang.org/grpc"
)
type args struct {
ListenAddr string `short:"l" long:"listen" default:"0.0.0.0"`
Port uint `short:"p" long:"port" default:"2233"`
server
}
func main() {
var c args
if _, err := flags.Parse(&c); err != nil {
log.Fatalf("parse args %v", err)
}
addr := fmt.Sprintf("%v:%v", c.ListenAddr, c.Port)
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("listen on %v failed %v", addr, err)
}
if _, ok := grpcupstream.MapAuthReply_Authtype_value[c.ToType]; !ok {
log.Fatalf("totype [%v] not allowed", c.ToType)
}
s := grpc.NewServer()
grpcupstream.RegisterUpstreamRegistryServer(s, &c.server)
if c.PrivateKey != "" {
gs, err := grpcsigner.NewSignerServer(func(string) crypto.Signer {
b, err := ioutil.ReadFile(c.PrivateKey)
if err != nil {
log.Fatalf("private key [%v] load failed %v", c.PrivateKey, err)
}
private, err := ssh.ParsePrivateKey(b)
if err != nil {
log.Fatalf("private key [%v] parse failed %v", c.PrivateKey, err)
}
field := reflect.ValueOf(private).Elem().FieldByName("signer")
signer := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface().(crypto.Signer)
log.Printf("remote signing with %v", c.PrivateKey)
return signer
})
if err != nil {
log.Fatalf("start grpc signer failed %v", err)
}
grpcsigner.RegisterSignerServer(s, gs)
}
log.Printf("serving on %v", addr)
panic(s.Serve(l))
}

View file

@ -1,710 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0-devel
// protoc v3.14.0
// source: upstream.proto
package grpcupstream
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MapAuthRequest_Authtype int32
const (
MapAuthRequest_NONE MapAuthRequest_Authtype = 0
MapAuthRequest_PASSWORD MapAuthRequest_Authtype = 1
MapAuthRequest_PUBLICKEY MapAuthRequest_Authtype = 2
)
// Enum value maps for MapAuthRequest_Authtype.
var (
MapAuthRequest_Authtype_name = map[int32]string{
0: "NONE",
1: "PASSWORD",
2: "PUBLICKEY",
}
MapAuthRequest_Authtype_value = map[string]int32{
"NONE": 0,
"PASSWORD": 1,
"PUBLICKEY": 2,
}
)
func (x MapAuthRequest_Authtype) Enum() *MapAuthRequest_Authtype {
p := new(MapAuthRequest_Authtype)
*p = x
return p
}
func (x MapAuthRequest_Authtype) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MapAuthRequest_Authtype) Descriptor() protoreflect.EnumDescriptor {
return file_upstream_proto_enumTypes[0].Descriptor()
}
func (MapAuthRequest_Authtype) Type() protoreflect.EnumType {
return &file_upstream_proto_enumTypes[0]
}
func (x MapAuthRequest_Authtype) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MapAuthRequest_Authtype.Descriptor instead.
func (MapAuthRequest_Authtype) EnumDescriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{4, 0}
}
type MapAuthReply_Authtype int32
const (
MapAuthReply_PASSTHROUGH MapAuthReply_Authtype = 0
MapAuthReply_DISCARD MapAuthReply_Authtype = 1
MapAuthReply_NONE MapAuthReply_Authtype = 2
MapAuthReply_PASSWORD MapAuthReply_Authtype = 3
MapAuthReply_PRIVATEKEY MapAuthReply_Authtype = 4
MapAuthReply_REMOTESIGNER MapAuthReply_Authtype = 5
)
// Enum value maps for MapAuthReply_Authtype.
var (
MapAuthReply_Authtype_name = map[int32]string{
0: "PASSTHROUGH",
1: "DISCARD",
2: "NONE",
3: "PASSWORD",
4: "PRIVATEKEY",
5: "REMOTESIGNER",
}
MapAuthReply_Authtype_value = map[string]int32{
"PASSTHROUGH": 0,
"DISCARD": 1,
"NONE": 2,
"PASSWORD": 3,
"PRIVATEKEY": 4,
"REMOTESIGNER": 5,
}
)
func (x MapAuthReply_Authtype) Enum() *MapAuthReply_Authtype {
p := new(MapAuthReply_Authtype)
*p = x
return p
}
func (x MapAuthReply_Authtype) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MapAuthReply_Authtype) Descriptor() protoreflect.EnumDescriptor {
return file_upstream_proto_enumTypes[1].Descriptor()
}
func (MapAuthReply_Authtype) Type() protoreflect.EnumType {
return &file_upstream_proto_enumTypes[1]
}
func (x MapAuthReply_Authtype) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MapAuthReply_Authtype.Descriptor instead.
func (MapAuthReply_Authtype) EnumDescriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{5, 0}
}
type FindUpstreamRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserName string `protobuf:"bytes,1,opt,name=userName,proto3" json:"userName,omitempty"`
FromAddr string `protobuf:"bytes,2,opt,name=fromAddr,proto3" json:"fromAddr,omitempty"`
}
func (x *FindUpstreamRequest) Reset() {
*x = FindUpstreamRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FindUpstreamRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FindUpstreamRequest) ProtoMessage() {}
func (x *FindUpstreamRequest) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FindUpstreamRequest.ProtoReflect.Descriptor instead.
func (*FindUpstreamRequest) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{0}
}
func (x *FindUpstreamRequest) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *FindUpstreamRequest) GetFromAddr() string {
if x != nil {
return x.FromAddr
}
return ""
}
type FindUpstreamReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ToAddr string `protobuf:"bytes,1,opt,name=toAddr,proto3" json:"toAddr,omitempty"`
MappedUserName string `protobuf:"bytes,2,opt,name=mappedUserName,proto3" json:"mappedUserName,omitempty"`
Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`
}
func (x *FindUpstreamReply) Reset() {
*x = FindUpstreamReply{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FindUpstreamReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FindUpstreamReply) ProtoMessage() {}
func (x *FindUpstreamReply) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FindUpstreamReply.ProtoReflect.Descriptor instead.
func (*FindUpstreamReply) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{1}
}
func (x *FindUpstreamReply) GetToAddr() string {
if x != nil {
return x.ToAddr
}
return ""
}
func (x *FindUpstreamReply) GetMappedUserName() string {
if x != nil {
return x.MappedUserName
}
return ""
}
func (x *FindUpstreamReply) GetMetadata() string {
if x != nil {
return x.Metadata
}
return ""
}
type VerifyHostKeyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"`
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
Key []byte `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"`
}
func (x *VerifyHostKeyRequest) Reset() {
*x = VerifyHostKeyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyHostKeyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyHostKeyRequest) ProtoMessage() {}
func (x *VerifyHostKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyHostKeyRequest.ProtoReflect.Descriptor instead.
func (*VerifyHostKeyRequest) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{2}
}
func (x *VerifyHostKeyRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
func (x *VerifyHostKeyRequest) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func (x *VerifyHostKeyRequest) GetKey() []byte {
if x != nil {
return x.Key
}
return nil
}
type VerifyHostKeyReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Verified bool `protobuf:"varint,1,opt,name=verified,proto3" json:"verified,omitempty"`
}
func (x *VerifyHostKeyReply) Reset() {
*x = VerifyHostKeyReply{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyHostKeyReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyHostKeyReply) ProtoMessage() {}
func (x *VerifyHostKeyReply) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyHostKeyReply.ProtoReflect.Descriptor instead.
func (*VerifyHostKeyReply) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{3}
}
func (x *VerifyHostKeyReply) GetVerified() bool {
if x != nil {
return x.Verified
}
return false
}
type MapAuthRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserName string `protobuf:"bytes,1,opt,name=userName,proto3" json:"userName,omitempty"`
FromAddr string `protobuf:"bytes,2,opt,name=fromAddr,proto3" json:"fromAddr,omitempty"`
Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`
AuthType MapAuthRequest_Authtype `protobuf:"varint,4,opt,name=authType,proto3,enum=grpcupstream.MapAuthRequest_Authtype" json:"authType,omitempty"`
AuthParam []byte `protobuf:"bytes,5,opt,name=authParam,proto3" json:"authParam,omitempty"`
}
func (x *MapAuthRequest) Reset() {
*x = MapAuthRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MapAuthRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MapAuthRequest) ProtoMessage() {}
func (x *MapAuthRequest) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MapAuthRequest.ProtoReflect.Descriptor instead.
func (*MapAuthRequest) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{4}
}
func (x *MapAuthRequest) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *MapAuthRequest) GetFromAddr() string {
if x != nil {
return x.FromAddr
}
return ""
}
func (x *MapAuthRequest) GetMetadata() string {
if x != nil {
return x.Metadata
}
return ""
}
func (x *MapAuthRequest) GetAuthType() MapAuthRequest_Authtype {
if x != nil {
return x.AuthType
}
return MapAuthRequest_NONE
}
func (x *MapAuthRequest) GetAuthParam() []byte {
if x != nil {
return x.AuthParam
}
return nil
}
type MapAuthReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
MappedAuthType MapAuthReply_Authtype `protobuf:"varint,1,opt,name=mappedAuthType,proto3,enum=grpcupstream.MapAuthReply_Authtype" json:"mappedAuthType,omitempty"`
MappedAuthParam []byte `protobuf:"bytes,2,opt,name=mappedAuthParam,proto3" json:"mappedAuthParam,omitempty"`
}
func (x *MapAuthReply) Reset() {
*x = MapAuthReply{}
if protoimpl.UnsafeEnabled {
mi := &file_upstream_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MapAuthReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MapAuthReply) ProtoMessage() {}
func (x *MapAuthReply) ProtoReflect() protoreflect.Message {
mi := &file_upstream_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MapAuthReply.ProtoReflect.Descriptor instead.
func (*MapAuthReply) Descriptor() ([]byte, []int) {
return file_upstream_proto_rawDescGZIP(), []int{5}
}
func (x *MapAuthReply) GetMappedAuthType() MapAuthReply_Authtype {
if x != nil {
return x.MappedAuthType
}
return MapAuthReply_PASSTHROUGH
}
func (x *MapAuthReply) GetMappedAuthParam() []byte {
if x != nil {
return x.MappedAuthParam
}
return nil
}
var File_upstream_proto protoreflect.FileDescriptor
var file_upstream_proto_rawDesc = []byte{
0x0a, 0x0e, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x4d,
0x0a, 0x13, 0x46, 0x69, 0x6e, 0x64, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x22, 0x6f, 0x0a,
0x11, 0x46, 0x69, 0x6e, 0x64, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x6d, 0x61,
0x70, 0x70, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x5e,
0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x30,
0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x52,
0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64,
0x22, 0xf8, 0x01, 0x0a, 0x0e, 0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12,
0x1a, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54,
0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63,
0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x74, 0x79, 0x70, 0x65,
0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75,
0x74, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61,
0x75, 0x74, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x31, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68,
0x74, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0c,
0x0a, 0x08, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09,
0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x22, 0xe9, 0x01, 0x0a, 0x0c,
0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x4b, 0x0a, 0x0e,
0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x2e, 0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x2e, 0x41, 0x75, 0x74, 0x68, 0x74, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x6d, 0x61, 0x70, 0x70, 0x65,
0x64, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x6d, 0x61, 0x70,
0x70, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x50, 0x61,
0x72, 0x61, 0x6d, 0x22, 0x62, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x74, 0x79, 0x70, 0x65, 0x12,
0x0f, 0x0a, 0x0b, 0x50, 0x41, 0x53, 0x53, 0x54, 0x48, 0x52, 0x4f, 0x55, 0x47, 0x48, 0x10, 0x00,
0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x43, 0x41, 0x52, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a,
0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x53, 0x53, 0x57,
0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45,
0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x53,
0x49, 0x47, 0x4e, 0x45, 0x52, 0x10, 0x05, 0x32, 0x88, 0x02, 0x0a, 0x10, 0x55, 0x70, 0x73, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x54, 0x0a, 0x0c,
0x46, 0x69, 0x6e, 0x64, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x2e, 0x67,
0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x46, 0x69, 0x6e, 0x64,
0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x46,
0x69, 0x6e, 0x64, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x57, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x6f, 0x73, 0x74,
0x4b, 0x65, 0x79, 0x12, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x4b, 0x65, 0x79,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70,
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x6f, 0x73,
0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x07, 0x4d,
0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x2e, 0x4d, 0x61, 0x70, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x74, 0x67, 0x31, 0x32, 0x33, 0x2f, 0x73, 0x73, 0x68, 0x70, 0x69, 0x70, 0x65, 0x72, 0x2f,
0x73, 0x73, 0x68, 0x70, 0x69, 0x70, 0x65, 0x72, 0x64, 0x2f, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_upstream_proto_rawDescOnce sync.Once
file_upstream_proto_rawDescData = file_upstream_proto_rawDesc
)
func file_upstream_proto_rawDescGZIP() []byte {
file_upstream_proto_rawDescOnce.Do(func() {
file_upstream_proto_rawDescData = protoimpl.X.CompressGZIP(file_upstream_proto_rawDescData)
})
return file_upstream_proto_rawDescData
}
var file_upstream_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_upstream_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_upstream_proto_goTypes = []interface{}{
(MapAuthRequest_Authtype)(0), // 0: grpcupstream.MapAuthRequest.Authtype
(MapAuthReply_Authtype)(0), // 1: grpcupstream.MapAuthReply.Authtype
(*FindUpstreamRequest)(nil), // 2: grpcupstream.FindUpstreamRequest
(*FindUpstreamReply)(nil), // 3: grpcupstream.FindUpstreamReply
(*VerifyHostKeyRequest)(nil), // 4: grpcupstream.VerifyHostKeyRequest
(*VerifyHostKeyReply)(nil), // 5: grpcupstream.VerifyHostKeyReply
(*MapAuthRequest)(nil), // 6: grpcupstream.MapAuthRequest
(*MapAuthReply)(nil), // 7: grpcupstream.MapAuthReply
}
var file_upstream_proto_depIdxs = []int32{
0, // 0: grpcupstream.MapAuthRequest.authType:type_name -> grpcupstream.MapAuthRequest.Authtype
1, // 1: grpcupstream.MapAuthReply.mappedAuthType:type_name -> grpcupstream.MapAuthReply.Authtype
2, // 2: grpcupstream.UpstreamRegistry.FindUpstream:input_type -> grpcupstream.FindUpstreamRequest
4, // 3: grpcupstream.UpstreamRegistry.VerifyHostKey:input_type -> grpcupstream.VerifyHostKeyRequest
6, // 4: grpcupstream.UpstreamRegistry.MapAuth:input_type -> grpcupstream.MapAuthRequest
3, // 5: grpcupstream.UpstreamRegistry.FindUpstream:output_type -> grpcupstream.FindUpstreamReply
5, // 6: grpcupstream.UpstreamRegistry.VerifyHostKey:output_type -> grpcupstream.VerifyHostKeyReply
7, // 7: grpcupstream.UpstreamRegistry.MapAuth:output_type -> grpcupstream.MapAuthReply
5, // [5:8] is the sub-list for method output_type
2, // [2:5] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_upstream_proto_init() }
func file_upstream_proto_init() {
if File_upstream_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_upstream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FindUpstreamRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_upstream_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FindUpstreamReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_upstream_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyHostKeyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_upstream_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyHostKeyReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_upstream_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MapAuthRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_upstream_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MapAuthReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_upstream_proto_rawDesc,
NumEnums: 2,
NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_upstream_proto_goTypes,
DependencyIndexes: file_upstream_proto_depIdxs,
EnumInfos: file_upstream_proto_enumTypes,
MessageInfos: file_upstream_proto_msgTypes,
}.Build()
File_upstream_proto = out.File
file_upstream_proto_rawDesc = nil
file_upstream_proto_goTypes = nil
file_upstream_proto_depIdxs = nil
}

View file

@ -1,58 +0,0 @@
syntax = "proto3";
package grpcupstream;
option go_package = "github.com/tg123/sshpiper/sshpiperd/upstream/grpcupstream";
service UpstreamRegistry {
rpc FindUpstream (FindUpstreamRequest) returns (FindUpstreamReply) {}
rpc VerifyHostKey (VerifyHostKeyRequest) returns (VerifyHostKeyReply) {}
rpc MapAuth(MapAuthRequest) returns (MapAuthReply) {}
}
message FindUpstreamRequest {
string userName = 1;
string fromAddr = 2;
}
message FindUpstreamReply {
string toAddr = 1;
string mappedUserName = 2;
string metadata = 3;
}
message VerifyHostKeyRequest {
string hostname = 1;
string address = 2;
bytes key = 3;
}
message VerifyHostKeyReply {
bool verified = 1;
}
message MapAuthRequest {
string userName = 1;
string fromAddr = 2;
string metadata = 3;
enum Authtype {
NONE = 0;
PASSWORD = 1;
PUBLICKEY = 2;
}
Authtype authType = 4;
bytes authParam = 5;
}
message MapAuthReply {
enum Authtype {
PASSTHROUGH = 0;
DISCARD = 1;
NONE = 2;
PASSWORD = 3;
PRIVATEKEY = 4;
REMOTESIGNER = 5;
}
Authtype mappedAuthType = 1;
bytes mappedAuthParam = 2;
}

View file

@ -1,173 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package grpcupstream
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// UpstreamRegistryClient is the client API for UpstreamRegistry service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UpstreamRegistryClient interface {
FindUpstream(ctx context.Context, in *FindUpstreamRequest, opts ...grpc.CallOption) (*FindUpstreamReply, error)
VerifyHostKey(ctx context.Context, in *VerifyHostKeyRequest, opts ...grpc.CallOption) (*VerifyHostKeyReply, error)
MapAuth(ctx context.Context, in *MapAuthRequest, opts ...grpc.CallOption) (*MapAuthReply, error)
}
type upstreamRegistryClient struct {
cc grpc.ClientConnInterface
}
func NewUpstreamRegistryClient(cc grpc.ClientConnInterface) UpstreamRegistryClient {
return &upstreamRegistryClient{cc}
}
func (c *upstreamRegistryClient) FindUpstream(ctx context.Context, in *FindUpstreamRequest, opts ...grpc.CallOption) (*FindUpstreamReply, error) {
out := new(FindUpstreamReply)
err := c.cc.Invoke(ctx, "/grpcupstream.UpstreamRegistry/FindUpstream", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *upstreamRegistryClient) VerifyHostKey(ctx context.Context, in *VerifyHostKeyRequest, opts ...grpc.CallOption) (*VerifyHostKeyReply, error) {
out := new(VerifyHostKeyReply)
err := c.cc.Invoke(ctx, "/grpcupstream.UpstreamRegistry/VerifyHostKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *upstreamRegistryClient) MapAuth(ctx context.Context, in *MapAuthRequest, opts ...grpc.CallOption) (*MapAuthReply, error) {
out := new(MapAuthReply)
err := c.cc.Invoke(ctx, "/grpcupstream.UpstreamRegistry/MapAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// UpstreamRegistryServer is the server API for UpstreamRegistry service.
// All implementations must embed UnimplementedUpstreamRegistryServer
// for forward compatibility
type UpstreamRegistryServer interface {
FindUpstream(context.Context, *FindUpstreamRequest) (*FindUpstreamReply, error)
VerifyHostKey(context.Context, *VerifyHostKeyRequest) (*VerifyHostKeyReply, error)
MapAuth(context.Context, *MapAuthRequest) (*MapAuthReply, error)
mustEmbedUnimplementedUpstreamRegistryServer()
}
// UnimplementedUpstreamRegistryServer must be embedded to have forward compatible implementations.
type UnimplementedUpstreamRegistryServer struct {
}
func (UnimplementedUpstreamRegistryServer) FindUpstream(context.Context, *FindUpstreamRequest) (*FindUpstreamReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method FindUpstream not implemented")
}
func (UnimplementedUpstreamRegistryServer) VerifyHostKey(context.Context, *VerifyHostKeyRequest) (*VerifyHostKeyReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyHostKey not implemented")
}
func (UnimplementedUpstreamRegistryServer) MapAuth(context.Context, *MapAuthRequest) (*MapAuthReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method MapAuth not implemented")
}
func (UnimplementedUpstreamRegistryServer) mustEmbedUnimplementedUpstreamRegistryServer() {}
// UnsafeUpstreamRegistryServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to UpstreamRegistryServer will
// result in compilation errors.
type UnsafeUpstreamRegistryServer interface {
mustEmbedUnimplementedUpstreamRegistryServer()
}
func RegisterUpstreamRegistryServer(s grpc.ServiceRegistrar, srv UpstreamRegistryServer) {
s.RegisterService(&UpstreamRegistry_ServiceDesc, srv)
}
func _UpstreamRegistry_FindUpstream_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FindUpstreamRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UpstreamRegistryServer).FindUpstream(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpcupstream.UpstreamRegistry/FindUpstream",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UpstreamRegistryServer).FindUpstream(ctx, req.(*FindUpstreamRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UpstreamRegistry_VerifyHostKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyHostKeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UpstreamRegistryServer).VerifyHostKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpcupstream.UpstreamRegistry/VerifyHostKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UpstreamRegistryServer).VerifyHostKey(ctx, req.(*VerifyHostKeyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UpstreamRegistry_MapAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MapAuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UpstreamRegistryServer).MapAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpcupstream.UpstreamRegistry/MapAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UpstreamRegistryServer).MapAuth(ctx, req.(*MapAuthRequest))
}
return interceptor(ctx, in, info, handler)
}
// UpstreamRegistry_ServiceDesc is the grpc.ServiceDesc for UpstreamRegistry service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var UpstreamRegistry_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpcupstream.UpstreamRegistry",
HandlerType: (*UpstreamRegistryServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "FindUpstream",
Handler: _UpstreamRegistry_FindUpstream_Handler,
},
{
MethodName: "VerifyHostKey",
Handler: _UpstreamRegistry_VerifyHostKey_Handler,
},
{
MethodName: "MapAuth",
Handler: _UpstreamRegistry_MapAuth_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "upstream.proto",
}

View file

@ -1,79 +0,0 @@
# Kubernetes upstream for SSHPiper
Kubernetes api is called to retreive sshpiper configuration.
## Usage
First install CRD
```
$ kubectl apply -f https://raw.githubusercontent.com/pockost/sshpipe-k8s-lib/v0.0.3/artifacts/sshpipe.yaml
```
You can now add a new sshpipe object
```
apiVersion: pockost.com/v1beta1
kind: SshPipe
metadata:
name: sftp2
spec:
users:
- user2
target:
name: sftp2
```
You can find more example in [the example folder](example)
## ssh host key
If the `/etc/ssh/ssh_host_rsa_key` does not exist at startup the
sshpiper container generate one. This mean each time the `sshpiper` pod
is recreated a new `ssh_host_rsa_key` will be generated.
To prevent this you can generate a new `ssh_host_rsa_key` and store this
one in a Secret object.
```
$ docker run --rm farmer1992/sshpiperd /sshpiperd genkey > ssh_host_rsa_key
$ kubectl -n sshpiper create secret generic sshpiper --from-file ssh_host_rsa_key
```
You can now inject this secret in /etc/ssh/ssh_host_rsa_key.
```
apiVersion: apps/v1
kind: Deployment
metadata:
name: sshpiper
namespace: sshpiper
spec:
replicas: 1
selector:
matchLabels:
app: sshpiper
template:
metadata:
labels:
app: sshpiper
spec:
serviceAccountName: sshpiper
containers:
- name: sshpiper
image: lermit/sshpiper
imagePullPolicy: Always
env:
- name: SSHPIPERD_UPSTREAM_DRIVER
value: kubernetes
volumeMounts:
- name: secrets
mountPath: /etc/ssh/ssh_host_rsa_key
subPath: ssh_host_rsa_key
volumes:
- name: secrets
secret:
secretName: sshpiper
```
**Note**: You can uncomment corresponding line in the example folder.

View file

@ -1,4 +0,0 @@
* Permit connection with KUBECONFIG env var
* Detect port from service
* Use Watch in order to subscribe to SshPipe CRD change in order to reduce api call
* Create helm chart

View file

@ -1,75 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: sshpipes.pockost.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: pockost.com
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1beta1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
additionalPrinterColumns:
- jsonPath: .spec.users
name: Users
type: string
- jsonPath: .spec.target.name
name: Service
type: string
- jsonPath: .metadata.creationTimestamp
description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
name: Age
type: date
schema:
openAPIV3Schema:
description: Sshpipe is a pipe configuration for sshpipe
type: object
required:
- metadata
- spec
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
type: object
required:
- users
- target
properties:
users:
description: list of users that should be routed to selected target service.
type: array
items:
type: string
target:
description: service to route ssh/sftp connection to.
type: object
required:
- name
properties:
name:
description: The service name
type: string
port:
description: The service port
type: integer
default: 22
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: sshpipes
# singular name to be used as an alias on the CLI and for display
singular: sshpipe
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: SshPipe

View file

@ -1,118 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sftp1
spec:
replicas: 1
selector:
matchLabels:
app: sftp1
template:
metadata:
labels:
app: sftp1
spec:
containers:
- name: sftp
image: atmoz/sftp
volumeMounts:
- name: data
mountPath: /home/user1/
args:
- "user1:pass"
volumes:
- name: data
persistentVolumeClaim:
claimName: sftp1-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sftp1-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
# The amount of the volume's storage to request
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: sftp1
spec:
ports:
- port: 22
selector:
app: sftp1
---
apiVersion: pockost.com/v1beta1
kind: SshPipe
metadata:
name: sftp1
spec:
users:
- user1
target:
name: sftp1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sftp2
spec:
replicas: 1
selector:
matchLabels:
app: sftp2
template:
metadata:
labels:
app: sftp2
spec:
containers:
- name: sftp
image: atmoz/sftp
volumeMounts:
- name: data
mountPath: /home/user2/
args:
- "user2:pass"
volumes:
- name: data
persistentVolumeClaim:
claimName: sftp2-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sftp2-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
# The amount of the volume's storage to request
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: sftp2
spec:
ports:
- port: 22
selector:
app: sftp2
---
apiVersion: pockost.com/v1beta1
kind: SshPipe
metadata:
name: sftp2
spec:
users:
- user2
target:
name: sftp2

View file

@ -1,80 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: sshpiper
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sshpiper
namespace: sshpiper
spec:
replicas: 1
selector:
matchLabels:
app: sshpiper
template:
metadata:
labels:
app: sshpiper
spec:
serviceAccountName: sshpiper
containers:
- name: sshpiper
image: lermit/sshpiper
imagePullPolicy: Always
env:
- name: SSHPIPERD_UPSTREAM_DRIVER
value: kubernetes
# volumeMounts:
# - name: secrets
# mountPath: /etc/ssh/ssh_host_rsa_key
# subPath: ssh_host_rsa_key
# volumes:
# - name: secrets
# secret:
# secretName: sshpiper
---
apiVersion: v1
kind: Service
metadata:
name: sshpiper
namespace: sshpiper
spec:
type: LoadBalancer
ports:
- port: 2222
targetPort: 2222
selector:
app: sshpiper
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sshpiper
namespace: sshpiper
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sshpiper
namespace: sshpiper
rules:
- apiGroups: ["pockost.com"]
resources: ["sshpipes"]
verbs: ["watch", "get", "list"]
---
# This role binding allows "jane" to read pods in the "default" namespace.
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sshpiper
subjects:
- kind: ServiceAccount
name: sshpiper
namespace: sshpiper
roleRef:
kind: ClusterRole #this must be Role or ClusterRole
name: sshpiper
apiGroup: rbac.authorization.k8s.io

View file

@ -1,124 +0,0 @@
package kubernetes
import (
"context"
"fmt"
"net"
//"path/filepath"
//"strings"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"golang.org/x/crypto/ssh"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
//"k8s.io/client-go/util/homedir"
//"k8s.io/client-go/tools/clientcmd"
sshpipeclientset "github.com/pockost/sshpipe-k8s-lib/pkg/client/clientset/versioned"
)
type pipeConfig struct {
Username string `kubernetes:"username"`
UpstreamHost string `kubernetes:"upstream_host"`
}
// type createPipeCtx struct {
// pipe pipeConfig
// conn ssh.ConnMetadata
// challengeContext ssh.AdditionalChallengeContext
// }
func (p *plugin) getClientSet() (*sshpipeclientset.Clientset, error) {
// var kubeconfig string
// home := homedir.HomeDir()
// kubeconfig = filepath.Join(home, ".kube", "config")
// // use the current context in kubeconfig
// config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
// if err != nil {
// return nil, err
// }
config, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
// create the clientset
clientset, err := sshpipeclientset.NewForConfig(config)
if err != nil {
return nil, err
}
return clientset, nil
}
func (p *plugin) getConfig(clientset *sshpipeclientset.Clientset, sshPipeName string) (pipeConfig, error) {
listOptions := metav1.ListOptions{}
pipes, err := clientset.PockostV1beta1().SshPipes("").List(context.TODO(), listOptions)
if err != nil {
return pipeConfig{}, err
}
if err != nil {
return pipeConfig{}, err
}
for _, pipe := range pipes.Items {
if pipe.Name == sshPipeName {
targetHost := fmt.Sprintf("%s.%s", pipe.Spec.Target.Name, pipe.ObjectMeta.Namespace)
return pipeConfig{
Username: pipe.Spec.Users[0],
UpstreamHost: targetHost,
}, nil
}
}
return pipeConfig{}, fmt.Errorf("sshPipe [%s] not found", sshPipeName)
}
func (p *plugin) createAuthPipe(pipe pipeConfig, conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (*ssh.AuthPipe, error) {
hostKeyCallback := ssh.InsecureIgnoreHostKey()
a := &ssh.AuthPipe{
User: pipe.Username,
UpstreamHostKeyCallback: hostKeyCallback,
}
a.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (ssh.AuthPipeType, ssh.AuthMethod, error) {
return ssh.AuthPipeTypePassThrough, nil, nil
}
return a, nil
}
func (p *plugin) findUpstream(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
user := conn.User()
clientset, err := p.getClientSet()
if err != nil {
return nil, nil, err
}
// Get config from k8s
pipeConfig, err := p.getConfig(clientset, user)
if err != nil {
return nil, nil, err
}
// Retreive corresponding configuration
c, err := upstream.DialForSSH(pipeConfig.UpstreamHost)
if err != nil {
return nil, nil, err
}
a, err := p.createAuthPipe(pipeConfig, conn, challengeContext)
if err != nil {
return nil, nil, err
}
p.logger.Printf("SSH Pipe [%s] forwarding connection to [%s] with user [%s]", user, pipeConfig.UpstreamHost, pipeConfig.Username)
return c, a, nil
}

View file

@ -1,57 +0,0 @@
package kubernetes
import (
"context"
"fmt"
"github.com/tg123/sshpiper/sshpiperd/upstream"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Return All pipes inside upstream
func (p *plugin) ListPipe() ([]upstream.Pipe, error) {
pipes := []upstream.Pipe{}
clientset, err := p.getClientSet()
if err != nil {
return nil, err
}
pipeList, err := clientset.PockostV1beta1().SshPipes("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, p := range pipeList.Items {
targetHost := fmt.Sprintf("%s.%s", p.Spec.Target.Name, p.ObjectMeta.Namespace)
pipes = append(pipes, upstream.Pipe{
Username: p.Name,
UpstreamUsername: p.Spec.Users[0],
Host: targetHost,
Port: 22,
})
}
return pipes, nil
}
// Create a pipe inside upstream
func (p *plugin) CreatePipe(opt upstream.CreatePipeOption) error {
return nil
}
// Remove a pipe from upstream
func (p *plugin) RemovePipe(name string) error {
clientset, err := p.getClientSet()
if err != nil {
return err
}
pipeList, err := clientset.PockostV1beta1().SshPipes("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
return err
}
for _, p := range pipeList.Items {
if p.Name == name {
return clientset.PockostV1beta1().SshPipes(p.ObjectMeta.Namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
}
return fmt.Errorf("SSH Pipe [%s] not found", name)
}

View file

@ -1,39 +0,0 @@
package kubernetes
import (
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/sshpiperd/upstream"
)
type plugin struct {
Config struct {
}
logger *log.Logger
}
// The name of the Plugin
func (p *plugin) GetName() string {
return "kubernetes"
}
// A ref to a struct which holds the options for the plugins
// will be populated by cmd or other plugin runners
func (p *plugin) GetOpts() interface{} {
return &p.Config
}
// Will be called before the Plugin is used to ensure the Plugin is ready
func (p *plugin) Init(logger *log.Logger) error {
p.logger = logger
return nil
}
func (p *plugin) GetHandler() upstream.Handler {
return p.findUpstream
}
func init() {
upstream.Register("kubernetes", &plugin{})
}

View file

@ -1,124 +0,0 @@
package upstream
import (
"fmt"
"net"
"strconv"
"golang.org/x/crypto/ssh"
"github.com/tg123/sshpiper/sshpiperd/registry"
"github.com/tg123/sshpiper/sshpiperd/v0bridge"
)
// Handler will be installed into sshpiper and help to establish the connection to upstream
// the returned auth pipe is to map/convert downstream auth method to another auth for
// connecting to upstream.
// e.g. map downstream public key to another upstream private key
type Handler func(conn ssh.ConnMetadata, challengeContext ssh.ChallengeContext) (net.Conn, *v0bridge.AuthPipe, error)
// CreatePipeOption contains options for creating a pipe to upstream
type CreatePipeOption struct {
Username string
UpstreamUsername string
Host string
Port int
}
// Pipe is a connection which linked downstream and upstream
// SSHPiper searches pipe base on username
type Pipe struct {
Username string
UpstreamUsername string
Host string
Port int
}
// PipeManager manages pipe inside upstream
type PipeManager interface {
// Return All pipes inside upstream
ListPipe() ([]Pipe, error)
// Create a pipe inside upstream
CreatePipe(opt CreatePipeOption) error
// Remove a pipe from upstream
RemovePipe(name string) error
}
// Provider is a factory for Upstream Provider
type Provider interface {
registry.Plugin
PipeManager
GetHandler() Handler
}
// temp
type V1Provider interface {
registry.Plugin
InstallUpstream(config *ssh.PiperConfig) error
}
var (
drivers = registry.NewRegistry()
)
// Register adds an upstream provider with given name to registry
func Register(name string, driver Provider) {
drivers.Register(name, driver)
}
// All return all registered upstream providers
func All() []string {
return drivers.Drivers()
}
// Get returns an upstream provider by name, return nil if not found
func Get(name string) Provider {
if d, ok := drivers.Get(name).(Provider); ok {
return d
}
return nil
}
// SplitHostPortForSSH is the modified version of net.SplitHostPort but return port 22 is no port is specified
func SplitHostPortForSSH(addr string) (host string, port int, err error) {
host = addr
h, p, err := net.SplitHostPort(host)
if err == nil {
host = h
port, err = strconv.Atoi(p)
if err != nil {
return
}
} else if host != "" {
// test valid after concat :22
if _, _, err = net.SplitHostPort(host + ":22"); err == nil {
port = 22
}
}
if host == "" {
err = fmt.Errorf("empty addr")
}
return
}
// DialForSSH is the modified version of net.Dial, would add ":22" automaticlly
func DialForSSH(addr string) (net.Conn, error) {
if _, _, err := net.SplitHostPort(addr); err != nil && addr != "" {
// test valid after concat :22
if _, _, err := net.SplitHostPort(addr + ":22"); err == nil {
addr += ":22"
}
}
return net.Dial("tcp", addr)
}

View file

@ -1,250 +0,0 @@
package yaml
import (
"bytes"
"fmt"
"io/ioutil"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"gopkg.in/yaml.v3"
)
// Return All pipes inside upstream
func (p *plugin) ListPipe() ([]upstream.Pipe, error) {
config, err := p.loadConfig()
if err != nil {
return nil, err
}
out, err := yaml.Marshal(config)
if err != nil {
return nil, err
}
fmt.Println(string(out))
var pipes []upstream.Pipe
for _, pipe := range config.Pipes {
host, port, err := upstream.SplitHostPortForSSH(pipe.UpstreamHost)
if err != nil {
return nil, err
}
mappeduser := pipe.Authmap.MappedUsername
if mappeduser == "" {
mappeduser = pipe.Username
}
pipes = append(pipes, upstream.Pipe{
Host: host,
Port: port,
UpstreamUsername: mappeduser,
Username: pipe.Username,
})
}
return pipes, nil
}
// func findnode(root *yaml.Node, test func(*yaml.Node) bool) *yaml.Node {
// var q []*yaml.Node
// q = append(q, root)
// for len(q) > 0 {
// e := q[0]
// q = q[1:]
// if test(e) {
// return e
// }
// for _, n := range e.Content {
// q = append(q, n)
// }
// }
// return nil
// }
func findByMapKey(m *yaml.Node, k string) (*yaml.Node, int) {
for i := 1; i < len(m.Content); i += 2 {
if m.Content[i-1].Value == k {
return m.Content[i], i
}
}
return nil, -1
}
func toYamlNode(s interface{}) (*yaml.Node, error) {
t := yaml.Node{}
out, err := yaml.Marshal(s)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(out, &t)
if err != nil {
return nil, err
}
return t.Content[0], nil
}
func (p *plugin) loadConfigRaw() ([]byte, *yaml.Node, error) {
var config yaml.Node
configbyte, err := ioutil.ReadFile(p.Config.File)
if err != nil {
return nil, nil, err
}
err = yaml.Unmarshal(configbyte, &config)
if err != nil {
return nil, nil, err
}
return configbyte, &config, nil
}
func (p *plugin) writeConfig(config []byte) error {
return ioutil.WriteFile(p.Config.File, config, 0600)
}
func toPipeConfig(opt upstream.CreatePipeOption) pipeConfig {
p := pipeConfig{
Username: opt.Username,
UpstreamHost: fmt.Sprintf("%v:%v", opt.Host, opt.Port),
}
if len(opt.UpstreamUsername) > 0 {
p.Authmap.MappedUsername = opt.UpstreamUsername
}
return p
}
// Create a pipe inside upstream
func (p *plugin) CreatePipe(opt upstream.CreatePipeOption) error {
configbyte, config, err := p.loadConfigRaw()
if err != nil {
return err
}
if len(config.Content) == 0 {
var buf bytes.Buffer
buf.Write(configbyte)
fmt.Fprintln(&buf)
out, err := yaml.Marshal(piperConfig{
Version: "0.1",
Pipes: []pipeConfig{toPipeConfig(opt)},
})
if err != nil {
return err
}
buf.Write(out)
return p.writeConfig(buf.Bytes())
}
pipes, idx := findByMapKey(config.Content[0], "pipes")
if idx > 0 && pipes.Tag == "!!null" {
// replace null
t, err := toYamlNode([]pipeConfig{toPipeConfig(opt)})
if err != nil {
return err
}
config.Content[0].Content[idx] = t
out, err := yaml.Marshal(config)
if err != nil {
return err
}
return p.writeConfig(out)
}
if pipes.Kind != yaml.SequenceNode {
return fmt.Errorf("pipes should be !!seq")
}
for _, pnode := range pipes.Content {
pipe, _ := findByMapKey(pnode, "username")
if pipe != nil && pipe.Value == opt.Username {
return fmt.Errorf("username [%v] already exists", opt.Username)
}
}
// append
{
t, err := toYamlNode(toPipeConfig(opt))
if err != nil {
return err
}
pipes.Content = append(pipes.Content, t)
out, err := yaml.Marshal(config)
if err != nil {
return err
}
return p.writeConfig(out)
}
}
// Remove a pipe from upstream
func (p *plugin) RemovePipe(name string) error {
_, config, err := p.loadConfigRaw()
if err != nil {
return err
}
if len(config.Content) == 0 {
return nil
}
pipes, idx := findByMapKey(config.Content[0], "pipes")
if idx > 0 && pipes.Tag == "!!null" {
return nil
}
if pipes.Kind != yaml.SequenceNode {
return fmt.Errorf("pipes should be !!seq")
}
for i, pnode := range pipes.Content {
pipe, _ := findByMapKey(pnode, "username")
if pipe != nil && pipe.Value == name {
rest := pipes.Content[i+1:]
pipes.Content = append(pipes.Content[:i], rest...)
out, err := yaml.Marshal(config)
if err != nil {
return err
}
return p.writeConfig(out)
}
}
return nil
}

View file

@ -1,41 +0,0 @@
package yaml
import (
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/sshpiperd/upstream"
)
type plugin struct {
Config struct {
File string `long:"upstream-yaml-file" default:"/var/sshpiper/sshpiperd.yaml" description:"Yaml config file path" env:"SSHPIPERD_UPSTREAM_YAML_FILE" ini-name:"upstream-yaml-file"`
NoCheckPerm bool `long:"upstream-yaml-nocheckperm" description:"Disable 0400 checking when using config file" env:"SSHPIPERD_UPSTREAM_YAML_NOCHECKPERM" ini-name:"upstream-yaml-nocheckperm"`
}
logger *log.Logger
}
// The name of the Plugin
func (p *plugin) GetName() string {
return "yaml"
}
// A ref to a struct which holds the options for the plugins
// will be populated by cmd or other plugin runners
func (p *plugin) GetOpts() interface{} {
return &p.Config
}
// Will be called before the Plugin is used to ensure the Plugin is ready
func (p *plugin) Init(logger *log.Logger) error {
p.logger = logger
return nil
}
func (p *plugin) GetHandler() upstream.Handler {
return p.findUpstream
}
func init() {
upstream.Register("yaml", &plugin{})
}

View file

@ -1,165 +0,0 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$ref": "#/definitions/sshpiperd",
"definitions": {
"sshpiperd": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string"
},
"pipes": {
"type": "array",
"items": {
"$ref": "#/definitions/pipe"
}
}
},
"required": [
"pipes",
"version"
]
},
"pipe": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string"
},
"upstream_host": {
"type": "string"
},
"ignore_hostkey": {
"type": "boolean"
},
"authmap": {
"$ref": "#/definitions/authmap"
},
"username_regex_match": {
"type": "boolean"
},
"known_hosts": {
"type": "string"
},
"known_hosts_data": {
"type": "string"
}
},
"required": [
"authmap",
"upstream_host"
]
},
"authmap": {
"type": "object",
"additionalProperties": false,
"properties": {
"mapped_username": {
"type": "string"
},
"from": {
"type": "array",
"items": {
"$ref": "#/definitions/from"
}
},
"to": {
"$ref": "#/definitions/to"
},
"no_passthrough": {
"type": "boolean"
}
}
},
"from": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"oneOf": [
{
"enum": [
"none",
"password",
"publickey",
"any"
]
}
]
},
"password": {
"type": "string"
},
"authorized_keys": {
"type": "string"
},
"authorized_keys_data": {
"type": "string"
},
"allow_any_public_key": {
"type": "boolean"
}
},
"required": [
"type"
]
},
"to": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"oneOf": [
{
"enum": [
"none",
"password",
"privatekey"
]
}
]
},
"password": {
"type": "string"
},
"private_key": {
"type": "string"
},
"private_key_data": {
"type": "string"
},
"key_map": {
"type": "array",
"items": {
"$ref": "#/definitions/keymap"
}
}
},
"required": [
"type"
]
},
"keymap": {
"type": "object",
"additionalProperties": false,
"properties": {
"authorized_keys": {
"type": "string"
},
"authorized_keys_data": {
"type": "string"
},
"private_key": {
"type": "string"
},
"private_key_data": {
"type": "string"
}
}
}
}
}

View file

@ -1,380 +0,0 @@
package yaml
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"github.com/tg123/sshpiper/sshpiperd/upstream"
"github.com/tg123/sshpiper/sshpiperd/v0bridge"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
"gopkg.in/yaml.v3"
)
type pipeConfig struct {
Username string `yaml:"username"`
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
UpstreamHost string `yaml:"upstream_host"`
Authmap struct {
MappedUsername string `yaml:"mapped_username,omitempty"`
From []struct {
Type string `yaml:"type"`
Password string `yaml:"password,omitempty"`
AuthorizedKeys string `yaml:"authorized_keys,omitempty"`
AuthorizedKeysData string `yaml:"authorized_keys_data,omitempty"`
AllowAnyPublicKey bool `yaml:"allow_any_public_key,omitempty"`
} `yaml:"from,flow"`
To struct {
Type string `yaml:"type"`
Password string `yaml:"password,omitempty"`
PrivateKey string `yaml:"private_key,omitempty"`
PrivateKeyData string `yaml:"private_key_data,omitempty"`
KeyMap []struct {
AuthorizedKeys string `yaml:"authorized_keys,omitempty"`
AuthorizedKeysData string `yaml:"authorized_keys_data,omitempty"`
PrivateKey string `yaml:"private_key,omitempty"`
PrivateKeyData string `yaml:"private_key_data,omitempty"`
} `yaml:"key_map,flow"`
} `yaml:"to,flow"`
NoPassthrough bool `yaml:"no_passthrough,omitempty"`
} `yaml:"authmap,omitempty,flow"`
KnownHosts string `yaml:"known_hosts,omitempty"`
KnownHostsData string `yaml:"known_hosts_data,omitempty"`
IgnoreHostkey bool `yaml:"ignore_hostkey,omitempty"`
}
type piperConfig struct {
Version string `yaml:"version"`
Pipes []pipeConfig `yaml:"pipes,flow"`
}
func (p *plugin) checkPerm() error {
filename := p.Config.File
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
if p.Config.NoCheckPerm {
return nil
}
if fi.Mode().Perm()&0077 != 0 {
return fmt.Errorf("%v's perm is too open", filename)
}
return nil
}
func (p *plugin) loadConfig() (piperConfig, error) {
var config piperConfig
err := p.checkPerm()
if err != nil {
return config, err
}
configbyte, err := ioutil.ReadFile(p.Config.File)
if err != nil {
return config, err
}
err = yaml.Unmarshal(configbyte, &config)
if err != nil {
return config, err
}
return config, nil
}
type createPipeCtx struct {
pipe pipeConfig
conn ssh.ConnMetadata
challengeContext ssh.ChallengeContext
}
func (p *plugin) loadFileOrDecode(file string, base64data string, ctx createPipeCtx) ([]byte, error) {
if file != "" {
file = os.Expand(file, func(placeholderName string) string {
switch placeholderName {
case "USER":
return ctx.conn.User()
case "MAPPED_USER":
return ctx.pipe.Authmap.MappedUsername
}
return os.Getenv(placeholderName)
})
if !filepath.IsAbs(file) {
file = filepath.Join(filepath.Dir(p.Config.File), file)
}
return ioutil.ReadFile(file)
}
if base64data != "" {
return base64.StdEncoding.DecodeString(base64data)
}
return nil, nil
}
func (p *plugin) createAuthPipe(pipe pipeConfig, conn ssh.ConnMetadata, challengeContext ssh.ChallengeContext) (*v0bridge.AuthPipe, error) {
ctx := createPipeCtx{pipe, conn, challengeContext}
hostKeyCallback := ssh.InsecureIgnoreHostKey()
if !pipe.IgnoreHostkey {
data, err := p.loadFileOrDecode(pipe.KnownHosts, pipe.KnownHostsData, ctx)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf("no known hosts spicified")
}
hostKeyCallback, err = knownhosts.NewFromReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
}
to := func(key ssh.PublicKey) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
switch pipe.Authmap.To.Type {
case "none":
return v0bridge.AuthPipeTypeNone, nil, nil
case "password":
return v0bridge.AuthPipeTypeMap, ssh.Password(pipe.Authmap.To.Password), nil
case "privatekey":
privateBytes, err := p.loadFileOrDecode(pipe.Authmap.To.PrivateKey, pipe.Authmap.To.PrivateKeyData, ctx)
if err != nil {
return v0bridge.AuthPipeTypeDiscard, nil, err
}
// did not find to 1 private key try key map
if len(privateBytes) == 0 && key != nil {
for _, privkey := range pipe.Authmap.To.KeyMap {
rest, err := p.loadFileOrDecode(privkey.AuthorizedKeys, privkey.AuthorizedKeysData, ctx)
if err != nil {
return v0bridge.AuthPipeTypeDiscard, nil, err
}
var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
if err != nil {
return v0bridge.AuthPipeTypeDiscard, nil, err
}
keydata := key.Marshal()
if bytes.Equal(authedPubkey.Marshal(), keydata) {
privateBytes, err = p.loadFileOrDecode(privkey.PrivateKey, privkey.PrivateKeyData, ctx)
if err != nil {
return v0bridge.AuthPipeTypeDiscard, nil, err
}
if len(privateBytes) > 0 {
// found mapped
break
}
}
}
}
}
if len(privateBytes) == 0 {
return v0bridge.AuthPipeTypeDiscard, nil, fmt.Errorf("no private key found")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return v0bridge.AuthPipeTypeDiscard, nil, err
}
return v0bridge.AuthPipeTypeMap, ssh.PublicKeys(private), nil
default:
p.logger.Printf("unsupport type [%v] fallback to passthrough", pipe.Authmap.To.Type)
}
if pipe.Authmap.NoPassthrough {
return v0bridge.AuthPipeTypeDiscard, nil, nil
}
return v0bridge.AuthPipeTypePassThrough, nil, nil
}
allowPasswords := make(map[string]bool)
var allowPubKeys []ssh.PublicKey
allowAnyPubKey := false
a := &v0bridge.AuthPipe{
User: pipe.Authmap.MappedUsername,
UpstreamHostKeyCallback: hostKeyCallback,
}
for _, from := range pipe.Authmap.From {
switch from.Type {
case "none":
if a.NoneAuthCallback == nil {
a.NoneAuthCallback = func(conn ssh.ConnMetadata) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
return to(nil)
}
}
case "password":
allowPasswords[from.Password] = true
if a.PasswordCallback == nil {
a.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
_, ok := allowPasswords[string(password)]
if ok {
return to(nil)
}
if pipe.Authmap.NoPassthrough {
return v0bridge.AuthPipeTypeDiscard, nil, nil
}
return v0bridge.AuthPipeTypePassThrough, nil, nil
}
}
case "publickey":
allowAnyPubKey = allowAnyPubKey || from.AllowAnyPublicKey
if !allowAnyPubKey {
rest, err := p.loadFileOrDecode(from.AuthorizedKeys, from.AuthorizedKeysData, ctx)
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
}
allowPubKeys = append(allowPubKeys, authedPubkey)
}
}
if a.PublicKeyCallback == nil {
a.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
if allowAnyPubKey {
return to(key)
}
keydata := key.Marshal()
for _, authedPubkey := range allowPubKeys {
if bytes.Equal(authedPubkey.Marshal(), keydata) {
return to(key)
}
}
if pipe.Authmap.NoPassthrough {
return v0bridge.AuthPipeTypeDiscard, nil, nil
}
// will fail but discard will lead a timeout
return v0bridge.AuthPipeTypePassThrough, nil, nil
}
}
case "any":
a.NoneAuthCallback = func(conn ssh.ConnMetadata) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
return to(nil)
}
a.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
return to(nil)
}
a.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (v0bridge.AuthPipeType, ssh.AuthMethod, error) {
return to(key)
}
return a, nil
default:
p.logger.Printf("unsupport type [%v], ignore section", from.Type)
}
}
return a, nil
}
func (p *plugin) findUpstream(conn ssh.ConnMetadata, challengeContext ssh.ChallengeContext) (net.Conn, *v0bridge.AuthPipe, error) {
user := conn.User()
config, err := p.loadConfig()
if err != nil {
return nil, nil, err
}
for _, pipe := range config.Pipes {
matched := pipe.Username == user
if pipe.UsernameRegexMatch {
matched, _ = regexp.MatchString(pipe.Username, user)
}
if matched {
p.logger.Printf("mapping [%v] to [%v@%v]", user, pipe.Authmap.MappedUsername, pipe.UpstreamHost)
c, err := upstream.DialForSSH(pipe.UpstreamHost)
if err != nil {
return nil, nil, err
}
a, err := p.createAuthPipe(pipe, conn, challengeContext)
if err != nil {
return nil, nil, err
}
return c, a, nil
}
}
return nil, nil, fmt.Errorf("username [%v] not found", user)
}

View file

@ -1,17 +0,0 @@
package utils
import (
"fmt"
"net"
"strings"
)
func FormatIPAddress(host string) string {
ip4 := net.ParseIP(host).To4()
if ip4 != nil || !strings.Contains(host, ":") {
return host
}
return fmt.Sprintf("[%s]", host)
}

View file

@ -1,200 +0,0 @@
package v0bridge
import (
"fmt"
"net"
"golang.org/x/crypto/ssh"
)
type AuthPipeType int
const (
// AuthPipeTypePassThrough does nothing but pass auth message to upstream
AuthPipeTypePassThrough AuthPipeType = iota
// AuthPipeTypeMap converts auth message to AuthMetod return by callback and pass it to upstream
AuthPipeTypeMap
// AuthPipeTypeDiscard discards auth message, do not pass it to uptream
AuthPipeTypeDiscard
// AuthPipeTypeNone converts auth message to NoneAuth and pass it to upstream
AuthPipeTypeNone
)
// AuthPipe contains the callbacks of auth msg mapping from downstream to upstream
//
// when AuthPipeType == AuthPipeTypeMap && AuthMethod == PublicKey
// SSHPiper will sign the auth packet message using the returned Signer.
// This func might be called twice, one is for query message, the other
// is real auth packet message.
// If any error occurs during this period, a NoneAuth packet will be sent to
// upstream ssh server instead.
// More info: https://github.com/tg123/sshpiper#publickey-sign-again
type AuthPipe struct {
// Username to upstream
User string
// NoneAuthCallback, if non-nil, is called when downstream requests a none auth,
// typically the first auth msg from client to see what auth methods can be used..
NoneAuthCallback func(conn ssh.ConnMetadata) (AuthPipeType, ssh.AuthMethod, error)
// PublicKeyCallback, if non-nil, is called when downstream requests a password auth.
PasswordCallback func(conn ssh.ConnMetadata, password []byte) (AuthPipeType, ssh.AuthMethod, error)
// PublicKeyCallback, if non-nil, is called when downstream requests a publickey auth.
PublicKeyCallback func(conn ssh.ConnMetadata, key ssh.PublicKey) (AuthPipeType, ssh.AuthMethod, error)
// UpstreamHostKeyCallback is called during the cryptographic
// handshake to validate the uptream server's host key. The piper
// configuration must supply this callback for the connection
// to succeed. The functions InsecureIgnoreHostKey or
// FixedHostKey can be used for simplistic host key checks.
UpstreamHostKeyCallback ssh.HostKeyCallback
}
type proxy struct {
handler func(conn ssh.ConnMetadata, challengeContext ssh.ChallengeContext) (net.Conn, *AuthPipe, error)
allowedMethod map[string]bool
}
// ChallengedUsername unused
func (p *proxy) ChallengedUsername() string {
return ""
}
// Meta unused
func (p *proxy) Meta() interface{} {
return nil
}
func (p *proxy) CreateChallengeContext(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
return p, nil
}
func CreateUpstream(conn net.Conn, pipe *AuthPipe, authType AuthPipeType, oldMethod, mappedMethod ssh.AuthMethod) (*ssh.Upstream, error) {
clientConfig := ssh.ClientConfig{
User: pipe.User,
HostKeyCallback: pipe.UpstreamHostKeyCallback,
}
switch authType {
case AuthPipeTypePassThrough:
clientConfig.Auth = []ssh.AuthMethod{oldMethod}
case AuthPipeTypeMap:
clientConfig.Auth = []ssh.AuthMethod{mappedMethod}
case AuthPipeTypeDiscard:
return nil, fmt.Errorf("msg is discarded")
case AuthPipeTypeNone:
clientConfig.Auth = []ssh.AuthMethod{ssh.NoneAuth()}
}
return &ssh.Upstream{
Conn: conn,
ClientConfig: clientConfig,
}, nil
}
func (p *proxy) NextAuthMethods(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) {
var allow []string
for k, v := range p.allowedMethod {
if v {
allow = append(allow, k)
}
}
return allow, nil
}
func (p *proxy) UpstreamAuthFailureCallback(onn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) {
noMoreMethodErr, ok := err.(ssh.NoMoreMethodsErr)
if ok {
for _, allowed := range noMoreMethodErr.Allowed {
p.allowedMethod[allowed] = true
}
return
}
p.allowedMethod[method] = false
}
func (p *proxy) NoneAuthCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
c, pipe, err := p.handler(conn, challengeCtx)
if err != nil {
return nil, err
}
if pipe.NoneAuthCallback == nil {
return CreateUpstream(c, pipe, AuthPipeTypePassThrough, ssh.NoneAuth(), nil)
}
t, m, err := pipe.NoneAuthCallback(conn)
if err != nil {
return nil, err
}
return CreateUpstream(c, pipe, t, ssh.NoneAuth(), m)
}
func (p *proxy) PasswordCallback(conn ssh.ConnMetadata, password []byte, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
c, pipe, err := p.handler(conn, challengeCtx)
if err != nil {
return nil, err
}
if pipe.PasswordCallback == nil {
return CreateUpstream(c, pipe, AuthPipeTypePassThrough, ssh.Password(string(password)), nil)
}
t, m, err := pipe.PasswordCallback(conn, password)
if err != nil {
return nil, err
}
return CreateUpstream(c, pipe, t, ssh.Password(string(password)), m)
}
func (p *proxy) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
c, pipe, err := p.handler(conn, challengeCtx)
if err != nil {
return nil, err
}
if pipe.PublicKeyCallback == nil {
return CreateUpstream(c, pipe, AuthPipeTypePassThrough, ssh.NoneAuth(), nil)
}
t, m, err := pipe.PublicKeyCallback(conn, key)
if err != nil {
return nil, err
}
return CreateUpstream(c, pipe, t, ssh.NoneAuth(), m) // cannt passthrough public key, use none instead
}
func InstallUpstream(config *ssh.PiperConfig, handler func(conn ssh.ConnMetadata, challengeContext ssh.ChallengeContext) (net.Conn, *AuthPipe, error)) error {
if handler == nil {
return fmt.Errorf("handler is nil")
}
p := &proxy{
handler: handler,
allowedMethod: map[string]bool{
"none": true,
},
}
config.CreateChallengeContext = p.CreateChallengeContext
config.NextAuthMethods = p.NextAuthMethods
config.UpstreamAuthFailureCallback = p.UpstreamAuthFailureCallback
config.NoneAuthCallback = p.NoneAuthCallback
config.PasswordCallback = p.PasswordCallback
config.PublicKeyCallback = p.PublicKeyCallback
return nil
}

View file

@ -1,46 +0,0 @@
package main
import (
"os"
"runtime/debug"
"text/template"
)
var version = "(devel)"
func showVersion() {
versionTemplate := template.Must(template.New("ver").Parse(`
sshpiperd by Boshi Lian<farmer1992@gmail.com>
https://github.com/tg123/sshpiper
Version : {{.VER}}
Go Runtime : {{.GOVER}}
Git Commit : {{.GITHASH}}
Timestamp : {{.TIMESTAMP}}
`[1:]))
data := struct {
VER string
GOVER string
GITHASH string
TIMESTAMP string
}{
VER: version,
}
bi, ok := debug.ReadBuildInfo()
if ok {
for _, s := range bi.Settings {
switch s.Key {
case "vcs.revision":
data.GITHASH = s.Value[:9]
case "vcs.time":
data.TIMESTAMP = s.Value
}
}
data.GOVER = bi.GoVersion
}
_ = versionTemplate.Execute(os.Stdout, data)
}