web api, orphaned connections detector, null driver -> nil driver, refactor root command

This commit is contained in:
Pavel 2023-11-22 22:21:11 +03:00
parent b89011c631
commit 33290aa5c7
19 changed files with 480 additions and 106 deletions

View File

@ -6,12 +6,14 @@ import (
"os/signal"
"syscall"
"github.com/Neur0toxine/sshpoke/internal/api"
"github.com/Neur0toxine/sshpoke/internal/api/plugin"
"github.com/Neur0toxine/sshpoke/internal/api/web"
"github.com/Neur0toxine/sshpoke/internal/config"
"github.com/Neur0toxine/sshpoke/internal/docker"
"github.com/Neur0toxine/sshpoke/internal/logger"
"github.com/Neur0toxine/sshpoke/internal/server"
"github.com/Neur0toxine/sshpoke/pkg/dto"
plugin2 "github.com/Neur0toxine/sshpoke/pkg/plugin"
"github.com/go-playground/validator/v10"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -28,53 +30,23 @@ var rootCmd = &cobra.Command{
Short: "Expose your Docker services to the Internet via SSH.",
Long: `sshpoke is a CLI application that listens to the docker socket and automatically exposes relevant services to the Internet.`,
Run: func(cmd *cobra.Command, args []string) {
go api.StartPluginAPI()
var err error
ctx, cancel := context.WithCancel(context.Background())
server.DefaultManager = server.NewManager(ctx, config.Default.Servers, config.Default.DefaultServer)
docker.Default, err = docker.New(ctx)
if err != nil {
logger.Sugar.Fatalf("cannot connect to docker daemon: %s", err)
}
for id, item := range docker.Default.Containers() {
err := server.DefaultManager.ProcessEvent(dto.Event{
Type: dto.EventStart,
Container: item,
})
if err != nil {
logger.Sugar.Errorw("cannot expose container", "id", id, "error", err)
}
}
events, err := docker.Default.Listen()
if err != nil {
logger.Sugar.Fatalf("cannot listen to docker events: %s", err)
}
go func() {
logger.Sugar.Debug("listening for docker events...")
for event := range events {
err := server.DefaultManager.ProcessEvent(event)
if err != nil {
logger.Sugar.Errorw("cannot expose container",
"id", event.Container.ID, "error", err)
}
}
}()
runPluginServer()
runWebServer()
runDockerEventListener(ctx)
shutdown := makeShutdownFunc(cancel)
linuxSig := make(chan os.Signal, 1)
signal.Notify(linuxSig)
for sig := range linuxSig {
switch sig {
case os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM:
cancel()
server.DefaultManager.WaitForShutdown()
logger.Sugar.Infof("received %s, exiting...", sig)
os.Exit(0)
shutdown(sig)
default:
}
}
shutdown(syscall.SIGHUP)
},
}
@ -116,6 +88,72 @@ func initConfig() {
if err := validator.New().Struct(config.Default); err != nil {
log.Fatalf("invalid configuration: %s", err)
}
if config.Default.API.Web.Port == 0 {
config.Default.API.Web.Port = web.DefaultPort
}
if config.Default.API.Plugin.Port == 0 {
config.Default.API.Plugin.Port = plugin2.DefaultPort
}
logger.Initialize()
logger.Sugar.Debugw("configuration loaded", "config", config.Default)
}
func runPluginServer() {
port := config.Default.API.Plugin.Port
if port == 0 {
port = plugin2.DefaultPort
}
go plugin.StartServer(port, logger.Sugar.With("component", "pluginServer"))
}
func runWebServer() {
port := config.Default.API.Web.Port
if port == 0 {
port = web.DefaultPort
}
go web.StartServer(
config.Default.API.Web.Token, port, logger.Sugar.With("component", "webServer"), config.Default.Debug)
}
func runDockerEventListener(ctx context.Context) {
var err error
docker.Default, err = docker.New(ctx)
if err != nil {
logger.Sugar.Fatalf("cannot connect to docker daemon: %s", err)
}
for id, item := range docker.Default.Containers() {
err := server.DefaultManager.ProcessEvent(dto.Event{
Type: dto.EventStart,
Container: item,
})
if err != nil {
logger.Sugar.Errorw("cannot expose container", "id", id, "error", err)
}
}
events, err := docker.Default.Listen()
if err != nil {
logger.Sugar.Fatalf("cannot listen to docker events: %s", err)
}
go func() {
logger.Sugar.Debug("listening for docker events...")
for event := range events {
err := server.DefaultManager.ProcessEvent(event)
if err != nil {
logger.Sugar.Errorw("cannot expose container",
"id", event.Container.ID, "error", err)
}
}
}()
}
func makeShutdownFunc(cancel func()) func(os.Signal) {
return func(sig os.Signal) {
cancel()
server.DefaultManager.WaitForShutdown()
logger.Sugar.Infof("received %s, exiting...", sig)
os.Exit(0)
}
}

View File

@ -153,5 +153,5 @@ servers:
# This token will be used by plugin while connecting to gRPC API.
token: key
- name: noop
# Null driver doesn't do anything. This driver will automatically be used for servers with invalid 'driver' value.
driver: null
# Nil driver doesn't do anything. This driver will automatically be used for servers with invalid 'driver' value.
driver: nil

16
go.mod
View File

@ -6,10 +6,13 @@ require (
github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/function61/gokit v0.0.0-20231117065306-355fe206d542
github.com/gin-contrib/secure v0.0.1
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.16.0
github.com/jonstacks/iomerge v0.0.0-20200607001240-c9a527e8abe8
github.com/kevinburke/ssh_config v1.2.0
github.com/mitchellh/mapstructure v1.5.0
github.com/rs/cors/wrapper/gin v0.0.0-20231013084403-73f81b45a644
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.17.0
@ -24,33 +27,46 @@ require (
require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/changkun/lockfree v0.0.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/cors v1.8.1 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect

55
go.sum
View File

@ -42,9 +42,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/changkun/lockfree v0.0.1 h1:5WefVJLglY4IHRqOQmh6Ao6wkJYaJkarshKU8VUtId4=
github.com/changkun/lockfree v0.0.1/go.mod h1:3bKiaXn/iNzIPlSvSOMSVbRQUQtAp8qUAyBUtzU11s4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -81,17 +87,28 @@ github.com/function61/gokit v0.0.0-20231117065306-355fe206d542 h1:a9BTN+DOboRkVi
github.com/function61/gokit v0.0.0-20231117065306-355fe206d542/go.mod h1:sJY957+7ush4oj4ElOMhUFaFIriAFNAGYzVh2tFJNy0=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/secure v0.0.1 h1:DMMx3xXDY+MLA9kzIPHksyzC5/V5J6014c/WAmdS2gQ=
github.com/gin-contrib/secure v0.0.1/go.mod h1:6kseOBFrSR3Is/kM1jDhCg/WsXAMvKJkuPvG9dGph/c=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -136,6 +153,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -164,12 +182,18 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonstacks/iomerge v0.0.0-20200607001240-c9a527e8abe8 h1:avdze4CXO+1TsCV84EH7ueX5WOc0GDjDYCyQWlC51Lo=
github.com/jonstacks/iomerge v0.0.0-20200607001240-c9a527e8abe8/go.mod h1:D+xdhbGYvTi/6hHTULOhUiYwEM89FvmRfPKEms6MJsc=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -178,14 +202,24 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@ -204,6 +238,10 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.8.1 h1:OrP+y5H+5Md29ACTA9imbALaKHwOSUZkcizaG0LT5ow=
github.com/rs/cors v1.8.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors/wrapper/gin v0.0.0-20231013084403-73f81b45a644 h1:BBwREPixt0iE77C9z7DOenoeh5OGFrzyL1cWOp5oQTs=
github.com/rs/cors/wrapper/gin v0.0.0-20231013084403-73f81b45a644/go.mod h1:gmu40DuK3SLdKUzGOUofS3UDZwyeOUy6ZjPPuaALatw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
@ -224,17 +262,25 @@ github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -253,6 +299,9 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
golang.design/x/lockfree v0.0.1/go.mod h1:iaZUx6UgZaOdePjzI6wFd+seYMl1i0rsG8+xKvA8c4I=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -363,6 +412,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -389,7 +439,9 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -566,6 +618,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -582,5 +636,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -1,4 +1,4 @@
package api
package plugin
import (
"context"
@ -6,13 +6,11 @@ import (
"fmt"
"net"
"github.com/Neur0toxine/sshpoke/internal/config"
"github.com/Neur0toxine/sshpoke/internal/logger"
"github.com/Neur0toxine/sshpoke/internal/server"
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
"github.com/Neur0toxine/sshpoke/pkg/convert"
plugin2 "github.com/Neur0toxine/sshpoke/pkg/plugin"
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/emptypb"
@ -22,6 +20,7 @@ var ErrUnauthorized = errors.New("unauthorized")
type pluginAPI struct {
pb.UnimplementedPluginServiceServer
log *zap.SugaredLogger
}
func (p *pluginAPI) Event(_ *emptypb.Empty, stream pb.PluginService_EventServer) error {
@ -29,13 +28,13 @@ func (p *pluginAPI) Event(_ *emptypb.Empty, stream pb.PluginService_EventServer)
if pl == nil {
return ErrUnauthorized
}
logger.Sugar.Debugw("attached plugin event stream", "serverName", pl.Name())
p.log.Debugw("attached plugin event stream", "serverName", pl.Name())
err := pl.Listen(stream.Context(), &Stream{stream: stream})
if err != nil {
logger.Sugar.Debugw("detached plugin event stream", "serverName", pl.Name(), "error", err)
p.log.Debugw("detached plugin event stream", "serverName", pl.Name(), "error", err)
return err
}
logger.Sugar.Debugw("detached plugin event stream", "serverName", pl.Name())
p.log.Debugw("detached plugin event stream", "serverName", pl.Name())
return nil
}
@ -60,20 +59,16 @@ func (p *pluginAPI) receiverForContext(ctx context.Context) plugin.Plugin {
return server.DefaultManager.PluginByToken(tokens[0])
}
func StartPluginAPI() {
port := config.Default.API.Plugin.Port
if port == 0 {
port = plugin2.DefaultPort
}
func StartServer(port int, log *zap.SugaredLogger) {
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
logger.Sugar.Errorf("cannot start plugin API server on port %d: %s", port, err)
log.Errorf("cannot start plugin API server on port %d: %s", port, err)
return
}
s := grpc.NewServer()
pb.RegisterPluginServiceServer(s, &pluginAPI{})
logger.Sugar.Debugf("starting plugin server on :%d", port)
pb.RegisterPluginServiceServer(s, &pluginAPI{log: log})
log.Debugf("starting plugin server on :%d", port)
if err := s.Serve(socket); err != nil {
logger.Sugar.Fatalf("cannot start plugin server on :%d: %s", port, err)
log.Fatalf("cannot start plugin server on :%d: %s", port, err)
}
}

View File

@ -1,4 +1,4 @@
package api
package plugin
import (
"github.com/Neur0toxine/sshpoke/pkg/convert"

View File

@ -0,0 +1,12 @@
package handler
import (
"net/http"
"github.com/Neur0toxine/sshpoke/internal/config"
"github.com/gin-gonic/gin"
)
func Config(c *gin.Context) {
c.JSON(http.StatusOK, config.Default)
}

View File

@ -0,0 +1,12 @@
package handler
import (
"net/http"
"github.com/Neur0toxine/sshpoke/internal/server"
"github.com/gin-gonic/gin"
)
func Status(c *gin.Context) {
c.JSON(http.StatusOK, server.DefaultManager.StatusMap())
}

View File

@ -0,0 +1,31 @@
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
const (
AuthHeader = "Authorization"
bearerPrefix = "Bearer "
)
func Auth(token string) gin.HandlerFunc {
if token == "" {
return func(c *gin.Context) {}
}
return func(c *gin.Context) {
header := c.GetHeader(AuthHeader)
if strings.HasPrefix(header, bearerPrefix) {
header = header[len(bearerPrefix):]
}
if header != token {
c.AbortWithStatus(http.StatusUnauthorized)
}
c.Next()
}
}

View File

@ -0,0 +1,38 @@
package middleware
import (
"fmt"
"net/http"
"time"
"github.com/Neur0toxine/sshpoke/internal/api/web/middleware/log"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func SetupLogger(zapLog *zap.SugaredLogger) gin.HandlerFunc {
gin.DefaultWriter = log.AsWriter(zapLog, zap.DebugLevel)
gin.DefaultErrorWriter = log.AsWriter(zapLog, zap.ErrorLevel)
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
zapLog.Debugw("route",
"method", httpMethod,
"path", absolutePath,
"handler", handlerName,
"handlerNum", nuHandlers)
}
return func(c *gin.Context) {
start := time.Now()
c.Next()
now := time.Now()
zapLog.Debugw(fmt.Sprintf("%d %s | %s %s",
c.Writer.Status(), http.StatusText(c.Writer.Status()), c.Request.Method, c.Request.URL.String()),
"ts", now,
"latency", now.Sub(start),
"clientIp", c.ClientIP(),
"method", c.Request.Method,
"status", c.Writer.Status(),
"error", c.Errors.ByType(gin.ErrorTypePrivate).String(),
"bodyLength", c.Writer.Size())
}
}

View File

@ -0,0 +1,37 @@
package log
import (
"io"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type writerLogger struct {
log *zap.SugaredLogger
level zapcore.Level
}
func AsWriter(log *zap.SugaredLogger, level zapcore.Level) io.Writer {
return &writerLogger{log: log}
}
func (w *writerLogger) Write(p []byte) (n int, err error) {
switch w.level {
case zapcore.DebugLevel:
w.log.Debug(string(p))
case zapcore.InfoLevel:
w.log.Info(string(p))
case zapcore.WarnLevel:
w.log.Warn(string(p))
case zapcore.ErrorLevel:
w.log.Error(string(p))
case zapcore.DPanicLevel:
w.log.DPanic(string(p))
case zapcore.PanicLevel:
w.log.Panic(string(p))
case zapcore.FatalLevel:
w.log.Fatal(string(p))
}
return len(p), nil
}

View File

@ -0,0 +1,57 @@
package web
import (
"net"
"strconv"
"github.com/Neur0toxine/sshpoke/internal/api/web/handler"
"github.com/Neur0toxine/sshpoke/internal/api/web/middleware"
"github.com/Neur0toxine/sshpoke/internal/logger"
"github.com/gin-contrib/secure"
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
"go.uber.org/zap"
)
const (
DefaultPort = 25680
httpProto = "http://"
)
func StartServer(token string, port int, log *zap.SugaredLogger, debug bool) {
if !debug {
gin.SetMode(gin.ReleaseMode)
}
logMiddleware := middleware.SetupLogger(log)
g := gin.New()
g.Use(logMiddleware)
g.Use(cors.New(cors.Options{
AllowedOrigins: []string{
httpProto + net.JoinHostPort("localhost", strconv.Itoa(port)),
httpProto + net.JoinHostPort("127.0.0.1", strconv.Itoa(port)),
},
AllowedMethods: []string{"HEAD", "GET", "POST", "PUT", "DELETE"},
MaxAge: 60 * 10,
AllowCredentials: true,
}))
g.Use(secure.New(secureConfig()))
router(g, token)
err := g.Run(net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
if err != nil {
logger.Sugar.Errorf("cannot start Web API server on port %d: %s", port, err)
}
}
func router(g *gin.Engine, token string) {
api := g.Group("/api/v1")
{
api.Use(middleware.Auth(token))
api.GET("/config", handler.Config)
api.GET("/status", handler.Status)
}
}
func secureConfig() secure.Config {
cfg := secure.DefaultConfig()
cfg.SSLRedirect = false
return cfg
}

View File

@ -11,33 +11,33 @@ import (
var Default Config
type Config struct {
Debug bool `mapstructure:"debug"`
API API `mapstructure:"api"`
Docker DockerConfig `mapstructure:"docker"`
DefaultServer string `mapstructure:"default_server"`
Servers []Server `mapstructure:"servers"`
Debug bool `mapstructure:"debug" json:"debug"`
API API `mapstructure:"api" json:"api"`
Docker DockerConfig `mapstructure:"docker" json:"docker"`
DefaultServer string `mapstructure:"default_server" json:"default_server"`
Servers []Server `mapstructure:"servers" json:"servers"`
}
type API struct {
Web WebAPI `mapstructure:"web"`
Plugin PluginAPI `mapstructure:"plugin"`
Web WebAPI `mapstructure:"web" json:"web"`
Plugin PluginAPI `mapstructure:"plugin" json:"plugin"`
}
type WebAPI struct {
Port int `mapstructure:"port" validate:"gte=0,lte=65535"`
Token string `mapstructure:"token"`
Port int `mapstructure:"port" json:"port" validate:"gte=0,lte=65535"`
Token string `mapstructure:"token" json:"token"`
}
type PluginAPI struct {
Port int `mapstructure:"port" validate:"gte=0,lte=65535"`
Port int `mapstructure:"port" json:"port" validate:"gte=0,lte=65535"`
}
type DockerConfig struct {
FromEnv *bool `mapstructure:"from_env,omitempty"`
CertPath string `mapstructure:"cert_path"`
TLSVerify *bool `mapstructure:"tls_verify,omitempty"`
Host string `mapstructure:"host"`
Version string `mapstructure:"version"`
FromEnv *bool `mapstructure:"from_env,omitempty" json:"from_env"`
CertPath string `mapstructure:"cert_path" json:"cert_path,omitempty"`
TLSVerify *bool `mapstructure:"tls_verify,omitempty" json:"tls_verify,omitempty"`
Host string `mapstructure:"host" json:"host,omitempty"`
Version string `mapstructure:"version" json:"version,omitempty"`
}
type DriverParams map[string]interface{}
@ -47,13 +47,13 @@ type DriverType string
const (
DriverSSH DriverType = "ssh"
DriverPlugin DriverType = "plugin"
DriverNull DriverType = "null"
DriverNil DriverType = "nil"
)
type Server struct {
Name string `mapstructure:"name" validate:"required"`
Driver DriverType `mapstructure:"driver"`
Params DriverParams `mapstructure:"params"`
Name string `mapstructure:"name" json:"name" validate:"required"`
Driver DriverType `mapstructure:"driver" json:"driver"`
Params DriverParams `mapstructure:"params" json:"params,omitempty"`
}
func (d DockerConfig) Opts(c *client.Client) error {

View File

@ -50,10 +50,10 @@ func (d *Docker) Containers() map[string]dto.Container {
return containers
}
func (d *Docker) GetContainer(id string) (dto.Container, bool) {
func (d *Docker) GetContainer(id string, all bool) (dto.Container, bool) {
container, err := d.cli.ContainerList(d.ctx, types.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("id", id)),
All: true,
All: all,
})
if err != nil || len(container) != 1 {
return dto.Container{}, false

View File

@ -3,6 +3,7 @@ package docker
import (
"net"
"strconv"
"strings"
"github.com/Neur0toxine/sshpoke/internal/logger"
"github.com/Neur0toxine/sshpoke/pkg/dto"
@ -78,7 +79,7 @@ func dockerContainerToInternal(container types.Container) (result dto.Container,
return dto.Container{
ID: container.ID,
Names: container.Names,
Names: convertNames(container.Names),
IP: ip,
Port: uint16(port),
Server: labels.Server,
@ -86,6 +87,17 @@ func dockerContainerToInternal(container types.Container) (result dto.Container,
}, true
}
func convertNames(src []string) []string {
if len(src) == 0 {
return nil
}
dst := make([]string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = strings.TrimLeft(src[i], "/")
}
return dst
}
func getKeyVal(m map[string]*network.EndpointSettings) *network.EndpointSettings {
for _, v := range m {
return v

View File

@ -5,7 +5,7 @@ import (
"github.com/Neur0toxine/sshpoke/internal/config"
"github.com/Neur0toxine/sshpoke/internal/server/driver/base"
"github.com/Neur0toxine/sshpoke/internal/server/driver/null"
"github.com/Neur0toxine/sshpoke/internal/server/driver/nil"
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh"
)
@ -16,9 +16,9 @@ func New(ctx context.Context, name string, driver config.DriverType, params conf
return ssh.New(ctx, name, params)
case config.DriverPlugin:
return plugin.New(ctx, name, params)
case config.DriverNull:
case config.DriverNil:
fallthrough
default:
return null.New(ctx, name, params)
return nil.New(ctx, name, params)
}
}

View File

@ -1,4 +1,4 @@
package null
package nil
import (
"context"
@ -8,26 +8,26 @@ import (
"github.com/Neur0toxine/sshpoke/pkg/dto"
)
// Null driver only logs container events to debug log. It is used when user provides invalid driver type.
// Nil driver only logs container events to debug log. It is used when user provides invalid driver type.
// You can use it directly, but it won't do anything, so... why bother?
type Null struct {
type Nil struct {
base.Base
}
func New(ctx context.Context, name string, params config.DriverParams) (base.Driver, error) {
return &Null{
return &Nil{
Base: base.New(ctx, name),
}, nil
}
func (d *Null) Handle(event dto.Event) error {
d.Log().Debugw("handling event with null driver", "serverName", d.Name(), "event", event)
func (d *Nil) Handle(event dto.Event) error {
d.Log().Debugw("handling event with nil driver", "serverName", d.Name(), "event", event)
switch event.Type {
case dto.EventStart:
d.PushEventStatus(dto.EventStatus{
Type: dto.EventStart,
ID: event.Container.ID,
Domain: "https://" + event.Container.ID + "null.dev",
Domain: "https://" + event.Container.ID + ".nil.dev",
})
case dto.EventStop, dto.EventShutdown:
d.PushEventStatus(dto.EventStatus{
@ -38,8 +38,8 @@ func (d *Null) Handle(event dto.Event) error {
return nil
}
func (d *Null) Driver() config.DriverType {
return config.DriverNull
func (d *Nil) Driver() config.DriverType {
return config.DriverNil
}
func (d *Null) WaitForShutdown() {}
func (d *Nil) WaitForShutdown() {}

View File

@ -3,7 +3,9 @@ package server
import (
"context"
"errors"
"strings"
"sync"
"time"
"github.com/Neur0toxine/sshpoke/internal/config"
"github.com/Neur0toxine/sshpoke/internal/docker"
@ -16,14 +18,17 @@ import (
type Manager struct {
rw sync.RWMutex
forwardsLock sync.Mutex
servers map[string]base.Driver
plugins map[string]plugin.Plugin
statusMap map[string]serverStatus
statusMap map[string]ServerStatus
forwards map[string]bool
statusLock sync.RWMutex
ctx context.Context
defaultServer string
}
type serverStatus struct {
type ServerStatus struct {
Name string `json:"name"`
Connections Connections `json:"connections"`
}
@ -36,8 +41,11 @@ var (
func NewManager(ctx context.Context, servers []config.Server, defaultServer string) *Manager {
m := &Manager{
ctx: ctx,
servers: make(map[string]base.Driver),
plugins: make(map[string]plugin.Plugin),
statusMap: make(map[string]ServerStatus),
forwards: make(map[string]bool),
defaultServer: defaultServer,
}
for _, serverConfig := range servers {
@ -61,7 +69,9 @@ func NewManager(ctx context.Context, servers []config.Server, defaultServer stri
m.plugins[pl.Token()] = pl
}
m.servers[serverConfig.Name] = server
m.statusMap[serverConfig.Name] = ServerStatus{Name: serverConfig.Name, Connections: make(Connections)}
}
go m.runMarkAndSweepForwards()
return m
}
@ -79,7 +89,22 @@ func (m *Manager) ProcessEvent(event dto.Event) error {
if !ok {
return ErrNoSuchServer
}
return srv.Handle(event)
if err := srv.Handle(event); err != nil {
return err
}
defer m.forwardsLock.Unlock()
m.forwardsLock.Lock()
switch event.Type {
case dto.EventStart:
m.forwards[m.forwardID(serverName, event.Container.ID)] = false
case dto.EventStop, dto.EventError, dto.EventShutdown:
delete(m.forwards, m.forwardID(serverName, event.Container.ID))
}
return nil
}
func (m *Manager) forwardID(serverName, containerID string) string {
return serverName + ":" + containerID
}
func (m *Manager) eventStatusCallback(serverName string) base.EventStatusCallback {
@ -91,28 +116,74 @@ func (m *Manager) eventStatusCallback(serverName string) base.EventStatusCallbac
func (m *Manager) processEventStatus(serverName string, event dto.EventStatus) {
logger.Sugar.Debugw("received EventStatus from server",
"serverName", serverName, "eventStatus", event)
m.statusLock.RLock()
_, exists := m.statusMap[serverName]
if !exists {
return
}
m.statusLock.RUnlock()
defer m.statusLock.Unlock()
m.statusLock.Lock()
item, found := docker.Default.GetContainer(event.ID)
item, found := docker.Default.GetContainer(event.ID, true)
if !found {
return
}
defer m.forwardsLock.Unlock()
m.forwardsLock.Lock()
switch event.Type {
case dto.EventStart:
defer m.statusLock.Unlock()
m.statusLock.Lock()
item.Domain = event.Domain
m.forwards[m.forwardID(serverName, item.ID)] = false
m.statusMap[serverName].Connections[item.ID] = item
case dto.EventStop, dto.EventShutdown, dto.EventError:
defer m.statusLock.Unlock()
m.statusLock.Lock()
item.Domain = ""
delete(m.forwards, m.forwardID(serverName, item.ID))
delete(m.statusMap[serverName].Connections, item.ID)
default:
return
}
m.statusMap[serverName].Connections[item.ID] = item
}
func (m *Manager) StatusMap() map[string]ServerStatus {
defer m.statusLock.RUnlock()
m.statusLock.RLock()
return m.statusMap
}
// runMarkAndSweepForwards runs mark-and-sweep on the started forwards every 10 seconds.
// This job is necessary because Docker sometimes forgets to notify us that containers
// were stopped (usually happens when spamming Ctrl+C after `docker compose run`).
func (m *Manager) runMarkAndSweepForwards() {
ticker := time.NewTicker(time.Second * 10)
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C:
m.markAndSweepForwards()
}
}
}
// markAndSweepForwards marks stopped containers for removal on the first run and removes them from forwards later.
// This job will remove containers from forwards if Docker didn't notify us about stopping containers for some reason.
func (m *Manager) markAndSweepForwards() {
defer m.forwardsLock.Unlock()
m.forwardsLock.Lock()
for id, state := range m.forwards {
forwardIDs := strings.Split(id, ":")
serverName, containerID := forwardIDs[0], forwardIDs[1]
_, found := docker.Default.GetContainer(containerID, false)
if found {
m.forwards[id] = false // unmark
} else {
if state {
m.processEventStatus(serverName, dto.EventStatus{
Type: dto.EventStop,
ID: containerID,
})
continue
}
m.forwards[id] = true // mark
}
}
}
func (m *Manager) PluginByToken(token string) plugin.Plugin {

View File

@ -57,6 +57,6 @@ type Container struct {
IP net.IP `json:"ip"`
Port uint16 `json:"port"`
Server string `json:"-"`
RemoteHost string `json:"remote_host"`
RemoteHost string `json:"remote_host,omitempty"`
Domain string `json:"domain"`
}