cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.10.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

config/configuration.go

399lines · modecode

1package config
2
3import (
4 "fmt"
5 "io"
6 "net/url"
7 "os"
8 "path/filepath"
9 "runtime"
10 "time"
11
12 homedir "github.com/mitchellh/go-homedir"
13 "github.com/pkg/errors"
14 "github.com/rs/zerolog"
15 "github.com/urfave/cli/v2"
16 yaml "gopkg.in/yaml.v2"
17
18 "github.com/cloudflare/cloudflared/validation"
19)
20
21var (
22 // DefaultConfigFiles is the file names from which we attempt to read configuration.
23 DefaultConfigFiles = []string{"config.yml", "config.yaml"}
24
25 // DefaultUnixConfigLocation is the primary location to find a config file
26 DefaultUnixConfigLocation = "/usr/local/etc/cloudflared"
27
28 // DefaultUnixLogLocation is the primary location to find log files
29 DefaultUnixLogLocation = "/var/log/cloudflared"
30
31 // Launchd doesn't set root env variables, so there is default
32 // Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
33 defaultUserConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp"}
34 defaultNixConfigDirs = []string{"/etc/cloudflared", DefaultUnixConfigLocation}
35
36 ErrNoConfigFile = fmt.Errorf("Cannot determine default configuration path. No file %v in %v", DefaultConfigFiles, DefaultConfigSearchDirectories())
37)
38
39const (
40 DefaultCredentialFile = "cert.pem"
41
42 // BastionFlag is to enable bastion, or jump host, operation
43 BastionFlag = "bastion"
44)
45
46// DefaultConfigDirectory returns the default directory of the config file
47func DefaultConfigDirectory() string {
48 if runtime.GOOS == "windows" {
49 path := os.Getenv("CFDPATH")
50 if path == "" {
51 path = filepath.Join(os.Getenv("ProgramFiles(x86)"), "cloudflared")
52 if _, err := os.Stat(path); os.IsNotExist(err) { //doesn't exist, so return an empty failure string
53 return ""
54 }
55 }
56 return path
57 }
58 return DefaultUnixConfigLocation
59}
60
61// DefaultLogDirectory returns the default directory for log files
62func DefaultLogDirectory() string {
63 if runtime.GOOS == "windows" {
64 return DefaultConfigDirectory()
65 }
66 return DefaultUnixLogLocation
67}
68
69// DefaultConfigPath returns the default location of a config file
70func DefaultConfigPath() string {
71 dir := DefaultConfigDirectory()
72 if dir == "" {
73 return DefaultConfigFiles[0]
74 }
75 return filepath.Join(dir, DefaultConfigFiles[0])
76}
77
78// DefaultConfigSearchDirectories returns the default folder locations of the config
79func DefaultConfigSearchDirectories() []string {
80 dirs := make([]string, len(defaultUserConfigDirs))
81 copy(dirs, defaultUserConfigDirs)
82 if runtime.GOOS != "windows" {
83 dirs = append(dirs, defaultNixConfigDirs...)
84 }
85 return dirs
86}
87
88// FileExists checks to see if a file exist at the provided path.
89func FileExists(path string) (bool, error) {
90 f, err := os.Open(path)
91 if err != nil {
92 if os.IsNotExist(err) {
93 // ignore missing files
94 return false, nil
95 }
96 return false, err
97 }
98 _ = f.Close()
99 return true, nil
100}
101
102// FindDefaultConfigPath returns the first path that contains a config file.
103// If none of the combination of DefaultConfigSearchDirectories() and DefaultConfigFiles
104// contains a config file, return empty string.
105func FindDefaultConfigPath() string {
106 for _, configDir := range DefaultConfigSearchDirectories() {
107 for _, configFile := range DefaultConfigFiles {
108 dirPath, err := homedir.Expand(configDir)
109 if err != nil {
110 continue
111 }
112 path := filepath.Join(dirPath, configFile)
113 if ok, _ := FileExists(path); ok {
114 return path
115 }
116 }
117 }
118 return ""
119}
120
121// FindOrCreateConfigPath returns the first path that contains a config file
122// or creates one in the primary default path if it doesn't exist
123func FindOrCreateConfigPath() string {
124 path := FindDefaultConfigPath()
125
126 if path == "" {
127 // create the default directory if it doesn't exist
128 path = DefaultConfigPath()
129 if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
130 return ""
131 }
132
133 // write a new config file out
134 file, err := os.Create(path)
135 if err != nil {
136 return ""
137 }
138 defer file.Close()
139
140 logDir := DefaultLogDirectory()
141 _ = os.MkdirAll(logDir, os.ModePerm) //try and create it. Doesn't matter if it succeed or not, only byproduct will be no logs
142
143 c := Root{
144 LogDirectory: logDir,
145 }
146 if err := yaml.NewEncoder(file).Encode(&c); err != nil {
147 return ""
148 }
149 }
150
151 return path
152}
153
154// ValidateUnixSocket ensures --unix-socket param is used exclusively
155// i.e. it fails if a user specifies both --url and --unix-socket
156func ValidateUnixSocket(c *cli.Context) (string, error) {
157 if c.IsSet("unix-socket") && (c.IsSet("url") || c.NArg() > 0) {
158 return "", errors.New("--unix-socket must be used exclusivly.")
159 }
160 return c.String("unix-socket"), nil
161}
162
163// ValidateUrl will validate url flag correctness. It can be either from --url or argument
164// Notice ValidateUnixSocket, it will enforce --unix-socket is not used with --url or argument
165func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
166 var url = c.String("url")
167 if allowURLFromArgs && c.NArg() > 0 {
168 if c.IsSet("url") {
169 return nil, errors.New("Specified origin urls using both --url and argument. Decide which one you want, I can only support one.")
170 }
171 url = c.Args().Get(0)
172 }
173 validUrl, err := validation.ValidateUrl(url)
174 return validUrl, err
175}
176
177type UnvalidatedIngressRule struct {
178 Hostname string
179 Path string
180 Service string
181 OriginRequest OriginRequestConfig `yaml:"originRequest"`
182}
183
184// OriginRequestConfig is a set of optional fields that users may set to
185// customize how cloudflared sends requests to origin services. It is used to set
186// up general config that apply to all rules, and also, specific per-rule
187// config.
188// Note: To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
189type OriginRequestConfig struct {
190 // HTTP proxy timeout for establishing a new connection
191 ConnectTimeout *time.Duration `yaml:"connectTimeout"`
192 // HTTP proxy timeout for completing a TLS handshake
193 TLSTimeout *time.Duration `yaml:"tlsTimeout"`
194 // HTTP proxy TCP keepalive duration
195 TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive"`
196 // HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
197 NoHappyEyeballs *bool `yaml:"noHappyEyeballs"`
198 // HTTP proxy maximum keepalive connection pool size
199 KeepAliveConnections *int `yaml:"keepAliveConnections"`
200 // HTTP proxy timeout for closing an idle connection
201 KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout"`
202 // Sets the HTTP Host header for the local webserver.
203 HTTPHostHeader *string `yaml:"httpHostHeader"`
204 // Hostname on the origin server certificate.
205 OriginServerName *string `yaml:"originServerName"`
206 // Path to the CA for the certificate of your origin.
207 // This option should be used only if your certificate is not signed by Cloudflare.
208 CAPool *string `yaml:"caPool"`
209 // Disables TLS verification of the certificate presented by your origin.
210 // Will allow any certificate from the origin to be accepted.
211 // Note: The connection from your machine to Cloudflare's Edge is still encrypted.
212 NoTLSVerify *bool `yaml:"noTLSVerify"`
213 // Disables chunked transfer encoding.
214 // Useful if you are running a WSGI server.
215 DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding"`
216 // Runs as jump host
217 BastionMode *bool `yaml:"bastionMode"`
218 // Listen address for the proxy.
219 ProxyAddress *string `yaml:"proxyAddress"`
220 // Listen port for the proxy.
221 ProxyPort *uint `yaml:"proxyPort"`
222 // Valid options are 'socks' or empty.
223 ProxyType *string `yaml:"proxyType"`
224 // IP rules for the proxy service
225 IPRules []IngressIPRule `yaml:"ipRules"`
226}
227
228type IngressIPRule struct {
229 Prefix *string `yaml:"prefix"`
230 Ports []int `yaml:"ports"`
231 Allow bool `yaml:"allow"`
232}
233
234type Configuration struct {
235 TunnelID string `yaml:"tunnel"`
236 Ingress []UnvalidatedIngressRule
237 WarpRouting WarpRoutingConfig `yaml:"warp-routing"`
238 OriginRequest OriginRequestConfig `yaml:"originRequest"`
239 sourceFile string
240}
241
242type WarpRoutingConfig struct {
243 Enabled bool `yaml:"enabled"`
244}
245
246type configFileSettings struct {
247 Configuration `yaml:",inline"`
248 // older settings will be aggregated into the generic map, should be read via cli.Context
249 Settings map[string]interface{} `yaml:",inline"`
250}
251
252func (c *Configuration) Source() string {
253 return c.sourceFile
254}
255
256func (c *configFileSettings) Int(name string) (int, error) {
257 if raw, ok := c.Settings[name]; ok {
258 if v, ok := raw.(int); ok {
259 return v, nil
260 }
261 return 0, fmt.Errorf("expected int found %T for %s", raw, name)
262 }
263 return 0, nil
264}
265
266func (c *configFileSettings) Duration(name string) (time.Duration, error) {
267 if raw, ok := c.Settings[name]; ok {
268 switch v := raw.(type) {
269 case time.Duration:
270 return v, nil
271 case string:
272 return time.ParseDuration(v)
273 }
274 return 0, fmt.Errorf("expected duration found %T for %s", raw, name)
275 }
276 return 0, nil
277}
278
279func (c *configFileSettings) Float64(name string) (float64, error) {
280 if raw, ok := c.Settings[name]; ok {
281 if v, ok := raw.(float64); ok {
282 return v, nil
283 }
284 return 0, fmt.Errorf("expected float found %T for %s", raw, name)
285 }
286 return 0, nil
287}
288
289func (c *configFileSettings) String(name string) (string, error) {
290 if raw, ok := c.Settings[name]; ok {
291 if v, ok := raw.(string); ok {
292 return v, nil
293 }
294 return "", fmt.Errorf("expected string found %T for %s", raw, name)
295 }
296 return "", nil
297}
298
299func (c *configFileSettings) StringSlice(name string) ([]string, error) {
300 if raw, ok := c.Settings[name]; ok {
301 if slice, ok := raw.([]interface{}); ok {
302 strSlice := make([]string, len(slice))
303 for i, v := range slice {
304 str, ok := v.(string)
305 if !ok {
306 return nil, fmt.Errorf("expected string, found %T for %v", i, v)
307 }
308 strSlice[i] = str
309 }
310 return strSlice, nil
311 }
312 return nil, fmt.Errorf("expected string slice found %T for %s", raw, name)
313 }
314 return nil, nil
315}
316
317func (c *configFileSettings) IntSlice(name string) ([]int, error) {
318 if raw, ok := c.Settings[name]; ok {
319 if slice, ok := raw.([]interface{}); ok {
320 intSlice := make([]int, len(slice))
321 for i, v := range slice {
322 str, ok := v.(int)
323 if !ok {
324 return nil, fmt.Errorf("expected int, found %T for %v ", v, v)
325 }
326 intSlice[i] = str
327 }
328 return intSlice, nil
329 }
330 if v, ok := raw.([]int); ok {
331 return v, nil
332 }
333 return nil, fmt.Errorf("expected int slice found %T for %s", raw, name)
334 }
335 return nil, nil
336}
337
338func (c *configFileSettings) Generic(name string) (cli.Generic, error) {
339 return nil, errors.New("option type Generic not supported")
340}
341
342func (c *configFileSettings) Bool(name string) (bool, error) {
343 if raw, ok := c.Settings[name]; ok {
344 if v, ok := raw.(bool); ok {
345 return v, nil
346 }
347 return false, fmt.Errorf("expected boolean found %T for %s", raw, name)
348 }
349 return false, nil
350}
351
352var configuration configFileSettings
353
354func GetConfiguration() *Configuration {
355 return &configuration.Configuration
356}
357
358// ReadConfigFile returns InputSourceContext initialized from the configuration file.
359// On repeat calls returns with the same file, returns without reading the file again; however,
360// if value of "config" flag changes, will read the new config file
361func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSettings, warnings string, err error) {
362 configFile := c.String("config")
363 if configuration.Source() == configFile || configFile == "" {
364 if configuration.Source() == "" {
365 return nil, "", ErrNoConfigFile
366 }
367 return &configuration, "", nil
368 }
369
370 log.Debug().Msgf("Loading configuration from %s", configFile)
371 file, err := os.Open(configFile)
372 if err != nil {
373 if os.IsNotExist(err) {
374 err = ErrNoConfigFile
375 }
376 return nil, "", err
377 }
378 defer file.Close()
379 if err := yaml.NewDecoder(file).Decode(&configuration); err != nil {
380 if err == io.EOF {
381 log.Error().Msgf("Configuration file %s was empty", configFile)
382 return &configuration, "", nil
383 }
384 return nil, "", errors.Wrap(err, "error parsing YAML in config file at "+configFile)
385 }
386 configuration.sourceFile = configFile
387
388 // Parse it again, with strict mode, to find warnings.
389 if file, err := os.Open(configFile); err == nil {
390 decoder := yaml.NewDecoder(file)
391 decoder.SetStrict(true)
392 var unusedConfig configFileSettings
393 if err := decoder.Decode(&unusedConfig); err != nil {
394 warnings = err.Error()
395 }
396 }
397
398 return &configuration, warnings, nil
399}
400