add yaml plugin
This commit is contained in:
parent
608a3e4081
commit
5bdf895dfc
5 changed files with 629 additions and 1 deletions
292
e2e/yaml_test.go
Normal file
292
e2e/yaml_test.go
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
package e2e_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const yamlConfigTemplate = `
|
||||||
|
version: "1.0"
|
||||||
|
pipes:
|
||||||
|
- from:
|
||||||
|
- username: "password_simple"
|
||||||
|
to:
|
||||||
|
host: host-password:2222
|
||||||
|
username: "user"
|
||||||
|
ignore_hostkey: true
|
||||||
|
- from:
|
||||||
|
- username: "password_.*_regex"
|
||||||
|
username_regex_match: true
|
||||||
|
to:
|
||||||
|
host: host-password:2222
|
||||||
|
username: "user"
|
||||||
|
ignore_hostkey: true
|
||||||
|
- from:
|
||||||
|
- username: "publickey_simple"
|
||||||
|
authorized_keys: {{ .AuthorizedKeys_Simple }}
|
||||||
|
to:
|
||||||
|
host: host-publickey:2222
|
||||||
|
username: "user"
|
||||||
|
private_key: {{ .PrivateKey }}
|
||||||
|
known_hosts_data: {{ .KnownHosts }}
|
||||||
|
- from:
|
||||||
|
- username: ".*"
|
||||||
|
username_regex_match: true
|
||||||
|
authorized_keys: {{ .AuthorizedKeys_Catchall }}
|
||||||
|
to:
|
||||||
|
host: host-publickey:2222
|
||||||
|
username: "user"
|
||||||
|
ignore_hostkey: true
|
||||||
|
private_key: {{ .PrivateKey }}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestYaml(t *testing.T) {
|
||||||
|
|
||||||
|
yamldir, err := os.MkdirTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
yamlfile, err := os.OpenFile(path.Join(yamldir, "config.yaml"), os.O_RDWR|os.O_CREATE, 0400)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// simple key
|
||||||
|
if err := runCmdAndWait("rm", "-f", path.Join(yamldir, "id_rsa_simple")); err != nil {
|
||||||
|
t.Errorf("failed to remove id_rsa: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-f",
|
||||||
|
path.Join(yamldir, "id_rsa_simple"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to generate private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// catch all key
|
||||||
|
if err := runCmdAndWait("rm", "-f", path.Join(yamldir, "id_rsa_catchall")); err != nil {
|
||||||
|
t.Errorf("failed to remove id_rsa: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-f",
|
||||||
|
path.Join(yamldir, "id_rsa_catchall"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to generate private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// upstream key
|
||||||
|
if err := runCmdAndWait("rm", "-f", path.Join(yamldir, "id_rsa")); err != nil {
|
||||||
|
t.Errorf("failed to remove id_rsa: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-f",
|
||||||
|
path.Join(yamldir, "id_rsa"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to generate private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"/bin/cp",
|
||||||
|
path.Join(yamldir, "id_rsa.pub"),
|
||||||
|
"/sshconfig_publickey/.ssh/authorized_keys",
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to copy public key: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
knownHostsData, err := runAndGetStdout(
|
||||||
|
"ssh-keyscan",
|
||||||
|
"-p",
|
||||||
|
"2222",
|
||||||
|
"host-publickey",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to run ssh-keyscan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := template.Must(template.New("yaml").Parse(yamlConfigTemplate)).ExecuteTemplate(yamlfile, "yaml", struct {
|
||||||
|
KnownHosts string
|
||||||
|
PrivateKey string
|
||||||
|
|
||||||
|
AuthorizedKeys_Simple string
|
||||||
|
AuthorizedKeys_Catchall string
|
||||||
|
}{
|
||||||
|
KnownHosts: base64.StdEncoding.EncodeToString(knownHostsData),
|
||||||
|
PrivateKey: path.Join(yamldir, "id_rsa"),
|
||||||
|
|
||||||
|
AuthorizedKeys_Simple: path.Join(yamldir, "id_rsa_simple.pub"),
|
||||||
|
AuthorizedKeys_Catchall: path.Join(yamldir, "id_rsa_catchall.pub"),
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("Failed to write yaml file %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump config.yaml to stdout
|
||||||
|
_ = runCmdAndWait("cat", "-n", path.Join(yamldir, "config.yaml"))
|
||||||
|
|
||||||
|
piperaddr, piperport := nextAvailablePiperAddress()
|
||||||
|
|
||||||
|
piper, _, _, err := runCmd("/sshpiperd/sshpiperd",
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"/sshpiperd/plugins/yaml",
|
||||||
|
"--config",
|
||||||
|
yamlfile.Name(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to run sshpiperd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(piper)
|
||||||
|
waitForEndpointReady(piperaddr)
|
||||||
|
|
||||||
|
t.Run("password_simple", func(t *testing.T) {
|
||||||
|
randtext := uuid.New().String()
|
||||||
|
targetfie := uuid.New().String()
|
||||||
|
|
||||||
|
c, stdin, stdout, err := runCmd(
|
||||||
|
"ssh",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"-l",
|
||||||
|
"password_simple",
|
||||||
|
"127.0.0.1",
|
||||||
|
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ssh to piper, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(c)
|
||||||
|
|
||||||
|
enterPassword(stdin, stdout, "pass")
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // wait for file flush
|
||||||
|
|
||||||
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("password_regex", func(t *testing.T) {
|
||||||
|
randtext := uuid.New().String()
|
||||||
|
targetfie := uuid.New().String()
|
||||||
|
|
||||||
|
c, stdin, stdout, err := runCmd(
|
||||||
|
"ssh",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"-l",
|
||||||
|
"password_XXX_regex",
|
||||||
|
"127.0.0.1",
|
||||||
|
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ssh to piper, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(c)
|
||||||
|
|
||||||
|
enterPassword(stdin, stdout, "pass")
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // wait for file flush
|
||||||
|
|
||||||
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("publickey_simple", func(t *testing.T) {
|
||||||
|
randtext := uuid.New().String()
|
||||||
|
targetfie := uuid.New().String()
|
||||||
|
|
||||||
|
c, _, _, err := runCmd(
|
||||||
|
"ssh",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"-l",
|
||||||
|
"publickey_simple",
|
||||||
|
"-i",
|
||||||
|
path.Join(yamldir, "id_rsa_simple"),
|
||||||
|
"127.0.0.1",
|
||||||
|
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ssh to piper, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(c)
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // wait for file flush
|
||||||
|
|
||||||
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("catch_all", func(t *testing.T) {
|
||||||
|
randtext := uuid.New().String()
|
||||||
|
targetfie := uuid.New().String()
|
||||||
|
|
||||||
|
c, _, _, err := runCmd(
|
||||||
|
"ssh",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"-l",
|
||||||
|
"anyusername",
|
||||||
|
"-i",
|
||||||
|
path.Join(yamldir, "id_rsa_catchall"),
|
||||||
|
"127.0.0.1",
|
||||||
|
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ssh to piper, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(c)
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // wait for file flush
|
||||||
|
|
||||||
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
3
go.mod
3
go.mod
|
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/microsoft/kiota-authentication-azure-go v0.3.1
|
github.com/microsoft/kiota-authentication-azure-go v0.3.1
|
||||||
github.com/microsoftgraph/msgraph-sdk-go v0.28.0
|
github.com/microsoftgraph/msgraph-sdk-go v0.28.0
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/tg123/remotesigner v0.0.0-20210928104451-7c20285909d1
|
github.com/tg123/remotesigner v0.0.0-20210928104451-7c20285909d1
|
||||||
|
|
@ -18,6 +19,7 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
google.golang.org/grpc v1.47.0
|
google.golang.org/grpc v1.47.0
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -44,5 +46,4 @@ require (
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211001223012-bfb93cce50d9 // indirect
|
google.golang.org/genproto v0.0.0-20211001223012-bfb93cce50d9 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -89,6 +89,8 @@ github.com/microsoftgraph/msgraph-sdk-go v0.28.0/go.mod h1:MB2iPD8vd8RTRAqlKTZfh
|
||||||
github.com/microsoftgraph/msgraph-sdk-go-core v0.26.1 h1:wzEoPk6uyd+SkBur9NpzpPfKseUQYT1yjFkDUueLpfk=
|
github.com/microsoftgraph/msgraph-sdk-go-core v0.26.1 h1:wzEoPk6uyd+SkBur9NpzpPfKseUQYT1yjFkDUueLpfk=
|
||||||
github.com/microsoftgraph/msgraph-sdk-go-core v0.26.1/go.mod h1:cKWHZ9CfJnSvBJxzXnAXRA+SSy8JeAbS+4kOn3uyOIY=
|
github.com/microsoftgraph/msgraph-sdk-go-core v0.26.1/go.mod h1:cKWHZ9CfJnSvBJxzXnAXRA+SSy8JeAbS+4kOn3uyOIY=
|
||||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
||||||
|
|
|
||||||
52
plugin/yaml/main.go
Normal file
52
plugin/yaml/main.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tg123/sshpiper/libplugin"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
plugin := newYamlPlugin()
|
||||||
|
|
||||||
|
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
|
||||||
|
Name: "yaml",
|
||||||
|
Usage: "sshpiperd yaml plugin",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Usage: "path to yaml config file",
|
||||||
|
Required: true,
|
||||||
|
EnvVars: []string{"SSHPIPERD_YAML_CONFIG"},
|
||||||
|
Destination: &plugin.File,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-check-perm",
|
||||||
|
Usage: "disable 0400 checking",
|
||||||
|
EnvVars: []string{"SSHPIPERD_YAML_NOCHECKPERM"},
|
||||||
|
Destination: &plugin.NoCheckPerm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
|
||||||
|
|
||||||
|
return &libplugin.SshPiperPluginConfig{
|
||||||
|
|
||||||
|
NextAuthMethodsCallback: func(_ libplugin.ConnMetadata) ([]string, error) {
|
||||||
|
return plugin.supportedMethods()
|
||||||
|
},
|
||||||
|
|
||||||
|
PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) {
|
||||||
|
return plugin.findAndCreateUpstream(conn, string(password), nil)
|
||||||
|
},
|
||||||
|
|
||||||
|
PublicKeyCallback: func(conn libplugin.ConnMetadata, key []byte) (*libplugin.Upstream, error) {
|
||||||
|
return plugin.findAndCreateUpstream(conn, "", key)
|
||||||
|
},
|
||||||
|
|
||||||
|
VerifyHostKeyCallback: func(conn libplugin.ConnMetadata, hostname, netaddr string, key []byte) error {
|
||||||
|
return plugin.verifyHostKey(conn, hostname, netaddr, key)
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
281
plugin/yaml/yaml.go
Normal file
281
plugin/yaml/yaml.go
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/tg123/sshpiper/libplugin"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/knownhosts"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pipeConfigFrom struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
|
||||||
|
AuthorizedKeys string `yaml:"authorized_keys,omitempty"`
|
||||||
|
AuthorizedKeysData string `yaml:"authorized_keys_data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeConfigTo struct {
|
||||||
|
Username string `yaml:"username,omitempty"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
PrivateKey string `yaml:"private_key,omitempty"`
|
||||||
|
PrivateKeyData string `yaml:"private_key_data,omitempty"`
|
||||||
|
KnownHosts string `yaml:"known_hosts,omitempty"`
|
||||||
|
KnownHostsData string `yaml:"known_hosts_data,omitempty"`
|
||||||
|
IgnoreHostkey bool `yaml:"ignore_hostkey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeConfig struct {
|
||||||
|
From []pipeConfigFrom `yaml:"from,flow"`
|
||||||
|
To pipeConfigTo `yaml:"to,flow"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type piperConfig struct {
|
||||||
|
Version string `yaml:"version"`
|
||||||
|
Pipes []pipeConfig `yaml:"pipes,flow"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type plugin struct {
|
||||||
|
File string
|
||||||
|
NoCheckPerm bool
|
||||||
|
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func newYamlPlugin() *plugin {
|
||||||
|
return &plugin{
|
||||||
|
cache: cache.New(1*time.Minute, 10*time.Minute),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) checkPerm() error {
|
||||||
|
filename := p.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.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.File)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(configbyte, &config)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) loadFileOrDecode(file string, base64data string, vars map[string]string) ([]byte, error) {
|
||||||
|
if file != "" {
|
||||||
|
|
||||||
|
file = os.Expand(file, func(placeholderName string) string {
|
||||||
|
v, ok := vars[placeholderName]
|
||||||
|
if ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Getenv(placeholderName)
|
||||||
|
})
|
||||||
|
|
||||||
|
if !filepath.IsAbs(file) {
|
||||||
|
file = filepath.Join(filepath.Dir(p.File), file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.ReadFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if base64data != "" {
|
||||||
|
return base64.StdEncoding.DecodeString(base64data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) supportedMethods() ([]string, error) {
|
||||||
|
config, err := p.loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
set := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, pipe := range config.Pipes {
|
||||||
|
for _, from := range pipe.From {
|
||||||
|
if from.AuthorizedKeys != "" || from.AuthorizedKeysData != "" {
|
||||||
|
set["publickey"] = true // found authorized_keys, so we support publickey
|
||||||
|
} else {
|
||||||
|
set["password"] = true // no authorized_keys, so we support password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var methods []string
|
||||||
|
for k := range set {
|
||||||
|
methods = append(methods, k)
|
||||||
|
}
|
||||||
|
return methods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) verifyHostKey(conn libplugin.ConnMetadata, hostname, netaddr string, key []byte) error {
|
||||||
|
item, found := p.cache.Get(conn.UniqueID())
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("connection expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
to := item.(*pipeConfigTo)
|
||||||
|
|
||||||
|
data, err := p.loadFileOrDecode(to.KnownHosts, to.KnownHostsData, map[string]string{
|
||||||
|
"DOWNSTREAM_USER": conn.User(),
|
||||||
|
"UPSTREAM_USER": to.Username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hostKeyCallback, err := knownhosts.NewFromReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := ssh.ParsePublicKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", netaddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostKeyCallback(hostname, addr, pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) createUpstream(conn libplugin.ConnMetadata, to pipeConfigTo, originPassword string) (*libplugin.Upstream, error) {
|
||||||
|
|
||||||
|
host, port, err := libplugin.SplitHostPortForSSH(to.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u := &libplugin.Upstream{
|
||||||
|
Host: host,
|
||||||
|
Port: int32(port),
|
||||||
|
UserName: to.Username,
|
||||||
|
IgnoreHostKey: to.IgnoreHostkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
pass := to.Password
|
||||||
|
if pass == "" {
|
||||||
|
pass = originPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
// password found
|
||||||
|
if pass != "" {
|
||||||
|
u.Auth = libplugin.CreatePasswordAuth([]byte(pass))
|
||||||
|
return u, p.cache.Add(conn.UniqueID(), &to, cache.DefaultExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try private key
|
||||||
|
data, err := p.loadFileOrDecode(to.PrivateKey, to.PrivateKeyData, map[string]string{
|
||||||
|
"DOWNSTREAM_USER": conn.User(),
|
||||||
|
"UPSTREAM_USER": to.Username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
u.Auth = libplugin.CreatePrivateKeyAuth(data)
|
||||||
|
return u, p.cache.Add(conn.UniqueID(), &to, cache.DefaultExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no password or private key found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password string, publicKey []byte) (*libplugin.Upstream, error) {
|
||||||
|
user := conn.User()
|
||||||
|
|
||||||
|
config, err := p.loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pipe := range config.Pipes {
|
||||||
|
for _, from := range pipe.From {
|
||||||
|
matched := from.Username == user
|
||||||
|
|
||||||
|
if from.UsernameRegexMatch {
|
||||||
|
matched, _ = regexp.MatchString(from.Username, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == nil {
|
||||||
|
return p.createUpstream(conn, pipe.To, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest, err := p.loadFileOrDecode(from.AuthorizedKeys, from.AuthorizedKeysData, map[string]string{
|
||||||
|
"DOWNSTREAM_USER": user,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var authedPubkey ssh.PublicKey
|
||||||
|
for len(rest) > 0 {
|
||||||
|
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(authedPubkey.Marshal(), publicKey) {
|
||||||
|
return p.createUpstream(conn, pipe.To, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no matching pipe for username [%v] found", user)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue