feat: add --http-proxy flag for configurable HTTP CONNECT proxy
Add network.httpProxyUrl config field and --http-proxy CLI flag (default: http://localhost:42051) for apps that only understand HTTP proxies (opencode, Node.js tools, etc.). macOS daemon mode now sets: - ALL_PROXY=socks5h:// for SOCKS5-aware apps (curl, git) - HTTP_PROXY/HTTPS_PROXY=http:// for HTTP-proxy-aware apps Credentials from the SOCKS5 proxy URL are automatically injected into the HTTP proxy URL when not explicitly configured.
This commit is contained in:
@@ -31,6 +31,7 @@ var (
|
|||||||
monitor bool
|
monitor bool
|
||||||
settingsPath string
|
settingsPath string
|
||||||
proxyURL string
|
proxyURL string
|
||||||
|
httpProxyURL string
|
||||||
dnsAddr string
|
dnsAddr string
|
||||||
cmdString string
|
cmdString string
|
||||||
exposePorts []string
|
exposePorts []string
|
||||||
@@ -99,6 +100,7 @@ Configuration file format:
|
|||||||
rootCmd.Flags().BoolVarP(&monitor, "monitor", "m", false, "Monitor and log sandbox violations")
|
rootCmd.Flags().BoolVarP(&monitor, "monitor", "m", false, "Monitor and log sandbox violations")
|
||||||
rootCmd.Flags().StringVarP(&settingsPath, "settings", "s", "", "Path to settings file (default: OS config directory)")
|
rootCmd.Flags().StringVarP(&settingsPath, "settings", "s", "", "Path to settings file (default: OS config directory)")
|
||||||
rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (default: socks5://localhost:42052)")
|
rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (default: socks5://localhost:42052)")
|
||||||
|
rootCmd.Flags().StringVar(&httpProxyURL, "http-proxy", "", "HTTP CONNECT proxy URL (default: http://localhost:42051)")
|
||||||
rootCmd.Flags().StringVar(&dnsAddr, "dns", "", "DNS server address on host (default: localhost:42053)")
|
rootCmd.Flags().StringVar(&dnsAddr, "dns", "", "DNS server address on host (default: localhost:42053)")
|
||||||
rootCmd.Flags().StringVarP(&cmdString, "c", "c", "", "Run command string directly (like sh -c)")
|
rootCmd.Flags().StringVarP(&cmdString, "c", "c", "", "Run command string directly (like sh -c)")
|
||||||
rootCmd.Flags().StringArrayVarP(&exposePorts, "port", "p", nil, "Expose port for inbound connections (can be used multiple times)")
|
rootCmd.Flags().StringArrayVarP(&exposePorts, "port", "p", nil, "Expose port for inbound connections (can be used multiple times)")
|
||||||
@@ -228,6 +230,9 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
if proxyURL != "" {
|
if proxyURL != "" {
|
||||||
cfg.Network.ProxyURL = proxyURL
|
cfg.Network.ProxyURL = proxyURL
|
||||||
}
|
}
|
||||||
|
if httpProxyURL != "" {
|
||||||
|
cfg.Network.HTTPProxyURL = httpProxyURL
|
||||||
|
}
|
||||||
if dnsAddr != "" {
|
if dnsAddr != "" {
|
||||||
cfg.Network.DnsAddr = dnsAddr
|
cfg.Network.DnsAddr = dnsAddr
|
||||||
}
|
}
|
||||||
@@ -240,6 +245,12 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
fmt.Fprintf(os.Stderr, "[greywall] Defaulting proxy to socks5://localhost:42052\n")
|
fmt.Fprintf(os.Stderr, "[greywall] Defaulting proxy to socks5://localhost:42052\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cfg.Network.HTTPProxyURL == "" {
|
||||||
|
cfg.Network.HTTPProxyURL = "http://localhost:42051"
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "[greywall] Defaulting HTTP proxy to http://localhost:42051\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
if cfg.Network.DnsAddr == "" {
|
if cfg.Network.DnsAddr == "" {
|
||||||
cfg.Network.DnsAddr = "localhost:42053"
|
cfg.Network.DnsAddr = "localhost:42053"
|
||||||
if debug {
|
if debug {
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ type Config struct {
|
|||||||
|
|
||||||
// NetworkConfig defines network restrictions.
|
// NetworkConfig defines network restrictions.
|
||||||
type NetworkConfig struct {
|
type NetworkConfig struct {
|
||||||
ProxyURL string `json:"proxyUrl,omitempty"` // External SOCKS5 proxy (e.g. socks5://host:1080)
|
ProxyURL string `json:"proxyUrl,omitempty"` // External SOCKS5 proxy (e.g. socks5://host:1080)
|
||||||
DnsAddr string `json:"dnsAddr,omitempty"` // DNS server address on host (e.g. localhost:3153)
|
HTTPProxyURL string `json:"httpProxyUrl,omitempty"` // HTTP CONNECT proxy (e.g. http://host:42051)
|
||||||
|
DnsAddr string `json:"dnsAddr,omitempty"` // DNS server address on host (e.g. localhost:3153)
|
||||||
AllowUnixSockets []string `json:"allowUnixSockets,omitempty"`
|
AllowUnixSockets []string `json:"allowUnixSockets,omitempty"`
|
||||||
AllowAllUnixSockets bool `json:"allowAllUnixSockets,omitempty"`
|
AllowAllUnixSockets bool `json:"allowAllUnixSockets,omitempty"`
|
||||||
AllowLocalBinding bool `json:"allowLocalBinding,omitempty"`
|
AllowLocalBinding bool `json:"allowLocalBinding,omitempty"`
|
||||||
@@ -203,6 +204,11 @@ func (c *Config) Validate() error {
|
|||||||
return fmt.Errorf("invalid network.proxyUrl %q: %w", c.Network.ProxyURL, err)
|
return fmt.Errorf("invalid network.proxyUrl %q: %w", c.Network.ProxyURL, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.Network.HTTPProxyURL != "" {
|
||||||
|
if err := validateHTTPProxyURL(c.Network.HTTPProxyURL); err != nil {
|
||||||
|
return fmt.Errorf("invalid network.httpProxyUrl %q: %w", c.Network.HTTPProxyURL, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if c.Network.DnsAddr != "" {
|
if c.Network.DnsAddr != "" {
|
||||||
if err := validateHostPort(c.Network.DnsAddr); err != nil {
|
if err := validateHostPort(c.Network.DnsAddr); err != nil {
|
||||||
return fmt.Errorf("invalid network.dnsAddr %q: %w", c.Network.DnsAddr, err)
|
return fmt.Errorf("invalid network.dnsAddr %q: %w", c.Network.DnsAddr, err)
|
||||||
@@ -273,6 +279,24 @@ func validateProxyURL(proxyURL string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateHTTPProxyURL validates an HTTP CONNECT proxy URL.
|
||||||
|
func validateHTTPProxyURL(proxyURL string) error {
|
||||||
|
u, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid URL: %w", err)
|
||||||
|
}
|
||||||
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
return errors.New("HTTP proxy URL must use http:// or https:// scheme")
|
||||||
|
}
|
||||||
|
if u.Hostname() == "" {
|
||||||
|
return errors.New("HTTP proxy URL must include a hostname")
|
||||||
|
}
|
||||||
|
if u.Port() == "" {
|
||||||
|
return errors.New("HTTP proxy URL must include a port")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateHostPort validates a host:port address.
|
// validateHostPort validates a host:port address.
|
||||||
func validateHostPort(addr string) error {
|
func validateHostPort(addr string) error {
|
||||||
// Must contain a colon separating host and port
|
// Must contain a colon separating host and port
|
||||||
@@ -407,9 +431,10 @@ func Merge(base, override *Config) *Config {
|
|||||||
AllowPty: base.AllowPty || override.AllowPty,
|
AllowPty: base.AllowPty || override.AllowPty,
|
||||||
|
|
||||||
Network: NetworkConfig{
|
Network: NetworkConfig{
|
||||||
// ProxyURL/DnsAddr: override wins if non-empty
|
// ProxyURL/HTTPProxyURL/DnsAddr: override wins if non-empty
|
||||||
ProxyURL: mergeString(base.Network.ProxyURL, override.Network.ProxyURL),
|
ProxyURL: mergeString(base.Network.ProxyURL, override.Network.ProxyURL),
|
||||||
DnsAddr: mergeString(base.Network.DnsAddr, override.Network.DnsAddr),
|
HTTPProxyURL: mergeString(base.Network.HTTPProxyURL, override.Network.HTTPProxyURL),
|
||||||
|
DnsAddr: mergeString(base.Network.DnsAddr, override.Network.DnsAddr),
|
||||||
|
|
||||||
// Append slices (base first, then override additions)
|
// Append slices (base first, then override additions)
|
||||||
AllowUnixSockets: mergeStrings(base.Network.AllowUnixSockets, override.Network.AllowUnixSockets),
|
AllowUnixSockets: mergeStrings(base.Network.AllowUnixSockets, override.Network.AllowUnixSockets),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
tunIP = "198.18.0.1"
|
tunIP = "198.18.0.1"
|
||||||
dnsRelayIP = "127.0.0.2"
|
dnsRelayIP = "127.0.0.2"
|
||||||
dnsRelayPort = "15353" // high port; pf rdr rewrites port 53 → this port
|
dnsRelayPort = "15353" // high port; pf rdr rewrites port 53 → this port
|
||||||
defaultDNSTarget = "127.0.0.1:42053" // proxy's DNS resolver (UDP), used when dnsAddr is not configured
|
defaultDNSTarget = "127.0.0.1:42053" // proxy's DNS resolver (UDP), used when dnsAddr is not configured
|
||||||
pfAnchorName = "co.greyhaven.greywall"
|
pfAnchorName = "co.greyhaven.greywall"
|
||||||
|
|
||||||
|
|||||||
@@ -763,27 +763,29 @@ func WrapCommandMacOS(cfg *config.Config, command string, exposedPorts []int, da
|
|||||||
if socks5hURL != "" {
|
if socks5hURL != "" {
|
||||||
// ALL_PROXY uses socks5h:// (DNS resolved at proxy side) for
|
// ALL_PROXY uses socks5h:// (DNS resolved at proxy side) for
|
||||||
// SOCKS5-aware apps (curl, git).
|
// SOCKS5-aware apps (curl, git).
|
||||||
// HTTP_PROXY/HTTPS_PROXY use http:// pointing to the GreyHaven
|
// HTTP_PROXY/HTTPS_PROXY use the configured HTTP CONNECT proxy
|
||||||
// HTTP CONNECT proxy (port 42051) for apps that only understand
|
// for apps that only understand HTTP proxies (opencode, Node.js
|
||||||
// HTTP proxies (opencode, Node.js tools, etc.). The CONNECT
|
// tools, etc.). The CONNECT proxy resolves DNS server-side.
|
||||||
// proxy resolves DNS server-side.
|
httpProxyURL := cfg.Network.HTTPProxyURL
|
||||||
httpProxyURL := "http://localhost:42051"
|
// Inject credentials from the SOCKS5 proxy URL into the HTTP proxy
|
||||||
if u, err := url.Parse(socks5hURL); err == nil {
|
// URL if the HTTP proxy URL doesn't already have credentials.
|
||||||
userinfo := ""
|
if httpProxyURL != "" {
|
||||||
if u.User != nil {
|
if hu, err := url.Parse(httpProxyURL); err == nil && hu.User == nil {
|
||||||
userinfo = u.User.String() + "@"
|
if su, err := url.Parse(socks5hURL); err == nil && su.User != nil {
|
||||||
|
hu.User = su.User
|
||||||
|
httpProxyURL = hu.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
host := u.Hostname()
|
|
||||||
if host == "" {
|
|
||||||
host = "localhost"
|
|
||||||
}
|
|
||||||
httpProxyURL = "http://" + userinfo + host + ":42051"
|
|
||||||
}
|
}
|
||||||
sandboxEnvs = append(sandboxEnvs,
|
sandboxEnvs = append(sandboxEnvs,
|
||||||
"ALL_PROXY="+socks5hURL, "all_proxy="+socks5hURL,
|
"ALL_PROXY="+socks5hURL, "all_proxy="+socks5hURL,
|
||||||
"HTTP_PROXY="+httpProxyURL, "http_proxy="+httpProxyURL,
|
|
||||||
"HTTPS_PROXY="+httpProxyURL, "https_proxy="+httpProxyURL,
|
|
||||||
)
|
)
|
||||||
|
if httpProxyURL != "" {
|
||||||
|
sandboxEnvs = append(sandboxEnvs,
|
||||||
|
"HTTP_PROXY="+httpProxyURL, "http_proxy="+httpProxyURL,
|
||||||
|
"HTTPS_PROXY="+httpProxyURL, "https_proxy="+httpProxyURL,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
termEnvs := getTerminalEnvVars()
|
termEnvs := getTerminalEnvVars()
|
||||||
parts = append(parts, "sudo", "-u", uid, "-g", daemonSession.SandboxGroup, "env")
|
parts = append(parts, "sudo", "-u", uid, "-g", daemonSession.SandboxGroup, "env")
|
||||||
|
|||||||
Reference in New Issue
Block a user