Skip to content

Commit f85c0f1

Browse files
committed
TUN-8730: implement diag/configuration
Implements the endpoint that retrieves the configuration of a running instance. The configuration consists in a map of cli flag to the provided value along with the uid that of the user that started the process
1 parent 4b0b6dc commit f85c0f1

File tree

6 files changed

+277
-24
lines changed

6 files changed

+277
-24
lines changed

cmd/cloudflared/tunnel/cmd.go

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,92 @@ var (
127127
"most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+
128128
"any existing DNS records for this hostname.", overwriteDNSFlag)
129129
deprecatedClassicTunnelErr = fmt.Errorf("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)")
130+
nonSecretFlagsList = []string{
131+
"config",
132+
"autoupdate-freq",
133+
"no-autoupdate",
134+
"metrics",
135+
"pidfile",
136+
"url",
137+
"hello-world",
138+
"socks5",
139+
"proxy-connect-timeout",
140+
"proxy-tls-timeout",
141+
"proxy-tcp-keepalive",
142+
"proxy-no-happy-eyeballs",
143+
"proxy-keepalive-connections",
144+
"proxy-keepalive-timeout",
145+
"proxy-connection-timeout",
146+
"proxy-expect-continue-timeout",
147+
"http-host-header",
148+
"origin-server-name",
149+
"unix-socket",
150+
"origin-ca-pool",
151+
"no-tls-verify",
152+
"no-chunked-encoding",
153+
"http2-origin",
154+
"management-hostname",
155+
"service-op-ip",
156+
"local-ssh-port",
157+
"ssh-idle-timeout",
158+
"ssh-max-timeout",
159+
"bucket-name",
160+
"region-name",
161+
"s3-url-host",
162+
"host-key-path",
163+
"ssh-server",
164+
"bastion",
165+
"proxy-address",
166+
"proxy-port",
167+
"loglevel",
168+
"transport-loglevel",
169+
"logfile",
170+
"log-directory",
171+
"trace-output",
172+
"proxy-dns",
173+
"proxy-dns-port",
174+
"proxy-dns-address",
175+
"proxy-dns-upstream",
176+
"proxy-dns-max-upstream-conns",
177+
"proxy-dns-bootstrap",
178+
"is-autoupdated",
179+
"edge",
180+
"region",
181+
"edge-ip-version",
182+
"edge-bind-address",
183+
"cacert",
184+
"hostname",
185+
"id",
186+
"lb-pool",
187+
"api-url",
188+
"metrics-update-freq",
189+
"tag",
190+
"heartbeat-interval",
191+
"heartbeat-count",
192+
"max-edge-addr-retries",
193+
"retries",
194+
"ha-connections",
195+
"rpc-timeout",
196+
"write-stream-timeout",
197+
"quic-disable-pmtu-discovery",
198+
"quic-connection-level-flow-control-limit",
199+
"quic-stream-level-flow-control-limit",
200+
"label",
201+
"grace-period",
202+
"compression-quality",
203+
"use-reconnect-token",
204+
"dial-edge-timeout",
205+
"stdin-control",
206+
"name",
207+
"ui",
208+
"quick-service",
209+
"max-fetch-size",
210+
"post-quantum",
211+
"management-diagnostics",
212+
"protocol",
213+
"overwrite-dns",
214+
"help",
215+
}
130216
)
131217

