simplify plugin api, better settings validation
This commit is contained in:
parent
fc81ebe650
commit
7c443887dc
10
cmd/root.go
10
cmd/root.go
@ -6,12 +6,12 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/sshpoke/internal/api"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/config"
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/docker"
|
"github.com/Neur0toxine/sshpoke/internal/docker"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/logger"
|
"github.com/Neur0toxine/sshpoke/internal/logger"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/plugin"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server"
|
"github.com/Neur0toxine/sshpoke/internal/server"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -24,7 +24,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Short: "Expose your Docker services to the Internet via SSH.",
|
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.`,
|
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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
go plugin.StartAPIServer()
|
go api.StartPluginAPI()
|
||||||
var err error
|
var err error
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
server.DefaultManager = server.NewManager(ctx, config.Default.Servers, config.Default.DefaultServer)
|
server.DefaultManager = server.NewManager(ctx, config.Default.Servers, config.Default.DefaultServer)
|
||||||
@ -34,8 +34,8 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
for id, item := range docker.Default.Containers() {
|
for id, item := range docker.Default.Containers() {
|
||||||
err := server.DefaultManager.ProcessEvent(model.Event{
|
err := server.DefaultManager.ProcessEvent(dto.Event{
|
||||||
Type: model.EventStart,
|
Type: dto.EventStart,
|
||||||
ID: id,
|
ID: id,
|
||||||
Container: item,
|
Container: item,
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package plugin
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -8,10 +8,11 @@ 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/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server"
|
"github.com/Neur0toxine/sshpoke/internal/server"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
|
||||||
pb "github.com/Neur0toxine/sshpoke/pkg/plugin"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
plugin2 "github.com/Neur0toxine/sshpoke/pkg/plugin"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
@ -43,14 +44,14 @@ func (p *pluginAPI) EventStatus(ctx context.Context, msg *pb.EventStatusMessage)
|
|||||||
if pl == nil {
|
if pl == nil {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
pl.HandleStatus(model.EventRequest{
|
pl.HandleStatus(dto.EventStatus{
|
||||||
ID: msg.Id,
|
ID: msg.Id,
|
||||||
Error: msg.Error,
|
Error: msg.Error,
|
||||||
})
|
})
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pluginAPI) receiverForContext(ctx context.Context) *plugin.Plugin {
|
func (p *pluginAPI) receiverForContext(ctx context.Context) plugin.Plugin {
|
||||||
md, ok := metadata.FromIncomingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@ -62,10 +63,10 @@ func (p *pluginAPI) receiverForContext(ctx context.Context) *plugin.Plugin {
|
|||||||
return server.DefaultManager.PluginByToken(tokens[0])
|
return server.DefaultManager.PluginByToken(tokens[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartAPIServer() {
|
func StartPluginAPI() {
|
||||||
port := config.Default.PluginAPIPort
|
port := config.Default.PluginAPIPort
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = 3000
|
port = plugin2.DefaultPort
|
||||||
}
|
}
|
||||||
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
15
internal/api/stream.go
Normal file
15
internal/api/stream.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/convert"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
stream pb.PluginService_EventServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) Send(event dto.Event) error {
|
||||||
|
return s.stream.Send(convert.AppEventToMessage(event))
|
||||||
|
}
|
@ -7,7 +7,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/internal/model"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
"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"
|
||||||
@ -31,7 +31,7 @@ func New(ctx context.Context) (*Docker, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Docker) Containers() map[string]model.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")),
|
||||||
})
|
})
|
||||||
@ -39,7 +39,7 @@ func (d *Docker) Containers() map[string]model.Container {
|
|||||||
logger.Sugar.Errorf("cannot get containers list: %s", err)
|
logger.Sugar.Errorf("cannot get containers list: %s", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
containers := map[string]model.Container{}
|
containers := map[string]dto.Container{}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
container, ok := dockerContainerToInternal(item)
|
container, ok := dockerContainerToInternal(item)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -50,13 +50,13 @@ func (d *Docker) Containers() map[string]model.Container {
|
|||||||
return containers
|
return containers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Docker) Listen() (chan model.Event, error) {
|
func (d *Docker) Listen() (chan dto.Event, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
output := make(chan model.Event)
|
output := make(chan dto.Event)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
eventSource, errSource := cli.Events(d.ctx, types.EventsOptions{
|
eventSource, errSource := cli.Events(d.ctx, types.EventsOptions{
|
||||||
@ -64,8 +64,8 @@ func (d *Docker) Listen() (chan model.Event, error) {
|
|||||||
})
|
})
|
||||||
select {
|
select {
|
||||||
case event := <-eventSource:
|
case event := <-eventSource:
|
||||||
eventType := model.TypeFromAction(event.Action)
|
eventType := dto.TypeFromAction(event.Action)
|
||||||
if (eventType != model.EventStart && eventType != model.EventStop) || !actorEnabled(event.Actor) {
|
if (eventType != dto.EventStart && eventType != dto.EventStop) || !actorEnabled(event.Actor) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
container, err := d.cli.ContainerList(d.ctx, types.ContainerListOptions{
|
container, err := d.cli.ContainerList(d.ctx, types.ContainerListOptions{
|
||||||
@ -81,13 +81,13 @@ func (d *Docker) Listen() (chan model.Event, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newEvent := model.Event{
|
newEvent := dto.Event{
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
ID: event.Actor.ID,
|
ID: event.Actor.ID,
|
||||||
Container: converted,
|
Container: converted,
|
||||||
}
|
}
|
||||||
msg := "exposing container"
|
msg := "exposing container"
|
||||||
if eventType == model.EventStop {
|
if eventType == dto.EventStop {
|
||||||
msg = "stopping container"
|
msg = "stopping container"
|
||||||
}
|
}
|
||||||
logger.Sugar.Debugw(msg,
|
logger.Sugar.Debugw(msg,
|
||||||
@ -96,8 +96,7 @@ func (d *Docker) Listen() (chan model.Event, error) {
|
|||||||
"container.ip", converted.IP.String(),
|
"container.ip", converted.IP.String(),
|
||||||
"container.port", converted.Port,
|
"container.port", converted.Port,
|
||||||
"container.server", converted.Server,
|
"container.server", converted.Server,
|
||||||
"container.prefix", converted.Prefix,
|
"container.prefix", converted.Prefix)
|
||||||
"container.domain", converted.Domain)
|
|
||||||
output <- newEvent
|
output <- newEvent
|
||||||
case err := <-errSource:
|
case err := <-errSource:
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/logger"
|
"github.com/Neur0toxine/sshpoke/internal/logger"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/model"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
"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"
|
||||||
@ -18,7 +18,6 @@ type labelsConfig struct {
|
|||||||
Server string `mapstructure:"sshpoke.server"`
|
Server string `mapstructure:"sshpoke.server"`
|
||||||
Port string `mapstructure:"sshpoke.port"`
|
Port string `mapstructure:"sshpoke.port"`
|
||||||
Prefix string `mapstructure:"sshpoke.prefix"`
|
Prefix string `mapstructure:"sshpoke.prefix"`
|
||||||
Domain string `mapstructure:"sshpoke.domain"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type boolStr string
|
type boolStr string
|
||||||
@ -35,7 +34,7 @@ func actorEnabled(actor events.Actor) bool {
|
|||||||
return boolStr(label).Bool()
|
return boolStr(label).Bool()
|
||||||
}
|
}
|
||||||
|
|
||||||
func dockerContainerToInternal(container types.Container) (result model.Container, ok bool) {
|
func dockerContainerToInternal(container types.Container) (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)
|
||||||
@ -77,12 +76,11 @@ func dockerContainerToInternal(container types.Container) (result model.Containe
|
|||||||
return result, false
|
return result, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.Container{
|
return dto.Container{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Port: uint16(port),
|
Port: uint16(port),
|
||||||
Server: labels.Server,
|
Server: labels.Server,
|
||||||
Prefix: labels.Prefix,
|
Prefix: labels.Prefix,
|
||||||
Domain: labels.Domain,
|
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/model"
|
|
||||||
pb "github.com/Neur0toxine/sshpoke/pkg/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Stream struct {
|
|
||||||
stream pb.PluginService_EventServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) Send(event model.Event) error {
|
|
||||||
return s.stream.Send(s.eventToMessage(event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) messageToEvent(event *pb.EventMessage) model.Event {
|
|
||||||
return model.Event{
|
|
||||||
Type: s.pbEventTypeToApp(event.Type),
|
|
||||||
ID: event.Id,
|
|
||||||
Container: model.Container{
|
|
||||||
IP: net.ParseIP(event.Container.Ip),
|
|
||||||
Port: uint16(event.Container.Port),
|
|
||||||
Server: event.Container.Server,
|
|
||||||
Prefix: event.Container.Prefix,
|
|
||||||
Domain: event.Container.Domain,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) eventToMessage(event model.Event) *pb.EventMessage {
|
|
||||||
return &pb.EventMessage{
|
|
||||||
Type: s.appEventTypeToPB(event.Type),
|
|
||||||
Id: event.ID,
|
|
||||||
Container: &pb.Container{
|
|
||||||
Ip: event.Container.IP.String(),
|
|
||||||
Port: uint32(event.Container.Port),
|
|
||||||
Server: event.Container.Server,
|
|
||||||
Prefix: event.Container.Prefix,
|
|
||||||
Domain: event.Container.Domain,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) pbEventTypeToApp(typ pb.EventType) model.EventType {
|
|
||||||
val := model.EventType(typ.Number())
|
|
||||||
if val > model.EventStart {
|
|
||||||
return model.EventUnknown
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) appEventTypeToPB(typ model.EventType) pb.EventType {
|
|
||||||
switch typ {
|
|
||||||
case 0:
|
|
||||||
return pb.EventType_EVENT_START
|
|
||||||
case 1:
|
|
||||||
return pb.EventType_EVENT_STOP
|
|
||||||
case 2:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return pb.EventType_EVENT_UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/config"
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/model"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DriverConstructor func(ctx context.Context, name string, params config.DriverParams) (Driver, error)
|
type DriverConstructor func(ctx context.Context, name string, params config.DriverParams) (Driver, error)
|
||||||
|
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
Handle(event model.Event) error
|
Name() string
|
||||||
|
Handle(event dto.Event) error
|
||||||
Driver() config.DriverType
|
Driver() config.DriverType
|
||||||
WaitForShutdown()
|
WaitForShutdown()
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ 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/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Null driver only logs container events to debug log. It is used when user provides invalid driver type.
|
// Null driver only logs container events to debug log. It is used when user provides invalid driver type.
|
||||||
@ -19,11 +19,15 @@ func New(ctx context.Context, name string, params config.DriverParams) (iface.Dr
|
|||||||
return &Null{name: name}, nil
|
return &Null{name: name}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Null) Handle(event model.Event) error {
|
func (d *Null) Handle(event dto.Event) error {
|
||||||
logger.Sugar.Debugw("handling event with null driver", "serverName", d.name, "event", event)
|
logger.Sugar.Debugw("handling event with null driver", "serverName", d.name, "event", event)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Null) Name() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Null) Driver() config.DriverType {
|
func (d *Null) Driver() config.DriverType {
|
||||||
return config.DriverNull
|
return config.DriverNull
|
||||||
}
|
}
|
||||||
|
@ -8,31 +8,38 @@ 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/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/util"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/util"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrAlreadyConnected = errors.New("already connected")
|
var ErrAlreadyConnected = errors.New("already connected")
|
||||||
|
|
||||||
// Plugin driver uses RPC to communicate with external plugin.
|
// Driver plugin uses RPC to communicate with external plugin.
|
||||||
type Plugin struct {
|
type Driver struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
name string
|
name string
|
||||||
params Params
|
params Params
|
||||||
send *Queue[model.Event]
|
send *Queue[dto.Event]
|
||||||
listening atomic.Bool
|
listening atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventStream interface {
|
type EventStream interface {
|
||||||
Send(event model.Event) error
|
Send(event dto.Event) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
iface.Driver
|
||||||
|
Token() string
|
||||||
|
Listen(ctx context.Context, stream EventStream) error
|
||||||
|
HandleStatus(event dto.EventStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, name string, params config.DriverParams) (iface.Driver, error) {
|
func New(ctx context.Context, name string, params config.DriverParams) (iface.Driver, error) {
|
||||||
drv := &Plugin{
|
drv := &Driver{
|
||||||
name: name,
|
name: name,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
send: NewQueue[model.Event](),
|
send: NewQueue[dto.Event](),
|
||||||
}
|
}
|
||||||
if err := util.UnmarshalParams(params, &drv.params); err != nil {
|
if err := util.UnmarshalParams(params, &drv.params); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -40,27 +47,28 @@ func New(ctx context.Context, name string, params config.DriverParams) (iface.Dr
|
|||||||
return drv, nil
|
return drv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Handle(event model.Event) error {
|
func (d *Driver) Handle(event dto.Event) error {
|
||||||
if d.isDone() {
|
if d.isDone() {
|
||||||
|
d.send.Enqueue(dto.Event{Type: dto.EventShutdown})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
d.send.Enqueue(event)
|
d.send.Enqueue(event)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Name() string {
|
func (d *Driver) Name() string {
|
||||||
return d.name
|
return d.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Driver() config.DriverType {
|
func (d *Driver) Driver() config.DriverType {
|
||||||
return config.DriverPlugin
|
return config.DriverPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Token() string {
|
func (d *Driver) Token() string {
|
||||||
return d.params.Token
|
return d.params.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Listen(ctx context.Context, stream EventStream) error {
|
func (d *Driver) Listen(ctx context.Context, stream EventStream) error {
|
||||||
if d.listening.Load() {
|
if d.listening.Load() {
|
||||||
return ErrAlreadyConnected
|
return ErrAlreadyConnected
|
||||||
}
|
}
|
||||||
@ -88,11 +96,11 @@ func (d *Plugin) Listen(ctx context.Context, stream EventStream) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) HandleStatus(event model.EventRequest) {
|
func (d *Driver) HandleStatus(event dto.EventStatus) {
|
||||||
logger.Sugar.Errorw("plugin error", "serverName", d.name, "id", event.ID, "error", event.Error)
|
logger.Sugar.Errorw("plugin error", "serverName", d.name, "id", event.ID, "error", event.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) isDone() bool {
|
func (d *Driver) isDone() bool {
|
||||||
select {
|
select {
|
||||||
case <-d.ctx.Done():
|
case <-d.ctx.Done():
|
||||||
return true
|
return true
|
||||||
@ -101,7 +109,7 @@ func (d *Plugin) isDone() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) WaitForShutdown() {
|
func (d *Driver) WaitForShutdown() {
|
||||||
<-d.ctx.Done()
|
<-d.ctx.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/config"
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/util"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/util"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/proto/sshtun"
|
"github.com/Neur0toxine/sshpoke/internal/server/proto/sshtun"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSH struct {
|
type SSH struct {
|
||||||
@ -21,7 +21,7 @@ type SSH struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
container model.Container
|
container dto.Container
|
||||||
tun *sshtun.Tunnel
|
tun *sshtun.Tunnel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +33,15 @@ func New(ctx context.Context, name string, params config.DriverParams) (iface.Dr
|
|||||||
return drv, nil
|
return drv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSH) Handle(event model.Event) error {
|
func (d *SSH) Handle(event dto.Event) error {
|
||||||
// TODO: Implement event handling & connections management.
|
// TODO: Implement event handling & connections management.
|
||||||
return errors.New(d.name + " server handler is not implemented yet")
|
return errors.New(d.name + " server handler is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *SSH) Name() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
func (d *SSH) Driver() config.DriverType {
|
func (d *SSH) Driver() config.DriverType {
|
||||||
return config.DriverSSH
|
return config.DriverSSH
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,16 @@ 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/internal/model"
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/iface"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/plugin"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
rw sync.RWMutex
|
rw sync.RWMutex
|
||||||
servers map[string]iface.Driver
|
servers map[string]iface.Driver
|
||||||
|
plugins map[string]plugin.Plugin
|
||||||
defaultServer string
|
defaultServer string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ var (
|
|||||||
func NewManager(ctx context.Context, servers []config.Server, defaultServer string) *Manager {
|
func NewManager(ctx context.Context, servers []config.Server, defaultServer string) *Manager {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
servers: make(map[string]iface.Driver),
|
servers: make(map[string]iface.Driver),
|
||||||
|
plugins: make(map[string]plugin.Plugin),
|
||||||
defaultServer: defaultServer,
|
defaultServer: defaultServer,
|
||||||
}
|
}
|
||||||
for _, serverConfig := range servers {
|
for _, serverConfig := range servers {
|
||||||
@ -36,12 +38,25 @@ func NewManager(ctx context.Context, servers []config.Server, defaultServer stri
|
|||||||
logger.Sugar.Errorf("cannot initialize server '%s': %s", serverConfig.Name, err)
|
logger.Sugar.Errorf("cannot initialize server '%s': %s", serverConfig.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if server.Driver() == config.DriverPlugin {
|
||||||
|
pl := server.(plugin.Plugin)
|
||||||
|
if pl.Token() == "" {
|
||||||
|
logger.Sugar.Warnf("server '%s' will not work because it doesn't have a token", pl.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existing, found := m.plugins[pl.Token()]
|
||||||
|
if found {
|
||||||
|
logger.Sugar.Fatalw("two plugins cannot have the same token",
|
||||||
|
"plugin1", existing.Name(), "plugin2", pl.Name(), "token", pl.Token())
|
||||||
|
}
|
||||||
|
m.plugins[pl.Token()] = pl
|
||||||
|
}
|
||||||
m.servers[serverConfig.Name] = server
|
m.servers[serverConfig.Name] = server
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) ProcessEvent(event model.Event) error {
|
func (m *Manager) ProcessEvent(event dto.Event) error {
|
||||||
serverName := event.Container.Server
|
serverName := event.Container.Server
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
serverName = m.defaultServer
|
serverName = m.defaultServer
|
||||||
@ -58,20 +73,12 @@ func (m *Manager) ProcessEvent(event model.Event) error {
|
|||||||
return srv.Handle(event)
|
return srv.Handle(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PluginByToken(token string) *plugin.Plugin {
|
func (m *Manager) PluginByToken(token string) plugin.Plugin {
|
||||||
defer m.rw.RUnlock()
|
server, ok := m.plugins[token]
|
||||||
m.rw.RLock()
|
if !ok {
|
||||||
for _, srv := range m.servers {
|
return nil
|
||||||
if srv.Driver() != config.DriverPlugin {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pl := srv.(*plugin.Plugin)
|
|
||||||
if pl.Token() != token {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return pl
|
|
||||||
}
|
}
|
||||||
return nil
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) WaitForShutdown() {
|
func (m *Manager) WaitForShutdown() {
|
||||||
|
@ -121,7 +121,7 @@ func (t Tunnel) Bind(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
ln.Close()
|
ln.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.Logger.Printf("(%v) binded Tunnel", t)
|
t.Logger.Printf("(%v) bound Tunnel", t)
|
||||||
defer t.Logger.Printf("(%v) collapsed Tunnel", t)
|
defer t.Logger.Printf("(%v) collapsed Tunnel", t)
|
||||||
|
|
||||||
// Accept all incoming connections.
|
// Accept all incoming connections.
|
||||||
|
65
pkg/convert/convert.go
Normal file
65
pkg/convert/convert.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MessageToAppEvent(event *pb.EventMessage) dto.Event {
|
||||||
|
return dto.Event{
|
||||||
|
Type: MessageEventTypeToApp(event.Type),
|
||||||
|
ID: event.Id,
|
||||||
|
Container: dto.Container{
|
||||||
|
IP: net.ParseIP(event.Container.Ip),
|
||||||
|
Port: uint16(event.Container.Port),
|
||||||
|
Server: event.Container.Server,
|
||||||
|
Prefix: event.Container.Prefix,
|
||||||
|
Domain: event.Container.Domain,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppEventToMessage(event dto.Event) *pb.EventMessage {
|
||||||
|
return &pb.EventMessage{
|
||||||
|
Type: AppEventTypeToMessage(event.Type),
|
||||||
|
Id: event.ID,
|
||||||
|
Container: &pb.Container{
|
||||||
|
Ip: event.Container.IP.String(),
|
||||||
|
Port: uint32(event.Container.Port),
|
||||||
|
Server: event.Container.Server,
|
||||||
|
Prefix: event.Container.Prefix,
|
||||||
|
Domain: event.Container.Domain,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppEventStatusToMessage(status dto.EventStatus) *pb.EventStatusMessage {
|
||||||
|
return &pb.EventStatusMessage{
|
||||||
|
Id: status.ID,
|
||||||
|
Error: status.Error,
|
||||||
|
Domain: status.Domain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MessageEventTypeToApp(typ pb.EventType) dto.EventType {
|
||||||
|
val := dto.EventType(typ.Number())
|
||||||
|
if val < dto.EventStart || val > dto.EventUnknown {
|
||||||
|
return dto.EventUnknown
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppEventTypeToMessage(typ dto.EventType) pb.EventType {
|
||||||
|
switch typ {
|
||||||
|
case 0:
|
||||||
|
return pb.EventType_EVENT_START
|
||||||
|
case 1:
|
||||||
|
return pb.EventType_EVENT_STOP
|
||||||
|
case 2:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return pb.EventType_EVENT_UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package model
|
package dto
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
@ -7,6 +7,7 @@ type EventType uint8
|
|||||||
const (
|
const (
|
||||||
EventStart EventType = iota
|
EventStart EventType = iota
|
||||||
EventStop
|
EventStop
|
||||||
|
EventShutdown
|
||||||
EventUnknown
|
EventUnknown
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,9 +28,10 @@ type Event struct {
|
|||||||
Container Container
|
Container Container
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventRequest struct {
|
type EventStatus struct {
|
||||||
ID string
|
ID string
|
||||||
Error string
|
Error string
|
||||||
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
68
pkg/plugin/client.go
Normal file
68
pkg/plugin/client.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/convert"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
parent pb.PluginServiceClient
|
||||||
|
token string
|
||||||
|
close func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(addr, token string) (*Client, error) {
|
||||||
|
conn, err := grpc.Dial(normalizeAddr(addr), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c := &Client{
|
||||||
|
parent: pb.NewPluginServiceClient(conn),
|
||||||
|
token: token,
|
||||||
|
close: conn.Close,
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(c, connCloser)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Event(ctx context.Context) (*Stream, error) {
|
||||||
|
stream, err := c.parent.Event(ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Stream{stream: stream}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) EventStatus(ctx context.Context, status dto.EventStatus) error {
|
||||||
|
_, err := c.parent.EventStatus(ctx, convert.AppEventStatusToMessage(status))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func connCloser(c *Client) {
|
||||||
|
_ = c.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeAddr(addr string) string {
|
||||||
|
addr = strings.TrimSpace(addr)
|
||||||
|
if strings.HasPrefix(addr, "grpc://") {
|
||||||
|
addr = addr[7:]
|
||||||
|
}
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil && err.Error() == "missing port in address" {
|
||||||
|
host, port, err = net.SplitHostPort(addr + ":" + strconv.Itoa(DefaultPort))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return host + ":" + port
|
||||||
|
}
|
@ -1,2 +1,2 @@
|
|||||||
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb.proto
|
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/pb.proto
|
||||||
package plugin
|
package plugin
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
option go_package = "github.com/Neur0toxine/sshpoke/pkg/plugin";
|
option go_package = "github.com/Neur0toxine/sshpoke/pkg/plugin/pb";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
service PluginService {
|
service PluginService {
|
||||||
rpc Event (google.protobuf.Empty) returns (stream EventMessage);
|
rpc Event (google.protobuf.Empty) returns (stream EventMessage);
|
||||||
rpc EventStatus (EventStatusMessage) returns (google.protobuf.Empty);
|
rpc EventStatus (EventStatusMessage) returns (google.protobuf.Empty);
|
||||||
rpc Shutdown (stream google.protobuf.Empty) returns (google.protobuf.Empty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EventType {
|
enum EventType {
|
||||||
EVENT_START = 0;
|
EVENT_START = 0;
|
||||||
EVENT_STOP = 1;
|
EVENT_STOP = 1;
|
||||||
EVENT_UNKNOWN = 2;
|
EVENT_SHUTDOWN = 2;
|
||||||
|
EVENT_UNKNOWN = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Container {
|
message Container {
|
||||||
@ -33,4 +33,5 @@ message EventMessage {
|
|||||||
message EventStatusMessage {
|
message EventStatusMessage {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string error = 2;
|
string error = 2;
|
||||||
|
string domain = 3;
|
||||||
}
|
}
|
3
pkg/plugin/port.go
Normal file
3
pkg/plugin/port.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
const DefaultPort = 25681
|
19
pkg/plugin/stream.go
Normal file
19
pkg/plugin/stream.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/convert"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
|
"github.com/Neur0toxine/sshpoke/pkg/plugin/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
stream pb.PluginService_EventClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) Receive() (dto.Event, error) {
|
||||||
|
data, err := s.stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return dto.Event{}, err
|
||||||
|
}
|
||||||
|
return convert.MessageToAppEvent(data), nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user