-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
config.go
548 lines (474 loc) · 17.6 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
package config
import (
"bytes"
"fmt"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
model "github.com/DataDog/agent-payload/process"
"github.com/DataDog/datadog-agent/pkg/config"
"github.com/DataDog/datadog-agent/pkg/process/util"
"github.com/DataDog/datadog-agent/pkg/process/util/api"
"github.com/DataDog/datadog-agent/pkg/util/fargate"
"github.com/DataDog/datadog-agent/pkg/util/log"
)
var (
// defaultProxyPort is the default port used for proxies.
// This mirrors the configuration for the infrastructure agent.
defaultProxyPort = 3128
// defaultSystemProbeSocketPath is the default unix socket path to be used for connecting to the system probe
defaultSystemProbeSocketPath = "/opt/datadog-agent/run/sysprobe.sock"
// defaultSystemProbeFilePath is the default logging file for the system probe
defaultSystemProbeFilePath = "/var/log/datadog/system-probe.log"
processChecks = []string{"process", "rtprocess"}
containerChecks = []string{"container", "rtcontainer"}
)
type proxyFunc func(*http.Request) (*url.URL, error)
// WindowsConfig stores all windows-specific configuration for the process-agent.
type WindowsConfig struct {
// Number of checks runs between refreshes of command-line arguments
ArgsRefreshInterval int
// Controls getting process arguments immediately when a new process is discovered
AddNewArgs bool
}
// AgentConfig is the global config for the process-agent. This information
// is sourced from config files and the environment variables.
type AgentConfig struct {
Enabled bool
HostName string
APIEndpoints []api.Endpoint
OrchestratorEndpoints []api.Endpoint
LogFile string
LogLevel string
LogToConsole bool
QueueSize int
Blacklist []*regexp.Regexp
Scrubber *DataScrubber
MaxPerMessage int
MaxConnsPerMessage int
AllowRealTime bool
Transport *http.Transport `json:"-"`
DDAgentBin string
StatsdHost string
StatsdPort int
ProcessExpVarPort int
// host type of the agent, used to populate container payload with additional host information
ContainerHostType model.ContainerHostType
// System probe collection configuration
EnableSystemProbe bool
DisableTCPTracing bool
DisableUDPTracing bool
DisableIPv6Tracing bool
DisableDNSInspection bool
CollectLocalDNS bool
CollectDNSStats bool
SystemProbeSocketPath string
SystemProbeLogFile string
MaxTrackedConnections uint
SysProbeBPFDebug bool
ExcludedBPFLinuxVersions []string
ExcludedSourceConnections map[string][]string
ExcludedDestinationConnections map[string][]string
EnableConntrack bool
ConntrackIgnoreENOBUFS bool
ConntrackMaxStateSize int
SystemProbeDebugPort int
ClosedChannelSize int
MaxClosedConnectionsBuffered int
MaxConnectionsStateBuffered int
// Orchestrator collection configuration
OrchestrationCollectionEnabled bool
KubeClusterName string
// Check config
EnabledChecks []string
CheckIntervals map[string]time.Duration
// Internal store of a proxy used for generating the Transport
proxy proxyFunc
// Windows-specific config
Windows WindowsConfig
}
// CheckIsEnabled returns a bool indicating if the given check name is enabled.
func (a AgentConfig) CheckIsEnabled(checkName string) bool {
return util.StringInSlice(a.EnabledChecks, checkName)
}
// CheckInterval returns the interval for the given check name, defaulting to 10s if not found.
func (a AgentConfig) CheckInterval(checkName string) time.Duration {
d, ok := a.CheckIntervals[checkName]
if !ok {
log.Errorf("missing check interval for '%s', you must set a default", checkName)
d = 10 * time.Second
}
return d
}
const (
defaultProcessEndpoint = "https://process.datadoghq.com"
defaultOrchestratorEndpoint = "https://orchestrator.datadoghq.com"
maxMessageBatch = 100
maxConnsMessageBatch = 1000
defaultMaxTrackedConnections = 65536
)
// NewDefaultTransport provides a http transport configuration with sane default timeouts
func NewDefaultTransport() *http.Transport {
return &http.Transport{
MaxIdleConns: 5,
IdleConnTimeout: 90 * time.Second,
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 10 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 5 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
// NewDefaultAgentConfig returns an AgentConfig with defaults initialized
func NewDefaultAgentConfig(canAccessContainers bool) *AgentConfig {
processEndpoint, err := url.Parse(defaultProcessEndpoint)
if err != nil {
// This is a hardcoded URL so parsing it should not fail
panic(err)
}
orchestratorEndpoint, err := url.Parse(defaultOrchestratorEndpoint)
if err != nil {
// This is a hardcoded URL so parsing it should not fail
panic(err)
}
var enabledChecks []string
if canAccessContainers {
enabledChecks = containerChecks
}
ac := &AgentConfig{
Enabled: canAccessContainers, // We'll always run inside of a container.
APIEndpoints: []api.Endpoint{{Endpoint: processEndpoint}},
OrchestratorEndpoints: []api.Endpoint{{Endpoint: orchestratorEndpoint}},
LogFile: defaultLogFilePath,
LogLevel: "info",
LogToConsole: false,
QueueSize: 20,
MaxPerMessage: 100,
MaxConnsPerMessage: 600,
AllowRealTime: true,
HostName: "",
Transport: NewDefaultTransport(),
ProcessExpVarPort: 6062,
ContainerHostType: model.ContainerHostType_notSpecified,
// Statsd for internal instrumentation
StatsdHost: "127.0.0.1",
StatsdPort: 8125,
// System probe collection configuration
EnableSystemProbe: false,
DisableTCPTracing: false,
DisableUDPTracing: false,
DisableIPv6Tracing: false,
DisableDNSInspection: false,
SystemProbeSocketPath: defaultSystemProbeSocketPath,
SystemProbeLogFile: defaultSystemProbeFilePath,
MaxTrackedConnections: defaultMaxTrackedConnections,
EnableConntrack: true,
ConntrackIgnoreENOBUFS: false,
ClosedChannelSize: 500,
ConntrackMaxStateSize: defaultMaxTrackedConnections * 2,
// Check config
EnabledChecks: enabledChecks,
CheckIntervals: map[string]time.Duration{
"process": 10 * time.Second,
"rtprocess": 2 * time.Second,
"container": 10 * time.Second,
"rtcontainer": 2 * time.Second,
"connections": 30 * time.Second,
"pod": 10 * time.Second,
},
// DataScrubber to hide command line sensitive words
Scrubber: NewDefaultDataScrubber(),
Blacklist: make([]*regexp.Regexp, 0),
// Windows process config
Windows: WindowsConfig{
ArgsRefreshInterval: 15, // with default 20s check interval we refresh every 5m
AddNewArgs: true,
},
}
// Set default values for proc/sys paths if unset.
// Don't set this is /host is not mounted to use context within container.
// Generally only applicable for container-only cases like Fargate.
if config.IsContainerized() && util.PathExists("/host") {
if v := os.Getenv("HOST_PROC"); v == "" {
os.Setenv("HOST_PROC", "/host/proc")
}
if v := os.Getenv("HOST_SYS"); v == "" {
os.Setenv("HOST_SYS", "/host/sys")
}
}
return ac
}
func loadConfigIfExists(path string) error {
if util.PathExists(path) {
config.Datadog.AddConfigPath(path)
if strings.HasSuffix(path, ".yaml") { // If they set a config file directly, let's try to honor that
config.Datadog.SetConfigFile(path)
}
if err := config.LoadWithoutSecret(); err != nil {
return err
}
} else {
log.Infof("no config exists at %s, ignoring...", path)
}
return nil
}
// NewAgentConfig returns an AgentConfig using a configuration file. It can be nil
// if there is no file available. In this case we'll configure only via environment.
func NewAgentConfig(loggerName config.LoggerName, yamlPath, netYamlPath string) (*AgentConfig, error) {
var err error
// Note: This only considers container sources that are already setup. It's possible that container sources may
// need a few minutes to be ready on newly provisioned hosts.
_, err = util.GetContainers()
canAccessContainers := err == nil
cfg := NewDefaultAgentConfig(canAccessContainers)
// For Agent 6 we will have a YAML config file to use.
if err := loadConfigIfExists(yamlPath); err != nil {
return nil, err
}
if err := cfg.LoadProcessYamlConfig(yamlPath); err != nil {
return nil, err
}
// (Re)configure the logging from our configuration
if err := setupLogger(loggerName, cfg.LogFile, cfg); err != nil {
log.Errorf("failed to setup configured logger: %s", err)
return nil, err
}
// For system probe, there is an additional config file that is shared with the system-probe
loadConfigIfExists(netYamlPath)
if err = cfg.loadSysProbeYamlConfig(netYamlPath); err != nil {
return nil, err
}
// TODO: Once proxies have been moved to common config util, remove this
if cfg.proxy, err = proxyFromEnv(cfg.proxy); err != nil {
log.Errorf("error parsing environment proxy settings, not using a proxy: %s", err)
cfg.proxy = nil
}
// Python-style log level has WARNING vs WARN
if strings.ToLower(cfg.LogLevel) == "warning" {
cfg.LogLevel = "warn"
}
if cfg.HostName == "" {
if fargate.IsFargateInstance() {
if hostname, err := fargate.GetFargateHost(); err == nil {
cfg.HostName = hostname
} else {
log.Errorf("Cannot get Fargate host: %v", err)
}
} else if hostname, err := getHostname(cfg.DDAgentBin); err == nil {
cfg.HostName = hostname
}
}
cfg.ContainerHostType = getContainerHostType()
if cfg.proxy != nil {
cfg.Transport.Proxy = cfg.proxy
}
// sanity check. This element is used with the modulo operator (%), so it can't be zero.
// if it is, log the error, and assume the config was attempting to disable
if cfg.Windows.ArgsRefreshInterval == 0 {
log.Warnf("invalid configuration: windows_collect_skip_new_args was set to 0. Disabling argument collection")
cfg.Windows.ArgsRefreshInterval = -1
}
// activate the pod collection if enabled and we have the cluster name set
if cfg.OrchestrationCollectionEnabled && cfg.KubeClusterName != "" {
cfg.EnabledChecks = append(cfg.EnabledChecks, "pod")
}
return cfg, nil
}
// NewSystemProbeConfig returns a system-probe specific AgentConfig using a configuration file. It can be nil
// if there is no file available. In this case we'll configure only via environment.
func NewSystemProbeConfig(loggerName config.LoggerName, yamlPath string) (*AgentConfig, error) {
cfg := NewDefaultAgentConfig(false) // We don't access the container APIs in the system-probe
// When the system-probe is enabled in a separate container, we need a way to also disable the system-probe
// packaged in the main agent container (without disabling network collection on the process-agent).
//
// If this environment flag is set, it'll sure it will not start
if ok, _ := isAffirmative(os.Getenv("DD_SYSTEM_PROBE_EXTERNAL")); ok {
cfg.EnableSystemProbe = false
return cfg, nil
}
loadConfigIfExists(yamlPath)
if err := cfg.loadSysProbeYamlConfig(yamlPath); err != nil {
return nil, err
}
// (Re)configure the logging from our configuration, with the system probe log file + config options
if err := setupLogger(loggerName, cfg.SystemProbeLogFile, cfg); err != nil {
log.Errorf("failed to setup configured logger: %s", err)
return nil, err
}
return cfg, nil
}
// getContainerHostType uses the fargate library to detect container environment and returns the protobuf version of it
func getContainerHostType() model.ContainerHostType {
switch fargate.GetOrchestrator() {
case fargate.ECS:
return model.ContainerHostType_fargateECS
case fargate.EKS:
return model.ContainerHostType_fargateEKS
}
return model.ContainerHostType_notSpecified
}
func loadEnvVariables() {
// The following environment variables will be loaded in the order listed, meaning variables
// further down the list may override prior variables.
for _, variable := range []struct{ env, cfg string }{
{"DD_PROCESS_AGENT_CONTAINER_SOURCE", "process_config.container_source"},
{"DD_SCRUB_ARGS", "process_config.scrub_args"},
{"DD_STRIP_PROCESS_ARGS", "process_config.strip_proc_arguments"},
{"DD_PROCESS_AGENT_URL", "process_config.process_dd_url"},
{"DD_ORCHESTRATOR_URL", "process_config.orchestrator_dd_url"},
// System probe specific configuration (Beta)
{"DD_SYSTEM_PROBE_ENABLED", "system_probe_config.enabled"},
{"DD_SYSPROBE_SOCKET", "system_probe_config.sysprobe_socket"},
{"DD_SYSTEM_PROBE_CONNTRACK_IGNORE_ENOBUFS", "system_probe_config.conntrack_ignore_enobufs"},
{"DD_DISABLE_TCP_TRACING", "system_probe_config.disable_tcp"},
{"DD_DISABLE_UDP_TRACING", "system_probe_config.disable_udp"},
{"DD_DISABLE_IPV6_TRACING", "system_probe_config.disable_ipv6"},
{"DD_DISABLE_DNS_INSPECTION", "system_probe_config.disable_dns_inspection"},
{"DD_COLLECT_LOCAL_DNS", "system_probe_config.collect_local_dns"},
{"DD_HOSTNAME", "hostname"},
{"DD_DOGSTATSD_PORT", "dogstatsd_port"},
{"DD_BIND_HOST", "bind_host"},
{"HTTPS_PROXY", "proxy.https"},
{"DD_PROXY_HTTPS", "proxy.https"},
{"DD_LOGS_STDOUT", "log_to_console"},
{"LOG_TO_CONSOLE", "log_to_console"},
{"DD_LOG_TO_CONSOLE", "log_to_console"},
{"LOG_LEVEL", "log_level"}, // Support LOG_LEVEL and DD_LOG_LEVEL but prefer DD_LOG_LEVEL
{"DD_LOG_LEVEL", "log_level"},
} {
if v, ok := os.LookupEnv(variable.env); ok {
config.Datadog.Set(variable.cfg, v)
}
}
// Support API_KEY and DD_API_KEY but prefer DD_API_KEY.
apiKey, envKey := os.Getenv("DD_API_KEY"), "DD_API_KEY"
if apiKey == "" {
apiKey, envKey = os.Getenv("API_KEY"), "API_KEY"
}
if apiKey != "" { // We don't want to overwrite the API KEY provided as an environment variable
log.Infof("overriding API key from env %s value", envKey)
config.Datadog.Set("api_key", strings.TrimSpace(strings.Split(apiKey, ",")[0]))
}
if v := os.Getenv("DD_CUSTOM_SENSITIVE_WORDS"); v != "" {
config.Datadog.Set("process_config.custom_sensitive_words", strings.Split(v, ","))
}
}
// IsBlacklisted returns a boolean indicating if the given command is blacklisted by our config.
func IsBlacklisted(cmdline []string, blacklist []*regexp.Regexp) bool {
cmd := strings.Join(cmdline, " ")
for _, b := range blacklist {
if b.MatchString(cmd) {
return true
}
}
return false
}
func isAffirmative(value string) (bool, error) {
if value == "" {
return false, fmt.Errorf("value is empty")
}
v := strings.ToLower(value)
return v == "true" || v == "yes" || v == "1", nil
}
// getHostname shells out to obtain the hostname used by the infra agent
// falling back to os.Hostname() if it is unavailable
func getHostname(ddAgentBin string) (string, error) {
cmd := exec.Command(ddAgentBin, "hostname")
// Copying all environment variables to child process
// Windows: Required, so the child process can load DLLs, etc.
// Linux: Optional, but will make use of DD_HOSTNAME and DOCKER_DD_AGENT if they exist
cmd.Env = os.Environ()
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
log.Infof("error retrieving dd-agent hostname, falling back to os.Hostname(): %v", err)
return os.Hostname()
}
hostname := strings.TrimSpace(stdout.String())
if hostname == "" {
log.Infof("error retrieving dd-agent hostname, falling back to os.Hostname(): %s", stderr.String())
return os.Hostname()
}
return hostname, err
}
// proxyFromEnv parses out the proxy configuration from the ENV variables in a
// similar way to getProxySettings and, if enough values are available, returns
// a new proxy URL value. If the environment is not set for this then the
// `defaultVal` is returned.
func proxyFromEnv(defaultVal proxyFunc) (proxyFunc, error) {
var host string
scheme := "http"
if v := os.Getenv("PROXY_HOST"); v != "" {
// accept either http://myproxy.com or myproxy.com
if i := strings.Index(v, "://"); i != -1 {
// when available, parse the scheme from the url
scheme = v[0:i]
host = v[i+3:]
} else {
host = v
}
}
if host == "" {
return defaultVal, nil
}
port := defaultProxyPort
if v := os.Getenv("PROXY_PORT"); v != "" {
port, _ = strconv.Atoi(v)
}
var user, password string
if v := os.Getenv("PROXY_USER"); v != "" {
user = v
}
if v := os.Getenv("PROXY_PASSWORD"); v != "" {
password = v
}
return constructProxy(host, scheme, port, user, password)
}
// constructProxy constructs a *url.Url for a proxy given the parts of a
// Note that we assume we have at least a non-empty host for this call but
// all other values can be their defaults (empty string or 0).
func constructProxy(host, scheme string, port int, user, password string) (proxyFunc, error) {
var userpass *url.Userinfo
if user != "" {
if password != "" {
userpass = url.UserPassword(user, password)
} else {
userpass = url.User(user)
}
}
var path string
if userpass != nil {
path = fmt.Sprintf("%s@%s:%v", userpass.String(), host, port)
} else {
path = fmt.Sprintf("%s:%v", host, port)
}
if scheme != "" {
path = fmt.Sprintf("%s://%s", scheme, path)
}
u, err := url.Parse(path)
if err != nil {
return nil, err
}
return http.ProxyURL(u), nil
}
func setupLogger(loggerName config.LoggerName, logFile string, cfg *AgentConfig) error {
return config.SetupLogger(
loggerName,
cfg.LogLevel,
logFile,
config.GetSyslogURI(),
config.Datadog.GetBool("syslog_rfc"),
config.Datadog.GetBool("log_to_console"),
config.Datadog.GetBool("log_format_json"),
)
}