mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-11 01:12:17 +02:00
Let the browser VNC proxy carry plain RFB to a third-party server
This commit is contained in:
+13
-1
@@ -438,7 +438,7 @@ func createGenerateVNCSessionKeyMethod() js.Func {
|
||||
}
|
||||
|
||||
// createVNCProxyMethod creates the VNC proxy method for raw TCP-over-WebSocket bridging.
|
||||
// JS signature: createVNCProxy(hostname, port, mode?, username?, keySessionID?, sessionID?, width?, height?, peerPublicKey?, ipVersion?)
|
||||
// JS signature: createVNCProxy(hostname, port, mode?, username?, keySessionID?, sessionID?, width?, height?, peerPublicKey?, ipVersion?, external?)
|
||||
//
|
||||
// mode: "attach" (default) or "session"
|
||||
// username: required when mode is "session"
|
||||
@@ -447,6 +447,9 @@ func createGenerateVNCSessionKeyMethod() js.Func {
|
||||
// width/height: requested viewport size for session mode (0 = server default)
|
||||
// peerPublicKey: base64 X25519 static pubkey of the destination peer (required for auth)
|
||||
// ipVersion: address family to dial: 4, 6, or 0/omitted for automatic
|
||||
// external: true to carry plain RFB to a third-party VNC server on the
|
||||
// peer, which performs no NetBird authentication and ignores
|
||||
// every parameter above except port and ipVersion
|
||||
func createVNCProxyMethod(client *netbird.Client) js.Func {
|
||||
return js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||
params, err := parseVNCProxyArgs(args)
|
||||
@@ -470,6 +473,7 @@ func createVNCProxyMethod(client *netbird.Client) js.Func {
|
||||
PeerPublicKey: params.peerPublicKey,
|
||||
KeySessionID: params.keySessionID,
|
||||
IPVersion: params.ipVersion,
|
||||
External: params.external,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -485,6 +489,7 @@ type vncProxyParams struct {
|
||||
height uint16
|
||||
peerPublicKey string
|
||||
ipVersion int
|
||||
external bool
|
||||
rejectViaPromise bool
|
||||
}
|
||||
|
||||
@@ -503,9 +508,16 @@ func parseVNCProxyArgs(args []js.Value) (vncProxyParams, error) {
|
||||
if err := parseVNCProxyOptionalNumbers(args, &p); err != nil {
|
||||
return p, err
|
||||
}
|
||||
parseVNCProxyOptionalFlags(args, &p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func parseVNCProxyOptionalFlags(args []js.Value, p *vncProxyParams) {
|
||||
if len(args) > 10 && args[10].Type() == js.TypeBoolean {
|
||||
p.external = args[10].Bool()
|
||||
}
|
||||
}
|
||||
|
||||
func parseVNCProxyRequiredArgs(args []js.Value, p *vncProxyParams) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("hostname and port required")
|
||||
|
||||
@@ -144,8 +144,12 @@ type VNCProxy struct {
|
||||
}
|
||||
|
||||
type vncDestination struct {
|
||||
address string
|
||||
network string
|
||||
address string
|
||||
network string
|
||||
// external marks a destination that speaks plain RFB rather than
|
||||
// NetBird's session protocol, so the connection header is not written
|
||||
// and the stream is piped from the first byte the server sends.
|
||||
external bool
|
||||
mode byte
|
||||
username string
|
||||
sessionPriv []byte
|
||||
@@ -205,6 +209,12 @@ type ProxyRequest struct {
|
||||
// 4, 6, or 0 for automatic selection. Mirrors the SSH proxy so the
|
||||
// dashboard can resolve a peer label to a specific family.
|
||||
IPVersion int
|
||||
// External addresses a third-party VNC server on the peer instead of
|
||||
// NetBird's embedded one. The proxy then carries plain RFB and performs
|
||||
// no NetBird authentication, so the server's own security type decides
|
||||
// who gets in, and Mode, Username, SessionID, Width, Height,
|
||||
// PeerPublicKey and KeySessionID have no meaning.
|
||||
External bool
|
||||
}
|
||||
|
||||
// CreateProxy creates a new proxy endpoint for the given VNC destination.
|
||||
@@ -225,12 +235,26 @@ func (p *VNCProxy) CreateProxy(req ProxyRequest) js.Value {
|
||||
dest := vncDestination{
|
||||
address: address,
|
||||
network: netutil.TCPNetwork(req.IPVersion),
|
||||
external: req.External,
|
||||
mode: m,
|
||||
username: username,
|
||||
sessionID: sessionID,
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
if req.External {
|
||||
// Refusing rather than ignoring these: a caller that supplied a session
|
||||
// key expects the connection to be authenticated, and one that asked
|
||||
// for a virtual session expects a second desktop. Carrying on would
|
||||
// hand them a plain attach session that looks like what they asked for.
|
||||
if req.KeySessionID != "" {
|
||||
return rejectedPromise("external VNC destination cannot use a NetBird session key")
|
||||
}
|
||||
if mode == "session" {
|
||||
return rejectedPromise("external VNC destination cannot start a virtual session")
|
||||
}
|
||||
return p.newProxyPromise(address, mode, username, dest)
|
||||
}
|
||||
if req.KeySessionID != "" {
|
||||
kp, ok := lookupSessionKey(req.KeySessionID)
|
||||
if !ok {
|
||||
@@ -434,14 +458,17 @@ func (p *VNCProxy) connectToVNC(conn *vncConnection) {
|
||||
conn.vncConn = vncConn
|
||||
conn.mu.Unlock()
|
||||
|
||||
// Send the NetBird VNC session header before the RFB handshake.
|
||||
if err := p.sendSessionHeader(vncConn, conn.destination); err != nil {
|
||||
log.Errorf("send VNC session header: %v", err)
|
||||
if conn.wsHandlers.Get("close").Truthy() {
|
||||
conn.wsHandlers.Call("close", wsCodeSessionSetup, fmt.Sprintf("send session header: %v", err))
|
||||
// An external server speaks RFB from its first byte, so anything written
|
||||
// ahead of the version exchange would corrupt it.
|
||||
if !conn.destination.external {
|
||||
if err := p.sendSessionHeader(vncConn, conn.destination); err != nil {
|
||||
log.Errorf("send VNC session header: %v", err)
|
||||
if conn.wsHandlers.Get("close").Truthy() {
|
||||
conn.wsHandlers.Call("close", wsCodeSessionSetup, fmt.Sprintf("send session header: %v", err))
|
||||
}
|
||||
p.cleanupConnection(conn)
|
||||
return
|
||||
}
|
||||
p.cleanupConnection(conn)
|
||||
return
|
||||
}
|
||||
|
||||
// WS→TCP payloads are enqueued in arrival order by the onGoMessage handler
|
||||
|
||||
Reference in New Issue
Block a user