Support target-free SOCKS5 port mappings with reference protection and UI workflows. Include connection-stage diagnostics, SSH fingerprint confirmation, asset impact checks, listener traffic visibility, SQLite snapshots, account mapping improvements, and MySQL discovery fixes. Update feature rationale and validation docs; Go checks, frontend build and 121 browser tests passed.
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
type ConnectionInfo struct {
|
|
RXBytes int64 `json:"rxBytes"`
|
|
TXBytes int64 `json:"txBytes"`
|
|
ID string `json:"id"`
|
|
GatewayID string `json:"gatewayId"`
|
|
Protocol string `json:"protocol"`
|
|
Username string `json:"username"`
|
|
Source string `json:"source"`
|
|
StartedAt time.Time `json:"startedAt"`
|
|
DurationSeconds float64 `json:"durationSeconds"`
|
|
Authenticated bool `json:"authenticated"`
|
|
TargetAccountID string `json:"targetAccountId"`
|
|
TargetID string `json:"targetId"`
|
|
TargetHost string `json:"targetHost"`
|
|
TargetPort int `json:"targetPort"`
|
|
Disconnecting bool `json:"disconnecting"`
|
|
}
|
|
|
|
// Count at the client socket boundary: RX is client -> gateway; TX is gateway
|
|
// -> client. Do not expose ReaderFrom/WriterTo, which could bypass accounting.
|
|
type trafficConn struct {
|
|
net.Conn
|
|
record func(rx, tx int)
|
|
}
|
|
|
|
func (c *trafficConn) Read(p []byte) (int, error) {
|
|
n, err := c.Conn.Read(p)
|
|
if n > 0 {
|
|
c.record(n, 0)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (c *trafficConn) Write(p []byte) (int, error) {
|
|
n, err := c.Conn.Write(p)
|
|
if n > 0 {
|
|
c.record(0, n)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (c *trafficConn) CloseWrite() error {
|
|
if half, ok := c.Conn.(interface{ CloseWrite() error }); ok {
|
|
return half.CloseWrite()
|
|
}
|
|
return c.Conn.Close()
|
|
}
|
|
|
|
type connectionState struct {
|
|
ConnectionInfo
|
|
conn net.Conn
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func (m *Manager) Connections() []ConnectionInfo {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
result := make([]ConnectionInfo, 0, len(m.connections))
|
|
for _, state := range m.connections {
|
|
info := state.ConnectionInfo
|
|
info.DurationSeconds = time.Since(info.StartedAt).Seconds()
|
|
result = append(result, info)
|
|
}
|
|
sort.Slice(result, func(i, j int) bool { return result[i].StartedAt.Before(result[j].StartedAt) })
|
|
return result
|
|
}
|
|
|
|
// Disconnect closes only the selected connection, cancelling its upstream work.
|
|
func (m *Manager) Disconnect(id string) (ConnectionInfo, bool) {
|
|
m.mu.Lock()
|
|
state, ok := m.connections[id]
|
|
if !ok {
|
|
m.mu.Unlock()
|
|
return ConnectionInfo{}, false
|
|
}
|
|
state.Disconnecting = true
|
|
info := state.ConnectionInfo
|
|
m.mu.Unlock()
|
|
state.cancel()
|
|
state.conn.Close()
|
|
return info, true
|
|
}
|
|
|
|
func (m *Manager) OnShutdown(hook func()) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.shutdownHooks = append(m.shutdownHooks, hook)
|
|
}
|
|
|
|
// CertificateExpirations covers certificates configured on the gateway listener.
|
|
func (m *Manager) CertificateExpirations() map[string]time.Time {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
result := map[string]time.Time{}
|
|
if m.tls == nil {
|
|
return result
|
|
}
|
|
for i, cert := range m.tls.Certificates {
|
|
leaf := cert.Leaf
|
|
if leaf == nil && len(cert.Certificate) > 0 {
|
|
leaf, _ = x509.ParseCertificate(cert.Certificate[0])
|
|
}
|
|
if leaf != nil {
|
|
result[fmt.Sprintf("gateway-tls-%d", i)] = leaf.NotAfter
|
|
}
|
|
}
|
|
return result
|
|
}
|