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:
@@ -34,7 +34,7 @@ Flow: User → Client:8080,2222 → Encrypted Tunnel (Port 9000) → Server:80,2
|
||||
**Server Configuration** (`server.yaml`):
|
||||
```yaml
|
||||
instance_id: teleport-server-01
|
||||
listen_address: :9000
|
||||
listen_address: 127.0.0.1:9000 # loopback; use 0.0.0.0:9000 to publish
|
||||
remote_address: ""
|
||||
ports:
|
||||
- "tcp://192.168.1.100:80"
|
||||
@@ -50,6 +50,7 @@ write_timeout: 30s
|
||||
instance_id: teleport-client-01
|
||||
listen_address: ""
|
||||
remote_address: server.example.com:9000
|
||||
bind_address: 127.0.0.1
|
||||
ports:
|
||||
- "tcp://80:8080"
|
||||
- "tcp://22:2222"
|
||||
@@ -59,9 +60,11 @@ read_timeout: 30s
|
||||
write_timeout: 30s
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
- User connects to `localhost:8080` → Traffic flows through encrypted tunnel on port 9000 → Server forwards to remote web server at `192.168.1.100:80`
|
||||
- User connects to `localhost:2222` → Traffic flows through same encrypted tunnel on port 9000 → Server forwards to remote SSH server at `192.168.1.200:22`
|
||||
**Usage**:
|
||||
- User connects to `127.0.0.1:8080` (client local listener, loopback by default) → Traffic flows through encrypted tunnel on `127.0.0.1:9000` → Server forwards to remote web server at `192.168.1.100:80`
|
||||
- User connects to `127.0.0.1:2222` → Traffic flows through the same encrypted tunnel → Server forwards to remote SSH server at `192.168.1.200:22`
|
||||
|
||||
Local client ports bind `127.0.0.1` unless you set `bind_address: 0.0.0.0` or a per-rule host such as `"tcp://22:0.0.0.0:2222"`. Server `listen_address: 127.0.0.1:9000` is loopback-only; `0.0.0.0:9000` (or `:9000`) publishes the tunnel on all interfaces and must be explicit.
|
||||
|
||||
Both services share the same encrypted tunnel connection!
|
||||
|
||||
@@ -116,7 +119,7 @@ Generate example configuration files:
|
||||
|
||||
```yaml
|
||||
instance_id: teleport-server-01
|
||||
listen_address: :8080
|
||||
listen_address: 127.0.0.1:8080 # use 0.0.0.0:8080 to accept remote clients
|
||||
remote_address: ""
|
||||
ports:
|
||||
- "tcp://localhost:80"
|
||||
@@ -143,6 +146,7 @@ dns_server:
|
||||
instance_id: teleport-client-01
|
||||
listen_address: ""
|
||||
remote_address: localhost:8080
|
||||
bind_address: 127.0.0.1
|
||||
ports:
|
||||
- "tcp://80:8080"
|
||||
encryption_key: your-secure-encryption-key-change-this-to-something-random
|
||||
@@ -169,16 +173,19 @@ dns_server:
|
||||
## Configuration Fields
|
||||
|
||||
- `instance_id`: Unique identifier for this teleport instance
|
||||
- `listen_address`: Address to listen on (server mode) - format: `host:port`
|
||||
- `listen_address`: Address the **server** binds for the encrypted tunnel (`host:port`). Examples use `127.0.0.1:9000`. Use `0.0.0.0:9000` or `:9000` only when you intend to publish the server on all interfaces.
|
||||
- `remote_address`: Address of remote teleport server (client mode) - format: `host:port`
|
||||
- `bind_address`: Host for **client** local TCP/UDP listeners (default: `127.0.0.1`). Set to `0.0.0.0` to accept connections from other machines. Publishing on all interfaces is never implicit.
|
||||
- `ports`: Array of port forwarding rules in URL-style format
|
||||
- **Server format**: `protocol://target:targetport` - forwards to remote target
|
||||
- **Client format**: `protocol://targetport:localport` - forwards to teleport server's targetport, listens on localport
|
||||
- **Client format**: `protocol://targetport:localport` - forwards to teleport server's targetport, listens on `bind_address:localport` (default `127.0.0.1`)
|
||||
- **Client with bind**: `protocol://targetport:bindhost:localport` - per-rule listen host, e.g. `"tcp://22:0.0.0.0:2222"`
|
||||
- Examples:
|
||||
- `"tcp://localhost:80"` (server) - listen on port 80, forward to localhost:80
|
||||
- `"tcp://server-a:22"` (server) - listen on port 22, forward to server-a:22
|
||||
- `"tcp://80:8080"` (client) - listen on port 8080, forward to teleport server's port 80
|
||||
- `"udp://53:5353"` (client) - listen on port 5353, forward to teleport server's port 53
|
||||
- `"tcp://localhost:80"` (server) - forward to localhost:80
|
||||
- `"tcp://server-a:22"` (server) - forward to server-a:22
|
||||
- `"tcp://80:8080"` (client) - listen on `127.0.0.1:8080`, forward to teleport server's port 80
|
||||
- `"tcp://22:0.0.0.0:2222"` (client) - listen on all interfaces port 2222
|
||||
- `"udp://53:5353"` (client) - listen on `127.0.0.1:5353`, forward to teleport server's port 53
|
||||
- `encryption_key`: Shared secret key for encryption (must be the same on both sides)
|
||||
- `keep_alive`: Enable TCP keep-alive
|
||||
- `read_timeout`: Read timeout duration
|
||||
@@ -192,6 +199,7 @@ dns_server:
|
||||
- `dns_server`: DNS server configuration
|
||||
- `enabled`: Enable built-in DNS server
|
||||
- `listen_port`: Port for DNS server to listen on
|
||||
- `bind_address`: DNS listen host (default: `127.0.0.1`). Set to `0.0.0.0` to serve DNS on all interfaces; the default is not an open recursive forwarder on every interface.
|
||||
- `backup_server`: Backup DNS server for fallback (e.g., "8.8.8.8:53")
|
||||
- `custom_records`: Array of custom DNS records
|
||||
- `name`: Domain name (e.g., "example.com")
|
||||
@@ -278,14 +286,15 @@ Teleport uses URL-style port mapping format for cleaner configuration:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "tcp://80:8080" # Listen on port 8080, forward to teleport server's port 80
|
||||
- "udp://53:5353" # Listen on port 5353, forward to teleport server's port 53
|
||||
- "tcp://22:2222" # Listen on port 2222, forward to teleport server's port 22
|
||||
- "tcp://80:8080" # Listen on 127.0.0.1:8080, forward to teleport server's port 80
|
||||
- "udp://53:5353" # Listen on 127.0.0.1:5353, forward to teleport server's port 53
|
||||
- "tcp://22:0.0.0.0:2222" # Explicit all-interfaces bind
|
||||
```
|
||||
|
||||
**Format**:
|
||||
- **Server**: `"protocol://target:targetport"` - listen on targetport, forward to target:targetport
|
||||
- **Client**: `"protocol://targetport:localport"` - listen on localport, forward to teleport server's targetport
|
||||
**Format**:
|
||||
- **Server**: `"protocol://target:targetport"` - forward to target:targetport
|
||||
- **Client**: `"protocol://targetport:localport"` - listen on `127.0.0.1:localport` (or `bind_address`), forward to teleport server's targetport
|
||||
- **Client with bind**: `"protocol://targetport:bindhost:localport"` - listen on bindhost:localport
|
||||
|
||||
**Examples:**
|
||||
```yaml
|
||||
@@ -295,11 +304,12 @@ ports:
|
||||
- "tcp://server-a:22" # Listen on port 22, forward to server-a:22
|
||||
- "udp://dns-server:53" # Listen on port 53, forward to dns-server:53
|
||||
|
||||
# Client configurations
|
||||
# Client configurations
|
||||
ports:
|
||||
- "tcp://80:8080" # Listen on port 8080, forward to teleport server's port 80
|
||||
- "tcp://22:2222" # Listen on port 2222, forward to teleport server's port 22
|
||||
- "udp://53:5353" # Listen on port 5353, forward to teleport server's port 53
|
||||
- "tcp://80:8080" # Listen on 127.0.0.1:8080, forward to teleport server's port 80
|
||||
- "tcp://22:2222" # Listen on 127.0.0.1:2222, forward to teleport server's port 22
|
||||
- "tcp://22:0.0.0.0:2222" # Listen on all interfaces (explicit)
|
||||
- "udp://53:5353" # Listen on 127.0.0.1:5353, forward to teleport server's port 53
|
||||
```
|
||||
|
||||
**Example: Remote SSH Access**
|
||||
@@ -308,10 +318,10 @@ To access SSH on Server A through teleport server on Server B:
|
||||
**Server B configuration:**
|
||||
```yaml
|
||||
instance_id: teleport-server-b
|
||||
listen_address: :8080
|
||||
listen_address: 127.0.0.1:8080
|
||||
remote_address: ""
|
||||
ports:
|
||||
- "tcp://server-a:22" # Listen on port 22, forward to server-a:22
|
||||
- "tcp://server-a:22" # Forward to server-a:22
|
||||
encryption_key: your-shared-key
|
||||
```
|
||||
|
||||
@@ -320,8 +330,9 @@ encryption_key: your-shared-key
|
||||
instance_id: teleport-client-c
|
||||
listen_address: ""
|
||||
remote_address: server-b:8080
|
||||
bind_address: 127.0.0.1
|
||||
ports:
|
||||
- "tcp://22:2222" # Listen on local port 2222, forward to teleport server's port 22
|
||||
- "tcp://22:2222" # Listen on 127.0.0.1:2222, forward to teleport server's port 22
|
||||
encryption_key: your-shared-key
|
||||
```
|
||||
|
||||
@@ -345,7 +356,7 @@ The program validates your configuration and will show clear error messages if:
|
||||
|
||||
**Valid Server Configuration:**
|
||||
```yaml
|
||||
listen_address: :8080
|
||||
listen_address: 127.0.0.1:8080
|
||||
remote_address: ""
|
||||
ports:
|
||||
- "tcp://localhost:22"
|
||||
@@ -355,6 +366,7 @@ ports:
|
||||
```yaml
|
||||
listen_address: ""
|
||||
remote_address: server:8080
|
||||
bind_address: 127.0.0.1
|
||||
ports:
|
||||
- "tcp://22:2222"
|
||||
```
|
||||
@@ -369,6 +381,7 @@ ports:
|
||||
- Encryption keys are validated for entropy and strength
|
||||
- Logging includes automatic sanitization of sensitive data
|
||||
- Rate limiting prevents abuse and DoS attacks
|
||||
- Client local listeners and the built-in DNS server bind `127.0.0.1` by default so LAN/internet hosts cannot use your tunnel unless you set `bind_address` / a per-rule host to `0.0.0.0`
|
||||
|
||||
## Example Use Cases
|
||||
|
||||
@@ -394,7 +407,7 @@ ports:
|
||||
- Generate: `./teleport -generate-config -config teleport-client.yaml`
|
||||
- Edit the configuration file with local ports and remote server address
|
||||
- Run: `./teleport -config teleport-client.yaml`
|
||||
- Connect to `localhost:local_port` to access the remote service
|
||||
- Connect to `127.0.0.1:local_port` (loopback by default) to access the remote service
|
||||
|
||||
## Multi-Client Support
|
||||
|
||||
@@ -428,6 +441,7 @@ The built-in DNS server provides:
|
||||
dns_server:
|
||||
enabled: true
|
||||
listen_port: 5353
|
||||
bind_address: 127.0.0.1
|
||||
backup_server: 8.8.8.8:53
|
||||
custom_records:
|
||||
- name: api.local
|
||||
@@ -510,8 +524,8 @@ Teleport includes sophisticated logging with:
|
||||
|
||||
- The encryption key must be identical on both server and client
|
||||
- Use `./teleport --generate-key` to create a secure random encryption key
|
||||
- The server listens on the specified `listen_address` for incoming connections
|
||||
- The client connects to the remote server and forwards local connections
|
||||
- The server listens on the specified `listen_address` for incoming tunnel connections (examples bind loopback; `0.0.0.0` must be explicit)
|
||||
- The client connects to the remote server and forwards local connections from `bind_address` (default `127.0.0.1`)
|
||||
- All port forwarding is bidirectional
|
||||
- Port format uses URL-style conventions: `"protocol://target:port"`
|
||||
- Configuration files use YAML format for better readability
|
||||
|
||||
@@ -93,9 +93,11 @@ func (tc *TeleportClient) startPortForwarding(rule config.PortRule) {
|
||||
|
||||
// startTCPForwarding starts TCP port forwarding
|
||||
func (tc *TeleportClient) startTCPForwarding(rule config.PortRule) {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", rule.LocalPort))
|
||||
listenAddr := tc.config.ClientListenAddr(rule)
|
||||
listener, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"addr": listenAddr,
|
||||
"port": rule.LocalPort,
|
||||
"error": err,
|
||||
}).Error("Failed to start TCP listener")
|
||||
@@ -104,6 +106,7 @@ func (tc *TeleportClient) startTCPForwarding(rule config.PortRule) {
|
||||
defer listener.Close()
|
||||
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"listen_addr": listenAddr,
|
||||
"local_port": rule.LocalPort,
|
||||
"remote_addr": tc.config.RemoteAddress,
|
||||
"remote_port": rule.RemotePort,
|
||||
@@ -254,9 +257,11 @@ func (tc *TeleportClient) returnConnection(conn net.Conn) {
|
||||
|
||||
// startUDPForwarding starts UDP port forwarding
|
||||
func (tc *TeleportClient) startUDPForwarding(rule config.PortRule) {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", rule.LocalPort))
|
||||
listenAddr := tc.config.ClientListenAddr(rule)
|
||||
addr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||
if err != nil {
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"addr": listenAddr,
|
||||
"port": rule.LocalPort,
|
||||
"error": err,
|
||||
}).Error("Failed to resolve UDP address")
|
||||
@@ -266,6 +271,7 @@ func (tc *TeleportClient) startUDPForwarding(rule config.PortRule) {
|
||||
conn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"addr": listenAddr,
|
||||
"port": rule.LocalPort,
|
||||
"error": err,
|
||||
}).Error("Failed to start UDP listener")
|
||||
@@ -277,6 +283,7 @@ func (tc *TeleportClient) startUDPForwarding(rule config.PortRule) {
|
||||
tc.udpMutex.Unlock()
|
||||
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"listen_addr": listenAddr,
|
||||
"local_port": rule.LocalPort,
|
||||
"remote_addr": tc.config.RemoteAddress,
|
||||
"remote_port": rule.RemotePort,
|
||||
@@ -641,7 +648,12 @@ func (tc *TeleportClient) handleTCPConnection(clientConn net.Conn, rule config.P
|
||||
defer tc.returnConnection(serverConn)
|
||||
|
||||
// Send port forward request to server
|
||||
request := types.PortForwardRequest(rule)
|
||||
request := types.PortForwardRequest{
|
||||
LocalPort: rule.LocalPort,
|
||||
RemotePort: rule.RemotePort,
|
||||
Protocol: rule.Protocol,
|
||||
TargetHost: rule.TargetHost,
|
||||
}
|
||||
|
||||
if err := tc.sendRequestToConnection(serverConn, request); err != nil {
|
||||
logger.WithField("error", err).Error("Failed to send port forward request")
|
||||
|
||||
+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},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -135,6 +136,14 @@ encryption_key: a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03
|
||||
if config.DNSServer.BackupServer != "8.8.8.8:53" {
|
||||
t.Errorf("Expected default backup server '8.8.8.8:53', got '%s'", config.DNSServer.BackupServer)
|
||||
}
|
||||
|
||||
if config.BindAddress != DefaultBindAddress {
|
||||
t.Errorf("Expected default BindAddress %q, got %q", DefaultBindAddress, config.BindAddress)
|
||||
}
|
||||
|
||||
if config.DNSServer.BindAddress != DefaultBindAddress {
|
||||
t.Errorf("Expected default DNS BindAddress %q, got %q", DefaultBindAddress, config.DNSServer.BindAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectMode(t *testing.T) {
|
||||
@@ -361,3 +370,218 @@ encryption_key: test-key
|
||||
t.Errorf("Expected error message to mention 'protocol://', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultLocalListenAddrIsLoopback(t *testing.T) {
|
||||
if got := LocalListenAddr("", 9000); got != "127.0.0.1:9000" {
|
||||
t.Fatalf("empty host: got %q want 127.0.0.1:9000", got)
|
||||
}
|
||||
if got := LocalListenAddr("127.0.0.1", 2222); got != "127.0.0.1:2222" {
|
||||
t.Fatalf("loopback host: got %q", got)
|
||||
}
|
||||
|
||||
cfg := &Config{}
|
||||
tcpRule := PortRule{LocalPort: 8080, RemotePort: 80, Protocol: "tcp"}
|
||||
udpRule := PortRule{LocalPort: 5353, RemotePort: 53, Protocol: "udp"}
|
||||
if got := cfg.ClientListenAddr(tcpRule); got != "127.0.0.1:8080" {
|
||||
t.Fatalf("default TCP listen: got %q want 127.0.0.1:8080", got)
|
||||
}
|
||||
if got := cfg.ClientListenAddr(udpRule); got != "127.0.0.1:5353" {
|
||||
t.Fatalf("default UDP listen: got %q want 127.0.0.1:5353", got)
|
||||
}
|
||||
|
||||
dnsCfg := DNSServerConfig{ListenPort: 5353}
|
||||
if got := dnsCfg.ListenAddr(); got != "127.0.0.1:5353" {
|
||||
t.Fatalf("default DNS listen: got %q want 127.0.0.1:5353", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitBindAddressOverride(t *testing.T) {
|
||||
if got := LocalListenAddr("0.0.0.0", 9000); got != "0.0.0.0:9000" {
|
||||
t.Fatalf("0.0.0.0 override: got %q", got)
|
||||
}
|
||||
|
||||
cfg := &Config{BindAddress: "0.0.0.0"}
|
||||
rule := PortRule{LocalPort: 2222, RemotePort: 22, Protocol: "tcp"}
|
||||
if got := cfg.ClientListenAddr(rule); got != "0.0.0.0:2222" {
|
||||
t.Fatalf("global bind_address 0.0.0.0: got %q", got)
|
||||
}
|
||||
|
||||
rule.BindAddress = "10.0.0.5"
|
||||
if got := cfg.ClientListenAddr(rule); got != "10.0.0.5:2222" {
|
||||
t.Fatalf("per-rule host should win: got %q", got)
|
||||
}
|
||||
|
||||
dnsCfg := DNSServerConfig{ListenPort: 5353, BindAddress: "0.0.0.0"}
|
||||
if got := dnsCfg.ListenAddr(); got != "0.0.0.0:5353" {
|
||||
t.Fatalf("DNS bind_address 0.0.0.0: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalListenAddrBinds(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", LocalListenAddr("", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("listen default: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
ip := ln.Addr().(*net.TCPAddr).IP
|
||||
if !ip.IsLoopback() {
|
||||
t.Fatalf("default TCP bind is not loopback: %v", ip)
|
||||
}
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", LocalListenAddr("", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("resolve default udp: %v", err)
|
||||
}
|
||||
uc, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("listen default udp: %v", err)
|
||||
}
|
||||
defer uc.Close()
|
||||
if !uc.LocalAddr().(*net.UDPAddr).IP.IsLoopback() {
|
||||
t.Fatalf("default UDP bind is not loopback: %v", uc.LocalAddr())
|
||||
}
|
||||
|
||||
all, err := net.Listen("tcp", LocalListenAddr("0.0.0.0", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("listen 0.0.0.0: %v", err)
|
||||
}
|
||||
defer all.Close()
|
||||
if !all.Addr().(*net.TCPAddr).IP.IsUnspecified() {
|
||||
t.Fatalf("explicit 0.0.0.0 bind is not unspecified: %v", all.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortRuleBindHostFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
expected PortRule
|
||||
}{
|
||||
{
|
||||
name: "client with loopback bind",
|
||||
config: `
|
||||
instance_id: test
|
||||
remote_address: localhost:8080
|
||||
ports:
|
||||
- tcp://22:127.0.0.1:2222
|
||||
encryption_key: a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03
|
||||
`,
|
||||
expected: PortRule{LocalPort: 2222, RemotePort: 22, Protocol: "tcp", BindAddress: "127.0.0.1"},
|
||||
},
|
||||
{
|
||||
name: "client with all-interfaces bind",
|
||||
config: `
|
||||
instance_id: test
|
||||
remote_address: localhost:8080
|
||||
ports:
|
||||
- tcp://22:0.0.0.0:2222
|
||||
encryption_key: a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03
|
||||
`,
|
||||
expected: PortRule{LocalPort: 2222, RemotePort: 22, Protocol: "tcp", BindAddress: "0.0.0.0"},
|
||||
},
|
||||
{
|
||||
name: "client two-part still defaults bind via config",
|
||||
config: `
|
||||
instance_id: test
|
||||
remote_address: localhost:8080
|
||||
ports:
|
||||
- tcp://80:8080
|
||||
encryption_key: a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03
|
||||
`,
|
||||
expected: PortRule{LocalPort: 8080, RemotePort: 80, Protocol: "tcp", BindAddress: ""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configFile := filepath.Join(tempDir, "test-config.yaml")
|
||||
if err := os.WriteFile(configFile, []byte(tt.config), 0644); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
cfg, err := LoadConfig(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if len(cfg.Ports) != 1 {
|
||||
t.Fatalf("expected 1 port, got %d", len(cfg.Ports))
|
||||
}
|
||||
port := cfg.Ports[0]
|
||||
if port.LocalPort != tt.expected.LocalPort || port.RemotePort != tt.expected.RemotePort ||
|
||||
port.Protocol != tt.expected.Protocol || port.BindAddress != tt.expected.BindAddress {
|
||||
t.Errorf("got %+v want %+v", port, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigGlobalBindAddress(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configFile := filepath.Join(tempDir, "cfg.yaml")
|
||||
content := `
|
||||
instance_id: test
|
||||
remote_address: localhost:8080
|
||||
bind_address: 0.0.0.0
|
||||
ports:
|
||||
- tcp://80:8080
|
||||
encryption_key: a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03
|
||||
dns_server:
|
||||
enabled: true
|
||||
listen_port: 5353
|
||||
bind_address: 0.0.0.0
|
||||
backup_server: 8.8.8.8:53
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, err := LoadConfig(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.BindAddress != "0.0.0.0" {
|
||||
t.Fatalf("BindAddress got %q", cfg.BindAddress)
|
||||
}
|
||||
if cfg.DNSServer.BindAddress != "0.0.0.0" {
|
||||
t.Fatalf("DNS BindAddress got %q", cfg.DNSServer.BindAddress)
|
||||
}
|
||||
if got := cfg.ClientListenAddr(cfg.Ports[0]); got != "0.0.0.0:8080" {
|
||||
t.Fatalf("client listen got %q", got)
|
||||
}
|
||||
if got := cfg.DNSServer.ListenAddr(); got != "0.0.0.0:5353" {
|
||||
t.Fatalf("dns listen got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateExampleConfigUsesLoopback(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
serverFile := filepath.Join(tempDir, "server.yaml")
|
||||
if err := GenerateExampleConfig(serverFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := os.ReadFile(serverFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := string(data)
|
||||
if strings.Contains(body, "listen_address: :8080") || strings.Contains(body, "listen_address: :9000") {
|
||||
t.Fatalf("generated server config still documents all-interfaces listen: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, "127.0.0.1:8080") {
|
||||
t.Fatalf("generated server config missing loopback listen_address: %s", body)
|
||||
}
|
||||
|
||||
clientFile := filepath.Join(tempDir, "client.yaml")
|
||||
if err := GenerateExampleConfig(clientFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, err := LoadConfig(clientFile)
|
||||
if err != nil {
|
||||
t.Fatalf("reload generated client: %v", err)
|
||||
}
|
||||
if cfg.BindAddress != DefaultBindAddress {
|
||||
t.Fatalf("generated client bind_address %q", cfg.BindAddress)
|
||||
}
|
||||
if got := cfg.ClientListenAddr(cfg.Ports[0]); !strings.HasPrefix(got, "127.0.0.1:") {
|
||||
t.Fatalf("generated client local listen %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -1,7 +1,6 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -18,9 +17,10 @@ func StartDNSServer(cfg *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
listenAddr := cfg.DNSServer.ListenAddr()
|
||||
// Create DNS server
|
||||
server := &dns.Server{
|
||||
Addr: fmt.Sprintf(":%d", cfg.DNSServer.ListenPort),
|
||||
Addr: listenAddr,
|
||||
Net: "udp",
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ func StartDNSServer(cfg *config.Config) {
|
||||
handleDNSQuery(w, r, cfg)
|
||||
})
|
||||
|
||||
logger.WithField("port", cfg.DNSServer.ListenPort).Info("DNS server started")
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"addr": listenAddr,
|
||||
"port": cfg.DNSServer.ListenPort,
|
||||
}).Info("DNS server started")
|
||||
|
||||
// Start server
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user