132218
func Flags() []cli.Flag {
@@ -465,7 +551,16 @@ func StartServer(
465551
observer.RegisterSink(tracker)
466552

467553
readinessServer := metrics.NewReadyServer(clientID, tracker)
468-
diagnosticHandler := diagnostic.NewDiagnosticHandler(log, 0, diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion), tunnelConfig.NamedTunnel.Credentials.TunnelID, clientID, tracker)
554+
diagnosticHandler := diagnostic.NewDiagnosticHandler(
555+
log,
556+
0,
557+
diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion),
558+
tunnelConfig.NamedTunnel.Credentials.TunnelID,
559+
clientID,
560+
tracker,
561+
c,
562+
nonSecretFlagsList,
563+
)
469564
metricsConfig := metrics.Config{
470565
ReadyServer: readinessServer,
471566
DiagnosticHandler: diagnosticHandler,

diagnostic/consts.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package diagnostic
33
import "time"
44

55
const (
6-
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
7-
collectorField = "collector" // used for logging purposes
8-
systemCollectorName = "system" // used for logging purposes
9-
tunnelStateCollectorName = "tunnelState" // used for logging purposes
6+
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
7+
collectorField = "collector" // used for logging purposes
8+
systemCollectorName = "system" // used for logging purposes
9+
tunnelStateCollectorName = "tunnelState" // used for logging purposes
10+
configurationCollectorName = "configuration" // used for logging purposes
11+
configurationKeyUid = "uid"
1012
)

diagnostic/handlers.go

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,28 @@ import (
44
"context"
55
"encoding/json"
66
"net/http"
7+
"os"
8+
"path/filepath"
9+
"strconv"
710
"time"
811

912
"github.com/google/uuid"
1013
"github.com/rs/zerolog"
14+
"github.com/urfave/cli/v2"
1115

16+
"github.com/cloudflare/cloudflared/logger"
1217
"github.com/cloudflare/cloudflared/tunnelstate"
1318
)
1419

1520
type Handler struct {
16-
log *zerolog.Logger
17-
timeout time.Duration
18-
systemCollector SystemCollector
19-
tunnelID uuid.UUID
20-
connectorID uuid.UUID
21-
tracker *tunnelstate.ConnTracker
21+
log *zerolog.Logger
22+
timeout time.Duration
23+
systemCollector SystemCollector
24+
tunnelID uuid.UUID
25+
connectorID uuid.UUID
26+
tracker *tunnelstate.ConnTracker
27+
cli *cli.Context
28+
flagInclusionList []string
2229
}
2330

2431
func NewDiagnosticHandler(
@@ -28,19 +35,23 @@ func NewDiagnosticHandler(
2835
tunnelID uuid.UUID,
2936
connectorID uuid.UUID,
3037
tracker *tunnelstate.ConnTracker,
38+
cli *cli.Context,
39+
flagInclusionList []string,
3140
) *Handler {
3241
logger := log.With().Logger()
3342
if timeout == 0 {
3443
timeout = defaultCollectorTimeout
3544
}
3645

3746
return &Handler{
38-
log: &logger,
39-
timeout: timeout,
40-
systemCollector: systemCollector,
41-
tunnelID: tunnelID,
42-
connectorID: connectorID,
43-
tracker: tracker,
47+
log: &logger,
48+
timeout: timeout,
49+
systemCollector: systemCollector,
50+
tunnelID: tunnelID,
51+
connectorID: connectorID,
52+
tracker: tracker,
53+
cli: cli,
54+
flagInclusionList: flagInclusionList,
4455
}
4556
}
4657

@@ -110,8 +121,77 @@ func (handler *Handler) TunnelStateHandler(writer http.ResponseWriter, _ *http.R
110121
}
111122
}
112123

113-
func writeResponse(writer http.ResponseWriter, bytes []byte, logger *zerolog.Logger) {
114-
bytesWritten, err := writer.Write(bytes)
124+
func (handler *Handler) ConfigurationHandler(writer http.ResponseWriter, _ *http.Request) {
125+
log := handler.log.With().Str(collectorField, configurationCollectorName).Logger()
126+
log.Info().Msg("Collection started")
127+
128+
defer func() {
129+
log.Info().Msg("Collection finished")
130+
}()
131+
132+
flagsNames := handler.cli.FlagNames()
133+
flags := make(map[string]string, len(flagsNames))
134+
135+
for _, flag := range flagsNames {
136+
value := handler.cli.String(flag)
137+
138+
// empty values are not relevant
139+
if value == "" {
140+
continue
141+
}
142+
143+
// exclude flags that are sensitive
144+
isIncluded := handler.isFlagIncluded(flag)
145+
if !isIncluded {
146+
continue
147+
}
148+
149+
switch flag {
150+
case logger.LogDirectoryFlag:
151+
case logger.LogFileFlag:
152+
{
153+
// the log directory may be relative to the instance thus it must be resolved
154+
absolute, err := filepath.Abs(value)
155+
if err != nil {
156+
handler.log.Error().Err(err).Msgf("could not convert %s path to absolute", flag)
157+
} else {
158+
flags[flag] = absolute
159+
}
160+
}
161+
default:
162+
flags[flag] = value
163+
}
164+
}
165+
166+
// The UID is included to help the
167+
// diagnostic tool to understand
168+
// if this instance is managed or not.
169+
flags[configurationKeyUid] = strconv.Itoa(os.Getuid())
170+
encoder := json.NewEncoder(writer)
171+
172+
err := encoder.Encode(flags)
173+
if err != nil {
174+
handler.log.Error().Err(err).Msgf("error occurred whilst serializing response")
175+
writer.WriteHeader(http.StatusInternalServerError)
176+
}
177+
}
178+
179+
func (handler *Handler) isFlagIncluded(flag string) bool {
180+
isIncluded := false
181+
182+
for _, include := range handler.flagInclusionList {
183+
if include == flag {
184+
isIncluded = true
185+
186+
break
187+
}
188+
}
189+
190+
return isIncluded
191+
}
192+
193+
func writeResponse(w http.ResponseWriter, bytes []byte, logger *zerolog.Logger) {
194+
bytesWritten, err := w.Write(bytes)
115195
if err != nil {
116196
logger.Error().Err(err).Msg("error occurred writing response")
117197
} else if bytesWritten != len(bytes) {

diagnostic/handlers_test.go

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"errors"
7+
"flag"
78
"io"
89
"net"
910
"net/http"
@@ -14,6 +15,7 @@ import (
1415
"github.com/rs/zerolog"
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/require"
18+
"github.com/urfave/cli/v2"
1719

1820
"github.com/cloudflare/cloudflared/connection"
1921
"github.com/cloudflare/cloudflared/diagnostic"
@@ -28,6 +30,21 @@ const (
2830
errorKey = "errkey"
2931
)
3032

33+
func buildCliContext(t *testing.T, flags map[string]string) *cli.Context {
34+
t.Helper()
35+
36+
flagSet := flag.NewFlagSet("", flag.PanicOnError)
37+
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
38+
39+
for k, v := range flags {
40+
flagSet.String(k, v, "")
41+
err := ctx.Set(k, v)
42+
require.NoError(t, err)
43+
}
44+
45+
return ctx
46+
}
47+
3148
func newTrackerFromConns(t *testing.T, connections []tunnelstate.IndexedConnectionInfo) *tunnelstate.ConnTracker {
3249
t.Helper()
3350

@@ -45,6 +62,7 @@ func newTrackerFromConns(t *testing.T, connections []tunnelstate.IndexedConnecti
4562

4663
return tracker
4764
}
65+
4866
func setCtxValuesForSystemCollector(
4967
systemInfo *diagnostic.SystemInformation,
5068
rawInfo string,
@@ -104,7 +122,8 @@ func TestSystemHandler(t *testing.T) {
104122
for _, tCase := range tests {
105123
t.Run(tCase.name, func(t *testing.T) {
106124
t.Parallel()
107-
handler := diagnostic.NewDiagnosticHandler(&log, 0, &SystemCollectorMock{}, uuid.New(), uuid.New(), nil)
125+
126+
handler := diagnostic.NewDiagnosticHandler(&log, 0, &SystemCollectorMock{}, uuid.New(), uuid.New(), nil, nil, nil)
108127
recorder := httptest.NewRecorder()
109128
ctx := setCtxValuesForSystemCollector(tCase.systemInfo, tCase.rawInfo, tCase.err)
110129
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "/diag/syste,", nil)
@@ -162,7 +181,7 @@ func TestTunnelStateHandler(t *testing.T) {
162181
t.Run(tCase.name, func(t *testing.T) {
163182
t.Parallel()
164183
tracker := newTrackerFromConns(t, tCase.connections)
165-
handler := diagnostic.NewDiagnosticHandler(&log, 0, nil, tCase.tunnelID, tCase.clientID, tracker)
184+
handler := diagnostic.NewDiagnosticHandler(&log, 0, nil, tCase.tunnelID, tCase.clientID, tracker, nil, nil)
166185
recorder := httptest.NewRecorder()
167186
handler.TunnelStateHandler(recorder, nil)
168187
decoder := json.NewDecoder(recorder.Body)
@@ -182,3 +201,59 @@ func TestTunnelStateHandler(t *testing.T) {
182201
})
183202
}
184203
}
204+
205+
func TestConfigurationHandler(t *testing.T) {
206+
t.Parallel()
207+
208+
log := zerolog.Nop()
209+
210+
tests := []struct {
211+
name string
212+
flags map[string]string
213+
expected map[string]string
214+
}{
215+
{
216+
name: "empty cli",
217+
flags: make(map[string]string),
218+
expected: map[string]string{
219+
"uid": "0",
220+
},
221+
},
222+
{
223+
name: "cli with flags",
224+
flags: map[string]string{
225+
"a": "a",
226+
"b": "a",
227+
"c": "a",
228+
"d": "a",
229+
},
230+
expected: map[string]string{
231+
"b": "a",
232+
"c": "a",
233+
"d": "a",
234+
"uid": "0",
235+
},
236+
},
237+
}
238+
239+
for _, tCase := range tests {
240+
t.Run(tCase.name, func(t *testing.T) {
241+
var response map[string]string
242+
243+
t.Parallel()
244+
ctx := buildCliContext(t, tCase.flags)
245+
handler := diagnostic.NewDiagnosticHandler(&log, 0, nil, uuid.New(), uuid.New(), nil, ctx, []string{"b", "c", "d"})
246+
recorder := httptest.NewRecorder()
247+
handler.ConfigurationHandler(recorder, nil)
248+
decoder := json.NewDecoder(recorder.Body)
249+
err := decoder.Decode(&response)
250+
require.NoError(t, err)
251+
_, ok := response["uid"]
252+
assert.True(t, ok)
253+
delete(tCase.expected, "uid")
254+
delete(response, "uid")
255+
assert.Equal(t, http.StatusOK, recorder.Code)
256+
assert.Equal(t, tCase.expected, response)
257+
})
258+
}
259+
}

0 commit comments

Comments
 (0)