services configuration inside config file
This commit is contained in:
parent
64fa306f8d
commit
f169b7f137
@ -117,7 +117,7 @@ func runRestServer() {
|
|||||||
|
|
||||||
func runDockerEventListener(ctx context.Context) {
|
func runDockerEventListener(ctx context.Context) {
|
||||||
var err error
|
var err error
|
||||||
docker.Default, err = docker.New(ctx)
|
docker.Default, err = docker.New(ctx, config.Default.Services, config.Default.DefaultServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Sugar.Fatalf("cannot connect to docker daemon: %s", err)
|
logger.Sugar.Fatalf("cannot connect to docker daemon: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,31 @@ docker:
|
|||||||
version: ~
|
version: ~
|
||||||
# Default server to use if `sshpoke.server` is not specified in the target container labels.
|
# Default server to use if `sshpoke.server` is not specified in the target container labels.
|
||||||
default_server: mine
|
default_server: mine
|
||||||
|
# Services configuration. These values will have higher priority than the container labels.
|
||||||
|
services:
|
||||||
|
# Container ID, one of container names or matcher regex. This value will be used to determine which container will
|
||||||
|
# receive the labels from 'params' section. Add / at the start and the end of the value to use it as regex.
|
||||||
|
# Examples:
|
||||||
|
# - name: a77eaa474cc634f0e4da4d4b72ebf71c03d1ae69329a07e42b830375e247e613 # Match by container ID (exact matcher).
|
||||||
|
# - name: my-container # Match by container name (exact matcher).
|
||||||
|
# - name: /my-container/ # Match by container name via regular expression (e.g. 'my-container_1' will also match).
|
||||||
|
- name: /service-web/
|
||||||
|
# Same params as container labels but without 'sshpoke.' prefix.
|
||||||
|
params:
|
||||||
|
# Enable sshpoke for service. Replaces 'sshpoke.enable' in container labels.
|
||||||
|
enable: true
|
||||||
|
# Specifies container network. Replaces 'sshpoke.network' in container labels.
|
||||||
|
network: service_default
|
||||||
|
# Specifies server which will be used. Replaces 'sshpoke.server' in container labels.
|
||||||
|
server: ssh-demo-sish
|
||||||
|
# Specifies container port to be shared. Replaces 'sshpoke.port' in container labels.
|
||||||
|
port: 80
|
||||||
|
# Specifies remote host to be used. Replaces 'sshpoke.remote_host' in container labels.
|
||||||
|
remote_host: remotehost
|
||||||
# Servers configuration.
|
# Servers configuration.
|
||||||
servers:
|
servers:
|
||||||
# Server name.
|
# Server name.
|
||||||
- name: mine
|
- name: ssh-demo-sish
|
||||||
# Server driver. Each driver has its own set of params. Supported drivers: ssh, plugin, null.
|
# Server driver. Each driver has its own set of params. Supported drivers: ssh, plugin, null.
|
||||||
driver: ssh
|
driver: ssh
|
||||||
params:
|
params:
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
@ -15,9 +16,23 @@ type Config struct {
|
|||||||
API API `mapstructure:"api" json:"api"`
|
API API `mapstructure:"api" json:"api"`
|
||||||
Docker DockerConfig `mapstructure:"docker" json:"docker"`
|
Docker DockerConfig `mapstructure:"docker" json:"docker"`
|
||||||
DefaultServer string `mapstructure:"default_server" json:"default_server"`
|
DefaultServer string `mapstructure:"default_server" json:"default_server"`
|
||||||
|
Services []Service `mapstructure:"services" json:"services,omitempty"`
|
||||||
Servers []Server `mapstructure:"servers" json:"servers"`
|
Servers []Server `mapstructure:"servers" json:"servers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Name smarttypes.MatchableString `mapstructure:"name" json:"name"`
|
||||||
|
Params ServiceLabels `mapstructure:"params" json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceLabels struct {
|
||||||
|
Enable smarttypes.BoolStr `mapstructure:"enable" json:"enable"`
|
||||||
|
Network string `mapstructure:"network" json:"network,omitempty"`
|
||||||
|
Server string `mapstructure:"server" json:"server,omitempty"`
|
||||||
|
Port string `mapstructure:"port" json:"port,omitempty"`
|
||||||
|
RemoteHost string `mapstructure:"remote_host" json:"remote_host,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
Rest WebAPI `mapstructure:"rest" json:"rest"`
|
Rest WebAPI `mapstructure:"rest" json:"rest"`
|
||||||
Plugin PluginAPI `mapstructure:"plugin" json:"plugin"`
|
Plugin PluginAPI `mapstructure:"plugin" json:"plugin"`
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/Neur0toxine/sshpoke/internal/config"
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/logger"
|
"github.com/Neur0toxine/sshpoke/internal/logger"
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
@ -18,19 +19,41 @@ var Default *Docker
|
|||||||
type Docker struct {
|
type Docker struct {
|
||||||
cli *client.Client
|
cli *client.Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
services map[smarttypes.MatchableString]config.ServiceLabels
|
||||||
|
defaultServer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context) (*Docker, error) {
|
func New(ctx context.Context, services []config.Service, defaultServer string) (*Docker, error) {
|
||||||
cli, err := client.NewClientWithOpts(config.Default.Docker.Opts)
|
cli, err := client.NewClientWithOpts(config.Default.Docker.Opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
servicesMap := make(map[smarttypes.MatchableString]config.ServiceLabels)
|
||||||
|
for _, svc := range services {
|
||||||
|
servicesMap[svc.Name] = svc.Params
|
||||||
|
}
|
||||||
return &Docker{
|
return &Docker{
|
||||||
cli: cli,
|
cli: cli,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
services: servicesMap,
|
||||||
|
defaultServer: defaultServer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Docker) findServiceLabels(id string, names []string) *config.ServiceLabels {
|
||||||
|
if labels, ok := d.services[smarttypes.MatchableString(id)]; ok {
|
||||||
|
return &labels
|
||||||
|
}
|
||||||
|
for matcher, labels := range d.services {
|
||||||
|
for _, name := range names {
|
||||||
|
if matcher.Match(name) {
|
||||||
|
return &labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Docker) Containers() map[string]dto.Container {
|
func (d *Docker) Containers() map[string]dto.Container {
|
||||||
items, err := d.cli.ContainerList(d.ctx, types.ContainerListOptions{
|
items, err := d.cli.ContainerList(d.ctx, types.ContainerListOptions{
|
||||||
Filters: filters.NewArgs(filters.Arg("status", "running")),
|
Filters: filters.NewArgs(filters.Arg("status", "running")),
|
||||||
@ -41,7 +64,7 @@ func (d *Docker) Containers() map[string]dto.Container {
|
|||||||
}
|
}
|
||||||
containers := map[string]dto.Container{}
|
containers := map[string]dto.Container{}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
container, ok := dockerContainerToInternal(item)
|
container, ok := dockerContainerToInternal(item, d.findServiceLabels(item.ID, item.Names), d.defaultServer)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -58,7 +81,8 @@ func (d *Docker) GetContainer(id string, all bool) (dto.Container, bool) {
|
|||||||
if err != nil || len(container) != 1 {
|
if err != nil || len(container) != 1 {
|
||||||
return dto.Container{}, false
|
return dto.Container{}, false
|
||||||
}
|
}
|
||||||
converted, ok := dockerContainerToInternal(container[0])
|
converted, ok := dockerContainerToInternal(
|
||||||
|
container[0], d.findServiceLabels(container[0].ID, container[0].Names), d.defaultServer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return dto.Container{}, false
|
return dto.Container{}, false
|
||||||
}
|
}
|
||||||
@ -92,7 +116,8 @@ func (d *Docker) Listen() (chan dto.Event, error) {
|
|||||||
"id", event.Actor.ID, "err", err)
|
"id", event.Actor.ID, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
converted, ok := dockerContainerToInternal(container[0])
|
converted, ok := dockerContainerToInternal(container[0],
|
||||||
|
d.findServiceLabels(container[0].ID, container[0].Names), d.defaultServer)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/logger"
|
"github.com/Neur0toxine/sshpoke/internal/logger"
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
@ -14,41 +16,60 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type labelsConfig struct {
|
type labelsConfig struct {
|
||||||
Enable boolStr `mapstructure:"sshpoke.enable"`
|
Enable smarttypes.BoolStr `mapstructure:"sshpoke.enable"`
|
||||||
Network string `mapstructure:"sshpoke.network"`
|
Network string `mapstructure:"sshpoke.network"`
|
||||||
Server string `mapstructure:"sshpoke.server"`
|
Server string `mapstructure:"sshpoke.server"`
|
||||||
Port string `mapstructure:"sshpoke.port"`
|
Port string `mapstructure:"sshpoke.port"`
|
||||||
RemoteHost string `mapstructure:"sshpoke.remote_host"`
|
RemoteHost string `mapstructure:"sshpoke.remote_host"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type boolStr string
|
func actorEnabled(actor events.Actor) smarttypes.BoolStr {
|
||||||
|
|
||||||
func (b boolStr) Bool() bool {
|
|
||||||
return string(b) == "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
func actorEnabled(actor events.Actor) bool {
|
|
||||||
label, ok := actor.Attributes["sshpoke.enable"]
|
label, ok := actor.Attributes["sshpoke.enable"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return boolStr(label).Bool()
|
return smarttypes.BoolFromStr(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dockerContainerToInternal(container types.Container) (result dto.Container, ok bool) {
|
func populateLabelsFromConfig(labels *labelsConfig, config *config.ServiceLabels) {
|
||||||
|
if labels.Enable != config.Enable {
|
||||||
|
labels.Enable = config.Enable
|
||||||
|
}
|
||||||
|
if labels.Server != config.Server {
|
||||||
|
labels.Server = config.Server
|
||||||
|
}
|
||||||
|
if labels.Network != config.Network {
|
||||||
|
labels.Network = config.Network
|
||||||
|
}
|
||||||
|
if labels.Port != config.Port {
|
||||||
|
labels.Port = config.Port
|
||||||
|
}
|
||||||
|
if labels.RemoteHost != config.RemoteHost {
|
||||||
|
labels.RemoteHost = config.RemoteHost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dockerContainerToInternal(
|
||||||
|
container types.Container, configLabels *config.ServiceLabels, defaultServer string) (result dto.Container, ok bool) {
|
||||||
var labels labelsConfig
|
var labels labelsConfig
|
||||||
if err := mapstructure.Decode(container.Labels, &labels); err != nil {
|
if err := mapstructure.Decode(container.Labels, &labels); err != nil {
|
||||||
logger.Sugar.Debugf("skipping container %s because configuration is invalid: %s", container.ID, err)
|
logger.Sugar.Debugf("skipping container %s because configuration is invalid: %s", container.ID, err)
|
||||||
return result, false
|
return result, false
|
||||||
}
|
}
|
||||||
if !labels.Enable.Bool() {
|
if configLabels != nil {
|
||||||
|
populateLabelsFromConfig(&labels, configLabels)
|
||||||
|
}
|
||||||
|
if !labels.Enable {
|
||||||
logger.Sugar.Debugf("skipping container %s because sshpoke is not enabled for it", container.ID)
|
logger.Sugar.Debugf("skipping container %s because sshpoke is not enabled for it", container.ID)
|
||||||
return result, false
|
return result, false
|
||||||
}
|
}
|
||||||
if labels.Server == "" {
|
if labels.Server == "" {
|
||||||
|
if defaultServer == "" {
|
||||||
logger.Sugar.Debugf("skipping container %s because 'sshpoke.server' is not defined", container.ID)
|
logger.Sugar.Debugf("skipping container %s because 'sshpoke.server' is not defined", container.ID)
|
||||||
return result, false
|
return result, false
|
||||||
}
|
}
|
||||||
|
labels.Server = defaultServer
|
||||||
|
}
|
||||||
if container.NetworkSettings == nil ||
|
if container.NetworkSettings == nil ||
|
||||||
container.NetworkSettings.Networks == nil ||
|
container.NetworkSettings.Networks == nil ||
|
||||||
len(container.NetworkSettings.Networks) == 0 {
|
len(container.NetworkSettings.Networks) == 0 {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh/knownhosts"
|
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh/knownhosts"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -145,8 +146,8 @@ func (d *SSH) buildHostKeyCallback() ssh.HostKeyCallback {
|
|||||||
return sshtun.FixedHostKeys(d.hostKeys)
|
return sshtun.FixedHostKeys(d.hostKeys)
|
||||||
}()
|
}()
|
||||||
if d.params.Auth.Type == types.AuthTypeKey && d.params.Auth.Directory != "" && len(d.hostKeys) == 0 {
|
if d.params.Auth.Type == types.AuthTypeKey && d.params.Auth.Directory != "" && len(d.hostKeys) == 0 {
|
||||||
knownHostsPath := types.SmartPath(path.Join(string(d.params.Auth.Directory), KnownHostsFile))
|
knownHostsPath := smarttypes.Path(path.Join(string(d.params.Auth.Directory), KnownHostsFile))
|
||||||
resolvedPath, err := knownHostsPath.Resolve(false)
|
resolvedPath, err := knownHostsPath.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ssh.InsecureIgnoreHostKey()
|
return ssh.InsecureIgnoreHostKey()
|
||||||
}
|
}
|
||||||
@ -237,7 +238,7 @@ func (d *SSH) populateFromSSHConfig() {
|
|||||||
if d.params.Auth.Directory == "" {
|
if d.params.Auth.Directory == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg, err := parseSSHConfig(types.SmartPath(path.Join(string(d.params.Auth.Directory), "config")))
|
cfg, err := parseSSHConfig(smarttypes.Path(path.Join(string(d.params.Auth.Directory), "config")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -255,7 +256,7 @@ func (d *SSH) populateFromSSHConfig() {
|
|||||||
d.params.Auth.User = user
|
d.params.Auth.User = user
|
||||||
}
|
}
|
||||||
if keyfile, err := hostCfg.Get("IdentityFile"); err == nil && keyfile != "" {
|
if keyfile, err := hostCfg.Get("IdentityFile"); err == nil && keyfile != "" {
|
||||||
resolvedKeyFile, err := types.SmartPath(keyfile).Resolve(false)
|
resolvedKeyFile, err := smarttypes.Path(keyfile).File()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d.params.Auth.Type = types.AuthTypeKey
|
d.params.Auth.Type = types.AuthTypeKey
|
||||||
d.params.Auth.Keyfile = resolvedKeyFile
|
d.params.Auth.Keyfile = resolvedKeyFile
|
||||||
@ -354,7 +355,7 @@ func (d *SSH) authenticator() ssh.AuthMethod {
|
|||||||
case types.AuthTypeKey:
|
case types.AuthTypeKey:
|
||||||
if d.params.Auth.Keyfile != "" {
|
if d.params.Auth.Keyfile != "" {
|
||||||
keyAuth, err := sshtun.AuthKeyFile(
|
keyAuth, err := sshtun.AuthKeyFile(
|
||||||
types.SmartPath(path.Join(d.params.Auth.Directory.String(), d.params.Auth.Keyfile)))
|
smarttypes.Path(path.Join(d.params.Auth.Directory.String(), d.params.Auth.Keyfile)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh/types"
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"github.com/kevinburke/ssh_config"
|
"github.com/kevinburke/ssh_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,8 +19,8 @@ func (c *hostConfig) Get(key string) (string, error) {
|
|||||||
return strings.TrimSpace(val), err
|
return strings.TrimSpace(val), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSSHConfig(filePath types.SmartPath) (*ssh_config.Config, error) {
|
func parseSSHConfig(filePath smarttypes.Path) (*ssh_config.Config, error) {
|
||||||
fileName, err := filePath.Resolve(false)
|
fileName, err := filePath.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh/types"
|
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AuthKeyFile(keyFile types.SmartPath) (ssh.AuthMethod, error) {
|
func AuthKeyFile(keyFile smarttypes.Path) (ssh.AuthMethod, error) {
|
||||||
key, err := readKey(keyFile)
|
key, err := readKey(keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -19,7 +19,7 @@ func AuthKeyFile(keyFile types.SmartPath) (ssh.AuthMethod, error) {
|
|||||||
return ssh.PublicKeys(key), nil
|
return ssh.PublicKeys(key), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthKeyDir(keyDir types.SmartPath) (ssh.AuthMethod, error) {
|
func AuthKeyDir(keyDir smarttypes.Path) (ssh.AuthMethod, error) {
|
||||||
keys, err := readKeys(keyDir)
|
keys, err := readKeys(keyDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -31,8 +31,8 @@ func AuthPassword(password string) ssh.AuthMethod {
|
|||||||
return ssh.Password(password)
|
return ssh.Password(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readKeys(keyDir types.SmartPath) ([]ssh.Signer, error) {
|
func readKeys(keyDir smarttypes.Path) ([]ssh.Signer, error) {
|
||||||
dir, err := keyDir.Resolve(true)
|
dir, err := keyDir.Directory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse keys: %s", err)
|
return nil, fmt.Errorf("cannot parse keys: %s", err)
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ func readKeys(keyDir types.SmartPath) ([]ssh.Signer, error) {
|
|||||||
if info.Size() < 256 {
|
if info.Size() < 256 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key, err := readKey(types.SmartPath(path.Join(dir, entry.Name())))
|
key, err := readKey(smarttypes.Path(path.Join(dir, entry.Name())))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -74,8 +74,8 @@ func readKeys(keyDir types.SmartPath) ([]ssh.Signer, error) {
|
|||||||
return keys, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readKey(keyFile types.SmartPath) (ssh.Signer, error) {
|
func readKey(keyFile smarttypes.Path) (ssh.Signer, error) {
|
||||||
fileName, err := keyFile.Resolve(false)
|
fileName, err := keyFile.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh/types"
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"github.com/kevinburke/ssh_config"
|
"github.com/kevinburke/ssh_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSSHConfig(filePath types.SmartPath) (*ssh_config.Config, error) {
|
func parseSSHConfig(filePath smarttypes.Path) (*ssh_config.Config, error) {
|
||||||
fileName, err := filePath.Resolve(false)
|
fileName, err := filePath.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path"
|
"github.com/Neur0toxine/sshpoke/pkg/smarttypes"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var envMatcherRegExp = regexp.MustCompile(`\$[\w\d\_]`)
|
type AuthType string
|
||||||
|
|
||||||
type (
|
|
||||||
AuthType string
|
|
||||||
SmartPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthTypePasswordless AuthType = "passwordless"
|
AuthTypePasswordless AuthType = "passwordless"
|
||||||
@ -27,45 +18,10 @@ type Auth struct {
|
|||||||
Type AuthType `mapstructure:"type" validate:"required,oneof=passwordless password key"`
|
Type AuthType `mapstructure:"type" validate:"required,oneof=passwordless password key"`
|
||||||
User string `mapstructure:"user"`
|
User string `mapstructure:"user"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
Directory SmartPath `mapstructure:"directory"`
|
Directory smarttypes.Path `mapstructure:"directory"`
|
||||||
Keyfile string `mapstructure:"keyfile"`
|
Keyfile string `mapstructure:"keyfile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k SmartPath) Resolve(shouldBeDirectory bool) (result string, err error) {
|
|
||||||
result = strings.TrimSpace(string(k))
|
|
||||||
if strings.HasPrefix(result, "~/") {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
result = path.Join(homeDir, result[2:])
|
|
||||||
}
|
|
||||||
for _, match := range envMatcherRegExp.FindAllString(string(k), -1) {
|
|
||||||
envVar := match[1:]
|
|
||||||
if envVar == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
envVar = os.Getenv(envVar)
|
|
||||||
result = strings.ReplaceAll(result, match, envVar)
|
|
||||||
}
|
|
||||||
result, err = filepath.Abs(result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !stat.IsDir() && shouldBeDirectory {
|
|
||||||
err = errors.New("is not a directory")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k SmartPath) String() string {
|
|
||||||
return string(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Auth) Validate() error {
|
func (a Auth) Validate() error {
|
||||||
if a.Type == AuthTypePassword && a.Password == "" {
|
if a.Type == AuthTypePassword && a.Password == "" {
|
||||||
return fmt.Errorf("password must be provided for authentication type '%s'", AuthTypePassword)
|
return fmt.Errorf("password must be provided for authentication type '%s'", AuthTypePassword)
|
||||||
|
19
pkg/smarttypes/bool_str.go
Normal file
19
pkg/smarttypes/bool_str.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package smarttypes
|
||||||
|
|
||||||
|
type BoolStr bool
|
||||||
|
|
||||||
|
func BoolFromStr(str string) BoolStr {
|
||||||
|
return str == "true" || str == "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BoolStr) MarshalText() ([]byte, error) {
|
||||||
|
if b {
|
||||||
|
return []byte("true"), nil
|
||||||
|
}
|
||||||
|
return []byte("false"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolStr) UnmarshalText(src []byte) error {
|
||||||
|
*b = BoolFromStr(string(src))
|
||||||
|
return nil
|
||||||
|
}
|
57
pkg/smarttypes/file_path.go
Normal file
57
pkg/smarttypes/file_path.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package smarttypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envMatcherRegExp = regexp.MustCompile(`\$[\w\d\_]`)
|
||||||
|
|
||||||
|
type Path string
|
||||||
|
|
||||||
|
func (k Path) File() (string, error) {
|
||||||
|
return k.resolve(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Path) Directory() (string, error) {
|
||||||
|
return k.resolve(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Path) resolve(shouldBeDirectory bool) (result string, err error) {
|
||||||
|
result = strings.TrimSpace(string(k))
|
||||||
|
if strings.HasPrefix(result, "~/") {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result = path.Join(homeDir, result[2:])
|
||||||
|
}
|
||||||
|
for _, match := range envMatcherRegExp.FindAllString(string(k), -1) {
|
||||||
|
envVar := match[1:]
|
||||||
|
if envVar == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
envVar = os.Getenv(envVar)
|
||||||
|
result = strings.ReplaceAll(result, match, envVar)
|
||||||
|
}
|
||||||
|
result, err = filepath.Abs(result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !stat.IsDir() && shouldBeDirectory {
|
||||||
|
err = errors.New("is not a directory")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Path) String() string {
|
||||||
|
return string(k)
|
||||||
|
}
|
47
pkg/smarttypes/matchable_string.go
Normal file
47
pkg/smarttypes/matchable_string.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package smarttypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MatchableString string
|
||||||
|
|
||||||
|
func (m MatchableString) isExpr() bool {
|
||||||
|
if len(m) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m[0] == '/' && m[len(m)-1] == '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatchableString) expr() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(string(m[1 : len(m)-2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatchableString) Match(val string) bool {
|
||||||
|
if m.isExpr() {
|
||||||
|
return m.expr().Match([]byte(val))
|
||||||
|
}
|
||||||
|
return val == string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatchableString) String() string {
|
||||||
|
return string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatchableString) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatchableString) UnmarshalText(src []byte) error {
|
||||||
|
if len(src) > 2 && src[0] == '/' && src[len(src)-1] == '/' {
|
||||||
|
_, err := regexp.Compile(string(src[1 : len(src)-2]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*m = MatchableString(src)
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user