Compare commits
2 Commits
v1.1.0-alp
...
v1.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 16d48ff906 | |||
| 6213ff8a30 |
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module tunnel_pls
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0
|
||||||
github.com/caddyserver/certmagic v0.25.0
|
github.com/caddyserver/certmagic v0.25.0
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -2,6 +2,8 @@ git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0 h1:RhcBKUG41/om4jgN+iF/vlY/RojTe
|
|||||||
git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
|
||||||
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0 h1:tpJSKjaSmV+vxxbVx6qnStjxFVXjj2M0rygWXxLb99o=
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0 h1:tpJSKjaSmV+vxxbVx6qnStjxFVXjj2M0rygWXxLb99o=
|
||||||
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
|
||||||
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0 h1:3xszIhck4wo9CoeRq9vnkar4PhY7kz9QrR30qj2XszA=
|
||||||
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0/go.mod h1:Weh6ZujgWmT8XxD3Qba7sJ6r5eyUMB9XSWynqdyOoLo=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
|||||||
@@ -210,115 +210,152 @@ func (c *Client) SubscribeEvents(ctx context.Context, identity, authToken string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) processEventStream(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events]) error {
|
func (c *Client) processEventStream(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events]) error {
|
||||||
|
handlers := c.eventHandlers(subscribe)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
recv, err := subscribe.Recv()
|
recv, err := subscribe.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch recv.GetType() {
|
|
||||||
case proto.EventType_SLUG_CHANGE:
|
handler, ok := handlers[recv.GetType()]
|
||||||
user := recv.GetSlugEvent().GetUser()
|
if !ok {
|
||||||
oldSlug := recv.GetSlugEvent().GetOld()
|
|
||||||
newSlug := recv.GetSlugEvent().GetNew()
|
|
||||||
var userSession *session.SSHSession
|
|
||||||
userSession, err = c.sessionRegistry.Get(types.SessionKey{
|
|
||||||
Id: oldSlug,
|
|
||||||
Type: types.HTTP,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errSend := subscribe.Send(&proto.Node{
|
|
||||||
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
|
||||||
Payload: &proto.Node_SlugEventResponse{
|
|
||||||
SlugEventResponse: &proto.SlugChangeEventResponse{
|
|
||||||
Success: false,
|
|
||||||
Message: err.Error(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if errSend != nil {
|
|
||||||
if c.isConnectionError(errSend) {
|
|
||||||
return errSend
|
|
||||||
}
|
|
||||||
log.Printf("non-connection send error for slug change failure: %v", errSend)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = c.sessionRegistry.Update(user, types.SessionKey{
|
|
||||||
Id: oldSlug,
|
|
||||||
Type: types.HTTP,
|
|
||||||
}, types.SessionKey{
|
|
||||||
Id: newSlug,
|
|
||||||
Type: types.HTTP,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errSend := subscribe.Send(&proto.Node{
|
|
||||||
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
|
||||||
Payload: &proto.Node_SlugEventResponse{
|
|
||||||
SlugEventResponse: &proto.SlugChangeEventResponse{
|
|
||||||
Success: false,
|
|
||||||
Message: err.Error(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if errSend != nil {
|
|
||||||
if c.isConnectionError(errSend) {
|
|
||||||
return errSend
|
|
||||||
}
|
|
||||||
log.Printf("non-connection send error for slug change failure: %v", errSend)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userSession.GetInteraction().Redraw()
|
|
||||||
err = subscribe.Send(&proto.Node{
|
|
||||||
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
|
||||||
Payload: &proto.Node_SlugEventResponse{
|
|
||||||
SlugEventResponse: &proto.SlugChangeEventResponse{
|
|
||||||
Success: true,
|
|
||||||
Message: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if c.isConnectionError(err) {
|
|
||||||
log.Printf("connection error sending slug change success: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("non-connection send error for slug change success: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case proto.EventType_GET_SESSIONS:
|
|
||||||
sessions := c.sessionRegistry.GetAllSessionFromUser(recv.GetGetSessionsEvent().GetIdentity())
|
|
||||||
var details []*proto.Detail
|
|
||||||
for _, ses := range sessions {
|
|
||||||
detail := ses.Detail()
|
|
||||||
details = append(details, &proto.Detail{
|
|
||||||
Node: config.Getenv("DOMAIN", "localhost"),
|
|
||||||
ForwardingType: detail.ForwardingType,
|
|
||||||
Slug: detail.Slug,
|
|
||||||
UserId: detail.UserID,
|
|
||||||
Active: detail.Active,
|
|
||||||
StartedAt: timestamppb.New(detail.StartedAt),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err = subscribe.Send(&proto.Node{
|
|
||||||
Type: proto.EventType_GET_SESSIONS,
|
|
||||||
Payload: &proto.Node_GetSessionsEvent{
|
|
||||||
GetSessionsEvent: &proto.GetSessionsResponse{
|
|
||||||
Details: details,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if c.isConnectionError(err) {
|
|
||||||
log.Printf("connection error sending sessions success: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("non-connection send error for sessions success: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Printf("Unknown event type received: %v", recv.GetType())
|
log.Printf("Unknown event type received: %v", recv.GetType())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = handler(recv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) eventHandlers(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events]) map[proto.EventType]func(*proto.Events) error {
|
||||||
|
return map[proto.EventType]func(*proto.Events) error{
|
||||||
|
proto.EventType_SLUG_CHANGE: func(evt *proto.Events) error { return c.handleSlugChange(subscribe, evt) },
|
||||||
|
proto.EventType_GET_SESSIONS: func(evt *proto.Events) error { return c.handleGetSessions(subscribe, evt) },
|
||||||
|
proto.EventType_TERMINATE_SESSION: func(evt *proto.Events) error { return c.handleTerminateSession(subscribe, evt) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handleSlugChange(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events], evt *proto.Events) error {
|
||||||
|
slugEvent := evt.GetSlugEvent()
|
||||||
|
user := slugEvent.GetUser()
|
||||||
|
oldSlug := slugEvent.GetOld()
|
||||||
|
newSlug := slugEvent.GetNew()
|
||||||
|
|
||||||
|
userSession, err := c.sessionRegistry.Get(types.SessionKey{Id: oldSlug, Type: types.HTTP})
|
||||||
|
if err != nil {
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
||||||
|
Payload: &proto.Node_SlugEventResponse{
|
||||||
|
SlugEventResponse: &proto.SlugChangeEventResponse{Success: false, Message: err.Error()},
|
||||||
|
},
|
||||||
|
}, "slug change failure response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.sessionRegistry.Update(user, types.SessionKey{Id: oldSlug, Type: types.HTTP}, types.SessionKey{Id: newSlug, Type: types.HTTP}); err != nil {
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
||||||
|
Payload: &proto.Node_SlugEventResponse{
|
||||||
|
SlugEventResponse: &proto.SlugChangeEventResponse{Success: false, Message: err.Error()},
|
||||||
|
},
|
||||||
|
}, "slug change failure response")
|
||||||
|
}
|
||||||
|
|
||||||
|
userSession.GetInteraction().Redraw()
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_SLUG_CHANGE_RESPONSE,
|
||||||
|
Payload: &proto.Node_SlugEventResponse{
|
||||||
|
SlugEventResponse: &proto.SlugChangeEventResponse{Success: true, Message: ""},
|
||||||
|
},
|
||||||
|
}, "slug change success response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handleGetSessions(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events], evt *proto.Events) error {
|
||||||
|
sessions := c.sessionRegistry.GetAllSessionFromUser(evt.GetGetSessionsEvent().GetIdentity())
|
||||||
|
|
||||||
|
var details []*proto.Detail
|
||||||
|
for _, ses := range sessions {
|
||||||
|
detail := ses.Detail()
|
||||||
|
details = append(details, &proto.Detail{
|
||||||
|
Node: config.Getenv("DOMAIN", "localhost"),
|
||||||
|
ForwardingType: detail.ForwardingType,
|
||||||
|
Slug: detail.Slug,
|
||||||
|
UserId: detail.UserID,
|
||||||
|
Active: detail.Active,
|
||||||
|
StartedAt: timestamppb.New(detail.StartedAt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_GET_SESSIONS,
|
||||||
|
Payload: &proto.Node_GetSessionsEvent{
|
||||||
|
GetSessionsEvent: &proto.GetSessionsResponse{Details: details},
|
||||||
|
},
|
||||||
|
}, "send get sessions response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handleTerminateSession(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events], evt *proto.Events) error {
|
||||||
|
terminate := evt.GetTerminateSessionEvent()
|
||||||
|
user := terminate.GetUser()
|
||||||
|
slug := terminate.GetSlug()
|
||||||
|
|
||||||
|
tunnelType, err := c.protoToTunnelType(terminate.GetTunnelType())
|
||||||
|
if err != nil {
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_TERMINATE_SESSION,
|
||||||
|
Payload: &proto.Node_TerminateSessionEventResponse{
|
||||||
|
TerminateSessionEventResponse: &proto.TerminateSessionEventResponse{Success: false, Message: err.Error()},
|
||||||
|
},
|
||||||
|
}, "terminate session invalid tunnel type")
|
||||||
|
}
|
||||||
|
|
||||||
|
userSession, err := c.sessionRegistry.GetWithUser(user, types.SessionKey{Id: slug, Type: tunnelType})
|
||||||
|
if err != nil {
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_TERMINATE_SESSION,
|
||||||
|
Payload: &proto.Node_TerminateSessionEventResponse{
|
||||||
|
TerminateSessionEventResponse: &proto.TerminateSessionEventResponse{Success: false, Message: err.Error()},
|
||||||
|
},
|
||||||
|
}, "terminate session fetch failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = userSession.GetLifecycle().Close(); err != nil {
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_TERMINATE_SESSION,
|
||||||
|
Payload: &proto.Node_TerminateSessionEventResponse{
|
||||||
|
TerminateSessionEventResponse: &proto.TerminateSessionEventResponse{Success: false, Message: err.Error()},
|
||||||
|
},
|
||||||
|
}, "terminate session close failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.sendNode(subscribe, &proto.Node{
|
||||||
|
Type: proto.EventType_TERMINATE_SESSION,
|
||||||
|
Payload: &proto.Node_TerminateSessionEventResponse{
|
||||||
|
TerminateSessionEventResponse: &proto.TerminateSessionEventResponse{Success: true, Message: ""},
|
||||||
|
},
|
||||||
|
}, "terminate session success response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendNode(subscribe grpc.BidiStreamingClient[proto.Node, proto.Events], node *proto.Node, context string) error {
|
||||||
|
if err := subscribe.Send(node); err != nil {
|
||||||
|
if c.isConnectionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("%s: %v", context, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) protoToTunnelType(t proto.TunnelType) (types.TunnelType, error) {
|
||||||
|
switch t {
|
||||||
|
case proto.TunnelType_HTTP:
|
||||||
|
return types.HTTP, nil
|
||||||
|
case proto.TunnelType_TCP:
|
||||||
|
return types.TCP, nil
|
||||||
|
default:
|
||||||
|
return types.UNKNOWN, fmt.Errorf("unknown tunnel type received")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Key = types.SessionKey
|
|||||||
|
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
Get(key Key) (session *SSHSession, err error)
|
Get(key Key) (session *SSHSession, err error)
|
||||||
|
GetWithUser(user string, key Key) (session *SSHSession, err error)
|
||||||
Update(user string, oldKey, newKey Key) error
|
Update(user string, oldKey, newKey Key) error
|
||||||
Register(key Key, session *SSHSession) (success bool)
|
Register(key Key, session *SSHSession) (success bool)
|
||||||
Remove(key Key)
|
Remove(key Key)
|
||||||
@@ -44,6 +45,17 @@ func (r *registry) Get(key Key) (session *SSHSession, err error) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *registry) GetWithUser(user string, key Key) (session *SSHSession, err error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
client, ok := r.byUser[user][key]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("session not found")
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *registry) Update(user string, oldKey, newKey Key) error {
|
func (r *registry) Update(user string, oldKey, newKey Key) error {
|
||||||
if oldKey.Type != newKey.Type {
|
if oldKey.Type != newKey.Type {
|
||||||
return fmt.Errorf("tunnel type cannot change")
|
return fmt.Errorf("tunnel type cannot change")
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ const (
|
|||||||
type TunnelType string
|
type TunnelType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HTTP TunnelType = "HTTP"
|
UNKNOWN TunnelType = "UNKNOWN"
|
||||||
TCP TunnelType = "TCP"
|
HTTP TunnelType = "HTTP"
|
||||||
|
TCP TunnelType = "TCP"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionKey struct {
|
type SessionKey struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user