Skip to content

Commit 8a3eade

Browse files
committed
TUN-7553: Add flag to enable management diagnostic services
With the new flag --management-diagnostics (an opt-in flag) cloudflared's will be able to report additional diagnostic information over the management.argotunnel.com request path. Additions include the /metrics prometheus endpoint; which is already bound to a local port via --metrics. /debug/pprof/(goroutine|heap) are also provided to allow for remotely retrieving heap information from a running cloudflared connector.
1 parent 39847a7 commit 8a3eade

File tree

6 files changed

+77
-20
lines changed

6 files changed

+77
-20
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2023.6.2
2+
### New Features
3+
- You can now enable additional diagnostics over the management.argotunnel.com service for your active cloudflared connectors via a new runtime flag `--management-diagnostics` (or env `TUNNEL_MANAGEMENT_DIAGNOSTICS`). This feature is provided as opt-out and requires the flag to enable. Endpoints such as /metrics provides your prometheus metrics endpoint another mechanism to be reached. Additionally /debug/pprof/(goroutine|heap) are also introduced to allow for remotely retrieving active pprof information from a running cloudflared connector.
4+
15
## 2023.4.1
26
### New Features
37
- You can now stream your logs from your remote cloudflared to your local terminal with `cloudflared tail <TUNNEL-ID>`. This new feature requires the remote cloudflared to be version 2023.4.1 or higher.

cmd/cloudflared/tunnel/cmd.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ func StartServer(
413413

414414
mgmt := management.New(
415415
c.String("management-hostname"),
416+
c.Bool("management-diagnostics"),
416417
serviceIP,
417418
clientID,
418419
c.String(connectorLabelFlag),
@@ -764,6 +765,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
764765
EnvVars: []string{"TUNNEL_POST_QUANTUM"},
765766
Hidden: FipsEnabled,
766767
}),
768+
altsrc.NewBoolFlag(&cli.BoolFlag{
769+
Name: "management-diagnostics",
770+
Usage: "Enables the in-depth diagnostic routes to be made available over the management service (/debug/pprof, /metrics, etc.)",
771+
EnvVars: []string{"TUNNEL_MANAGEMENT_DIAGNOSTICS"},
772+
Value: false,
773+
}),
767774
selectProtocolFlag,
768775
overwriteDNSFlag,
769776
}...)

component-tests/test_management.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_get_metrics(self, tmp_path, component_tests_config):
5555
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
5656
LOGGER.debug(config)
5757
config_path = write_config(tmp_path, config.full_config)
58-
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True):
58+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1", "--management-diagnostics"], new_process=True):
5959
wait_tunnel_ready(require_min_connections=1)
6060
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
6161
url = cfd_cli.get_management_url("metrics", config, config_path)
@@ -76,7 +76,7 @@ def test_get_pprof_heap(self, tmp_path, component_tests_config):
7676
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
7777
LOGGER.debug(config)
7878
config_path = write_config(tmp_path, config.full_config)
79-
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True):
79+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1", "--management-diagnostics"], new_process=True):
8080
wait_tunnel_ready(require_min_connections=1)
8181
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
8282
url = cfd_cli.get_management_url("debug/pprof/heap", config, config_path)
@@ -85,6 +85,26 @@ def test_get_pprof_heap(self, tmp_path, component_tests_config):
8585
# Assert response.
8686
assert resp.status_code == 200, "Expected cloudflared to return 200 for /debug/pprof/heap"
8787
assert resp.headers["Content-Type"] == "application/octet-stream", "Expected /debug/pprof/heap to have return a binary response"
88+
89+
"""
90+
test_get_metrics_when_disabled will verify that diagnostic endpoints (such as /metrics) return 404 and are unmounted.
91+
"""
92+
def test_get_metrics_when_disabled(self, tmp_path, component_tests_config):
93+
# TUN-7377 : wait_tunnel_ready does not work properly in windows.
94+
# Skipping this test for windows for now and will address it as part of tun-7377
95+
if platform.system() == "Windows":
96+
return
97+
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
98+
LOGGER.debug(config)
99+
config_path = write_config(tmp_path, config.full_config)
100+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True):
101+
wait_tunnel_ready(require_min_connections=1)
102+
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
103+
url = cfd_cli.get_management_url("metrics", config, config_path)
104+
resp = send_request(url)
105+
106+
# Assert response.
107+
assert resp.status_code == 404, "Expected cloudflared to return 404 for /metrics"
88108

89109

90110
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)

