remove old dir
This commit is contained in:
parent
19f851fc74
commit
17f558ae93
55 changed files with 0 additions and 5838 deletions
275
sshpiperd/cmd.go
275
sshpiperd/cmd.go
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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)
|
||||
}()
|
||||
}
|
||||
|
|
@ -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:
|
||||
37
sshpiperd/snap/snap/hooks/configure
vendored
37
sshpiperd/snap/snap/hooks/configure
vendored
|
|
@ -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
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
bin=$SNAP/sshpiperd
|
||||
key=$SNAP_COMMON/host_rsa_key
|
||||
|
||||
$bin genkey > $key
|
||||
|
|
@ -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]
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
You may want to use `sshpiperd pipe add ` to manage them.
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)"`
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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{})
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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{})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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{})
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue