diff --git a/README.md b/README.md index e897c3c58..4071ba729 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ customfields: # custom fields are added to falco events Akey: "AValue" Bkey: "BValue" Ckey: "CValue" -checkCert: true # check if ssl certificate of the output is valid (default: true) +mutualtlsfilespath: "/etc/certs" # folder which will used to store client.crt, client.key and ca.crt files for mutual tls (default: "/etc/certs") slack: webhookurl: "" # Slack WebhookURL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not empty, Slack output is enabled @@ -157,6 +157,8 @@ rocketchat: outputformat: "all" # all (default), text, fields minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) # messageformat: "Alert : rule *{{ .Rule }}* triggered by user *{{ index .OutputFields \"user.name\" }}*" # a Go template to format Rocketchat Text above Attachment, displayed in addition to the output from `ROCKETCHAT_OUTPUTFORMAT`, see [Slack Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) mattermost: webhookurl: "" # Mattermost WebhookURL (ex: http://XXXX/hooks/YYYY), if not empty, Mattermost output is enabled @@ -166,6 +168,8 @@ mattermost: outputformat: "all" # all (default), text, fields minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) # messageformat: "Alert : rule **{{ .Rule }}** triggered by user **{{ index .OutputFields \"user.name\" }}**" # a Go template to format Mattermost Text above Attachment, displayed in addition to the output from `MATTERMOST_OUTPUTFORMAT`, see [Slack Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) teams: webhookurl: "" # Teams WebhookURL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not empty, Teams output is enabled @@ -181,6 +185,8 @@ datadog: alertmanager: # hostport: "" # http://{domain or ip}:{port}, if not empty, Alertmanager output is enabled # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) elasticsearch: # hostport: "" # http://{domain or ip}:{port}, if not empty, Elasticsearch output is enabled @@ -188,6 +194,8 @@ elasticsearch: # type: "event" # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) # suffix: "daily" # date suffix for index rotation : daily (default), monthly, annually, none + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) influxdb: # hostport: "" # http://{domain or ip}:{port}, if not empty, Influxdb output is enabled @@ -195,20 +203,27 @@ influxdb: # user: "" # user to use if auth is enabled in Influxdb # password: "" # pasword to use if auth is enabled in Influxdb # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) loki: # hostport: "" # http://{domain or ip}:{port}, if not empty, Loki output is enabled # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) - -nats: - # hostport: "" # nats://{domain or ip}:{port}, if not empty, NATS output is enabled - # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # checkcert: true # check if ssl certificate of the output is valid (default: true) stan: # hostport: "" # nats://{domain or ip}:{port}, if not empty, STAN output is enabled # clusterid: "" # Cluster name, if not empty, STAN output is enabled # clientid: "" # Client ID, if not empty, STAN output is enabled # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) + +nats: + # hostport: "" # nats://{domain or ip}:{port}, if not empty, NATS output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) aws: # accesskeyid: "" # aws access key (optional if you use EC2 Instance Profile) @@ -262,12 +277,14 @@ webhook: # customHeaders: # Custom headers to add in POST, useful for Authentication # key: value # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) azure: - # eventHub: - # name: "" # The name of the Hub, if not empty, EventHub output is enabled - # namespace: "" # The name of the space the Hub is part of - # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + eventHub: + name: "" # Name of the Hub, if not empty, EventHub is enabled + namespace: "" # Name of the space the Hub is in + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) discord: webhookurl: "" # discord WebhookURL (ex: https://discord.com/api/webhooks/xxxxxxxxxx...), if not empty, Discord output is enabled @@ -307,6 +324,7 @@ kubeless: port: 8080 # Port of service of Kubeless function kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcoside is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # checkcert: true # check if ssl certificate of the output is valid (default: true) openfaas: functionname: "" # Name of OpenFaaS function, if not empty, OpenFaaS is enabled @@ -316,6 +334,12 @@ openfaas: gatewaynamespace: "openfaas" # Namespace of OpenFaaS Gateway, "openfaas" (default) kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcosidekick is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # checkcert: true # check if ssl certificate of the output is valid (default: true) + +rabbitmq: + url: "" # Rabbitmq URL, if not empty, Rabbitmq output is enabled + queue: "" # Rabbitmq Queue name + # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) wavefront: endpointtype: "direct" # Wavefront endpoint type, must be 'direct' or 'proxy'. If not empty, with endpointhost, Wavefront output is enabled @@ -356,8 +380,7 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : (default: false) - **CUSTOMFIELDS** : a list of comma separated custom fields to add to falco events, syntax is "key:value,key:value" -- **CHECKCERT**: check if ssl certificate of the output is valid (default: - `true`) + **MUTUALTLSFILESPATH**: path which will be used to stored certs and key for mutual tls authentication (default: "/etc/certs") - **SLACK_WEBHOOKURL** : Slack Webhook URL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not `empty`, Slack output is _enabled_ @@ -387,6 +410,10 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : `ROCKETCHAT_OUTPUTFORMAT`, see [Slack Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. +- **ROCKETCHAT_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **ROCKETCHAT_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **MATTERMOST_WEBHOOKURL** : Mattermost Webhook URL (ex: https://XXXX/hooks/YYYY), if not `empty`, Mattermost output is _enabled_ - **MATTERMOST_FOOTER** : Mattermost footer @@ -402,6 +429,10 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : `MATTERMOST_OUTPUTFORMAT`, see [Mattermost Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. +- **MATTERMOST_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **MATTERMOST_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **TEAMS_WEBHOOKURL** : Teams Webhook URL (ex: https://outlook.office.com/webhook/XXXXXX/IncomingWebhook/YYYYYY"), if not `empty`, Teams output is _enabled_ @@ -430,6 +461,10 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **ALERTMANAGER_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **ALERTMANAGER_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **ALERTMANAGER_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **ELASTICSEARCH_HOSTPORT** : Elasticsearch http://host:port, if not `empty`, Elasticsearch is _enabled_ - **ELASTICSEARCH_INDEX** : Elasticsearch index (default: falco) @@ -439,6 +474,10 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` - **ELASTICSEARCH_SUFFIX** : date suffix for index rotation : `daily` (default), `monthly`, `annually`, `none` +- **ELASTICSEARCH_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **ELASTICSEARCH_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **INFLUXDB_HOSTPORT** : Influxdb http://host:port, if not `empty`, Influxdb is _enabled_ - **INFLUXDB_DATABASE** : Influxdb database (default: falco) @@ -447,19 +486,33 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **INFLUXDB_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **INFLUXDB_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **INFLUXDB_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **LOKI_HOSTPORT** : Loki http://host:port, if not `empty`, Loki is _enabled_ - **LOKI_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **LOKI_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **NATS_HOSTPORT** : NATS "nats://host:port", if not `empty`, NATS is _enabled_ - **NATS_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **NATS_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **NATS_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **STAN_HOSTPORT** : NATS "nats://host:port", if not `empty`, STAN is _enabled_ - **STAN_CLUSTERID** : Cluster name, if not `empty`, STAN is _enabled_ - **STAN_CLIENTID** : Client ID to use, if not `empty`, STAN is _enabled_ - **STAN_MINIMUMPRIORITY** : minimum priority of event for using this output, order is +- **STAN_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **STAN_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` - **AWS_ACCESSKEYID** : AWS Access Key Id (optional if you use EC2 Instance Profile) @@ -524,6 +577,10 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **WEBHOOK_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **WEBHOOK_MUTUALTLS** : enable mutual tls authentication for this output (default: + `false`) +- **WEBHOOK_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **CLOUDEVENTS_ADDRESS** : CloudEvents consumer address, if not empty, CloudEvents output is _enabled_ - **CLOUDEVENTS_EXTENSIONS** : a list of comma separated extensions to add, @@ -587,6 +644,8 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **KUBELESS_MINIMUMPRIORITY**: "debug" # minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **KUBELESS_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **OPENFAAS_GATEWAYNAMESPACE** : Namespace of OpenFaaS Gateway, "openfaas" (default) - **OPENFAAS_GATEWAYSERVICE** : Service of OpenFaaS Gateway, "gateway" (default) - **OPENFAAS_FUNCTIONNAME** : Name of OpenFaaS function, if not empty, OpenFaaS is enabled @@ -597,6 +656,8 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **OPENFAAS_MINIMUMPRIORITY** : "debug" # minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **OPENFAAS_CHECKCERT** : check if ssl certificate of the output is valid (default: + `true`) - **WEBUI_URL** : WebUI URL, if not empty, WebUI output is _enabled_ - **RABBITMQ_URL**: Rabbitmq URL, if not empty, Rabbitmq output is enabled @@ -655,6 +716,16 @@ All logs are sent to `stdout`. 2019/05/10 14:32:06 [INFO] : Enabled Outputs : Slack Datadog ``` +## Mutual TLS ## + +Outputs with `mutualtls` enabled in their configuration require *client.crt*, *client.key* and *ca.crt* files to be stored in the path configured in **mutualtlsfilespath** global parameter (**important**: file names must be preserved) + +```bash +docker run -d -p 2801:2801 -e MUTUALTLSFILESPATH=/etc/certs -e ALERTMANAGER_HOSTPORT=https://XXXX -e ALERTMANAGER_MUTUALTLS=true -e INFLUXDB_HOSTPORT=https://XXXX -e INFLUXDB_MUTUALTLS=true -e WEBHOOK_ADDRESS=XXXX -v /localpath/myclientcert.crt:/etc/certs/client.crt -v /localpath/myclientkey.key:/etc/certs/client.key -v /localpath/ca.crt:/etc/certs/ca.crt falcosecurity/falcosidekick +``` + +In above example, the same client certificate will be used for both Alertmanager & InfluxDB outputs which have mutualtls flag set to true. + ## Metrics ### Golang ExpVar diff --git a/config.go b/config.go index 13f311f72..200f4712e 100644 --- a/config.go +++ b/config.go @@ -30,7 +30,7 @@ func getConfig() *types.Configuration { v.SetDefault("ListenAddress", "") v.SetDefault("ListenPort", 2801) v.SetDefault("Debug", false) - v.SetDefault("CheckCert", true) + v.SetDefault("MutualTlsFilesPath", "/etc/certs") v.SetDefault("Slack.WebhookURL", "") v.SetDefault("Slack.Footer", "https://github.com/falcosecurity/falcosidekick") v.SetDefault("Slack.Username", "Falcosidekick") @@ -38,6 +38,8 @@ func getConfig() *types.Configuration { v.SetDefault("Slack.OutputFormat", "all") v.SetDefault("Slack.MessageFormat", "") v.SetDefault("Slack.MinimumPriority", "") + v.SetDefault("Slack.MutualTLS", false) + v.SetDefault("Slack.CheckCert", true) v.SetDefault("Rocketchat.WebhookURL", "") v.SetDefault("Rocketchat.Footer", "https://github.com/falcosecurity/falcosidekick") v.SetDefault("Rocketchat.Username", "Falcosidekick") @@ -45,6 +47,8 @@ func getConfig() *types.Configuration { v.SetDefault("Rocketchat.OutputFormat", "all") v.SetDefault("Rocketchat.MessageFormat", "") v.SetDefault("Rocketchat.MinimumPriority", "") + v.SetDefault("Rocketchat.MutualTLS", false) + v.SetDefault("Rocketchat.CheckCert", true) v.SetDefault("Mattermost.WebhookURL", "") v.SetDefault("Mattermost.Footer", "https://github.com/falcosecurity/falcosidekick") v.SetDefault("Mattermost.Username", "Falcosidekick") @@ -52,30 +56,46 @@ func getConfig() *types.Configuration { v.SetDefault("Mattermost.OutputFormat", "all") v.SetDefault("Mattermost.MessageFormat", "") v.SetDefault("Mattermost.MinimumPriority", "") + v.SetDefault("Mattermost.MutualTLS", false) + v.SetDefault("Mattermost.CheckCert", true) v.SetDefault("Teams.WebhookURL", "") v.SetDefault("Teams.ActivityImage", "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick_color.png") v.SetDefault("Teams.OutputFormat", "all") v.SetDefault("Teams.MinimumPriority", "") + v.SetDefault("Teams.MutualTLS", false) + v.SetDefault("Teams.CheckCert", true) v.SetDefault("Datadog.APIKey", "") v.SetDefault("Datadog.Host", "https://api.datadoghq.com") v.SetDefault("Datadog.MinimumPriority", "") + v.SetDefault("Datadog.MutualTLS", false) + v.SetDefault("Datadog.CheckCert", true) v.SetDefault("Discord.WebhookURL", "") v.SetDefault("Discord.MinimumPriority", "") v.SetDefault("Discord.Icon", "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick_color.png") + v.SetDefault("Discord.MutualTLS", false) + v.SetDefault("Discord.CheckCert", true) v.SetDefault("Alertmanager.HostPort", "") v.SetDefault("Alertmanager.MinimumPriority", "") + v.SetDefault("Alertmanager.MutualTls", false) + v.SetDefault("Alertmanager.CheckCert", true) v.SetDefault("Elasticsearch.HostPort", "") v.SetDefault("Elasticsearch.Index", "falco") v.SetDefault("Elasticsearch.Type", "event") v.SetDefault("Elasticsearch.MinimumPriority", "") v.SetDefault("Elasticsearch.Suffix", "daily") + v.SetDefault("Elasticsearch.MutualTls", false) + v.SetDefault("Elasticsearch.CheckCert", true) v.SetDefault("Influxdb.HostPort", "") v.SetDefault("Influxdb.Database", "falco") v.SetDefault("Influxdb.User", "") v.SetDefault("Influxdb.Password", "") v.SetDefault("Influxdb.MinimumPriority", "") + v.SetDefault("Influxdb.MutualTls", false) + v.SetDefault("Influxdb.CheckCert", true) v.SetDefault("Loki.HostPort", "") v.SetDefault("Loki.MinimumPriority", "") + v.SetDefault("Loki.MutualTLS", false) + v.SetDefault("Loki.CheckCert", true) v.SetDefault("AWS.AccessKeyID", "") v.SetDefault("AWS.SecretAccessKey", "") v.SetDefault("AWS.Region", "") @@ -104,12 +124,18 @@ func getConfig() *types.Configuration { v.SetDefault("STAN.HostPort", "") v.SetDefault("STAN.ClusterID", "") v.SetDefault("STAN.ClientID", "") + v.SetDefault("STAN.MutualTls", false) + v.SetDefault("STAN.CheckCert", true) v.SetDefault("NATS.HostPort", "") v.SetDefault("NATS.ClusterID", "") v.SetDefault("NATS.ClientID", "") + v.SetDefault("NATS.MutualTls", false) + v.SetDefault("NATS.CheckCert", true) v.SetDefault("Opsgenie.Region", "us") v.SetDefault("Opsgenie.APIKey", "") v.SetDefault("Opsgenie.MinimumPriority", "") + v.SetDefault("Opsgenie.MutualTLS", false) + v.SetDefault("Opsgenie.CheckCert", true) v.SetDefault("Statsd.Forwarder", "") v.SetDefault("Statsd.Namespace", "falcosidekick.") v.SetDefault("Dogstatsd.Forwarder", "") @@ -117,8 +143,12 @@ func getConfig() *types.Configuration { v.SetDefault("Dogstatsd.Tags", []string{}) v.SetDefault("Webhook.Address", "") v.SetDefault("Webhook.MinimumPriority", "") + v.SetDefault("Webhook.MutualTls", false) + v.SetDefault("Webhook.CheckCert", true) v.SetDefault("CloudEvents.Address", "") v.SetDefault("CloudEvents.MinimumPriority", "") + v.SetDefault("CloudEvents.MutualTls", false) + v.SetDefault("CloudEvents.CheckCert", true) v.SetDefault("Azure.eventHub.Namespace", "") v.SetDefault("Azure.eventHub.Name", "") v.SetDefault("Azure.eventHub.MinimumPriority", "") @@ -133,16 +163,22 @@ func getConfig() *types.Configuration { v.SetDefault("Googlechat.OutputFormat", "all") v.SetDefault("Googlechat.MessageFormat", "") v.SetDefault("Googlechat.MinimumPriority", "") + v.SetDefault("Googlechat.MutualTls", false) + v.SetDefault("Googlechat.CheckCert", true) v.SetDefault("Kafka.HostPort", "") v.SetDefault("Kafka.Topic", "") v.SetDefault("Kafka.MinimumPriority", "") v.SetDefault("Pagerduty.RoutingKey", "") v.SetDefault("Pagerduty.MinimumPriority", "") + v.SetDefault("Googlechat.MutualTls", false) + v.SetDefault("Pagerduty.CheckCert", true) v.SetDefault("Kubeless.Namespace", "") v.SetDefault("Kubeless.Function", "") v.SetDefault("Kubeless.Port", 8080) v.SetDefault("Kubeless.Kubeconfig", "") v.SetDefault("Kubeless.MinimumPriority", "") + v.SetDefault("Kubeless.MutualTls", false) + v.SetDefault("Kubeless.CheckCert", true) v.SetDefault("Openfaas.GatewayNamespace", "openfaas") v.SetDefault("Openfaas.GatewayService", "gateway") @@ -151,8 +187,12 @@ func getConfig() *types.Configuration { v.SetDefault("Openfaas.GatewayPort", 8080) v.SetDefault("Openfaas.Kubeconfig", "") v.SetDefault("Openfaas.MinimumPriority", "") + v.SetDefault("Openfaas.MutualTls", false) + v.SetDefault("Openfaas.CheckCert", true) v.SetDefault("Webui.URL", "") + v.SetDefault("Webui.MutualTls", false) + v.SetDefault("Webui.CheckCert", true) v.SetDefault("Rabbitmq.URL", "") v.SetDefault("Rabbitmq.Queue", "") v.SetDefault("Rabbitmq.MinimumPriority", "") diff --git a/config_example.yaml b/config_example.yaml index bb312a19e..0f6d38f73 100644 --- a/config_example.yaml +++ b/config_example.yaml @@ -5,7 +5,7 @@ customfields: # custom fields are added to falco events Akey: "AValue" Bkey: "BValue" Ckey: "CValue" -checkCert: true # check if ssl certificate of the output is valid (default: true) +mutualtlsfilespath: "/etc/certs" # folder which will used to store client.crt, client.key and ca.crt files for mutual tls (default: "/etc/certs") slack: webhookurl: "" # Slack WebhookURL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not empty, Slack output is enabled @@ -23,6 +23,8 @@ rocketchat: outputformat: "all" # all (default), text, fields minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) # messageformat: "Alert : rule *{{ .Rule }}* triggered by user *{{ index .OutputFields \"user.name\" }}*" # a Go template to format Rocketchat Text above Attachment, displayed in addition to the output from `ROCKETCHAT_OUTPUTFORMAT`, see [Slack Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) mattermost: webhookurl: "" # Mattermost WebhookURL (ex: http://XXXX/hooks/YYYY), if not empty, Mattermosst output is enabled @@ -32,6 +34,8 @@ mattermost: outputformat: "all" # all (default), text, fields minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) # messageformat: "Alert : rule **{{ .Rule }}** triggered by user **{{ index .OutputFields \"user.name\" }}**" # a Go template to format Mattermost Text above Attachment, displayed in addition to the output from `MATTERMOST_OUTPUTFORMAT`, see [Slack Message Formatting](#slack-message-formatting) in the README for details. If empty, no Text is displayed before Attachment. + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) teams: webhookurl: "" # Teams WebhookURL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not empty, Teams output is enabled @@ -40,35 +44,58 @@ teams: minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) datadog: -# apikey: "" # Datadog API Key, if not empty, Datadog output is enabled -# host: "" # Datadog host. Override if you are on the Datadog EU site. Defaults to american site with "https://api.datadoghq.com" -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # apikey: "" # Datadog API Key, if not empty, Datadog output is enabled + # host: "" # Datadog host. Override if you are on the Datadog EU site. Defaults to american site with "https://api.datadoghq.com" + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) alertmanager: -# hostport: "" # http://{domain or ip}:{port}, if not empty, Alertmanager output is enabled -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # hostport: "" # http://{domain or ip}:{port}, if not empty, Alertmanager output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) elasticsearch: -# hostport: "" # http://{domain or ip}:{port}, if not empty, Elasticsearch output is enabled -# index: "falco" # index (default: falco) -# type: "event" -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) -# suffix: "daily" # date suffix for index rotation : daily (default), monthly, annually, none + # hostport: "" # http://{domain or ip}:{port}, if not empty, Elasticsearch output is enabled + # index: "falco" # index (default: falco) + # type: "event" + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # suffix: "daily" # date suffix for index rotation : daily (default), monthly, annually, none + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) influxdb: -# hostport: "" # http://{domain or ip}:{port}, if not empty, Influxdb output is enabled -# database: "falco" # Influxdb database (default: falco) -# user: "" # user to use if auth is enabled in Influxdb -# password: "" # pasword to use if auth is enabled in Influxdb -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # hostport: "" # http://{domain or ip}:{port}, if not empty, Influxdb output is enabled + # database: "falco" # Influxdb database (default: falco) + # user: "" # user to use if auth is enabled in Influxdb + # password: "" # pasword to use if auth is enabled in Influxdb + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) loki: -# hostport: "" # http://{domain or ip}:{port}, if not empty, Loki output is enabled -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # hostport: "" # http://{domain or ip}:{port}, if not empty, Loki output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # checkcert: true # check if ssl certificate of the output is valid (default: true) nats: -# hostport: "" # nats://{domain or ip}:{port}, if not empty, NATS output is enabled -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # hostport: "" # nats://{domain or ip}:{port}, if not empty, NATS output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) + +stan: + # hostport: "" # nats://{domain or ip}:{port}, if not empty, STAN output is enabled + # clusterid: "" # Cluster name, if not empty, STAN output is enabled + # clientid: "" # Client ID, if not empty, STAN output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) + +nats: + # hostport: "" # nats://{domain or ip}:{port}, if not empty, NATS output is enabled + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) aws: # accesskeyid: "" # aws access key (optional if you use EC2 Instance Profile) @@ -85,22 +112,22 @@ aws: rawjson: false # Send Raw JSON or parse it (default: false) # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) cloudwatchlogs: - # loggroup : "" # AWS CloudWatch Logs Group name, if not empty, CloudWatch Logs output is enabled - # logstream : "" # AWS CloudWatch Logs Stream name, if empty, Falcosidekick will try to create a log stream - # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # loggroup : "" # AWS CloudWatch Logs Group name, if not empty, CloudWatch Logs output is enabled + # logstream : "" # AWS CloudWatch Logs Stream name, if empty, Falcosidekick will try to create a log stream + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) s3: # bucket: "falcosidekick" # AWS S3, bucket name # prefix : "" # name of prefix, keys will have format: s3:////YYYY-MM-DD/YYYY-MM-DDTHH:mm:ss.s+01:00.json # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) smtp: -# hostport: "" # host:port address of SMTP server, if not empty, SMTP output is enabled -# user: "" # user to access SMTP server -# password: "" # password to access SMTP server -# from: "" # Sender address (mandatory if SMTP output is enabled) -# to: "" # comma-separated list of Recipident addresses, can't be empty (mandatory if SMTP output is enabled) -# outputformat: "" # html (default), text -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # hostport: "" # host:port address of SMTP server, if not empty, SMTP output is enabled + # user: "" # user to access SMTP server + # password: "" # password to access SMTP server + # from: "" # Sender address (mandatory if SMTP output is enabled) + # to: "" # comma-separated list of Recipident addresses, can't be empty (mandatory if SMTP output is enabled) + # outputformat: "" # html (default), text + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) statsd: forwarder: "" # The address for the StatsD forwarder, in the form "host:port", if not empty StatsD is enabled @@ -118,10 +145,12 @@ opsgenie: # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) webhook: -# address: "" # Webhook address, if not empty, Webhook output is enabled -# customHeaders: # Custom headers to add in POST, useful for Authentication -# key: value -# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # address: "" # Webhook address, if not empty, Webhook output is enabled + # customHeaders: # Custom headers to add in POST, useful for Authentication + # key: value + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # mutualtls: false # if true, checkcert flag will be ignored (server cert will always be checked) + # checkcert: true # check if ssl certificate of the output is valid (default: true) cloudevents: # address: "" # CloudEvents consumer http address, if not empty, CloudEvents output is enabled @@ -162,12 +191,17 @@ kafka: topic: "" # Name of the topic, if not empty, Kafka output is enabled # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) +pagerduty: + routingKey: "" # Pagerduty Routing Key, if not empty, Pagerduty output is enabled + minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + kubeless: function: "" # Name of Kubeless function, if not empty, Kubeless is enabled namespace: "" # Namespace of Kubeless function (mandatory) port: 8080 # Port of service of Kubeless function kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcoside is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + # checkcert: true # check if ssl certificate of the output is valid (default: true) openfaas: functionname: "" # Name of OpenFaaS function, if not empty, OpenFaaS is enabled @@ -177,9 +211,7 @@ openfaas: gatewaynamespace: "openfaas" # Namespace of OpenFaaS Gateway, "openfaas" (default) kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcosidekick is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) - -webui: - url: "" # WebUI URL, if not empty, WebUI output is enabled + # checkcert: true # check if ssl certificate of the output is valid (default: true) rabbitmq: url: "" # Rabbitmq URL, if not empty, Rabbitmq output is enabled @@ -196,3 +228,5 @@ wavefront: # batchsize: 10000 # Wavefront batch size. If empty uses the default 10000. Only used when endpointtype is 'direct' # flushintervalseconds: 1 # Wavefront flush interval in seconds. Defaults to 1 +webui: + url: "" # WebUI URL, if not empty, WebUI output is enabled diff --git a/main.go b/main.go index 082431126..833ccf41e 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,7 @@ func init() { if config.Slack.WebhookURL != "" { var err error - slackClient, err = outputs.NewClient("Slack", config.Slack.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + slackClient, err = outputs.NewClient("Slack", config.Slack.WebhookURL, config.Slack.MutualTLS, config.Slack.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Slack.WebhookURL = "" } else { @@ -99,7 +99,7 @@ func init() { if config.Rocketchat.WebhookURL != "" { var err error - rocketchatClient, err = outputs.NewClient("Rocketchat", config.Rocketchat.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + rocketchatClient, err = outputs.NewClient("Rocketchat", config.Rocketchat.WebhookURL, config.Rocketchat.MutualTLS, config.Rocketchat.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Rocketchat.WebhookURL = "" } else { @@ -109,7 +109,7 @@ func init() { if config.Mattermost.WebhookURL != "" { var err error - mattermostClient, err = outputs.NewClient("Mattermost", config.Mattermost.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + mattermostClient, err = outputs.NewClient("Mattermost", config.Mattermost.WebhookURL, config.Mattermost.MutualTLS, config.Mattermost.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Mattermost.WebhookURL = "" } else { @@ -119,7 +119,7 @@ func init() { if config.Teams.WebhookURL != "" { var err error - teamsClient, err = outputs.NewClient("Teams", config.Teams.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + teamsClient, err = outputs.NewClient("Teams", config.Teams.WebhookURL, config.Teams.MutualTLS, config.Teams.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Teams.WebhookURL = "" } else { @@ -129,7 +129,7 @@ func init() { if config.Datadog.APIKey != "" { var err error - datadogClient, err = outputs.NewClient("Datadog", config.Datadog.Host+outputs.DatadogPath+"?api_key="+config.Datadog.APIKey, config, stats, promStats, statsdClient, dogstatsdClient) + datadogClient, err = outputs.NewClient("Datadog", config.Datadog.Host+outputs.DatadogPath+"?api_key="+config.Datadog.APIKey, config.Datadog.MutualTLS, config.Datadog.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Datadog.APIKey = "" } else { @@ -139,7 +139,7 @@ func init() { if config.Discord.WebhookURL != "" { var err error - discordClient, err = outputs.NewClient("Discord", config.Discord.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + discordClient, err = outputs.NewClient("Discord", config.Discord.WebhookURL, config.Discord.MutualTLS, config.Discord.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Discord.WebhookURL = "" } else { @@ -149,7 +149,7 @@ func init() { if config.Alertmanager.HostPort != "" { var err error - alertmanagerClient, err = outputs.NewClient("AlertManager", config.Alertmanager.HostPort+outputs.AlertmanagerURI, config, stats, promStats, statsdClient, dogstatsdClient) + alertmanagerClient, err = outputs.NewClient("AlertManager", config.Alertmanager.HostPort+outputs.AlertmanagerURI, config.Alertmanager.MutualTLS, config.Alertmanager.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Alertmanager.HostPort = "" } else { @@ -159,7 +159,7 @@ func init() { if config.Elasticsearch.HostPort != "" { var err error - elasticsearchClient, err = outputs.NewClient("Elasticsearch", config.Elasticsearch.HostPort+"/"+config.Elasticsearch.Index+"/"+config.Elasticsearch.Type, config, stats, promStats, statsdClient, dogstatsdClient) + elasticsearchClient, err = outputs.NewClient("Elasticsearch", config.Elasticsearch.HostPort+"/"+config.Elasticsearch.Index+"/"+config.Elasticsearch.Type, config.Elasticsearch.MutualTLS, config.Elasticsearch.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Elasticsearch.HostPort = "" } else { @@ -169,7 +169,7 @@ func init() { if config.Loki.HostPort != "" { var err error - lokiClient, err = outputs.NewClient("Loki", config.Loki.HostPort+"/api/prom/push", config, stats, promStats, statsdClient, dogstatsdClient) + lokiClient, err = outputs.NewClient("Loki", config.Loki.HostPort+"/api/prom/push", config.Loki.MutualTLS, config.Loki.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Loki.HostPort = "" } else { @@ -179,7 +179,7 @@ func init() { if config.Nats.HostPort != "" { var err error - natsClient, err = outputs.NewClient("NATS", config.Nats.HostPort, config, stats, promStats, statsdClient, dogstatsdClient) + natsClient, err = outputs.NewClient("NATS", config.Nats.HostPort, config.Nats.MutualTLS, config.Nats.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Nats.HostPort = "" } else { @@ -189,7 +189,7 @@ func init() { if config.Stan.HostPort != "" && config.Stan.ClusterID != "" && config.Stan.ClientID != "" { var err error - stanClient, err = outputs.NewClient("STAN", config.Stan.HostPort, config, stats, promStats, statsdClient, dogstatsdClient) + stanClient, err = outputs.NewClient("STAN", config.Stan.HostPort, config.Stan.MutualTLS, config.Stan.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Stan.HostPort = "" config.Stan.ClusterID = "" @@ -206,7 +206,7 @@ func init() { } var err error - influxdbClient, err = outputs.NewClient("Influxdb", config.Influxdb.HostPort+"/write?db="+config.Influxdb.Database+credentials, config, stats, promStats, statsdClient, dogstatsdClient) + influxdbClient, err = outputs.NewClient("Influxdb", config.Influxdb.HostPort+"/write?db="+config.Influxdb.Database+credentials, config.Influxdb.MutualTLS, config.Influxdb.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Influxdb.HostPort = "" } else { @@ -263,7 +263,7 @@ func init() { if strings.ToLower(config.Opsgenie.Region) == "eu" { url = "https://api.eu.opsgenie.com/v2/alerts" } - opsgenieClient, err = outputs.NewClient("Opsgenie", url, config, stats, promStats, statsdClient, dogstatsdClient) + opsgenieClient, err = outputs.NewClient("Opsgenie", url, config.Opsgenie.MutualTLS, config.Opsgenie.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Opsgenie.APIKey = "" } else { @@ -273,7 +273,7 @@ func init() { if config.Webhook.Address != "" { var err error - webhookClient, err = outputs.NewClient("Webhook", config.Webhook.Address, config, stats, promStats, statsdClient, dogstatsdClient) + webhookClient, err = outputs.NewClient("Webhook", config.Webhook.Address, config.Webhook.MutualTLS, config.Webhook.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Webhook.Address = "" } else { @@ -283,7 +283,7 @@ func init() { if config.CloudEvents.Address != "" { var err error - cloudeventsClient, err = outputs.NewClient("CloudEvents", config.CloudEvents.Address, config, stats, promStats, statsdClient, dogstatsdClient) + cloudeventsClient, err = outputs.NewClient("CloudEvents", config.CloudEvents.Address, config.CloudEvents.MutualTLS, config.CloudEvents.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.CloudEvents.Address = "" } else { @@ -323,7 +323,7 @@ func init() { if config.Googlechat.WebhookURL != "" { var err error - googleChatClient, err = outputs.NewClient("Googlechat", config.Googlechat.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) + googleChatClient, err = outputs.NewClient("Googlechat", config.Googlechat.WebhookURL, config.Googlechat.MutualTLS, config.Googlechat.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Googlechat.WebhookURL = "" } else { @@ -346,7 +346,7 @@ func init() { var url = "https://events.pagerduty.com/v2/enqueue" var outputName = "Pagerduty" - pagerdutyClient, err = outputs.NewClient(outputName, url, config, stats, promStats, statsdClient, dogstatsdClient) + pagerdutyClient, err = outputs.NewClient(outputName, url, config.Pagerduty.MutualTLS, config.Pagerduty.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.Pagerduty.RoutingKey = "" @@ -369,7 +369,7 @@ func init() { if config.WebUI.URL != "" { var err error - webUIClient, err = outputs.NewClient("WebUI", config.WebUI.URL, config, stats, promStats, statsdClient, dogstatsdClient) + webUIClient, err = outputs.NewClient("WebUI", config.WebUI.URL, config.WebUI.MutualTLS, config.WebUI.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { config.WebUI.URL = "" } else { diff --git a/outputs/client.go b/outputs/client.go index 4ab89a9bf..74d98e80c 100644 --- a/outputs/client.go +++ b/outputs/client.go @@ -3,6 +3,7 @@ package outputs import ( "bytes" "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" @@ -52,17 +53,24 @@ var ErrClientCreation = errors.New("Client creation Error") // EnabledOutputs list all enabled outputs var EnabledOutputs []string +// files names are static fo the shake of helm and single docker compatibility +const MutualTLSClientCertFilename = "/client.crt" +const MutualTLSClientKeyFilename = "/client.key" +const MutualTLSCacertFilename = "/ca.crt" + // Client communicates with the different API. type Client struct { - OutputType string - EndpointURL *url.URL - Config *types.Configuration - Stats *types.Statistics - PromStats *types.PromStatistics - AWSSession *session.Session - StatsdClient *statsd.Client - DogstatsdClient *statsd.Client - GCPTopicClient *pubsub.Topic + OutputType string + EndpointURL *url.URL + MutualTLSEnabled bool + CheckCert bool + Config *types.Configuration + Stats *types.Statistics + PromStats *types.PromStatistics + AWSSession *session.Session + StatsdClient *statsd.Client + DogstatsdClient *statsd.Client + GCPTopicClient *pubsub.Topic GCSStorageClient *storage.Client KafkaProducer *kafka.Writer @@ -73,7 +81,7 @@ type Client struct { } // NewClient returns a new output.Client for accessing the different API. -func NewClient(outputType string, defaultEndpointURL string, config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { +func NewClient(outputType string, defaultEndpointURL string, mutualTLSEnabled bool, checkCert bool, config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { reg := regexp.MustCompile(`(http|nats)(s?)://.*`) if !reg.MatchString(defaultEndpointURL) { log.Printf("[ERROR] : %v - %v\n", outputType, "Bad Endpoint") @@ -88,7 +96,7 @@ func NewClient(outputType string, defaultEndpointURL string, config *types.Confi log.Printf("[ERROR] : %v - %v\n", outputType, err.Error()) return nil, ErrClientCreation } - return &Client{OutputType: outputType, EndpointURL: endpointURL, Config: config, Stats: stats, PromStats: promStats, StatsdClient: statsdClient, DogstatsdClient: dogstatsdClient}, nil + return &Client{OutputType: outputType, EndpointURL: endpointURL, MutualTLSEnabled: mutualTLSEnabled, Config: config, Stats: stats, PromStats: promStats, StatsdClient: statsdClient, DogstatsdClient: dogstatsdClient}, nil } // Post sends event (payload) to Output. @@ -115,10 +123,32 @@ func (c *Client) Post(payload interface{}) error { customTransport := http.DefaultTransport.(*http.Transport).Clone() - if c.Config.CheckCert == false { - // #nosec G402 This is only set as a result of explicit configuration + if c.MutualTLSEnabled { + // Load client cert + cert, err := tls.LoadX509KeyPair(c.Config.MutualTLSFilesPath+MutualTLSClientCertFilename, c.Config.MutualTLSFilesPath+MutualTLSClientKeyFilename) + if err != nil { + log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) + } + + // Load CA cert + caCert, err := ioutil.ReadFile(c.Config.MutualTLSFilesPath + MutualTLSCacertFilename) + if err != nil { + log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) customTransport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + MinVersion: tls.VersionTLS12, + } + } else { + // With MutualTLS enabled, the check cert flag is ignored + if c.CheckCert == false { + // #nosec G402 This is only set as a result of explicit configuration + customTransport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } } } diff --git a/outputs/client_test.go b/outputs/client_test.go index 0fd11999a..b80216586 100644 --- a/outputs/client_test.go +++ b/outputs/client_test.go @@ -1,11 +1,23 @@ package outputs import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "errors" + "io/ioutil" + "math/big" + "net" "net/http" "net/http/httptest" "net/url" + "os" "testing" + "time" "github.com/stretchr/testify/require" @@ -21,11 +33,11 @@ func TestNewClient(t *testing.T) { stats := &types.Statistics{} promStats := &types.PromStatistics{} - testClientOutput := Client{OutputType: "test", EndpointURL: u, Config: config, Stats: stats, PromStats: promStats} - _, err := NewClient("test", "localhost/%*$¨^!/:;", config, stats, promStats, nil, nil) + testClientOutput := Client{OutputType: "test", EndpointURL: u, MutualTLSEnabled: false, Config: config, Stats: stats, PromStats: promStats} + _, err := NewClient("test", "localhost/%*$¨^!/:;", false, true, config, stats, promStats, nil, nil) require.NotNil(t, err) - nc, err := NewClient("test", "http://localhost", config, stats, promStats, nil, nil) + nc, err := NewClient("test", "http://localhost", false, true, config, stats, promStats, nil, nil) require.Nil(t, err) require.Equal(t, &testClientOutput, nc) } @@ -64,7 +76,7 @@ func TestPost(t *testing.T) { "/429": ErrTooManyRequest, "/502": errors.New("502 Bad Gateway"), } { - nc, err := NewClient("", ts.URL+i, &types.Configuration{}, &types.Statistics{}, &types.PromStatistics{}, nil, nil) + nc, err := NewClient("", ts.URL+i, false, true, &types.Configuration{}, &types.Statistics{}, &types.PromStatistics{}, nil, nil) require.Nil(t, err) require.NotEmpty(t, nc) @@ -72,3 +84,203 @@ func TestPost(t *testing.T) { require.Equal(t, errPost, j) } } + +func TestMutualTlsPost(t *testing.T) { + config := &types.Configuration{} + config.MutualTLSFilesPath = "/tmp/falcosidekicktests" + // delete folder to avoid makedir failure + os.RemoveAll(config.MutualTLSFilesPath) + + serverTLSConf, err := certsetup(config) + if err != nil { + require.Nil(t, err) + } + + tlsURL := "127.0.0.1:5443" + + // set up the httptest.Server using our certificate signed by our CA + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Fatalf("expected method : POST, got %s\n", r.Method) + } + if r.URL.EscapedPath() == "/200" { + w.WriteHeader(http.StatusOK) + } + })) + // This Listen config is required since server.URL generates a "Server already started" Panic error + // Check https://golang.org/src/net/http/httptest/server.go#:~:text=s.URL + l, _ := net.Listen("tcp", tlsURL) + server.Listener = l + server.TLS = serverTLSConf + server.StartTLS() + defer server.Close() + + nc, err := NewClient("", server.URL+"/200", true, true, config, &types.Statistics{}, &types.PromStatistics{}, nil, nil) + require.Nil(t, err) + require.NotEmpty(t, nc) + + errPost := nc.Post("") + require.Nil(t, errPost) + +} + +func certsetup(config *types.Configuration) (serverTLSConf *tls.Config, err error) { + err = os.Mkdir(config.MutualTLSFilesPath, 0755) + if err != nil { + return nil, err + } + + // set up our CA certificate + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Sysdig"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Falco st"}, + PostalCode: []string{"94016"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + // create our private and public key + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, err + } + + // create the CA + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, err + } + + // pem encode + caPEM := new(bytes.Buffer) + pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + // save ca to ca.crt file (it will be used by Client) + err = ioutil.WriteFile(config.MutualTLSFilesPath+"/ca.crt", caPEM.Bytes(), 0600) + if err != nil { + return nil, err + } + + // set up our server certificate + cert := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Falco"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Falcosidekick st"}, + PostalCode: []string{"94016"}, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + // create server private key + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, err + } + + // sign server certificate with CA key + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, err + } + + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes()) + if err != nil { + return nil, err + } + + // create server TLS config + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caPEM.Bytes()) + serverTLSConf = &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientCAs: caCertPool, + RootCAs: caCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS12, + } + + // create client certificate + clientCert := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Falcosidekick"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Falcosidekickclient st"}, + PostalCode: []string{"94016"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + // create client private key + clientCertPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, err + } + + // sign client certificate with CA key + clientCertBytes, err := x509.CreateCertificate(rand.Reader, clientCert, ca, &clientCertPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, err + } + + clientCertPEM := new(bytes.Buffer) + pem.Encode(clientCertPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCertBytes, + }) + + // save client cert and key to client.crt and client.key + err = ioutil.WriteFile(config.MutualTLSFilesPath+"/client.crt", clientCertPEM.Bytes(), 0600) + if err != nil { + return nil, err + } + clientCertPrivKeyPEM := new(bytes.Buffer) + pem.Encode(clientCertPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(clientCertPrivKey), + }) + err = ioutil.WriteFile(config.MutualTLSFilesPath+"/client.key", clientCertPrivKeyPEM.Bytes(), 0600) + if err != nil { + return nil, err + } + return serverTLSConf, nil +} diff --git a/outputs/kubeless.go b/outputs/kubeless.go index 31aca0fdb..58ff131fd 100644 --- a/outputs/kubeless.go +++ b/outputs/kubeless.go @@ -38,6 +38,8 @@ func NewKubelessClient(config *types.Configuration, stats *types.Statistics, pro return NewClient( "Kubeless", "http://"+config.Kubeless.Function+"."+config.Kubeless.Namespace+".svc.cluster.local:"+strconv.Itoa(config.Kubeless.Port), + config.Kubeless.MutualTLS, + config.Kubeless.CheckCert, config, stats, promStats, diff --git a/outputs/openfaas.go b/outputs/openfaas.go index 14ebe7c18..a29c9e993 100644 --- a/outputs/openfaas.go +++ b/outputs/openfaas.go @@ -38,6 +38,8 @@ func NewOpenfaasClient(config *types.Configuration, stats *types.Statistics, pro return NewClient( Openfaas, "http://"+config.Openfaas.GatewayService+"."+config.Openfaas.GatewayNamespace+":"+strconv.Itoa(config.Openfaas.GatewayPort)+"/function/"+config.Openfaas.FunctionName+"."+config.Openfaas.FunctionNamespace, + config.Openfaas.MutualTLS, + config.Openfaas.CheckCert, config, stats, promStats, diff --git a/types/types.go b/types/types.go index 586b457ba..e48a2e758 100644 --- a/types/types.go +++ b/types/types.go @@ -20,40 +20,40 @@ type FalcoPayload struct { // Configuration is a struct to store configuration type Configuration struct { - CheckCert bool - Debug bool - ListenAddress string - ListenPort int - Customfields map[string]string - Slack SlackOutputConfig - Mattermost MattermostOutputConfig - Rocketchat RocketchatOutputConfig - Teams teamsOutputConfig - Datadog datadogOutputConfig - Discord DiscordOutputConfig - Alertmanager alertmanagerOutputConfig - Elasticsearch elasticsearchOutputConfig - Influxdb influxdbOutputConfig - Loki lokiOutputConfig - Nats natsOutputConfig - Stan stanOutputConfig - AWS awsOutputConfig - SMTP smtpOutputConfig - Opsgenie opsgenieOutputConfig - Statsd statsdOutputConfig - Dogstatsd statsdOutputConfig - Webhook WebhookOutputConfig - CloudEvents CloudEventsOutputConfig - Azure azureConfig - GCP gcpOutputConfig - Googlechat GooglechatConfig - Kafka kafkaConfig - Pagerduty PagerdutyConfig - Kubeless kubelessConfig - Openfaas openfaasConfig - WebUI WebUIOutputConfig - Rabbitmq RabbitmqConfig - Wavefront WavefrontOutputConfig + MutualTLSFilesPath string + Debug bool + ListenAddress string + ListenPort int + Customfields map[string]string + Slack SlackOutputConfig + Mattermost MattermostOutputConfig + Rocketchat RocketchatOutputConfig + Teams teamsOutputConfig + Datadog datadogOutputConfig + Discord DiscordOutputConfig + Alertmanager alertmanagerOutputConfig + Elasticsearch elasticsearchOutputConfig + Influxdb influxdbOutputConfig + Loki lokiOutputConfig + Nats natsOutputConfig + Stan stanOutputConfig + AWS awsOutputConfig + SMTP smtpOutputConfig + Opsgenie opsgenieOutputConfig + Statsd statsdOutputConfig + Dogstatsd statsdOutputConfig + Webhook WebhookOutputConfig + CloudEvents CloudEventsOutputConfig + Azure azureConfig + GCP gcpOutputConfig + Googlechat GooglechatConfig + Kafka kafkaConfig + Pagerduty PagerdutyConfig + Kubeless kubelessConfig + Openfaas openfaasConfig + WebUI WebUIOutputConfig + Rabbitmq RabbitmqConfig + Wavefront WavefrontOutputConfig } // SlackOutputConfig represents parameters for Slack @@ -66,6 +66,8 @@ type SlackOutputConfig struct { MinimumPriority string MessageFormat string MessageFormatTemplate *template.Template + CheckCert bool + MutualTLS bool } // RocketchatOutputConfig . @@ -78,6 +80,8 @@ type RocketchatOutputConfig struct { MinimumPriority string MessageFormat string MessageFormatTemplate *template.Template + CheckCert bool + MutualTLS bool } // MattermostOutputConfig represents parameters for Mattermost @@ -90,6 +94,8 @@ type MattermostOutputConfig struct { MinimumPriority string MessageFormat string MessageFormatTemplate *template.Template + CheckCert bool + MutualTLS bool } type WavefrontOutputConfig struct { @@ -108,12 +114,16 @@ type teamsOutputConfig struct { ActivityImage string OutputFormat string MinimumPriority string + CheckCert bool + MutualTLS bool } type datadogOutputConfig struct { APIKey string Host string MinimumPriority string + CheckCert bool + MutualTLS bool } // DiscordOutputConfig . @@ -121,11 +131,15 @@ type DiscordOutputConfig struct { WebhookURL string MinimumPriority string Icon string + CheckCert bool + MutualTLS bool } type alertmanagerOutputConfig struct { HostPort string MinimumPriority string + CheckCert bool + MutualTLS bool } type elasticsearchOutputConfig struct { @@ -134,6 +148,8 @@ type elasticsearchOutputConfig struct { Type string MinimumPriority string Suffix string + CheckCert bool + MutualTLS bool } type influxdbOutputConfig struct { @@ -142,16 +158,22 @@ type influxdbOutputConfig struct { User string Password string MinimumPriority string + CheckCert bool + MutualTLS bool } type lokiOutputConfig struct { HostPort string MinimumPriority string + CheckCert bool + MutualTLS bool } type natsOutputConfig struct { HostPort string MinimumPriority string + CheckCert bool + MutualTLS bool } type stanOutputConfig struct { @@ -159,6 +181,8 @@ type stanOutputConfig struct { ClusterID string ClientID string MinimumPriority string + CheckCert bool + MutualTLS bool } type awsOutputConfig struct { @@ -216,6 +240,8 @@ type opsgenieOutputConfig struct { Region string APIKey string MinimumPriority string + CheckCert bool + MutualTLS bool } // WebhookOutputConfig represents parameters for Webhook @@ -223,6 +249,8 @@ type WebhookOutputConfig struct { Address string CustomHeaders map[string]string MinimumPriority string + CheckCert bool + MutualTLS bool } // CloudEventsOutputConfig represents parameters for CloudEvents @@ -230,6 +258,8 @@ type CloudEventsOutputConfig struct { Address string Extensions map[string]string MinimumPriority string + CheckCert bool + MutualTLS bool } type statsdOutputConfig struct { @@ -273,6 +303,8 @@ type GooglechatConfig struct { MinimumPriority string MessageFormat string MessageFormatTemplate *template.Template + CheckCert bool + MutualTLS bool } type kafkaConfig struct { @@ -284,6 +316,8 @@ type kafkaConfig struct { type PagerdutyConfig struct { RoutingKey string MinimumPriority string + CheckCert bool + MutualTLS bool } type kubelessConfig struct { @@ -292,6 +326,8 @@ type kubelessConfig struct { Port int Kubeconfig string MinimumPriority string + CheckCert bool + MutualTLS bool } type openfaasConfig struct { @@ -302,11 +338,15 @@ type openfaasConfig struct { GatewayPort int Kubeconfig string MinimumPriority string + CheckCert bool + MutualTLS bool } // WebUIOutputConfig represents parameters for WebUI type WebUIOutputConfig struct { - URL string + URL string + CheckCert bool + MutualTLS bool } // RabbitmqConfig represents parameters for rabbitmq