management/service.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ const (
3131
reasonIdleLimitExceeded = "session was idle for too long"
3232
)
3333

34+
var (
35+
// CORS middleware required to allow dash to access management.argotunnel.com requests
36+
corsHandler = cors.Handler(cors.Options{
37+
// Allows for any subdomain of cloudflare.com
38+
AllowedOrigins: []string{"https://*.cloudflare.com"},
39+
// Required to present cookies or other authentication across origin boundries
40+
AllowCredentials: true,
41+
MaxAge: 300, // Maximum value not ignored by any of major browsers
42+
})
43+
)
44+
3445
type ManagementService struct {
3546
// The management tunnel hostname
3647
Hostname string
@@ -54,6 +65,7 @@ type ManagementService struct {
5465
}
5566

5667
func New(managementHostname string,
68+
enableDiagServices bool,
5769
serviceIP string,
5870
clientID uuid.UUID,
5971
label string,
@@ -71,25 +83,21 @@ func New(managementHostname string,
7183
}
7284
r := chi.NewRouter()
7385
r.Use(ValidateAccessTokenQueryMiddleware)
74-
r.Get("/ping", ping)
75-
r.Head("/ping", ping)
86+
87+
// Default management services
88+
r.With(corsHandler).Get("/ping", ping)
89+
r.With(corsHandler).Head("/ping", ping)
7690
r.Get("/logs", s.logs)
77-
r.Get("/metrics", s.metricsHandler.ServeHTTP)
91+
r.With(corsHandler).Get("/host_details", s.getHostDetails)
7892

79-
// Supports only heap and goroutine
80-
r.Get("/debug/pprof/{profile:heap|goroutine}", pprof.Index)
93+
// Diagnostic management services
94+
if enableDiagServices {
95+
// Prometheus endpoint
96+
r.With(corsHandler).Get("/metrics", s.metricsHandler.ServeHTTP)
97+
// Supports only heap and goroutine
98+
r.With(corsHandler).Get("/debug/pprof/{profile:heap|goroutine}", pprof.Index)
99+
}
81100

82-
r.Route("/host_details", func(r chi.Router) {
83-
// CORS middleware required to allow dash to access management.argotunnel.com requests
84-
r.Use(cors.Handler(cors.Options{
85-
// Allows for any subdomain of cloudflare.com
86-
AllowedOrigins: []string{"https://*.cloudflare.com"},
87-
// Required to present cookies or other authentication across origin boundries
88-
AllowCredentials: true,
89-
MaxAge: 300, // Maximum value not ignored by any of major browsers
90-
}))
91-
r.Get("/", s.getHostDetails)
92-
})
93101
s.router = r
94102
return s
95103
}

management/service_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package management
33
import (
44
"context"
55
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
69
"testing"
710
"time"
811

12+
"github.com/google/uuid"
913
"github.com/rs/zerolog"
1014
"github.com/stretchr/testify/assert"
1115
"github.com/stretchr/testify/require"
@@ -15,9 +19,23 @@ import (
1519
)
1620

1721
var (
18-
noopLogger = zerolog.New(io.Discard)
22+
noopLogger = zerolog.New(io.Discard)
23+
managementHostname = "https://management.argotunnel.com"
1924
)
2025

26+
func TestDisableDiagnosticRoutes(t *testing.T) {
27+
mgmt := New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &noopLogger, nil)
28+
for _, path := range []string{"/metrics", "/debug/pprof/goroutine", "/debug/pprof/heap"} {
29+
t.Run(strings.Replace(path, "/", "_", -1), func(t *testing.T) {
30+
req := httptest.NewRequest("GET", managementHostname+path+"?access_token="+validToken, nil)
31+
recorder := httptest.NewRecorder()
32+
mgmt.ServeHTTP(recorder, req)
33+
resp := recorder.Result()
34+
require.Equal(t, http.StatusNotFound, resp.StatusCode)
35+
})
36+
}
37+
}
38+
2139
func TestReadEventsLoop(t *testing.T) {
2240
sentEvent := EventStartStreaming{
2341
ClientEvent: ClientEvent{Type: StartStreaming},

orchestration/orchestrator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestUpdateConfiguration(t *testing.T) {
5151
initConfig := &Config{
5252
Ingress: &ingress.Ingress{},
5353
}
54-
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger)
54+
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger)
5555
require.NoError(t, err)
5656
initOriginProxy, err := orchestrator.GetOriginProxy()
5757
require.NoError(t, err)

0 commit comments

Comments
 (0)