@@ -2,6 +2,7 @@ package tail
2
2
3
3
import (
4
4
"encoding/json"
5
+ "errors"
5
6
"fmt"
6
7
"net/http"
7
8
"net/url"
@@ -10,28 +11,32 @@ import (
10
11
"syscall"
11
12
"time"
12
13
14
+ "github.com/google/uuid"
13
15
"github.com/mattn/go-colorable"
14
16
"github.com/rs/zerolog"
15
17
"github.com/urfave/cli/v2"
16
18
"nhooyr.io/websocket"
17
19
20
+ "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
21
+ "github.com/cloudflare/cloudflared/credentials"
18
22
"github.com/cloudflare/cloudflared/logger"
19
23
"github.com/cloudflare/cloudflared/management"
20
24
)
21
25
22
26
var (
23
- version string
27
+ buildInfo * cliutil. BuildInfo
24
28
)
25
29
26
- func Init (v string ) {
27
- version = v
30
+ func Init (bi * cliutil. BuildInfo ) {
31
+ buildInfo = bi
28
32
}
29
33
30
34
func Command () * cli.Command {
31
35
return & cli.Command {
32
- Name : "tail" ,
33
- Action : Run ,
34
- Usage : "Stream logs from a remote cloudflared" ,
36
+ Name : "tail" ,
37
+ Action : Run ,
38
+ Usage : "Stream logs from a remote cloudflared" ,
39
+ UsageText : "cloudflared tail [tail command options] [TUNNEL-ID]" ,
35
40
Flags : []cli.Flag {
36
41
& cli.StringFlag {
37
42
Name : "connector-id" ,
@@ -75,6 +80,12 @@ func Command() *cli.Command {
75
80
Usage : "Application logging level {debug, info, warn, error, fatal}" ,
76
81
EnvVars : []string {"TUNNEL_LOGLEVEL" },
77
82
},
83
+ & cli.StringFlag {
84
+ Name : credentials .OriginCertFlag ,
85
+ Usage : "Path to the certificate generated for your origin when you run cloudflared login." ,
86
+ EnvVars : []string {"TUNNEL_ORIGIN_CERT" },
87
+ Value : credentials .FindDefaultOriginCertPath (),
88
+ },
78
89
},
79
90
}
80
91
}
@@ -159,6 +170,59 @@ func parseFilters(c *cli.Context) (*management.StreamingFilters, error) {
159
170
}, nil
160
171
}
161
172
173
+ // getManagementToken will make a call to the Cloudflare API to acquire a management token for the requested tunnel.
174
+ func getManagementToken (c * cli.Context , log * zerolog.Logger ) (string , error ) {
175
+ userCreds , err := credentials .Read (c .String (credentials .OriginCertFlag ), log )
176
+ if err != nil {
177
+ return "" , err
178
+ }
179
+
180
+ client , err := userCreds .Client (c .String ("api-url" ), buildInfo .UserAgent (), log )
181
+ if err != nil {
182
+ return "" , err
183
+ }
184
+
185
+ tunnelIDString := c .Args ().First ()
186
+ if tunnelIDString == "" {
187
+ return "" , errors .New ("no tunnel ID provided" )
188
+ }
189
+ tunnelID , err := uuid .Parse (tunnelIDString )
190
+ if err != nil {
191
+ return "" , errors .New ("unable to parse provided tunnel id as a valid UUID" )
192
+ }
193
+
194
+ token , err := client .GetManagementToken (tunnelID )
195
+ if err != nil {
196
+ return "" , err
197
+ }
198
+
199
+ return token , nil
200
+ }
201
+
202
+ // buildURL will build the management url to contain the required query parameters to authenticate the request.
203
+ func buildURL (c * cli.Context , log * zerolog.Logger ) (url.URL , error ) {
204
+ var err error
205
+ managementHostname := c .String ("management-hostname" )
206
+ token := c .String ("token" )
207
+ if token == "" {
208
+ token , err = getManagementToken (c , log )
209
+ if err != nil {
210
+ return url.URL {}, fmt .Errorf ("unable to acquire management token for requested tunnel id: %w" , err )
211
+ }
212
+ }
213
+ query := url.Values {}
214
+ query .Add ("access_token" , token )
215
+ connector := c .String ("connector-id" )
216
+ if connector != "" {
217
+ connectorID , err := uuid .Parse (connector )
218
+ if err != nil {
219
+ return url.URL {}, fmt .Errorf ("unabled to parse 'connector-id' flag into a valid UUID: %w" , err )
220
+ }
221
+ query .Add ("connector_id" , connectorID .String ())
222
+ }
223
+ return url.URL {Scheme : "wss" , Host : managementHostname , Path : "/logs" , RawQuery : query .Encode ()}, nil
224
+ }
225
+
162
226
// Run implements a foreground runner
163
227
func Run (c * cli.Context ) error {
164
228
log := createLogger (c )
@@ -173,12 +237,14 @@ func Run(c *cli.Context) error {
173
237
return nil
174
238
}
175
239
176
- managementHostname := c .String ("management-hostname" )
177
- token := c .String ("token" )
178
- u := url.URL {Scheme : "wss" , Host : managementHostname , Path : "/logs" , RawQuery : "access_token=" + token }
240
+ u , err := buildURL (c , log )
241
+ if err != nil {
242
+ log .Err (err ).Msg ("unable to construct management request URL" )
243
+ return nil
244
+ }
179
245
180
246
header := make (http.Header )
181
- header .Add ("User-Agent" , "cloudflared/" + version )
247
+ header .Add ("User-Agent" , buildInfo . UserAgent () )
182
248
trace := c .String ("trace" )
183
249
if trace != "" {
184
250
header ["cf-trace-id" ] = []string {trace }
@@ -206,6 +272,11 @@ func Run(c *cli.Context) error {
206
272
log .Error ().Err (err ).Msg ("unable to request logs from management tunnel" )
207
273
return nil
208
274
}
275
+ log .Debug ().
276
+ Str ("tunnel-id" , c .Args ().First ()).
277
+ Str ("connector-id" , c .String ("connector-id" )).
278
+ Interface ("filters" , filters ).
279
+ Msg ("connected" )
209
280
210
281
readerDone := make (chan struct {})
211
282
0 commit comments