cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c54e8cd8e62e2b551bcedff0c25a80f4ec0f38fa

Branches

Tags

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

Clone

HTTPS

Download ZIP

config/configuration.go

401lines · 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 `json:"hostname"`
179 Path string `json:"path"`
180 Service string `json:"service"`
181 OriginRequest OriginRequestConfig `yaml:"originRequest" json:"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:
189// - To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
190// - To specify a time.Duration in json, use int64 of the nanoseconds
191type OriginRequestConfig struct {
192 // HTTP proxy timeout for establishing a new connection
193 ConnectTimeout *time.Duration `yaml:"connectTimeout" json:"connectTimeout"`
194 // HTTP proxy timeout for completing a TLS handshake
195 TLSTimeout *time.Duration `yaml:"tlsTimeout" json:"tlsTimeout"`
196 // HTTP proxy TCP keepalive duration
197 TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
198 // HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
199 NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
200 // HTTP proxy maximum keepalive connection pool size
201 KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
202 // HTTP proxy timeout for closing an idle connection
203 KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
204 // Sets the HTTP Host header for the local webserver.
205 HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
206 // Hostname on the origin server certificate.
207 OriginServerName *string `yaml:"originServerName" json:"originServerName"`
208 // Path to the CA for the certificate of your origin.
209 // This option should be used only if your certificate is not signed by Cloudflare.
210 CAPool *string `yaml:"caPool" json:"caPool"`
211 // Disables TLS verification of the certificate presented by your origin.
212 // Will allow any certificate from the origin to be accepted.
213 // Note: The connection from your machine to Cloudflare's Edge is still encrypted.
214 NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
215 // Disables chunked transfer encoding.
216 // Useful if you are running a WSGI server.
217 DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
218 // Runs as jump host
219 BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
220 // Listen address for the proxy.
221 ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
222 // Listen port for the proxy.
223 ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
224 // Valid options are 'socks' or empty.
225 ProxyType *string `yaml:"proxyType" json:"proxyType"`
226 // IP rules for the proxy service
227 IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
228}
229
230type IngressIPRule struct {
231 Prefix *string `yaml:"prefix" json:"prefix"`
232 Ports []int `yaml:"ports" json:"ports"`
233 Allow bool `yaml:"allow" json:"allow"`
234}
235
236type Configuration struct {
237 TunnelID string `yaml:"tunnel"`
238 Ingress []UnvalidatedIngressRule
239 WarpRouting WarpRoutingConfig `yaml:"warp-routing"`
240 OriginRequest OriginRequestConfig `yaml:"originRequest"`
241 sourceFile string
242}
243
244type WarpRoutingConfig struct {
245 Enabled bool `yaml:"enabled" json:"enabled"`
246}
247
248type configFileSettings struct {
249 Configuration `yaml:",inline"`
250 // older settings will be aggregated into the generic map, should be read via cli.Context
251 Settings map[string]interface{} `yaml:",inline"`
252}
253
254func (c *Configuration) Source() string {
255 return c.sourceFile
256}
257
258func (c *configFileSettings) Int(name string) (int, error) {
259 if raw, ok := c.Settings[name]; ok {
260 if v, ok := raw.(int); ok {
261 return v, nil
262 }
263 return 0, fmt.Errorf("expected int found %T for %s", raw, name)
264 }
265 return 0, nil
266}
267
268func (c *configFileSettings) Duration(name string) (time.Duration, error) {
269 if raw, ok := c.Settings[name]; ok {
270 switch v := raw.(type) {
271 case time.Duration:
272 return v, nil
273 case string:
274 return time.ParseDuration(v)
275 }
276 return 0, fmt.Errorf("expected duration found %T for %s", raw, name)
277 }
278 return 0, nil
279}
280
281func (c *configFileSettings) Float64(name string) (float64, error) {
282 if raw, ok := c.Settings[name]; ok {
283 if v, ok := raw.(float64); ok {
284 return v, nil
285 }
286 return 0, fmt.Errorf("expected float found %T for %s", raw, name)
287 }
288 return 0, nil
289}
290
291func (c *configFileSettings) String(name string) (string, error) {
292 if raw, ok := c.Settings[name]; ok {
293 if v, ok := raw.(string); ok {
294 return v, nil
295 }
296 return "", fmt.Errorf("expected string found %T for %s", raw, name)
297 }
298 return "", nil
299}
300
301func (c *configFileSettings) StringSlice(name string) ([]string, error) {
302 if raw, ok := c.Settings[name]; ok {
303 if slice, ok := raw.([]interface{}); ok {
304 strSlice := make([]string, len(slice))
305 for i, v := range slice {
306 str, ok := v.(string)
307 if !ok {
308 return nil, fmt.Errorf("expected string, found %T for %v", i, v)
309 }
310 strSlice[i] = str
311 }
312 return strSlice, nil
313 }
314 return nil, fmt.Errorf("expected string slice found %T for %s", raw, name)
315 }
316 return nil, nil
317}
318
319func (c *configFileSettings) IntSlice(name string) ([]int, error) {
320 if raw, ok := c.Settings[name]; ok {
321 if slice, ok := raw.([]interface{}); ok {
322 intSlice := make([]int, len(slice))
323 for i, v := range slice {
324 str, ok := v.(int)
325 if !ok {
326 return nil, fmt.Errorf("expected int, found %T for %v ", v, v)
327 }
328 intSlice[i] = str
329 }
330 return intSlice, nil
331 }
332 if v, ok := raw.([]int); ok {
333 return v, nil
334 }
335 return nil, fmt.Errorf("expected int slice found %T for %s", raw, name)
336 }
337 return nil, nil
338}
339
340func (c *configFileSettings) Generic(name string) (cli.Generic, error) {
341 return nil, errors.New("option type Generic not supported")
342}
343
344func (c *configFileSettings) Bool(name string) (bool, error) {
345 if raw, ok := c.Settings[name]; ok {
346 if v, ok := raw.(bool); ok {
347 return v, nil
348 }
349 return false, fmt.Errorf("expected boolean found %T for %s", raw, name)
350 }
351 return false, nil
352}
353
354var configuration configFileSettings
355
356func GetConfiguration() *Configuration {
357 return &configuration.Configuration
358}
359
360// ReadConfigFile returns InputSourceContext initialized from the configuration file.
361// On repeat calls returns with the same file, returns without reading the file again; however,
362// if value of "config" flag changes, will read the new config file
363func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSettings, warnings string, err error) {
364 configFile := c.String("config")
365 if configuration.Source() == configFile || configFile == "" {
366 if configuration.Source() == "" {
367 return nil, "", ErrNoConfigFile
368 }
369 return &configuration, "", nil
370 }
371
372 log.Debug().Msgf("Loading configuration from %s", configFile)
373 file, err := os.Open(configFile)
374 if err != nil {
375 if os.IsNotExist(err) {
376 err = ErrNoConfigFile
377 }
378 return nil, "", err
379 }
380 defer file.Close()
381 if err := yaml.NewDecoder(file).Decode(&configuration); err != nil {
382 if err == io.EOF {
383 log.Error().Msgf("Configuration file %s was empty", configFile)
384 return &configuration, "", nil
385 }
386 return nil, "", errors.Wrap(err, "error parsing YAML in config file at "+configFile)
387 }
388 configuration.sourceFile = configFile
389
390 // Parse it again, with strict mode, to find warnings.
391 if file, err := os.Open(configFile); err == nil {
392 decoder := yaml.NewDecoder(file)
393 decoder.SetStrict(true)
394 var unusedConfig configFileSettings
395 if err := decoder.Decode(&unusedConfig); err != nil {
396 warnings = err.Error()
397 }
398 }
399
400 return &configuration, warnings, nil
401}