feat: add OpenConsoleProxy service facade
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/grpc/pb"
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/proxy"
|
||||
)
|
||||
|
||||
// ErrAgentOffline means the console cannot be opened because the target's agent
|
||||
// is not on the command stream. Every console session is relayed by the agent,
|
||||
// so this is fatal rather than a degraded mode.
|
||||
var ErrAgentOffline = errors.New("agent is not connected")
|
||||
|
||||
// ConsoleProxy is a pending relay: a bound listener guacd can dial and a
|
||||
// dispatched command telling the agent to meet it.
|
||||
type ConsoleProxy struct {
|
||||
ProxyID string
|
||||
Host string
|
||||
Port int
|
||||
|
||||
session *proxy.Session
|
||||
}
|
||||
|
||||
func (c *ConsoleProxy) Close() {
|
||||
proxy.Default.Remove(c.ProxyID)
|
||||
c.session.Close("")
|
||||
}
|
||||
|
||||
func (c *ConsoleProxy) Reason() string { return c.session.Reason() }
|
||||
|
||||
func proxyListenHost() string {
|
||||
if v := os.Getenv("PROXY_LISTEN_HOST"); v != "" {
|
||||
return v
|
||||
}
|
||||
return "0.0.0.0"
|
||||
}
|
||||
|
||||
func proxyAdvertiseHost() string {
|
||||
if v := os.Getenv("PROXY_ADVERTISE_HOST"); v != "" {
|
||||
return v
|
||||
}
|
||||
return "server"
|
||||
}
|
||||
|
||||
func guacdAddr() string {
|
||||
if v := os.Getenv("GUACD_ADDR"); v != "" {
|
||||
return v
|
||||
}
|
||||
return "guacd:4822"
|
||||
}
|
||||
|
||||
// guacdHosts resolves guacd's address to the IPs allowed to claim a relay
|
||||
// listener. An unresolvable host yields an empty set, which allows any source:
|
||||
// refusing everything would take the console down entirely, so the narrower
|
||||
// protections (ephemeral port, 10s window, single accept) carry it instead.
|
||||
func guacdHosts(addr string) []string {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
host = addr
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return []string{ip.String()}
|
||||
}
|
||||
ips, err := net.LookupHost(host)
|
||||
if err != nil {
|
||||
log.Printf("proxy: cannot resolve guacd host %q, allowing any relay source: %v", host, err)
|
||||
return nil
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
// DispatchOpenProxy tells the agent to dial its own loopback on port and relay
|
||||
// it back under proxyID.
|
||||
func DispatchOpenProxy(serverID, proxyID string, port uint32) error {
|
||||
return Dispatcher.dispatch(serverID, &pb.ServerCommand{
|
||||
CommandId: proxyID,
|
||||
OpenProxy: &pb.OpenProxyCmd{ProxyId: proxyID, Port: port},
|
||||
})
|
||||
}
|
||||
|
||||
// OpenConsoleProxy binds a relay listener, registers it, and asks the agent to
|
||||
// connect. The caller must Close the result.
|
||||
func OpenConsoleProxy(instanceID, serverID string, targetPort int) (*ConsoleProxy, error) {
|
||||
if !Dispatcher.IsConnected(serverID) {
|
||||
return nil, ErrAgentOffline
|
||||
}
|
||||
|
||||
proxyID, err := proxy.NewID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate proxy id: %w", err)
|
||||
}
|
||||
|
||||
sess, err := proxy.NewSession(proxyListenHost(), guacdHosts(guacdAddr()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy.Default.Add(&proxy.Entry{
|
||||
ProxyID: proxyID,
|
||||
InstanceID: instanceID,
|
||||
ServerID: serverID,
|
||||
Session: sess,
|
||||
})
|
||||
|
||||
if err := DispatchOpenProxy(serverID, proxyID, uint32(targetPort)); err != nil {
|
||||
proxy.Default.Remove(proxyID)
|
||||
sess.Close("dispatch_failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ConsoleProxy{
|
||||
ProxyID: proxyID,
|
||||
Host: proxyAdvertiseHost(),
|
||||
Port: sess.Port(),
|
||||
session: sess,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user