Skip to content

Commit 50ac0e9

Browse files
authored
Enhance proxy handling and add IP name caching (#44)
* Enhance proxy handling and add IP name caching - Added support for proxy name usage in connection requests to work around strict proxies. - Enhanced test coverage for proxy functionality and integration tests. * Remove output comments from example CA tests
1 parent 30ea1c1 commit 50ac0e9

File tree

34 files changed

+1796
-677
lines changed

34 files changed

+1796
-677
lines changed

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ tunnel, allowing Probely to scan your internal applications.
88
The Agent is open-source, and the code is freely available on the official
99
[repository](https://github.com/probely/farcaster-onprem-agent).
1010

11-
The following diagram shows an example network topology for Farcaster agent based
12-
connectivity from a private client network (on-premise, private cloud, CI/CD, etc.) to
11+
The following diagram shows an example network topology for Farcaster agent based
12+
connectivity from a private client network (on-premise, private cloud, CI/CD, etc.) to
1313
the Probely Cloud infrastructure.
1414

1515
![Farcaster high-level network architecture](./assets/img_Farcaster_Network_Overview.png)
@@ -44,18 +44,18 @@ means: *all ports from 1024 to 2048, inclusive*.
4444
Notes:
4545

4646
1. `<agent-ip>` is the internal IP of the machine on your network where Probely's Farcaster Agent is running. The agent uses it to communicate with the Probely server.
47-
2. `<target-ip>` is the internal IP of your web application.
47+
2. `<target-ip>` is the internal IP of your web application.
4848
If your target is configured to use internal extra-hosts, include their IPs here.
4949
The same goes for the target login URL if a different internal web application serves it.
5050
3. `<target-port>` is the service port of the server of your web application.
5151
Typical values are 80 and 443.
52-
4. The IP addresses of these hosts are subject to change. We recommend allowing
52+
4. The IP addresses of these hosts are subject to change. We recommend allowing
5353
web access for the agent VM (HTTP and HTTPS ports). If this is not possible, the agent
5454
will use an HTTP proxy if you set the `HTTP_PROXY` variable.
5555
5. At this time, the hosts are: `registry.docker.io` and `registry-1.docker.io`
5656
6. This server receives connections from potentially vulnerable systems on your infrastructure.
5757
It is used, for example, to detect "Log4Shell"-type vulnerabilities.
58-
58+
5959
# Installation
6060

6161
The agent runs on a Docker container. It should work on any system with a Docker installation.
@@ -117,32 +117,32 @@ Probely's support team.
117117
Connecting to Probely (via UDP) ... done
118118
Setting local gateway rules ... done
119119
Starting WireGuard gateway ... done
120-
120+
121121
Running...
122122
```
123123
124-
Once up and running, the Agent in the Docker container knows the URL or IP of the target to scan from the target configuration in Probely. The Agent communicates with Probely to get this information before starting a scan.
124+
Once up and running, the Agent in the Docker container knows the URL or IP of the target to scan from the target configuration in Probely. The Agent communicates with Probely to get this information before starting a scan.
125125
Learn more about [how to scan internal applications with a Scanning Agent](https://help.probely.com/en/articles/4615595-how-to-scan-internal-applications-with-a-scanning-agent).
126126
127127
### Connection issues
128128
If the Agent is not connecting to Probely, please ensure that your [firewall](#network-requirements) is properly configured.
129-
130-
Alternatively, the agent can use an HTTP proxy to connect to Probely if the `HTTP_PROXY` environment variable is set on the `docker-compose.yml` file.
129+
130+
Alternatively, the agent can use a proxy to connect to Probely using standard environment variables. The agent honors `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` for outbound connections. HTTPS proxies are supported. `ALL_PROXY` is honored for WebSocket connections (ws://, wss://) via the standard library HTTP transport, but not for raw TCP connections.
131131
While the agent can use an HTTP proxy or a direct TCP connection to Probely, this can cause poor network performance. For more information, see this article about the [TCP Meltdown](https://web.archive.org/web/20220103191127/http://sites.inka.de/bigred/devel/tcp-tcp.html) problem. We **strongly recommend** that you allow the agent to connect to `54.247.135.113`, `44.212.186.140`, and `54.253.10.194` on `UDP` port `443`.
132132
133133
### Unsuccessful UDP connection issues
134-
If the Agent is not connecting through UDP, and you are getting the log:
135-
134+
If the Agent is not connecting through UDP, and you are getting the log:
135+
136136
```
137137
...
138138
Connecting to Probely (via UDP) ... unsuccessful
139139
Configuring fallback TCP tunnel ... done
140140
Connecting to Probely (via TCP) ... done
141141
...
142142
```
143-
144-
It's because the UDP connection is being blocked.
145-
143+
144+
It's because the UDP connection is being blocked.
145+
146146
To confirm if nothing is blocking the UDP connections, you can set up a UDP server using the following script **outside your network** to "echo" the received messages:
147147
148148
```python
@@ -163,8 +163,8 @@ if __name__ == "__main__":
163163
udp_server()
164164
```
165165
166-
And test it with:
167-
166+
And test it with:
167+
168168
```shell
169169
$ echo "AAAAAA" | nc -w 3 -u xx.xx.xx.xx 12345
170170
Received your message: AAAAAA

compose/docker-compose.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ services:
1515
# Probely's API URL. If not set, the Agent will use the API URLs for all regions.
1616
#
1717
# FARCASTER_FORCE_TCP
18-
# If set to true, the Agent will use TCP to connect to Probely.
18+
# If set to true, the Agent will use TCP to connect to Probely/Snyk.
19+
#
20+
# FARCASTER_PROXY_NAMES
21+
# If set to true, the Agent will use hostnames in proxy requests when available.
1922
#
2023
# HTTP_PROXY (optional)
2124
# An advanced option that can be used to configure an HTTP proxy for the Agent to connect to Probely.
@@ -25,10 +28,11 @@ services:
2528
- FARCASTER_API_URL
2629
- HTTP_PROXY
2730
- FARCASTER_FORCE_TCP
31+
- FARCASTER_PROXY_NAMES
2832
tmpfs:
2933
- /run
3034
cap_add:
31-
# Required for kernel support. If you remove this, the Agent will fall back to a userspace TCP/IP stack.
35+
# Required to use the kernel WireGuard implementation. If you remove this, the Agent will fall back to a userspace implementation.
3236
- NET_ADMIN
3337
restart: unless-stopped
3438

farcaster-go/agent/agent.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,26 +97,29 @@ type Agent struct {
9797
conns atomic.Uint32
9898
cancel chan struct{}
9999
log *zap.SugaredLogger
100-
// WaitGroup to track background goroutines
100+
// WaitGroup to track background goroutines.
101101
wg sync.WaitGroup
102-
// Flag to track if Close has already been called
102+
// Flag to track if Close has already been called.
103103
closing atomic.Bool
104-
// Enable IPv6 DNS resolution
104+
// Enable IPv6 DNS resolution.
105105
useIPv6 bool
106+
// Use hostnames in proxy requests when available.
107+
proxyUseNames bool
106108
}
107109

108110
// New creates a new agent.
109-
func New(token string, apiURLs []string, logger *zap.SugaredLogger, useIPv6 bool) *Agent {
111+
func New(token string, apiURLs []string, logger *zap.SugaredLogger, useIPv6 bool, proxyUseNames bool) *Agent {
110112
if logger == nil {
111113
logger = zap.NewNop().Sugar()
112114
}
113115
return &Agent{
114-
State: newState(),
115-
token: token,
116-
apiURLs: apiURLs,
117-
cancel: make(chan struct{}, 1), // Use buffered channel to ensure signal is not lost
118-
log: logger,
119-
useIPv6: useIPv6,
116+
State: newState(),
117+
token: token,
118+
apiURLs: apiURLs,
119+
cancel: make(chan struct{}, 1), // Use buffered channel to ensure signal is not lost
120+
log: logger,
121+
useIPv6: useIPv6,
122+
proxyUseNames: proxyUseNames,
120123
}
121124
}
122125

@@ -234,7 +237,7 @@ func (a *Agent) up(bind conn.Bind) error {
234237
// route traffic from remote peers to the local network without requiring
235238
// special privileges or devices (e.g. /dev/net/tun).
236239
mtu := min(gwCfg.MTU, 1340)
237-
a.gw, err = netstack.NewTUN(addr, "gateway", mtu, a.log, a.useIPv6)
240+
a.gw, err = netstack.NewTUN(addr, "gateway", mtu, a.log, a.useIPv6, a.proxyUseNames)
238241
if err != nil {
239242
return fmt.Errorf("failed to create gateway: %w", err)
240243
}

farcaster-go/agent/agent_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import (
1010
var token = os.Getenv("FARCASTER_AGENT_TOKEN")
1111

1212
func TestAgentLifecycle(t *testing.T) {
13+
if token == "" {
14+
t.Skip("Skipping TestAgentLifecycle: FARCASTER_AGENT_TOKEN not set")
15+
}
1316
logger := zap.NewNop().Sugar()
1417
useIPv6 := false
15-
a := New(token, nil, logger, useIPv6)
18+
proxyUseNames := false
19+
a := New(token, nil, logger, useIPv6, proxyUseNames)
1620
if a.CheckToken() != nil {
1721
t.Error("Valid token considered invalid")
1822
}
@@ -38,7 +42,7 @@ func TestAgentLifecycle(t *testing.T) {
3842
t.Logf("Closing agent a...")
3943
a.Close()
4044

41-
b := New(token, nil, logger, useIPv6)
45+
b := New(token, nil, logger, useIPv6, proxyUseNames)
4246
if b.CheckToken() != nil {
4347
t.Error("Valid token considered invalid")
4448
}

farcaster-go/cmd/farcasterd/root.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os/signal"
77
"path/filepath"
88
"runtime"
9+
"strconv"
910
"strings"
1011
"syscall"
1112
"time"
@@ -29,16 +30,17 @@ const (
2930
)
3031

3132
type agentConfig struct {
32-
token string
33-
apiURLs []string
34-
checkToken bool
35-
controlAPI string
36-
group string
37-
showVers bool
38-
logPath string
39-
debug bool
40-
apiURL string
41-
ipv6 bool
33+
token string
34+
apiURLs []string
35+
checkToken bool
36+
controlAPI string
37+
group string
38+
showVers bool
39+
logPath string
40+
debug bool
41+
apiURL string
42+
ipv6 bool
43+
proxyUseNames bool
4244
}
4345

4446
var (
@@ -94,7 +96,16 @@ func init() {
9496
rootCmd.PersistentFlags().BoolVarP(&appCfg.showVers, "version", "v", false, "Print the version and exit")
9597
rootCmd.PersistentFlags().StringVarP(&appCfg.logPath, "log", "l", "", "Log file path. Log to stderr if not specified")
9698
rootCmd.PersistentFlags().BoolVarP(&appCfg.debug, "debug", "d", false, "Enable debug logging")
99+
// Default from env var if present
100+
defaultProxyUseNames := false
101+
if v := strings.TrimSpace(os.Getenv("FARCASTER_PROXY_NAMES")); v != "" {
102+
if b, err := strconv.ParseBool(v); err == nil {
103+
defaultProxyUseNames = b
104+
}
105+
}
106+
97107
rootCmd.PersistentFlags().BoolVarP(&appCfg.ipv6, "ipv6", "", false, "Enable IPv6/AAAA DNS query resolution")
108+
rootCmd.PersistentFlags().BoolVar(&appCfg.proxyUseNames, "proxy-names", defaultProxyUseNames, "Use hostnames instead of IPs in proxy CONNECT/SOCKS5 requests")
98109
}
99110

100111
// Execute runs the agent.
@@ -126,7 +137,7 @@ func runAgent(cfg agentConfig) {
126137

127138
// If the --check-token flag is set, we just check if the token is valid.
128139
if cfg.checkToken {
129-
a := agent.New(cfg.token, cfg.apiURLs, logger, cfg.ipv6)
140+
a := agent.New(cfg.token, cfg.apiURLs, logger, cfg.ipv6, cfg.proxyUseNames)
130141
if err := a.CheckToken(); err != nil {
131142
logger.Errorf("Token validation failed: %v", err)
132143
exit(1)
@@ -151,7 +162,7 @@ func runAgent(cfg agentConfig) {
151162
}
152163

153164
startAgent := func() error {
154-
a := agent.New(cfg.token, cfg.apiURLs, logger, cfg.ipv6)
165+
a := agent.New(cfg.token, cfg.apiURLs, logger, cfg.ipv6, cfg.proxyUseNames)
155166
if err := a.ConnectWait(maxConnTries); err != nil {
156167
a.Close()
157168
return err

farcaster-go/config/config.go

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,17 @@ import (
2525
"golang.org/x/crypto/nacl/secretbox"
2626
"probely.com/farcaster/netutils"
2727
"probely.com/farcaster/settings"
28+
"probely.com/farcaster/tlsconfig"
2829

2930
"github.com/mr-tron/base58"
3031
)
3132

3233
const (
33-
defaultMTU = 1420
34-
defaultPort = 0
35-
timeout = time.Second * 30
34+
defaultMTU = 1420
35+
defaultPort = 0
36+
defaultTimeout = time.Second * 30
3637
)
3738

38-
var (
39-
configClient = createHTTPClient()
40-
)
41-
42-
// createHTTPClient creates an HTTP client with the appropriate TLS configuration
43-
func createHTTPClient() *http.Client {
44-
// Check if certificate verification should be skipped
45-
skipVerify := false
46-
if val := os.Getenv("FARCASTER_SKIP_CERT_VERIFY"); val != "" {
47-
switch strings.ToLower(val) {
48-
case "1", "ok", "true", "yes", "enable", "enabled":
49-
skipVerify = true
50-
}
51-
}
52-
53-
transport := &http.Transport{
54-
TLSClientConfig: &tls.Config{
55-
InsecureSkipVerify: skipVerify,
56-
},
57-
Proxy: http.ProxyFromEnvironment,
58-
}
59-
60-
return &http.Client{
61-
Timeout: timeout,
62-
Transport: transport,
63-
}
64-
}
65-
6639
// WireGuardConfig represents a WireGuard configuration. Not all fields are
6740
// supported.
6841
type WireGuardConfig struct {
@@ -327,6 +300,21 @@ func (c *FarcasterConfig) resolveEndpoint(peer *Peer) error {
327300
return fmt.Errorf("could not resolve host %s. All resolution attempts failed", host)
328301
}
329302

303+
func (c *FarcasterConfig) getHTTPClient(timeout time.Duration) *http.Client {
304+
tlsConfig, err := tlsconfig.GetTLSConfig()
305+
if err != nil {
306+
c.log.Warnf("Error getting TLS config: %v", err)
307+
tlsConfig = &tls.Config{}
308+
}
309+
return &http.Client{
310+
Timeout: timeout,
311+
Transport: &http.Transport{
312+
TLSClientConfig: tlsConfig,
313+
Proxy: http.ProxyFromEnvironment,
314+
},
315+
}
316+
}
317+
330318
// Fetch the configuration from the API.
331319
func (c *FarcasterConfig) fetch(url string) ([]byte, error) {
332320
var data []byte
@@ -352,7 +340,8 @@ func (c *FarcasterConfig) fetch(url string) ([]byte, error) {
352340
req.Header.Set("User-Agent", userAgent)
353341

354342
// Send the request.
355-
resp, err := configClient.Do(req)
343+
client := c.getHTTPClient(defaultTimeout)
344+
resp, err := client.Do(req)
356345
if err != nil {
357346
return nil, err
358347
}

0 commit comments

Comments
 (0)