Client TCP/UDP and the built-in DNS server bind loopback unless bind_address or a per-rule host (tcp://22:0.0.0.0:2222) is set. README and generated server examples no longer document :9000/:8080 as if they were localhost-only. Closes #3
This commit is contained in:
+106
-11
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -19,6 +20,7 @@ type Config struct {
|
||||
InstanceID string `yaml:"instance_id"`
|
||||
ListenAddress string `yaml:"listen_address"`
|
||||
RemoteAddress string `yaml:"remote_address"`
|
||||
BindAddress string `yaml:"bind_address"` // local TCP/UDP/DNS bind host; default 127.0.0.1
|
||||
Ports []PortRule `yaml:"ports"`
|
||||
EncryptionKey string `yaml:"encryption_key"`
|
||||
KeepAlive bool `yaml:"keep_alive"`
|
||||
@@ -31,10 +33,43 @@ type Config struct {
|
||||
|
||||
// PortRule defines a port forwarding rule
|
||||
type PortRule struct {
|
||||
LocalPort int `yaml:"local_port"`
|
||||
RemotePort int `yaml:"remote_port"`
|
||||
Protocol string `yaml:"protocol"` // "tcp" or "udp"
|
||||
TargetHost string `yaml:"target_host,omitempty"` // Target host for server-side forwarding (defaults to localhost)
|
||||
LocalPort int `yaml:"local_port"`
|
||||
RemotePort int `yaml:"remote_port"`
|
||||
Protocol string `yaml:"protocol"` // "tcp" or "udp"
|
||||
TargetHost string `yaml:"target_host,omitempty"` // Target host for server-side forwarding (defaults to localhost)
|
||||
BindAddress string `yaml:"-"` // Client local listen host; empty means use Config.BindAddress
|
||||
}
|
||||
|
||||
// DefaultBindAddress is the loopback host used when bind_address is omitted.
|
||||
// Binding 0.0.0.0 (all interfaces) requires an explicit override.
|
||||
const DefaultBindAddress = "127.0.0.1"
|
||||
|
||||
// NormalizeBindAddress returns host, or 127.0.0.1 when host is empty.
|
||||
func NormalizeBindAddress(host string) string {
|
||||
if host == "" {
|
||||
return DefaultBindAddress
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// LocalListenAddr returns host:port for a local listener. Empty host becomes 127.0.0.1.
|
||||
func LocalListenAddr(host string, port int) string {
|
||||
return net.JoinHostPort(NormalizeBindAddress(host), strconv.Itoa(port))
|
||||
}
|
||||
|
||||
// ClientListenAddr is the address the client binds for a port rule.
|
||||
// Per-rule BindAddress wins over Config.BindAddress; both default to 127.0.0.1.
|
||||
func (c *Config) ClientListenAddr(rule PortRule) string {
|
||||
host := rule.BindAddress
|
||||
if host == "" && c != nil {
|
||||
host = c.BindAddress
|
||||
}
|
||||
return LocalListenAddr(host, rule.LocalPort)
|
||||
}
|
||||
|
||||
// ListenAddr is the address the built-in DNS server binds. Default 127.0.0.1.
|
||||
func (d DNSServerConfig) ListenAddr() string {
|
||||
return LocalListenAddr(d.BindAddress, d.ListenPort)
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom YAML unmarshaling for PortRule
|
||||
@@ -133,21 +168,70 @@ func (p *PortRule) UnmarshalYAML(value *yaml.Node) error {
|
||||
p.TargetHost = "" // Client doesn't specify target host
|
||||
return nil
|
||||
}
|
||||
} else if len(addressParts) >= 3 {
|
||||
// Client format with bind host: protocol://targetport:bindhost:localport
|
||||
// e.g. tcp://22:127.0.0.1:2222 or tcp://22:0.0.0.0:2222
|
||||
firstColon := strings.Index(addressPart, ":")
|
||||
lastColon := strings.LastIndex(addressPart, ":")
|
||||
if firstColon < 0 || lastColon <= firstColon {
|
||||
return fmt.Errorf("invalid address format: %s (expected 'targetport:bindhost:localport')", addressPart)
|
||||
}
|
||||
targetPortStr := addressPart[:firstColon]
|
||||
bindHost := addressPart[firstColon+1 : lastColon]
|
||||
localPortStr := addressPart[lastColon+1:]
|
||||
|
||||
bindHost = strings.TrimPrefix(bindHost, "[")
|
||||
bindHost = strings.TrimSuffix(bindHost, "]")
|
||||
if bindHost == "" {
|
||||
return fmt.Errorf("bind host is required in format 'targetport:bindhost:localport'")
|
||||
}
|
||||
if len(bindHost) > 253 {
|
||||
return fmt.Errorf("bind host too long")
|
||||
}
|
||||
for _, c := range bindHost {
|
||||
if c < 32 || c > 126 {
|
||||
return fmt.Errorf("invalid character in bind host")
|
||||
}
|
||||
}
|
||||
|
||||
targetPort, err := strconv.Atoi(targetPortStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid target port: %s", targetPortStr)
|
||||
}
|
||||
localPort, err := strconv.Atoi(localPortStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid local port: %s", localPortStr)
|
||||
}
|
||||
if targetPort < 1 || targetPort > 65535 {
|
||||
return fmt.Errorf("invalid target port: %d (must be 1-65535)", targetPort)
|
||||
}
|
||||
if localPort < 1 || localPort > 65535 {
|
||||
return fmt.Errorf("invalid local port: %d (must be 1-65535)", localPort)
|
||||
}
|
||||
|
||||
p.LocalPort = localPort
|
||||
p.RemotePort = targetPort
|
||||
p.Protocol = protocol
|
||||
p.TargetHost = ""
|
||||
p.BindAddress = bindHost
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("invalid address format: %s (expected 'target:port' for server or 'targetport:localport' for client)", addressPart)
|
||||
return fmt.Errorf("invalid address format: %s (expected 'target:port' for server, 'targetport:localport' for client, or 'targetport:bindhost:localport' for client with bind)", addressPart)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom YAML marshaling for PortRule
|
||||
func (p PortRule) MarshalYAML() (interface{}, error) {
|
||||
// Use new format: protocol://target:targetport (server) or protocol://targetport:localport (client)
|
||||
// Server: protocol://target:targetport
|
||||
// Client: protocol://targetport:localport
|
||||
// Client with explicit bind: protocol://targetport:bindhost:localport
|
||||
if p.TargetHost == "" {
|
||||
// Client format: protocol://targetport:localport
|
||||
if p.BindAddress != "" {
|
||||
return fmt.Sprintf("%s://%d:%s:%d", p.Protocol, p.RemotePort, p.BindAddress, p.LocalPort), nil
|
||||
}
|
||||
return fmt.Sprintf("%s://%d:%d", p.Protocol, p.RemotePort, p.LocalPort), nil
|
||||
} else {
|
||||
// Server format: protocol://target:targetport
|
||||
return fmt.Sprintf("%s://%s:%d", p.Protocol, p.TargetHost, p.RemotePort), nil
|
||||
}
|
||||
return fmt.Sprintf("%s://%s:%d", p.Protocol, p.TargetHost, p.RemotePort), nil
|
||||
}
|
||||
|
||||
// RateLimitConfig defines rate limiting configuration
|
||||
@@ -162,6 +246,7 @@ type RateLimitConfig struct {
|
||||
type DNSServerConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
ListenPort int `yaml:"listen_port"`
|
||||
BindAddress string `yaml:"bind_address"`
|
||||
BackupServer string `yaml:"backup_server"`
|
||||
CustomRecords []DNSRecord `yaml:"custom_records"`
|
||||
}
|
||||
@@ -217,9 +302,15 @@ func LoadConfig(filename string) (*Config, error) {
|
||||
if config.RateLimit.WindowSize == 0 {
|
||||
config.RateLimit.WindowSize = 1 * time.Second
|
||||
}
|
||||
if config.BindAddress == "" {
|
||||
config.BindAddress = DefaultBindAddress
|
||||
}
|
||||
if config.DNSServer.ListenPort == 0 {
|
||||
config.DNSServer.ListenPort = 5353
|
||||
}
|
||||
if config.DNSServer.BindAddress == "" {
|
||||
config.DNSServer.BindAddress = DefaultBindAddress
|
||||
}
|
||||
if config.DNSServer.BackupServer == "" {
|
||||
config.DNSServer.BackupServer = "8.8.8.8:53"
|
||||
}
|
||||
@@ -457,8 +548,9 @@ func GenerateExampleConfig(filename string) error {
|
||||
// Generate server configuration
|
||||
config = Config{
|
||||
InstanceID: "teleport-server-01",
|
||||
ListenAddress: ":8080",
|
||||
ListenAddress: "127.0.0.1:8080",
|
||||
RemoteAddress: "",
|
||||
BindAddress: DefaultBindAddress,
|
||||
Ports: []PortRule{
|
||||
{LocalPort: 80, RemotePort: 80, Protocol: "tcp", TargetHost: "localhost"},
|
||||
},
|
||||
@@ -475,6 +567,7 @@ func GenerateExampleConfig(filename string) error {
|
||||
},
|
||||
DNSServer: DNSServerConfig{
|
||||
ListenPort: 5353,
|
||||
BindAddress: DefaultBindAddress,
|
||||
BackupServer: "8.8.8.8:53",
|
||||
CustomRecords: []DNSRecord{},
|
||||
},
|
||||
@@ -485,6 +578,7 @@ func GenerateExampleConfig(filename string) error {
|
||||
InstanceID: "teleport-client-01",
|
||||
ListenAddress: "",
|
||||
RemoteAddress: "localhost:8080",
|
||||
BindAddress: DefaultBindAddress,
|
||||
Ports: []PortRule{
|
||||
{LocalPort: 8080, RemotePort: 80, Protocol: "tcp", TargetHost: ""},
|
||||
},
|
||||
@@ -501,6 +595,7 @@ func GenerateExampleConfig(filename string) error {
|
||||
},
|
||||
DNSServer: DNSServerConfig{
|
||||
ListenPort: 5353,
|
||||
BindAddress: DefaultBindAddress,
|
||||
BackupServer: "8.8.8.8:53",
|
||||
CustomRecords: []DNSRecord{
|
||||
{Name: "app.local", Type: "A", Value: "127.0.0.1", TTL: 300},
|
||||
|
||||
Reference in New Issue
Block a user