cscli setup (#1923)
Detect running services and generate acquisition configuration
This commit is contained in:
parent
7e871d2278
commit
b6be18ca65
15 changed files with 4040 additions and 12 deletions
|
@ -242,6 +242,10 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
|
|||
rootCmd.AddCommand(NewNotificationsCmd())
|
||||
rootCmd.AddCommand(NewSupportCmd())
|
||||
|
||||
if fflag.CscliSetup.IsEnabled() {
|
||||
rootCmd.AddCommand(NewSetupCmd())
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if bincoverTesting != "" {
|
||||
log.Debug("coverage report is enabled")
|
||||
|
|
312
cmd/crowdsec-cli/setup.go
Normal file
312
cmd/crowdsec-cli/setup.go
Normal file
|
@ -0,0 +1,312 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
goccyyaml "github.com/goccy/go-yaml"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/setup"
|
||||
)
|
||||
|
||||
// NewSetupCmd defines the "cscli setup" command.
|
||||
func NewSetupCmd() *cobra.Command {
|
||||
cmdSetup := &cobra.Command{
|
||||
Use: "setup",
|
||||
Short: "Tools to configure crowdsec",
|
||||
Long: "Manage hub configuration and service detection",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
//
|
||||
// cscli setup detect
|
||||
//
|
||||
{
|
||||
cmdSetupDetect := &cobra.Command{
|
||||
Use: "detect",
|
||||
Short: "detect running services, generate a setup file",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: runSetupDetect,
|
||||
}
|
||||
|
||||
defaultServiceDetect := csconfig.DefaultConfigPath("hub", "detect.yaml")
|
||||
|
||||
flags := cmdSetupDetect.Flags()
|
||||
flags.String("detect-config", defaultServiceDetect, "path to service detection configuration")
|
||||
flags.Bool("list-supported-services", false, "do not detect; only print supported services")
|
||||
flags.StringSlice("force-unit", nil, "force detection of a systemd unit (can be repeated)")
|
||||
flags.StringSlice("force-process", nil, "force detection of a running process (can be repeated)")
|
||||
flags.StringSlice("skip-service", nil, "ignore a service, don't recommend hub/datasources (can be repeated)")
|
||||
flags.String("force-os-family", "", "override OS.Family: one of linux, freebsd, windows or darwin")
|
||||
flags.String("force-os-id", "", "override OS.ID=[debian | ubuntu | , redhat...]")
|
||||
flags.String("force-os-version", "", "override OS.RawVersion (of OS or Linux distribution)")
|
||||
flags.Bool("snub-systemd", false, "don't use systemd, even if available")
|
||||
flags.Bool("yaml", false, "output yaml, not json")
|
||||
cmdSetup.AddCommand(cmdSetupDetect)
|
||||
}
|
||||
|
||||
//
|
||||
// cscli setup install-hub
|
||||
//
|
||||
{
|
||||
cmdSetupInstallHub := &cobra.Command{
|
||||
Use: "install-hub [setup_file] [flags]",
|
||||
Short: "install items from a setup file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
DisableAutoGenTag: true,
|
||||
RunE: runSetupInstallHub,
|
||||
}
|
||||
|
||||
flags := cmdSetupInstallHub.Flags()
|
||||
flags.Bool("dry-run", false, "don't install anything; print out what would have been")
|
||||
cmdSetup.AddCommand(cmdSetupInstallHub)
|
||||
}
|
||||
|
||||
//
|
||||
// cscli setup datasources
|
||||
//
|
||||
{
|
||||
cmdSetupDataSources := &cobra.Command{
|
||||
Use: "datasources [setup_file] [flags]",
|
||||
Short: "generate datasource (acquisition) configuration from a setup file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
DisableAutoGenTag: true,
|
||||
RunE: runSetupDataSources,
|
||||
}
|
||||
|
||||
flags := cmdSetupDataSources.Flags()
|
||||
flags.String("to-dir", "", "write the configuration to a directory, in multiple files")
|
||||
cmdSetup.AddCommand(cmdSetupDataSources)
|
||||
}
|
||||
|
||||
//
|
||||
// cscli setup validate
|
||||
//
|
||||
{
|
||||
cmdSetupValidate := &cobra.Command{
|
||||
Use: "validate [setup_file]",
|
||||
Short: "validate a setup file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
DisableAutoGenTag: true,
|
||||
RunE: runSetupValidate,
|
||||
}
|
||||
|
||||
cmdSetup.AddCommand(cmdSetupValidate)
|
||||
}
|
||||
|
||||
return cmdSetup
|
||||
}
|
||||
|
||||
func runSetupDetect(cmd *cobra.Command, args []string) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
detectConfigFile, err := flags.GetString("detect-config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listSupportedServices, err := flags.GetBool("list-supported-services")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forcedUnits, err := flags.GetStringSlice("force-unit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forcedProcesses, err := flags.GetStringSlice("force-process")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forcedOSFamily, err := flags.GetString("force-os-family")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forcedOSID, err := flags.GetString("force-os-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forcedOSVersion, err := flags.GetString("force-os-version")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
skipServices, err := flags.GetStringSlice("skip-service")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
snubSystemd, err := flags.GetBool("snub-systemd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !snubSystemd {
|
||||
_, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
log.Debug("systemctl not available: snubbing systemd")
|
||||
snubSystemd = true
|
||||
}
|
||||
}
|
||||
|
||||
outYaml, err := flags.GetBool("yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if forcedOSFamily == "" && forcedOSID != "" {
|
||||
log.Debug("force-os-id is set: force-os-family defaults to 'linux'")
|
||||
forcedOSFamily = "linux"
|
||||
}
|
||||
|
||||
if listSupportedServices {
|
||||
supported, err := setup.ListSupported(detectConfigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, svc := range supported {
|
||||
fmt.Println(svc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := setup.DetectOptions{
|
||||
ForcedUnits: forcedUnits,
|
||||
ForcedProcesses: forcedProcesses,
|
||||
ForcedOS: setup.ExprOS{
|
||||
Family: forcedOSFamily,
|
||||
ID: forcedOSID,
|
||||
RawVersion: forcedOSVersion,
|
||||
},
|
||||
SkipServices: skipServices,
|
||||
SnubSystemd: snubSystemd,
|
||||
}
|
||||
|
||||
hubSetup, err := setup.Detect(detectConfigFile, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("detecting services: %w", err)
|
||||
}
|
||||
|
||||
setup, err := setupAsString(hubSetup, outYaml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(setup)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupAsString(cs setup.Setup, outYaml bool) (string, error) {
|
||||
var (
|
||||
ret []byte
|
||||
err error
|
||||
)
|
||||
|
||||
wrap := func(err error) error {
|
||||
return fmt.Errorf("while marshaling setup: %w", err)
|
||||
}
|
||||
|
||||
indentLevel := 2
|
||||
buf := &bytes.Buffer{}
|
||||
enc := yaml.NewEncoder(buf)
|
||||
enc.SetIndent(indentLevel)
|
||||
|
||||
if err = enc.Encode(cs); err != nil {
|
||||
return "", wrap(err)
|
||||
}
|
||||
|
||||
if err = enc.Close(); err != nil {
|
||||
return "", wrap(err)
|
||||
}
|
||||
|
||||
ret = buf.Bytes()
|
||||
|
||||
if !outYaml {
|
||||
// take a general approach to output json, so we avoid the
|
||||
// double tags in the structures and can use go-yaml features
|
||||
// missing from the json package
|
||||
ret, err = goccyyaml.YAMLToJSON(ret)
|
||||
if err != nil {
|
||||
return "", wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
func runSetupDataSources(cmd *cobra.Command, args []string) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
fromFile := args[0]
|
||||
|
||||
toDir, err := flags.GetString("to-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input, err := os.ReadFile(fromFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while reading setup file: %w", err)
|
||||
}
|
||||
|
||||
output, err := setup.DataSources(input, toDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if toDir == "" {
|
||||
fmt.Println(output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSetupInstallHub(cmd *cobra.Command, args []string) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
fromFile := args[0]
|
||||
|
||||
dryRun, err := flags.GetBool("dry-run")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input, err := os.ReadFile(fromFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while reading file %s: %w", fromFile, err)
|
||||
}
|
||||
|
||||
if err = setup.InstallHubItems(csConfig, input, dryRun); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSetupValidate(cmd *cobra.Command, args []string) error {
|
||||
fromFile := args[0]
|
||||
input, err := os.ReadFile(fromFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while reading stdin: %w", err)
|
||||
}
|
||||
|
||||
if err = setup.Validate(input); err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return fmt.Errorf("invalid setup file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
482
config/detect.yaml
Normal file
482
config/detect.yaml
Normal file
|
@ -0,0 +1,482 @@
|
|||
---
|
||||
version: 1.0
|
||||
|
||||
# TODO: This file must be reviewed before the `cscli setup` command becomes GA
|
||||
|
||||
detect:
|
||||
|
||||
#
|
||||
# crowdsecurity/apache2
|
||||
#
|
||||
|
||||
# XXX some distro is using this path?
|
||||
# - /var/log/*http*/*.log
|
||||
|
||||
apache2-systemd-deb:
|
||||
when:
|
||||
- UnitFound("apache2.service")
|
||||
- PathExists("/etc/debian_version")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: file
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
labels:
|
||||
type: apache2
|
||||
|
||||
apache2-systemd-rpm:
|
||||
when:
|
||||
- UnitFound("httpd.service")
|
||||
- PathExists("/etc/redhat-release")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: file
|
||||
filenames:
|
||||
- /var/log/httpd/*.log
|
||||
# XXX /var/log/*http*/*.log
|
||||
labels:
|
||||
type: apache2
|
||||
|
||||
#
|
||||
# crowdsecurity/asterisk
|
||||
#
|
||||
|
||||
asterisk-systemd:
|
||||
when:
|
||||
- UnitFound("asterisk.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/asterisk
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: asterisk
|
||||
filenames:
|
||||
- /var/log/asterisk/*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/caddy
|
||||
#
|
||||
|
||||
caddy-systemd:
|
||||
when:
|
||||
- UnitFound("caddy.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/caddy
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: caddy
|
||||
filenames:
|
||||
- /var/log/caddy/*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/dovecot
|
||||
#
|
||||
|
||||
dovecot-systemd:
|
||||
when:
|
||||
- UnitFound("dovecot.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/dovecot
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/mail.log
|
||||
|
||||
#
|
||||
# LePresidente/emby
|
||||
#
|
||||
|
||||
emby-systemd:
|
||||
when:
|
||||
- UnitFound("emby-server.service")
|
||||
install:
|
||||
collections:
|
||||
- LePresidente/emby
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: emby
|
||||
filenames:
|
||||
- /var/log/embyserver.txt
|
||||
|
||||
#
|
||||
# crowdsecurity/endlessh
|
||||
#
|
||||
|
||||
endlessh-systemd:
|
||||
when:
|
||||
- UnitFound("endlessh.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/endlessh
|
||||
datasource:
|
||||
source: journalctl
|
||||
labels:
|
||||
type: syslog
|
||||
# XXX this? or /var/log/syslog?
|
||||
journalctl_filter:
|
||||
- "_SYSTEMD_UNIT=endlessh.service"
|
||||
|
||||
#
|
||||
# crowdsecurity/gitea
|
||||
#
|
||||
|
||||
# XXX untested
|
||||
|
||||
gitea-systemd:
|
||||
when:
|
||||
- UnitFound("gitea.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/gitea
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: gitea
|
||||
filenames:
|
||||
- /var/log/gitea.log
|
||||
|
||||
#
|
||||
# crowdsecurity/haproxy
|
||||
#
|
||||
|
||||
haproxy-systemd:
|
||||
when:
|
||||
- UnitFound("haproxy.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/haproxy
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: haproxy
|
||||
filenames:
|
||||
- /var/log/haproxy/*.log
|
||||
|
||||
#
|
||||
# firewallservices/lemonldap-ng
|
||||
#
|
||||
|
||||
lemonldap-ng-systemd:
|
||||
when:
|
||||
- UnitFound("lemonldap-ng-fastcgi-server.service")
|
||||
install:
|
||||
collections:
|
||||
- firewallservices/lemonldap-ng
|
||||
#datasource:
|
||||
# # XXX todo where are the logs?
|
||||
# labels:
|
||||
# type: syslog
|
||||
|
||||
#
|
||||
# crowdsecurity/mariadb
|
||||
#
|
||||
|
||||
mariadb-systemd:
|
||||
when:
|
||||
- UnitFound("mariadb.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/mariadb
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: mysql
|
||||
filenames:
|
||||
- /var/log/mysql/error.log
|
||||
|
||||
#
|
||||
# crowdsecurity/mysql
|
||||
#
|
||||
|
||||
mysql-systemd:
|
||||
when:
|
||||
- UnitFound("mysql.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/mysql
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: mysql
|
||||
filenames:
|
||||
- /var/log/mysql/error.log
|
||||
|
||||
#
|
||||
# crowdsecurity/nginx
|
||||
#
|
||||
|
||||
nginx-systemd:
|
||||
when:
|
||||
- UnitFound("nginx.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/nginx
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: nginx
|
||||
filenames:
|
||||
- /var/log/nginx/*.log
|
||||
|
||||
openresty-systemd:
|
||||
when:
|
||||
- UnitFound("openresty.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/nginx
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: nginx
|
||||
filenames:
|
||||
- /usr/local/openresty/nginx/logs/*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/odoo
|
||||
#
|
||||
|
||||
odoo-systemd:
|
||||
when:
|
||||
- UnitFound("odoo.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/odoo
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: odoo
|
||||
filenames:
|
||||
- /var/log/odoo/*.log
|
||||
|
||||
#
|
||||
# LePresidente/ombi
|
||||
#
|
||||
|
||||
# This only works on deb-based systems. On other distributions, the
|
||||
# application is run from the release tarball and the log location depends on
|
||||
# the location it's run from.
|
||||
|
||||
ombi-systemd:
|
||||
when:
|
||||
- UnitFound("ombi.service")
|
||||
- PathExists("/etc/debian_version")
|
||||
install:
|
||||
collections:
|
||||
- LePresidente/ombi
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: ombi
|
||||
filenames:
|
||||
- /var/log/ombi/log-*.txt
|
||||
|
||||
#
|
||||
# crowdsecurity/pgsql
|
||||
#
|
||||
|
||||
pgsql-systemd-deb:
|
||||
when:
|
||||
- UnitFound("postgresql.service")
|
||||
- PathExists("/etc/debian_version")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/pgsql
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: postgres
|
||||
filenames:
|
||||
- /var/log/postgresql/*.log
|
||||
|
||||
pgsql-systemd-rpm:
|
||||
when:
|
||||
- UnitFound("postgresql.service")
|
||||
- PathExists("/etc/redhat-release")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/pgsql
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: postgres
|
||||
filenames:
|
||||
- /var/lib/pgsql/data/log/*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/postfix
|
||||
#
|
||||
|
||||
postfix-systemd:
|
||||
when:
|
||||
- UnitFound("postfix.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/postfix
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/mail.log
|
||||
|
||||
#
|
||||
# crowdsecurity/proftpd
|
||||
#
|
||||
|
||||
proftpd-systemd:
|
||||
when:
|
||||
- UnitFound("proftpd.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/proftpd
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: proftpd
|
||||
filenames:
|
||||
- /var/log/proftpd/*.log
|
||||
|
||||
#
|
||||
# fulljackz/pureftpd
|
||||
#
|
||||
|
||||
pureftpd-systemd:
|
||||
when:
|
||||
- UnitFound("pure-ftpd.service")
|
||||
install:
|
||||
collections:
|
||||
- fulljackz/pureftpd
|
||||
# XXX ?
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/pure-ftpd/*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/smb
|
||||
#
|
||||
|
||||
smb-systemd:
|
||||
when:
|
||||
# deb -> smbd.service
|
||||
# rpm -> smb.service
|
||||
- UnitFound("smbd.service") or UnitFound("smb.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/smb
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: smb
|
||||
filenames:
|
||||
- /var/log/samba*.log
|
||||
|
||||
#
|
||||
# crowdsecurity/sshd
|
||||
#
|
||||
|
||||
sshd-systemd:
|
||||
when:
|
||||
# deb -> ssh.service
|
||||
# rpm -> sshd.service
|
||||
- UnitFound("ssh.service") or UnitFound("sshd.service") or UnitFound("ssh.socket") or UnitFound("sshd.socket")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/sshd
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/auth.log
|
||||
- /var/log/sshd.log
|
||||
- /var/log/secure
|
||||
|
||||
#
|
||||
# crowdsecurity/suricata
|
||||
#
|
||||
|
||||
suricata-systemd:
|
||||
when:
|
||||
- UnitFound("suricata.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/suricata
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: suricata-evelogs
|
||||
filenames:
|
||||
- /var/log/suricata/eve.json
|
||||
|
||||
#
|
||||
# crowdsecurity/vsftpd
|
||||
#
|
||||
|
||||
vsftpd-systemd:
|
||||
when:
|
||||
- UnitFound("vsftpd.service")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/vsftpd
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: vsftpd
|
||||
filenames:
|
||||
- /var/log/vsftpd/*.log
|
||||
|
||||
#
|
||||
# Operating Systems
|
||||
#
|
||||
|
||||
linux:
|
||||
when:
|
||||
- OS.Family == "linux"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/linux
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/syslog
|
||||
- /var/log/kern.log
|
||||
- /var/log/messages
|
||||
|
||||
freebsd:
|
||||
when:
|
||||
- OS.Family == "freebsd"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/freebsd
|
||||
|
||||
windows:
|
||||
when:
|
||||
- OS.Family == "windows"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/windows
|
||||
|
||||
#
|
||||
# anti-lockout
|
||||
#
|
||||
|
||||
whitelists:
|
||||
install:
|
||||
parsers:
|
||||
- crowdsecurity/whitelists
|
17
go.mod
17
go.mod
|
@ -53,7 +53,7 @@ require (
|
|||
github.com/r3labs/diff/v2 v2.14.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
google.golang.org/grpc v1.47.0
|
||||
|
@ -65,6 +65,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/aquasecurity/table v1.8.0
|
||||
github.com/beevik/etree v1.1.0
|
||||
|
@ -75,11 +76,14 @@ require (
|
|||
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/google/winops v0.0.0-20211216095627-f0e86eb1453b
|
||||
github.com/ivanpirog/coloredcobra v1.0.1
|
||||
github.com/lithammer/dedent v1.1.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/segmentio/kafka-go v0.4.34
|
||||
github.com/shirou/gopsutil/v3 v3.22.12
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||
golang.org/x/sys v0.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apiserver v0.22.5
|
||||
)
|
||||
|
||||
|
@ -101,6 +105,7 @@ require (
|
|||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/analysis v0.19.16 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
|
@ -115,7 +120,7 @@ require (
|
|||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
|
||||
|
@ -136,6 +141,7 @@ require (
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.7 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
|
@ -154,6 +160,7 @@ require (
|
|||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
@ -163,9 +170,12 @@ require (
|
|||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/gjson v1.13.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||
github.com/vjeantet/grok v1.0.1 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.9.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
|
@ -177,7 +187,6 @@ require (
|
|||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.25.2 // indirect
|
||||
k8s.io/apimachinery v0.25.2 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
|
|
37
go.sum
37
go.sum
|
@ -57,6 +57,8 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
|
|||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
|
@ -120,8 +122,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
|
||||
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
|
@ -251,6 +251,8 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
|
|||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
|
@ -438,8 +440,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -641,8 +643,12 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
|
@ -770,6 +776,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
|
@ -836,6 +844,8 @@ github.com/segmentio/kafka-go v0.4.34 h1:Dm6YlLMiVSiwwav20KY0AoY63s661FXevwJ3CVH
|
|||
github.com/segmentio/kafka-go v0.4.34/go.mod h1:GAjxBQJdQMB5zfNA21AhpaqOB2Mu+w3De4ni3Gbm8y0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v3 v3.22.12 h1:oG0ns6poeUSxf78JtOsfygNWuEHYYz8hnnNg7P04TJs=
|
||||
github.com/shirou/gopsutil/v3 v3.22.12/go.mod h1:Xd7P1kwZcp5VW52+9XsirIKd/BROzbb2wdX3Kqlz9uI=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
|
@ -874,8 +884,9 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
@ -885,8 +896,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo=
|
||||
|
@ -898,6 +910,10 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
|||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
@ -928,6 +944,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
|
||||
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
|
@ -1136,6 +1154,7 @@ golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -1167,6 +1186,7 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -1186,8 +1206,9 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
340
pkg/setup/README.md
Normal file
340
pkg/setup/README.md
Normal file
|
@ -0,0 +1,340 @@
|
|||
|
||||
> **_NOTE_**: The following document describes an experimental, work-in-progress feature. To enable the `cscli setup` command, set the environment variable `CROWDSEC_FEATURE_CSCLI_SETUP=true` or add the line " - cscli_setup" to `/etc/crowdsec/feature.yaml`. Any feedback is welcome.
|
||||
|
||||
---
|
||||
|
||||
# cscli setup
|
||||
|
||||
The "cscli setup" command can configure a crowdsec instance based on the services that are installed or running on the server.
|
||||
|
||||
There are three main subcommands:
|
||||
|
||||
- `cscli setup detect`: *detect* the services, the OS family, version or the Linux distribution
|
||||
- `cscli setup install-hub`: *install* the recommended collections, parsers, etc. based on the detection result
|
||||
- `cscli setup datasources`: *generate* the appropriate acquisition rules
|
||||
|
||||
The setup command is used in the `wizard.sh` script, but can also be invoked by hand or customized via a configuration file
|
||||
by adding new services, log locations and detection rules.
|
||||
|
||||
Detection and installation are performed as separate steps, as you can see in the following diagram:
|
||||
|
||||
```
|
||||
+-------------+
|
||||
| |
|
||||
| detect.yaml |
|
||||
| |
|
||||
+-------------+
|
||||
|
|
||||
v
|
||||
setup detect
|
||||
|
|
||||
v
|
||||
+--------------+
|
||||
| +---> setup install-hub +-----------------------+
|
||||
| setup.yaml | | |
|
||||
| +---> setup datasources --->| etc/crowdsec/acquis.d |
|
||||
+--------------+ | |
|
||||
+-----------------------+
|
||||
```
|
||||
|
||||
You can inspect and customize the intermediary file (`setup.yaml`), which is useful
|
||||
in case of many instances, deployment automation or unusual setups.
|
||||
|
||||
A subcommand can be used to check your changes in this case:
|
||||
|
||||
- `cscli setup validate`: *validate* or report errors on a setup file
|
||||
|
||||
## Basic usage
|
||||
|
||||
Identify the existing services and write out what was detected:
|
||||
|
||||
```console
|
||||
# cscli setup detect > setup.yaml
|
||||
```
|
||||
|
||||
See what was found.
|
||||
|
||||
```console
|
||||
# cscli setup install-hub setup.yaml --dry-run
|
||||
dry-run: would install collection crowdsecurity/apache2
|
||||
dry-run: would install collection crowdsecurity/linux
|
||||
dry-run: would install collection crowdsecurity/pgsql
|
||||
dry-run: would install parser crowdsecurity/whitelists
|
||||
```
|
||||
|
||||
Install the objects (parsers, scenarios...) required to support the detected services:
|
||||
|
||||
```console
|
||||
# cscli setup install-hub setup.yaml
|
||||
INFO[29-06-2022 03:16:14 PM] crowdsecurity/apache2-logs : OK
|
||||
INFO[29-06-2022 03:16:14 PM] Enabled parsers : crowdsecurity/apache2-logs
|
||||
INFO[29-06-2022 03:16:14 PM] crowdsecurity/http-logs : OK
|
||||
[...]
|
||||
INFO[29-06-2022 03:16:18 PM] Enabled crowdsecurity/linux
|
||||
```
|
||||
|
||||
Generate the datasource configuration:
|
||||
|
||||
```console
|
||||
# cscli setup datasources setup.yaml --to-dir /etc/crowdsec/acquis.d
|
||||
```
|
||||
|
||||
With the above command, each detected service gets a corresponding file in the
|
||||
`acquis.d` directory. Running `cscli setup` again may add more services as they
|
||||
are detected, but datasource files or hub items are never removed
|
||||
automatically.
|
||||
|
||||
|
||||
## The detect.yaml file
|
||||
|
||||
A detect.yaml file is downloaded when you first install crowdsec, and is updated by the `cscli hub update`
|
||||
command.
|
||||
|
||||
> **_NOTE_**: XXX XXX - this is currently not the case, the file is distributed in the crowdsec repository, but it should change.
|
||||
|
||||
You can see the default location with `cscli setup detect --help | grep detect-config`
|
||||
|
||||
The YAML file contains a version number (always 1.0) and a list of sections, one per supported service.
|
||||
|
||||
Each service defines its detection rules, the recommended hub items and
|
||||
recommended datasources. The same software can be defined in multiple service
|
||||
sections: for example, apache on debian and fedora have different detection
|
||||
rules and different datasources so it requires two sections to support both platforms.
|
||||
|
||||
The following are minimal `detect.yaml` examples just to show a few concepts.
|
||||
|
||||
```yaml
|
||||
version: 1.0
|
||||
|
||||
services:
|
||||
|
||||
apache2:
|
||||
when:
|
||||
- ProcessRunning("apache2")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasources:
|
||||
source: file
|
||||
labels:
|
||||
type: apache2
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/httpd/*.log
|
||||
```
|
||||
|
||||
|
||||
- `ProcessRunning()` matches the process name of a running application. The
|
||||
`when:` clause can contain any number of expressions, they are all evaluated
|
||||
and must all return true for a service to be detected (implied *and* clause, no
|
||||
short-circuit). A missing or empty `when:` section is evaluated as true.
|
||||
The [expression
|
||||
engine](https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md)
|
||||
is the same one used by CrowdSec parser filters. You can force the detection of
|
||||
a process by using the `cscli setup detect... --force-process <processname>`
|
||||
flag. It will always behave as if `<processname>` was running.
|
||||
|
||||
The `install:` section can contain any number of collections, parsers, scenarios
|
||||
and postoverflows. In practices, it's most often a single collection.
|
||||
|
||||
The `datasource:` section is copied as-is in the acquisition file.
|
||||
|
||||
> **_NOTE_**: XXX TODO - the current version does not validate the `datasource:` mapping. Bad content is written to acquis.d until crowdsec chokes on it.
|
||||
|
||||
Detecting a running process may seem a good idea, but if a process manager like
|
||||
systemd is available it's better to ask it for the information we want.
|
||||
|
||||
|
||||
```yaml
|
||||
version: 1.0
|
||||
|
||||
services:
|
||||
|
||||
apache2-systemd:
|
||||
when:
|
||||
- UnitFound("apache2.service")
|
||||
- OS.ID != "centos"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
|
||||
apache2-systemd-centos:
|
||||
when:
|
||||
- UnitFound("httpd.service")
|
||||
- OS.ID == "centos"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/httpd/*.log
|
||||
```
|
||||
|
||||
Here we see two more detection methods:
|
||||
|
||||
- `UnitFound()` matches the name of systemd units, if the are in state enabled,
|
||||
generated or static. You can see here that CentOS is using a different unit
|
||||
name for Apache so it must have its own service section. You can force the
|
||||
detection of a unit by using the `cscli setup detect... --force-unit <unitname>` flag.
|
||||
|
||||
- OS.Family, OS.ID and OS.RawVersion are read from /etc/os-release in case of
|
||||
Linux, and detected by other methods for FreeBSD and Windows. Under FreeBSD
|
||||
and Windows, the value of OS.ID is the same as OS.Family. If OS detection
|
||||
fails, it can be overridden with the flags `--force-os-family`, `--force-os-id`
|
||||
and `--force-os-version`.
|
||||
|
||||
If you want to ignore one or more services (i.e. not install anything and not
|
||||
generate acquisition rules) you can specify it with `cscli setup detect...
|
||||
--skip-service <servicename>`. For example, `--skip-service apache2-systemd`.
|
||||
If you want to disable systemd unit detection, use `cscli setup detect... --snub-systemd`.
|
||||
|
||||
If you used the `--force-process` or `--force-unit` flags, but none of the
|
||||
defined services is looking for them, you'll have an error like "detecting
|
||||
services: process(es) forced but not supported".
|
||||
|
||||
> **_NOTE_**: XXX XXX - having an error for this is maybe too much, but can tell that a configuration is outdated. Could this be a warning with optional flag to make it an error?
|
||||
|
||||
We used the `OS.ID` value to check for the linux distribution, but since the same configuration
|
||||
is required for CentOS and the other RedHat derivatives, it's better to check for the existence
|
||||
of a file that is known to exist in all of them:
|
||||
|
||||
```yaml
|
||||
version: 1.0
|
||||
|
||||
services:
|
||||
|
||||
apache2-systemd-deb:
|
||||
when:
|
||||
- UnitFound("apache2.service")
|
||||
- PathExists("/etc/debian_version")
|
||||
install:
|
||||
# [...]
|
||||
|
||||
apache2-systemd-rpm:
|
||||
when:
|
||||
- UnitFound("httpd.service")
|
||||
- PathExists("/etc/redhat-release")
|
||||
install:
|
||||
# [...]
|
||||
```
|
||||
|
||||
- `PathExists()` evaluates to true if a file, directory or link exists at the
|
||||
given path. It does not check for broken links.
|
||||
|
||||
|
||||
|
||||
Rules can be used to detect operating systems and environments:
|
||||
|
||||
```yaml
|
||||
version: 1.0
|
||||
|
||||
services:
|
||||
|
||||
linux:
|
||||
when:
|
||||
- OS.Family == "linux"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/linux
|
||||
datasource:
|
||||
type: file
|
||||
labels:
|
||||
type: syslog
|
||||
log_files:
|
||||
- /var/log/syslog
|
||||
- /var/log/kern.log
|
||||
- /var/log/messages
|
||||
|
||||
freebsd:
|
||||
when:
|
||||
- OS.Family == "freebsd"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/freebsd
|
||||
|
||||
windows:
|
||||
when:
|
||||
- OS.Family == "windows"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/windows
|
||||
```
|
||||
|
||||
The OS object contains a methods to check for version numbers:
|
||||
`OS.VersionCheck("<constraint>")`. It uses the
|
||||
[Masterminds/semver](https://github.com/Masterminds/semver) package and accepts
|
||||
a variety of operators.
|
||||
|
||||
Instead of: OS.RawVersion == "1.2.3" you should use `OS.VersionCheck("~1")`,
|
||||
`OS.VersionCheck("~1.2")` depending if you want to match the major or the minor
|
||||
version. It's unlikely that you need to match the exact patch level.
|
||||
|
||||
Leading zeroes are permitted, to allow comparison of Ubuntu versions: strict semver rules would treat "22.04" as invalid.
|
||||
|
||||
|
||||
# The `setup.yaml` file
|
||||
|
||||
This file does not actually have a specific name, as it's usually written to standard output.
|
||||
|
||||
For example, on a Debian system running Apache under systemd you can execute:
|
||||
|
||||
```console
|
||||
$ cscli setup detect --yaml
|
||||
setup:
|
||||
- detected_service: apache2-systemd-deb
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
labels:
|
||||
type: apache2
|
||||
- detected_service: linux
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/linux
|
||||
datasource:
|
||||
filenames:
|
||||
- /var/log/syslog
|
||||
- /var/log/kern.log
|
||||
- /var/log/messages
|
||||
labels:
|
||||
type: syslog
|
||||
- detected_service: whitelists
|
||||
install:
|
||||
parsers:
|
||||
- crowdsecurity/whitelists
|
||||
```
|
||||
|
||||
The default output format is JSON, which is compatible with YAML but less readable to humans.
|
||||
|
||||
- `detected_service`: used to generate a name for the files written to `acquis.d`
|
||||
- `install`: can contain collections, parsers, scenarios, postoverflows
|
||||
- `datasource`: copied to `acquis.d`
|
||||
|
||||
|
||||
```console
|
||||
$ cscli setup datasources --help
|
||||
generate datasource (acquisition) configuration from a setup file
|
||||
|
||||
Usage:
|
||||
cscli setup datasources [setup_file] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for datasources
|
||||
--to-dir string write the configuration to a directory, in multiple files
|
||||
[...]
|
||||
```
|
||||
|
||||
If the `--to-dir` option is not specified, a single monolithic `acquis.yaml` is printed to the standard output.
|
||||
|
581
pkg/setup/detect.go
Normal file
581
pkg/setup/detect.go
Normal file
|
@ -0,0 +1,581 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/blackfireio/osinfo"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
// goccyyaml "github.com/goccy/go-yaml"
|
||||
|
||||
// "github.com/k0kubun/pp"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/acquisition"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
|
||||
)
|
||||
|
||||
// ExecCommand can be replaced with a mock during tests.
|
||||
var ExecCommand = exec.Command
|
||||
|
||||
// HubItems contains the objects that are recommended to support a service.
|
||||
type HubItems struct {
|
||||
Collections []string `yaml:"collections,omitempty"`
|
||||
Parsers []string `yaml:"parsers,omitempty"`
|
||||
Scenarios []string `yaml:"scenarios,omitempty"`
|
||||
PostOverflows []string `yaml:"postoverflows,omitempty"`
|
||||
}
|
||||
|
||||
type DataSourceItem map[string]interface{}
|
||||
|
||||
// ServiceSetup describes the recommendations (hub objects and datasources) for a detected service.
|
||||
type ServiceSetup struct {
|
||||
DetectedService string `yaml:"detected_service"`
|
||||
Install *HubItems `yaml:"install,omitempty"`
|
||||
DataSource DataSourceItem `yaml:"datasource,omitempty"`
|
||||
}
|
||||
|
||||
// Setup is a container for a list of ServiceSetup objects, allowing for future extensions.
|
||||
type Setup struct {
|
||||
Setup []ServiceSetup `yaml:"setup"`
|
||||
}
|
||||
|
||||
func validateDataSource(opaqueDS DataSourceItem) error {
|
||||
if len(opaqueDS) == 0 {
|
||||
// empty datasource is valid
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// formally validate YAML
|
||||
|
||||
commonDS := configuration.DataSourceCommonCfg{}
|
||||
body, err := yaml.Marshal(opaqueDS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(body, &commonDS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// source is mandatory // XXX unless it's not?
|
||||
|
||||
if commonDS.Source == "" {
|
||||
return fmt.Errorf("source is empty")
|
||||
}
|
||||
|
||||
|
||||
// source must be known
|
||||
|
||||
ds := acquisition.GetDataSourceIface(commonDS.Source)
|
||||
if ds == nil {
|
||||
return fmt.Errorf("unknown source '%s'", commonDS.Source)
|
||||
}
|
||||
|
||||
// unmarshal and validate the rest with the specific implementation
|
||||
|
||||
err = ds.UnmarshalConfig(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// pp.Println(ds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDetectConfig(file string) (DetectConfig, error) {
|
||||
var dc DetectConfig
|
||||
|
||||
yamlBytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return DetectConfig{}, fmt.Errorf("while reading file: %w", err)
|
||||
}
|
||||
|
||||
dec := yaml.NewDecoder(bytes.NewBuffer(yamlBytes))
|
||||
dec.KnownFields(true)
|
||||
|
||||
if err = dec.Decode(&dc); err != nil {
|
||||
return DetectConfig{}, fmt.Errorf("while parsing %s: %w", file, err)
|
||||
}
|
||||
|
||||
switch dc.Version {
|
||||
case "":
|
||||
return DetectConfig{}, fmt.Errorf("missing version tag (must be 1.0)")
|
||||
case "1.0":
|
||||
// all is well
|
||||
default:
|
||||
return DetectConfig{}, fmt.Errorf("unsupported version tag '%s' (must be 1.0)", dc.Version)
|
||||
}
|
||||
|
||||
for name, svc := range dc.Detect {
|
||||
err = validateDataSource(svc.DataSource)
|
||||
if err != nil {
|
||||
return DetectConfig{}, fmt.Errorf("invalid datasource for %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
// Service describes the rules for detecting a service and its recommended items.
|
||||
type Service struct {
|
||||
When []string `yaml:"when"`
|
||||
Install *HubItems `yaml:"install,omitempty"`
|
||||
DataSource DataSourceItem `yaml:"datasource,omitempty"`
|
||||
// AcquisYAML []byte
|
||||
}
|
||||
|
||||
// DetectConfig is the container of all detection rules (detect.yaml).
|
||||
type DetectConfig struct {
|
||||
Version string `yaml:"version"`
|
||||
Detect map[string]Service `yaml:"detect"`
|
||||
}
|
||||
|
||||
// ExprState keeps a global state for the duration of the service detection (cache etc.)
|
||||
type ExprState struct {
|
||||
unitsSearched map[string]bool
|
||||
detectOptions DetectOptions
|
||||
|
||||
// cache
|
||||
installedUnits map[string]bool
|
||||
// true if the list of running processes has already been retrieved, we can
|
||||
// avoid getting it a second time.
|
||||
processesSearched map[string]bool
|
||||
// cache
|
||||
runningProcesses map[string]bool
|
||||
}
|
||||
|
||||
// ExprServiceState keep a local state during the detection of a single service. It is reset before each service rules' evaluation.
|
||||
type ExprServiceState struct {
|
||||
detectedUnits []string
|
||||
}
|
||||
|
||||
// ExprOS contains the detected (or forced) OS fields available to the rule engine.
|
||||
type ExprOS struct {
|
||||
Family string
|
||||
ID string
|
||||
RawVersion string
|
||||
}
|
||||
|
||||
// This is not required with Masterminds/semver
|
||||
/*
|
||||
// normalizeVersion strips leading zeroes from each part, to allow comparison of ubuntu-like versions.
|
||||
func normalizeVersion(version string) string {
|
||||
// if it doesn't match a version string, return unchanged
|
||||
if ok := regexp.MustCompile(`^(\d+)(\.\d+)?(\.\d+)?$`).MatchString(version); !ok {
|
||||
// definitely not an ubuntu-like version, return unchanged
|
||||
return version
|
||||
}
|
||||
|
||||
ret := []rune{}
|
||||
|
||||
var cur rune
|
||||
|
||||
trim := true
|
||||
for _, next := range version + "." {
|
||||
if trim && cur == '0' && next != '.' {
|
||||
cur = next
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if cur != 0 {
|
||||
ret = append(ret, cur)
|
||||
}
|
||||
|
||||
trim = (cur == '.' || cur == 0)
|
||||
cur = next
|
||||
}
|
||||
|
||||
return string(ret)
|
||||
}
|
||||
*/
|
||||
|
||||
// VersionCheck returns true if the version of the OS matches the given constraint
|
||||
func (os ExprOS) VersionCheck(constraint string) (bool, error) {
|
||||
v, err := semver.NewVersion(os.RawVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(constraint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return c.Check(v), nil
|
||||
}
|
||||
|
||||
// VersionAtLeast returns true if the version of the OS is at least the given version.
|
||||
func (os ExprOS) VersionAtLeast(constraint string) (bool, error) {
|
||||
return os.VersionCheck(">=" + constraint)
|
||||
}
|
||||
|
||||
// VersionIsLower returns true if the version of the OS is lower than the given version.
|
||||
func (os ExprOS) VersionIsLower(version string) (bool, error) {
|
||||
result, err := os.VersionAtLeast(version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !result, nil
|
||||
}
|
||||
|
||||
// ExprEnvironment is used to expose functions and values to the rule engine.
|
||||
// It can cache the results of service detection commands, like systemctl etc.
|
||||
type ExprEnvironment struct {
|
||||
OS ExprOS
|
||||
|
||||
_serviceState *ExprServiceState
|
||||
_state *ExprState
|
||||
}
|
||||
|
||||
// NewExprEnvironment creates an environment object for the rule engine.
|
||||
func NewExprEnvironment(opts DetectOptions, os ExprOS) ExprEnvironment {
|
||||
return ExprEnvironment{
|
||||
_state: &ExprState{
|
||||
detectOptions: opts,
|
||||
|
||||
unitsSearched: make(map[string]bool),
|
||||
installedUnits: make(map[string]bool),
|
||||
|
||||
processesSearched: make(map[string]bool),
|
||||
runningProcesses: make(map[string]bool),
|
||||
},
|
||||
_serviceState: &ExprServiceState{},
|
||||
OS: os,
|
||||
}
|
||||
}
|
||||
|
||||
// PathExists returns true if the given path exists.
|
||||
func (e ExprEnvironment) PathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// UnitFound returns true if the unit is listed in the systemctl output.
|
||||
// Whether a disabled or failed unit is considered found or not, depends on the
|
||||
// systemctl parameters used.
|
||||
func (e ExprEnvironment) UnitFound(unitName string) (bool, error) {
|
||||
// fill initial caches
|
||||
if len(e._state.unitsSearched) == 0 {
|
||||
if !e._state.detectOptions.SnubSystemd {
|
||||
units, err := systemdUnitList()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, name := range units {
|
||||
e._state.installedUnits[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range e._state.detectOptions.ForcedUnits {
|
||||
e._state.installedUnits[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
e._state.unitsSearched[unitName] = true
|
||||
if e._state.installedUnits[unitName] {
|
||||
e._serviceState.detectedUnits = append(e._serviceState.detectedUnits, unitName)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ProcessRunning returns true if there is a running process with the given name.
|
||||
func (e ExprEnvironment) ProcessRunning(processName string) (bool, error) {
|
||||
if len(e._state.processesSearched) == 0 {
|
||||
procs, err := process.Processes()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("while looking up running processes: %w", err)
|
||||
}
|
||||
|
||||
for _, p := range procs {
|
||||
name, err := p.Name()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("while looking up running processes: %w", err)
|
||||
}
|
||||
|
||||
e._state.runningProcesses[name] = true
|
||||
}
|
||||
|
||||
for _, name := range e._state.detectOptions.ForcedProcesses {
|
||||
e._state.runningProcesses[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
e._state.processesSearched[processName] = true
|
||||
|
||||
return e._state.runningProcesses[processName], nil
|
||||
}
|
||||
|
||||
// applyRules checks if the 'when' expressions are true and returns a Service struct,
|
||||
// augmented with default values and anything that might be useful later on
|
||||
//
|
||||
// All expressions are evaluated (no short-circuit) because we want to know if there are errors.
|
||||
func applyRules(svc Service, env ExprEnvironment) (Service, bool, error) {
|
||||
newsvc := svc
|
||||
svcok := true
|
||||
env._serviceState = &ExprServiceState{}
|
||||
|
||||
for _, rule := range svc.When {
|
||||
out, err := expr.Eval(rule, env)
|
||||
log.Tracef(" Rule '%s' -> %t, %v", rule, out, err)
|
||||
|
||||
if err != nil {
|
||||
return Service{}, false, fmt.Errorf("rule '%s': %w", rule, err)
|
||||
}
|
||||
|
||||
outbool, ok := out.(bool)
|
||||
if !ok {
|
||||
return Service{}, false, fmt.Errorf("rule '%s': type must be a boolean", rule)
|
||||
}
|
||||
|
||||
svcok = svcok && outbool
|
||||
}
|
||||
|
||||
// if newsvc.Acquis == nil || (newsvc.Acquis.LogFiles == nil && newsvc.Acquis.JournalCTLFilter == nil) {
|
||||
// for _, unitName := range env._serviceState.detectedUnits {
|
||||
// if newsvc.Acquis == nil {
|
||||
// newsvc.Acquis = &AcquisItem{}
|
||||
// }
|
||||
// // if there is reference to more than one unit in the rules, we use the first one
|
||||
// newsvc.Acquis.JournalCTLFilter = []string{fmt.Sprintf(`_SYSTEMD_UNIT=%s`, unitName)}
|
||||
// break //nolint // we want to exit after one iteration
|
||||
// }
|
||||
// }
|
||||
|
||||
return newsvc, svcok, nil
|
||||
}
|
||||
|
||||
// filterWithRules decorates a DetectConfig map by filtering according to the when: clauses,
|
||||
// and applying default values or whatever useful to the Service items.
|
||||
func filterWithRules(dc DetectConfig, env ExprEnvironment) (map[string]Service, error) {
|
||||
ret := make(map[string]Service)
|
||||
|
||||
for name := range dc.Detect {
|
||||
//
|
||||
// an empty list of when: clauses defaults to true, if we want
|
||||
// to change this behavior, the place is here.
|
||||
// if len(svc.When) == 0 {
|
||||
// log.Warningf("empty 'when' clause: %+v", svc)
|
||||
// }
|
||||
//
|
||||
log.Trace("Evaluating rules for: ", name)
|
||||
|
||||
svc, ok, err := applyRules(dc.Detect[name], env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while looking for service %s: %w", name, err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Tracef(" Skipping %s", name)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
log.Tracef(" Detected %s", name)
|
||||
|
||||
ret[name] = svc
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// return units that have been forced but not searched yet.
|
||||
func (e ExprEnvironment) unsearchedUnits() []string {
|
||||
ret := []string{}
|
||||
|
||||
for _, unit := range e._state.detectOptions.ForcedUnits {
|
||||
if !e._state.unitsSearched[unit] {
|
||||
ret = append(ret, unit)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// return processes that have been forced but not searched yet.
|
||||
func (e ExprEnvironment) unsearchedProcesses() []string {
|
||||
ret := []string{}
|
||||
|
||||
for _, proc := range e._state.detectOptions.ForcedProcesses {
|
||||
if !e._state.processesSearched[proc] {
|
||||
ret = append(ret, proc)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// checkConsumedForcedItems checks if all the "forced" options (units or processes) have been evaluated during the service detection.
|
||||
func checkConsumedForcedItems(e ExprEnvironment) error {
|
||||
unconsumed := e.unsearchedUnits()
|
||||
|
||||
unitMsg := ""
|
||||
if len(unconsumed) > 0 {
|
||||
unitMsg = fmt.Sprintf("unit(s) forced but not supported: %v", unconsumed)
|
||||
}
|
||||
|
||||
unconsumed = e.unsearchedProcesses()
|
||||
|
||||
procsMsg := ""
|
||||
if len(unconsumed) > 0 {
|
||||
procsMsg = fmt.Sprintf("process(es) forced but not supported: %v", unconsumed)
|
||||
}
|
||||
|
||||
join := ""
|
||||
if unitMsg != "" && procsMsg != "" {
|
||||
join = "; "
|
||||
}
|
||||
|
||||
if unitMsg != "" || procsMsg != "" {
|
||||
return fmt.Errorf("%s%s%s", unitMsg, join, procsMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectOptions contains parameters for the Detect function.
|
||||
type DetectOptions struct {
|
||||
// slice of unit names that we want to force-detect
|
||||
ForcedUnits []string
|
||||
// slice of process names that we want to force-detect
|
||||
ForcedProcesses []string
|
||||
ForcedOS ExprOS
|
||||
SkipServices []string
|
||||
SnubSystemd bool
|
||||
}
|
||||
|
||||
// Detect performs the service detection from a given configuration.
|
||||
// It outputs a setup file that can be used as input to "cscli setup install-hub"
|
||||
// or "cscli setup datasources".
|
||||
func Detect(serviceDetectionFile string, opts DetectOptions) (Setup, error) {
|
||||
ret := Setup{}
|
||||
|
||||
// explicitly initialize to avoid json mashaling an empty slice as "null"
|
||||
ret.Setup = make([]ServiceSetup, 0)
|
||||
|
||||
log.Tracef("Reading detection rules: %s", serviceDetectionFile)
|
||||
|
||||
sc, err := readDetectConfig(serviceDetectionFile)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// // generate acquis.yaml snippet for this service
|
||||
// for key := range sc.Detect {
|
||||
// svc := sc.Detect[key]
|
||||
// if svc.Acquis != nil {
|
||||
// svc.AcquisYAML, err = yaml.Marshal(svc.Acquis)
|
||||
// if err != nil {
|
||||
// return ret, err
|
||||
// }
|
||||
// sc.Detect[key] = svc
|
||||
// }
|
||||
// }
|
||||
|
||||
var osfull *osinfo.OSInfo
|
||||
|
||||
os := opts.ForcedOS
|
||||
if os == (ExprOS{}) {
|
||||
osfull, err = osinfo.GetOSInfo()
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("detecting OS: %w", err)
|
||||
}
|
||||
|
||||
log.Tracef("Detected OS - %+v", *osfull)
|
||||
|
||||
os = ExprOS{
|
||||
Family: osfull.Family,
|
||||
ID: osfull.ID,
|
||||
RawVersion: osfull.Version,
|
||||
}
|
||||
} else {
|
||||
log.Tracef("Forced OS - %+v", os)
|
||||
}
|
||||
|
||||
if len(opts.ForcedUnits) > 0 {
|
||||
log.Tracef("Forced units - %v", opts.ForcedUnits)
|
||||
}
|
||||
|
||||
if len(opts.ForcedProcesses) > 0 {
|
||||
log.Tracef("Forced processes - %v", opts.ForcedProcesses)
|
||||
}
|
||||
|
||||
env := NewExprEnvironment(opts, os)
|
||||
|
||||
detected, err := filterWithRules(sc, env)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
if err = checkConsumedForcedItems(env); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// remove services the user asked to ignore
|
||||
for _, name := range opts.SkipServices {
|
||||
delete(detected, name)
|
||||
}
|
||||
|
||||
// sort the keys (service names) to have them in a predictable
|
||||
// order in the final output
|
||||
|
||||
keys := make([]string, 0)
|
||||
for k := range detected {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, name := range keys {
|
||||
svc := detected[name]
|
||||
// if svc.DataSource != nil {
|
||||
// if svc.DataSource.Labels["type"] == "" {
|
||||
// return Setup{}, fmt.Errorf("missing type label for service %s", name)
|
||||
// }
|
||||
// err = yaml.Unmarshal(svc.AcquisYAML, svc.DataSource)
|
||||
// if err != nil {
|
||||
// return Setup{}, fmt.Errorf("while unmarshaling datasource for service %s: %w", name, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
ret.Setup = append(ret.Setup, ServiceSetup{
|
||||
DetectedService: name,
|
||||
Install: svc.Install,
|
||||
DataSource: svc.DataSource,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ListSupported parses the configuration file and outputs a list of the supported services.
|
||||
func ListSupported(serviceDetectionFile string) ([]string, error) {
|
||||
dc, err := readDetectConfig(serviceDetectionFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]string, 0)
|
||||
for k := range dc.Detect {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys, nil
|
||||
}
|
1017
pkg/setup/detect_test.go
Normal file
1017
pkg/setup/detect_test.go
Normal file
File diff suppressed because it is too large
Load diff
9
pkg/setup/export_test.go
Normal file
9
pkg/setup/export_test.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package setup
|
||||
|
||||
var (
|
||||
SystemdUnitList = systemdUnitList
|
||||
FilterWithRules = filterWithRules
|
||||
ApplyRules = applyRules
|
||||
|
||||
// NormalizeVersion = normalizeVersion
|
||||
)
|
255
pkg/setup/install.go
Normal file
255
pkg/setup/install.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
goccyyaml "github.com/goccy/go-yaml"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||
)
|
||||
|
||||
// AcquisDocument is created from a SetupItem. It represents a single YAML document, and can be part of a multi-document file.
|
||||
type AcquisDocument struct {
|
||||
AcquisFilename string
|
||||
DataSource map[string]interface{}
|
||||
}
|
||||
|
||||
func decodeSetup(input []byte, fancyErrors bool) (Setup, error) {
|
||||
ret := Setup{}
|
||||
|
||||
// parse with goccy to have better error messages in many cases
|
||||
dec := goccyyaml.NewDecoder(bytes.NewBuffer(input), goccyyaml.Strict())
|
||||
|
||||
if err := dec.Decode(&ret); err != nil {
|
||||
if fancyErrors {
|
||||
return ret, fmt.Errorf("%v", goccyyaml.FormatError(err, true, true))
|
||||
}
|
||||
// XXX errors here are multiline, should we just print them to stderr instead of logging?
|
||||
return ret, fmt.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// parse again because goccy is not strict enough anyway
|
||||
dec2 := yaml.NewDecoder(bytes.NewBuffer(input))
|
||||
dec2.KnownFields(true)
|
||||
|
||||
if err := dec2.Decode(&ret); err != nil {
|
||||
return ret, fmt.Errorf("while unmarshaling setup file: %w", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// InstallHubItems installs the objects recommended in a setup file.
|
||||
func InstallHubItems(csConfig *csconfig.Config, input []byte, dryRun bool) error {
|
||||
setupEnvelope, err := decodeSetup(input, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := csConfig.LoadHub(); err != nil {
|
||||
return fmt.Errorf("loading hub: %w", err)
|
||||
}
|
||||
|
||||
if err := cwhub.SetHubBranch(); err != nil {
|
||||
return fmt.Errorf("setting hub branch: %w", err)
|
||||
}
|
||||
|
||||
if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
|
||||
return fmt.Errorf("getting hub index: %w", err)
|
||||
}
|
||||
|
||||
for _, setupItem := range setupEnvelope.Setup {
|
||||
forceAction := false
|
||||
downloadOnly := false
|
||||
install := setupItem.Install
|
||||
|
||||
if install == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(install.Collections) > 0 {
|
||||
for _, collection := range setupItem.Install.Collections {
|
||||
if dryRun {
|
||||
fmt.Println("dry-run: would install collection", collection)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cwhub.InstallItem(csConfig, collection, cwhub.COLLECTIONS, forceAction, downloadOnly); err != nil {
|
||||
return fmt.Errorf("while installing collection %s: %w", collection, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(install.Parsers) > 0 {
|
||||
for _, parser := range setupItem.Install.Parsers {
|
||||
if dryRun {
|
||||
fmt.Println("dry-run: would install parser", parser)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cwhub.InstallItem(csConfig, parser, cwhub.PARSERS, forceAction, downloadOnly); err != nil {
|
||||
return fmt.Errorf("while installing parser %s: %w", parser, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(install.Scenarios) > 0 {
|
||||
for _, scenario := range setupItem.Install.Scenarios {
|
||||
if dryRun {
|
||||
fmt.Println("dry-run: would install scenario", scenario)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cwhub.InstallItem(csConfig, scenario, cwhub.SCENARIOS, forceAction, downloadOnly); err != nil {
|
||||
return fmt.Errorf("while installing scenario %s: %w", scenario, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(install.PostOverflows) > 0 {
|
||||
for _, postoverflow := range setupItem.Install.PostOverflows {
|
||||
if dryRun {
|
||||
fmt.Println("dry-run: would install postoverflow", postoverflow)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cwhub.InstallItem(csConfig, postoverflow, cwhub.PARSERS_OVFLW, forceAction, downloadOnly); err != nil {
|
||||
return fmt.Errorf("while installing postoverflow %s: %w", postoverflow, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// marshalAcquisDocuments creates the monolithic file, or itemized files (if a directory is provided) with the acquisition documents.
|
||||
func marshalAcquisDocuments(ads []AcquisDocument, toDir string) (string, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
dashTerminator := false
|
||||
|
||||
disclaimer := `
|
||||
#
|
||||
# This file was automatically generated by "cscli setup datasources".
|
||||
# You can modify it by hand, but will be responsible for its maintenance.
|
||||
# To add datasources or logfiles, you can instead write a new configuration
|
||||
# in the directory defined by acquisition_dir.
|
||||
#
|
||||
|
||||
`
|
||||
|
||||
if toDir == "" {
|
||||
sb.WriteString(disclaimer)
|
||||
} else {
|
||||
_, err := os.Stat(toDir)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("directory %s does not exist", toDir)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ad := range ads {
|
||||
out, err := goccyyaml.MarshalWithOptions(ad.DataSource, goccyyaml.IndentSequence(true))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while encoding datasource: %w", err)
|
||||
}
|
||||
|
||||
if toDir != "" {
|
||||
if ad.AcquisFilename == "" {
|
||||
return "", fmt.Errorf("empty acquis filename")
|
||||
}
|
||||
|
||||
fname := filepath.Join(toDir, ad.AcquisFilename)
|
||||
fmt.Println("creating", fname)
|
||||
|
||||
f, err := os.Create(fname)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating acquisition file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(disclaimer)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
|
||||
}
|
||||
|
||||
_, err = f.Write(out)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
|
||||
}
|
||||
|
||||
f.Sync()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if dashTerminator {
|
||||
sb.WriteString("---\n")
|
||||
}
|
||||
|
||||
sb.Write(out)
|
||||
|
||||
dashTerminator = true
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// Validate checks the validity of a setup file.
|
||||
func Validate(input []byte) error {
|
||||
_, err := decodeSetup(input, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataSources generates the acquisition documents from a setup file.
|
||||
func DataSources(input []byte, toDir string) (string, error) {
|
||||
setupEnvelope, err := decodeSetup(input, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ads := make([]AcquisDocument, 0)
|
||||
|
||||
filename := func(basename string, ext string) string {
|
||||
if basename == "" {
|
||||
return basename
|
||||
}
|
||||
|
||||
return basename + ext
|
||||
}
|
||||
|
||||
for _, setupItem := range setupEnvelope.Setup {
|
||||
datasource := setupItem.DataSource
|
||||
|
||||
basename := ""
|
||||
if toDir != "" {
|
||||
basename = "setup." + setupItem.DetectedService
|
||||
}
|
||||
|
||||
if datasource == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ad := AcquisDocument{
|
||||
AcquisFilename: filename(basename, ".yaml"),
|
||||
DataSource: datasource,
|
||||
}
|
||||
ads = append(ads, ad)
|
||||
}
|
||||
|
||||
return marshalAcquisDocuments(ads, toDir)
|
||||
}
|
59
pkg/setup/units.go
Normal file
59
pkg/setup/units.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// systemdUnitList returns all enabled systemd units.
|
||||
// It needs to parse the table because -o json does not work everywhere.
|
||||
func systemdUnitList() ([]string, error) {
|
||||
wrap := func(err error) error {
|
||||
return fmt.Errorf("running systemctl: %w", err)
|
||||
}
|
||||
|
||||
ret := make([]string, 0)
|
||||
cmd := ExecCommand("systemctl", "list-unit-files", "--state=enabled,generated,static")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return ret, wrap(err)
|
||||
}
|
||||
|
||||
log.Debugf("Running systemctl...")
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return ret, wrap(err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
header := true // skip the first line
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) == 0 {
|
||||
break // the rest of the output is footer
|
||||
}
|
||||
|
||||
if !header {
|
||||
spaceIdx := strings.IndexRune(line, ' ')
|
||||
if spaceIdx == -1 {
|
||||
return ret, fmt.Errorf("can't parse systemctl output")
|
||||
}
|
||||
|
||||
line = line[:spaceIdx]
|
||||
ret = append(ret, line)
|
||||
}
|
||||
|
||||
header = false
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return ret, wrap(err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
32
pkg/setup/units_test.go
Normal file
32
pkg/setup/units_test.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package setup_test
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/setup"
|
||||
)
|
||||
|
||||
func TestSystemdUnitList(t *testing.T) {
|
||||
require := require.New(t)
|
||||
setup.ExecCommand = fakeExecCommand
|
||||
|
||||
defer func() { setup.ExecCommand = exec.Command }()
|
||||
|
||||
units, err := setup.SystemdUnitList() //nolint:typecheck,nolintlint // exported only for tests
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal([]string{
|
||||
"crowdsec-setup-detect.service",
|
||||
"apache2.service",
|
||||
"apparmor.service",
|
||||
"apport.service",
|
||||
"atop.service",
|
||||
"atopacct.service",
|
||||
"finalrd.service",
|
||||
"fwupd-refresh.service",
|
||||
"fwupd.service",
|
||||
}, units)
|
||||
}
|
816
tests/bats/07_setup.bats
Normal file
816
tests/bats/07_setup.bats
Normal file
|
@ -0,0 +1,816 @@
|
|||
#!/usr/bin/env bats
|
||||
# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si:
|
||||
|
||||
set -u
|
||||
|
||||
setup_file() {
|
||||
load "../lib/setup_file.sh"
|
||||
./instance-data load
|
||||
HUB_DIR=$(config_get '.config_paths.hub_dir')
|
||||
export HUB_DIR
|
||||
DETECT_YAML="${HUB_DIR}/detect.yaml"
|
||||
export DETECT_YAML
|
||||
# shellcheck disable=SC2154
|
||||
TESTDATA="${BATS_TEST_DIRNAME}/testdata/07_setup"
|
||||
export TESTDATA
|
||||
|
||||
export CROWDSEC_FEATURE_CSCLI_SETUP="true"
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
load "../lib/teardown_file.sh"
|
||||
}
|
||||
|
||||
setup() {
|
||||
load "../lib/setup.sh"
|
||||
load "../lib/bats-file/load.bash"
|
||||
load "../lib/bats-mock/load.bash"
|
||||
./instance-data load
|
||||
}
|
||||
|
||||
teardown() {
|
||||
./instance-crowdsec stop
|
||||
}
|
||||
|
||||
#----------
|
||||
|
||||
#shellcheck disable=SC2154
|
||||
@test "cscli setup" {
|
||||
rune -0 cscli help
|
||||
assert_line --regexp '^ +setup +Tools to configure crowdsec$'
|
||||
|
||||
rune -0 cscli setup --help
|
||||
assert_line 'Usage:'
|
||||
assert_line ' cscli setup [command]'
|
||||
assert_line 'Manage hub configuration and service detection'
|
||||
assert_line --partial "detect detect running services, generate a setup file"
|
||||
assert_line --partial "datasources generate datasource (acquisition) configuration from a setup file"
|
||||
assert_line --partial "install-hub install items from a setup file"
|
||||
assert_line --partial "validate validate a setup file"
|
||||
|
||||
# cobra should return error for non-existing sub-subcommands, but doesn't
|
||||
rune -0 cscli setup blahblah
|
||||
assert_line 'Usage:'
|
||||
}
|
||||
|
||||
@test "cscli setup detect --help; --detect-config" {
|
||||
rune -0 cscli setup detect --help
|
||||
assert_line --regexp "detect running services, generate a setup file"
|
||||
assert_line 'Usage:'
|
||||
assert_line ' cscli setup detect [flags]'
|
||||
assert_line --partial "--detect-config string path to service detection configuration (default \"${HUB_DIR}/detect.yaml\")"
|
||||
assert_line --partial "--force-process strings force detection of a running process (can be repeated)"
|
||||
assert_line --partial "--force-unit strings force detection of a systemd unit (can be repeated)"
|
||||
assert_line --partial "--list-supported-services do not detect; only print supported services"
|
||||
assert_line --partial "--force-os-family string override OS.Family: one of linux, freebsd, windows or darwin"
|
||||
assert_line --partial "--force-os-id string override OS.ID=[debian | ubuntu | , redhat...]"
|
||||
assert_line --partial "--force-os-version string override OS.RawVersion (of OS or Linux distribution)"
|
||||
assert_line --partial "--skip-service strings ignore a service, don't recommend hub/datasources (can be repeated)"
|
||||
|
||||
rune -1 --separate-stderr cscli setup detect --detect-config /path/does/not/exist
|
||||
assert_stderr --partial "detecting services: while reading file: open /path/does/not/exist: no such file or directory"
|
||||
|
||||
# rm -f "${HUB_DIR}/detect.yaml"
|
||||
}
|
||||
|
||||
@test "cscli setup detect (linux), --skip-service" {
|
||||
[[ ${OSTYPE} =~ linux.* ]] || skip
|
||||
tempfile=$(TMPDIR="$BATS_TEST_TMPDIR" mktemp)
|
||||
cat <<-EOT >"${tempfile}"
|
||||
version: 1.0
|
||||
detect:
|
||||
linux:
|
||||
when:
|
||||
- OS.Family == "linux"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/linux
|
||||
thewiz:
|
||||
when:
|
||||
- OS.Family != "linux"
|
||||
foobarbaz:
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --detect-config "$tempfile"
|
||||
assert_json '{setup:[{detected_service:"foobarbaz"},{detected_service:"linux",install:{collections:["crowdsecurity/linux"]}}]}'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --detect-config "$tempfile" --skip-service linux
|
||||
assert_json '{setup:[{detected_service:"foobarbaz"}]}'
|
||||
}
|
||||
|
||||
@test "cscli setup detect --force-os-*" {
|
||||
rune -0 --separate-stderr cscli setup detect --force-os-family linux --detect-config "${TESTDATA}/detect.yaml"
|
||||
rune -0 jq -cS '.setup[] | select(.detected_service=="linux")' <(output)
|
||||
assert_json '{detected_service:"linux",install:{collections:["crowdsecurity/linux"]},datasource:{source:"file",labels:{type:"syslog"},filenames:["/var/log/syslog","/var/log/kern.log","/var/log/messages"]}}'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-os-family freebsd --detect-config "${TESTDATA}/detect.yaml"
|
||||
rune -0 jq -cS '.setup[] | select(.detected_service=="freebsd")' <(output)
|
||||
assert_json '{detected_service:"freebsd",install:{collections:["crowdsecurity/freebsd"]}}'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-os-family windows --detect-config "${TESTDATA}/detect.yaml"
|
||||
rune -0 jq -cS '.setup[] | select(.detected_service=="windows")' <(output)
|
||||
assert_json '{detected_service:"windows",install:{collections:["crowdsecurity/windows"]}}'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-os-family darwin --detect-config "${TESTDATA}/detect.yaml"
|
||||
|
||||
# XXX do we want do disallow unknown family?
|
||||
# assert_stderr --partial "detecting services: OS 'darwin' not supported"
|
||||
|
||||
# XXX TODO force-os-id, force-os-version
|
||||
}
|
||||
|
||||
@test "cscli setup detect --list-supported-services" {
|
||||
tempfile=$(TMPDIR="$BATS_TEST_TMPDIR" mktemp)
|
||||
cat <<-EOT >"${tempfile}"
|
||||
version: 1.0
|
||||
detect:
|
||||
thewiz:
|
||||
foobarbaz:
|
||||
apache2:
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --list-supported-services --detect-config "$tempfile"
|
||||
# the service list is sorted
|
||||
assert_output - <<-EOT
|
||||
apache2
|
||||
foobarbaz
|
||||
thewiz
|
||||
EOT
|
||||
|
||||
cat <<-EOT >"${tempfile}"
|
||||
thisisajoke
|
||||
EOT
|
||||
|
||||
rune -1 --separate-stderr cscli setup detect --list-supported-services --detect-config "$tempfile"
|
||||
assert_stderr --partial "while parsing ${tempfile}: yaml: unmarshal errors:"
|
||||
|
||||
rm -f "$tempfile"
|
||||
}
|
||||
|
||||
@test "cscli setup detect (systemctl)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- UnitFound("mock-apache2.service")
|
||||
datasource:
|
||||
source: file
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache2
|
||||
EOT
|
||||
|
||||
# transparently mock systemctl. It's easier if you can tell the application
|
||||
# under test which executable to call (in which case just call $mock) but
|
||||
# here we do the symlink and $PATH dance as an example
|
||||
mocked_command="systemctl"
|
||||
|
||||
# mock setup
|
||||
mock="$(mock_create)"
|
||||
mock_path="${mock%/*}"
|
||||
mock_file="${mock##*/}"
|
||||
ln -sf "${mock_path}/${mock_file}" "${mock_path}/${mocked_command}"
|
||||
|
||||
#shellcheck disable=SC2030
|
||||
PATH="${mock_path}:${PATH}"
|
||||
|
||||
mock_set_output "$mock" \
|
||||
'UNIT FILE STATE VENDOR PRESET
|
||||
snap-bare-5.mount enabled enabled
|
||||
snap-core-13308.mount enabled enabled
|
||||
snap-firefox-1635.mount enabled enabled
|
||||
snap-fx-158.mount enabled enabled
|
||||
snap-gimp-393.mount enabled enabled
|
||||
snap-gtk\x2dcommon\x2dthemes-1535.mount enabled enabled
|
||||
snap-kubectl-2537.mount enabled enabled
|
||||
snap-rustup-1027.mount enabled enabled
|
||||
cups.path enabled enabled
|
||||
console-setup.service enabled enabled
|
||||
dmesg.service enabled enabled
|
||||
getty@.service enabled enabled
|
||||
grub-initrd-fallback.service enabled enabled
|
||||
irqbalance.service enabled enabled
|
||||
keyboard-setup.service enabled enabled
|
||||
mock-apache2.service enabled enabled
|
||||
networkd-dispatcher.service enabled enabled
|
||||
ua-timer.timer enabled enabled
|
||||
update-notifier-download.timer enabled enabled
|
||||
update-notifier-motd.timer enabled enabled
|
||||
|
||||
20 unit files listed.'
|
||||
mock_set_status "$mock" 1 2
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect
|
||||
rune -0 jq -c '.setup' <(output)
|
||||
|
||||
# If a call to UnitFoundwas part of the expression and it returned true,
|
||||
# there is a default journalctl_filter derived from the unit's name.
|
||||
assert_json '[{datasource:{source:"file",filename:"dummy.log",labels:{type:"apache2"}},detected_service:"apache2"}]'
|
||||
|
||||
# the command was called exactly once
|
||||
[[ $(mock_get_call_num "$mock") -eq 1 ]]
|
||||
|
||||
# the command was called with the expected parameters
|
||||
[[ $(mock_get_call_args "$mock" 1) == "list-unit-files --state=enabled,generated,static" ]]
|
||||
|
||||
rune -1 systemctl
|
||||
|
||||
# mock teardown
|
||||
unlink "${mock_path}/${mocked_command}"
|
||||
PATH="${PATH/${mock_path}:/}"
|
||||
}
|
||||
|
||||
# XXX this is the same boilerplate as the previous test, can be simplified
|
||||
@test "cscli setup detect (snub systemd)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- UnitFound("mock-apache2.service")
|
||||
datasource:
|
||||
source: file
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache2
|
||||
EOT
|
||||
|
||||
# transparently mock systemctl. It's easier if you can tell the application
|
||||
# under test which executable to call (in which case just call $mock) but
|
||||
# here we do the symlink and $PATH dance as an example
|
||||
mocked_command="systemctl"
|
||||
|
||||
# mock setup
|
||||
mock="$(mock_create)"
|
||||
mock_path="${mock%/*}"
|
||||
mock_file="${mock##*/}"
|
||||
ln -sf "${mock_path}/${mock_file}" "${mock_path}/${mocked_command}"
|
||||
|
||||
#shellcheck disable=SC2031
|
||||
PATH="${mock_path}:${PATH}"
|
||||
|
||||
# we don't really care about the output, it's not used anyway
|
||||
mock_set_output "$mock" ""
|
||||
mock_set_status "$mock" 1 2
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --snub-systemd
|
||||
|
||||
# setup must not be 'null', but an empty list
|
||||
assert_json '{setup:[]}'
|
||||
|
||||
# the command was never called
|
||||
[[ $(mock_get_call_num "$mock") -eq 0 ]]
|
||||
|
||||
rune -0 systemctl
|
||||
|
||||
# mock teardown
|
||||
unlink "${mock_path}/${mocked_command}"
|
||||
PATH="${PATH/${mock_path}:/}"
|
||||
}
|
||||
|
||||
@test "cscli setup detect --force-unit" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- UnitFound("force-apache2")
|
||||
datasource:
|
||||
source: file
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache2
|
||||
apache3:
|
||||
when:
|
||||
- UnitFound("force-apache3")
|
||||
datasource:
|
||||
source: file
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache3
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-unit force-apache2
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{datasource:{source:"file",filename:"dummy.log",labels:{"type":"apache2"}},detected_service:"apache2"}]'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-unit force-apache2,force-apache3
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{datasource:{source:"file",filename:"dummy.log",labels:{type:"apache2"}},detected_service:"apache2"},{datasource:{source:"file",filename:"dummy.log",labels:{"type":"apache3"}},detected_service:"apache3"}]'
|
||||
|
||||
# force-unit can be specified multiple times, the order does not matter
|
||||
rune -0 --separate-stderr cscli setup detect --force-unit force-apache3 --force-unit force-apache2
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{datasource:{source:"file",filename:"dummy.log",labels:{type:"apache2"}},detected_service:"apache2"},{datasource:{source:"file",filename:"dummy.log",labels:{type:"apache3"}},detected_service:"apache3"}]'
|
||||
|
||||
rune -1 --separate-stderr cscli setup detect --force-unit mock-doesnotexist
|
||||
assert_stderr --partial "detecting services: unit(s) forced but not supported: [mock-doesnotexist]"
|
||||
}
|
||||
|
||||
@test "cscli setup detect (process)" {
|
||||
# This is harder to mock, because gopsutil requires proc/ to be a mount
|
||||
# point. So we pick a process that exists for sure.
|
||||
expected_process=$(basename "$SHELL")
|
||||
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- ProcessRunning("${expected_process}")
|
||||
apache3:
|
||||
when:
|
||||
- ProcessRunning("this-does-not-exist")
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{detected_service:"apache2"}]'
|
||||
}
|
||||
|
||||
@test "cscli setup detect --force-process" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- ProcessRunning("force-apache2")
|
||||
apache3:
|
||||
when:
|
||||
- ProcessRunning("this-does-not-exist")
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-process force-apache2
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{detected_service:"apache2"}]'
|
||||
}
|
||||
|
||||
@test "cscli setup detect (acquisition only, no hub items)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- UnitFound("force-apache2")
|
||||
datasource:
|
||||
source: file
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache2
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-unit force-apache2
|
||||
rune -0 jq -cS '.setup' <(output)
|
||||
assert_json '[{datasource:{source:"file",filename:"dummy.log",labels:{type:"apache2"}},detected_service:"apache2"}]'
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-unit force-apache2 --yaml
|
||||
assert_output - <<-EOT
|
||||
setup:
|
||||
- detected_service: apache2
|
||||
datasource:
|
||||
filename: dummy.log
|
||||
labels:
|
||||
type: apache2
|
||||
source: file
|
||||
EOT
|
||||
}
|
||||
|
||||
@test "cscli setup detect (full acquisition section)" {
|
||||
skip "not supported yet"
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
foobar:
|
||||
datasource:
|
||||
filenames:
|
||||
- /path/to/log/*.log
|
||||
exclude_regexps:
|
||||
- ^/path/to/log/excludeme\.log$
|
||||
force_inotify: true
|
||||
mode: tail
|
||||
labels:
|
||||
type: foolog
|
||||
EOT
|
||||
|
||||
rune -0 cscli setup detect --yaml
|
||||
assert_output - <<-EOT
|
||||
setup:
|
||||
- detected_service: foobar
|
||||
datasource:
|
||||
filenames:
|
||||
- /path/to/log/*.log
|
||||
exclude_regexps:
|
||||
- ^/path/to/log/excludeme.log$
|
||||
force_inotify: true
|
||||
mode: tail
|
||||
labels:
|
||||
type: foolog
|
||||
EOT
|
||||
}
|
||||
|
||||
@test "cscli setup detect + acquis + install (no acquisition, no hub items)" {
|
||||
# no-op edge case, to make sure we don't crash
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
always:
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect
|
||||
assert_json '{setup:[{detected_service:"always"}]}'
|
||||
setup=$output
|
||||
rune -0 cscli setup datasources /dev/stdin <<<"$setup"
|
||||
rune -0 cscli setup install-hub /dev/stdin <<<"$setup"
|
||||
}
|
||||
|
||||
@test "cscli setup detect (with collections)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
foobar:
|
||||
when:
|
||||
- ProcessRunning("force-foobar")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/foobar
|
||||
qox:
|
||||
when:
|
||||
- ProcessRunning("test-qox")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/foobar
|
||||
apache2:
|
||||
when:
|
||||
- ProcessRunning("force-apache2")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-process force-apache2,force-foobar
|
||||
rune -0 jq -Sc '.setup | sort' <(output)
|
||||
assert_json '[{install:{collections:["crowdsecurity/apache2"]},detected_service:"apache2"},{install:{collections:["crowdsecurity/foobar"]},detected_service:"foobar"}]'
|
||||
}
|
||||
|
||||
@test "cscli setup detect (with acquisition)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
foobar:
|
||||
when:
|
||||
- ProcessRunning("force-foobar")
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: foobar
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --force-process force-foobar
|
||||
rune -0 yq -op '.setup | sort_keys(..)' <(output)
|
||||
assert_output - <<-EOT
|
||||
0.datasource.filenames.0 = /var/log/apache2/*.log
|
||||
0.datasource.filenames.1 = /var/log/*http*/*.log
|
||||
0.datasource.labels.type = foobar
|
||||
0.datasource.source = file
|
||||
0.detected_service = foobar
|
||||
EOT
|
||||
|
||||
rune -1 --separate-stderr cscli setup detect --force-process mock-doesnotexist
|
||||
assert_stderr --partial "detecting services: process(es) forced but not supported: [mock-doesnotexist]"
|
||||
}
|
||||
|
||||
@test "cscli setup detect (datasource validation)" {
|
||||
cat <<-EOT >"${DETECT_YAML}"
|
||||
version: 1.0
|
||||
detect:
|
||||
foobar:
|
||||
datasource:
|
||||
labels:
|
||||
type: something
|
||||
EOT
|
||||
|
||||
rune -1 --separate-stderr cscli setup detect
|
||||
assert_stderr --partial "detecting services: invalid datasource for foobar: source is empty"
|
||||
|
||||
# more datasource-specific tests are in detect_test.go
|
||||
}
|
||||
|
||||
@test "cscli setup install-hub (dry run)" {
|
||||
# it's not installed
|
||||
rune -0 --separate-stderr cscli collections list -o json
|
||||
rune -0 jq -r '.collections[].name' <(output)
|
||||
refute_line "crowdsecurity/apache2"
|
||||
|
||||
# we install it
|
||||
rune -0 --separate-stderr cscli setup install-hub /dev/stdin --dry-run <<< '{"setup":[{"install":{"collections":["crowdsecurity/apache2"]}}]}'
|
||||
assert_output 'dry-run: would install collection crowdsecurity/apache2'
|
||||
|
||||
# still not installed
|
||||
rune -0 --separate-stderr cscli collections list -o json
|
||||
rune -0 jq -r '.collections[].name' <(output)
|
||||
refute_line "crowdsecurity/apache2"
|
||||
}
|
||||
|
||||
@test "cscli setup install-hub (dry run: install multiple collections)" {
|
||||
# it's not installed
|
||||
rune -0 --separate-stderr cscli collections list -o json
|
||||
rune -0 jq -r '.collections[].name' <(output)
|
||||
refute_line "crowdsecurity/apache2"
|
||||
|
||||
# we install it
|
||||
rune -0 --separate-stderr cscli setup install-hub /dev/stdin --dry-run <<< '{"setup":[{"install":{"collections":["crowdsecurity/apache2"]}}]}'
|
||||
assert_output 'dry-run: would install collection crowdsecurity/apache2'
|
||||
|
||||
# still not installed
|
||||
rune -0 --separate-stderr cscli collections list -o json
|
||||
rune -0 jq -r '.collections[].name' <(output)
|
||||
refute_line "crowdsecurity/apache2"
|
||||
}
|
||||
|
||||
@test "cscli setup install-hub (dry run: install multiple collections, parsers, scenarios, postoverflows)" {
|
||||
rune -0 --separate-stderr cscli setup install-hub /dev/stdin --dry-run <<< '{"setup":[{"install":{"collections":["crowdsecurity/foo","johndoe/bar"],"parsers":["crowdsecurity/fooparser","johndoe/barparser"],"scenarios":["crowdsecurity/fooscenario","johndoe/barscenario"],"postoverflows":["crowdsecurity/foopo","johndoe/barpo"]}}]}'
|
||||
assert_line 'dry-run: would install collection crowdsecurity/foo'
|
||||
assert_line 'dry-run: would install collection johndoe/bar'
|
||||
assert_line 'dry-run: would install parser crowdsecurity/fooparser'
|
||||
assert_line 'dry-run: would install parser johndoe/barparser'
|
||||
assert_line 'dry-run: would install scenario crowdsecurity/fooscenario'
|
||||
assert_line 'dry-run: would install scenario johndoe/barscenario'
|
||||
assert_line 'dry-run: would install postoverflow crowdsecurity/foopo'
|
||||
assert_line 'dry-run: would install postoverflow johndoe/barpo'
|
||||
}
|
||||
|
||||
@test "cscli setup datasources" {
|
||||
rune -0 --separate-stderr cscli setup datasources --help
|
||||
assert_line --partial "--to-dir string write the configuration to a directory, in multiple files"
|
||||
|
||||
# single item
|
||||
|
||||
rune -0 --separate-stderr cscli setup datasources /dev/stdin <<-EOT
|
||||
setup:
|
||||
- datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
EOT
|
||||
|
||||
# remove diclaimer
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
labels:
|
||||
type: syslog
|
||||
source: file
|
||||
EOT
|
||||
|
||||
# multiple items
|
||||
|
||||
rune -0 --separate-stderr cscli setup datasources /dev/stdin <<-EOT
|
||||
setup:
|
||||
- datasource:
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
- datasource:
|
||||
labels:
|
||||
type: foobar
|
||||
filenames:
|
||||
- /var/log/foobar/*.log
|
||||
- datasource:
|
||||
labels:
|
||||
type: barbaz
|
||||
filenames:
|
||||
- /path/to/barbaz.log
|
||||
EOT
|
||||
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
labels:
|
||||
type: syslog
|
||||
---
|
||||
filenames:
|
||||
- /var/log/foobar/*.log
|
||||
labels:
|
||||
type: foobar
|
||||
---
|
||||
filenames:
|
||||
- /path/to/barbaz.log
|
||||
labels:
|
||||
type: barbaz
|
||||
EOT
|
||||
|
||||
# multiple items, to a directory
|
||||
|
||||
# avoid the BATS_TEST_TMPDIR variable, it can have a double //
|
||||
acquisdir=$(TMPDIR="$BATS_FILE_TMPDIR" mktemp -u)
|
||||
mkdir "$acquisdir"
|
||||
|
||||
rune -0 cscli setup datasources /dev/stdin --to-dir "$acquisdir" <<-EOT
|
||||
setup:
|
||||
- detected_service: apache2
|
||||
datasource:
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
- detected_service: foobar
|
||||
datasource:
|
||||
labels:
|
||||
type: foobar
|
||||
filenames:
|
||||
- /var/log/foobar/*.log
|
||||
- detected_service: barbaz
|
||||
datasource:
|
||||
labels:
|
||||
type: barbaz
|
||||
filenames:
|
||||
- /path/to/barbaz.log
|
||||
EOT
|
||||
|
||||
# XXX what if detected_service is missing?
|
||||
|
||||
rune -0 cat "${acquisdir}/setup.apache2.yaml"
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
labels:
|
||||
type: syslog
|
||||
EOT
|
||||
|
||||
rune -0 cat "${acquisdir}/setup.foobar.yaml"
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /var/log/foobar/*.log
|
||||
labels:
|
||||
type: foobar
|
||||
EOT
|
||||
|
||||
rune -0 cat "${acquisdir}/setup.barbaz.yaml"
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /path/to/barbaz.log
|
||||
labels:
|
||||
type: barbaz
|
||||
EOT
|
||||
|
||||
rm -rf -- "${acquisdir:?}"
|
||||
mkdir "$acquisdir"
|
||||
|
||||
# having both filenames and journalctl does not generate two files: the datasource is copied as-is, even if incorrect
|
||||
|
||||
rune -0 cscli setup datasources /dev/stdin --to-dir "$acquisdir" <<-EOT
|
||||
setup:
|
||||
- detected_service: apache2
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
labels:
|
||||
type: apache2
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
journalctl_filter:
|
||||
- _SYSTEMD_UNIT=apache2.service
|
||||
EOT
|
||||
|
||||
rune -0 cat "${acquisdir}/setup.apache2.yaml"
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
journalctl_filter:
|
||||
- _SYSTEMD_UNIT=apache2.service
|
||||
labels:
|
||||
type: apache2
|
||||
EOT
|
||||
|
||||
# the directory must exist
|
||||
rune -1 --separate-stderr cscli setup datasources /dev/stdin --to-dir /path/does/not/exist <<< '{}'
|
||||
assert_stderr --partial "directory /path/does/not/exist does not exist"
|
||||
|
||||
# of course it must be a directory
|
||||
|
||||
touch "${acquisdir}/notadir"
|
||||
|
||||
rune -1 --separate-stderr cscli setup datasources /dev/stdin --to-dir "${acquisdir}/notadir" <<-EOT
|
||||
setup:
|
||||
- detected_service: apache2
|
||||
datasource:
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
EOT
|
||||
assert_stderr --partial "open ${acquisdir}/notadir/setup.apache2.yaml: not a directory"
|
||||
|
||||
rm -rf -- "${acquisdir:?}"
|
||||
}
|
||||
|
||||
@test "cscli setup datasources (disclaimer)" {
|
||||
disclaimer="This file was automatically generated"
|
||||
|
||||
rune -0 --separate-stderr cscli setup datasources /dev/stdin <<<"setup:"
|
||||
rune -0 yq 'head_comment' <(output)
|
||||
assert_output --partial "$disclaimer"
|
||||
|
||||
rune -0 --separate-stderr cscli setup datasources /dev/stdin <<-EOT
|
||||
setup:
|
||||
- detected_service: something
|
||||
datasource:
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/something.log
|
||||
EOT
|
||||
rune -0 yq 'head_comment' <(output)
|
||||
assert_output --partial "$disclaimer"
|
||||
}
|
||||
|
||||
@test "cscli setup (custom journalctl filter)" {
|
||||
tempfile=$(TMPDIR="$BATS_TEST_TMPDIR" mktemp)
|
||||
cat <<-EOT >"${tempfile}"
|
||||
version: 1.0
|
||||
detect:
|
||||
thewiz:
|
||||
when:
|
||||
- UnitFound("thewiz.service")
|
||||
datasource:
|
||||
source: journalctl
|
||||
labels:
|
||||
type: thewiz
|
||||
journalctl_filter:
|
||||
- "SYSLOG_IDENTIFIER=TheWiz"
|
||||
EOT
|
||||
|
||||
rune -0 --separate-stderr cscli setup detect --detect-config "$tempfile" --force-unit thewiz.service
|
||||
rune -0 jq -cS '.' <(output)
|
||||
assert_json '{setup:[{datasource:{source:"journalctl",journalctl_filter:["SYSLOG_IDENTIFIER=TheWiz"],labels:{type:"thewiz"}},detected_service:"thewiz"}]}'
|
||||
rune -0 --separate-stderr cscli setup datasources <(output)
|
||||
rune -0 yq '. head_comment=""' <(output)
|
||||
assert_output - <<-EOT
|
||||
journalctl_filter:
|
||||
- SYSLOG_IDENTIFIER=TheWiz
|
||||
labels:
|
||||
type: thewiz
|
||||
source: journalctl
|
||||
EOT
|
||||
|
||||
rm -f "$tempfile"
|
||||
}
|
||||
|
||||
@test "cscli setup validate" {
|
||||
# an empty file is not enough
|
||||
rune -1 --separate-stderr cscli setup validate /dev/null
|
||||
assert_output "EOF"
|
||||
assert_stderr --partial "invalid setup file"
|
||||
|
||||
# this is ok; install nothing
|
||||
rune -0 --separate-stderr cscli setup validate /dev/stdin <<-EOT
|
||||
setup:
|
||||
EOT
|
||||
refute_output
|
||||
|
||||
rune -1 --separate-stderr cscli setup validate /dev/stdin <<-EOT
|
||||
se tup:
|
||||
EOT
|
||||
assert_output - <<-EOT
|
||||
[1:1] unknown field "se tup"
|
||||
> 1 | se tup:
|
||||
^
|
||||
EOT
|
||||
assert_stderr --partial "invalid setup file"
|
||||
|
||||
rune -1 --separate-stderr cscli setup validate /dev/stdin <<-EOT
|
||||
setup:
|
||||
alsdk al; sdf
|
||||
EOT
|
||||
assert_output "while unmarshaling setup file: yaml: line 2: could not find expected ':'"
|
||||
assert_stderr --partial "invalid setup file"
|
||||
}
|
||||
|
88
tests/bats/testdata/07_setup/detect.yaml
vendored
Normal file
88
tests/bats/testdata/07_setup/detect.yaml
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
# TODO: windows, use_time_machine, event support (see https://hub.crowdsec.net/author/crowdsecurity/collections/iis)
|
||||
|
||||
---
|
||||
version: 1.0
|
||||
|
||||
detect:
|
||||
apache2:
|
||||
when:
|
||||
- ProcessRunning("apache2")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: apache2
|
||||
filenames:
|
||||
- /var/log/apache2/*.log
|
||||
- /var/log/*http*/*.log
|
||||
- /var/log/httpd/*.log
|
||||
|
||||
apache2-systemd:
|
||||
when:
|
||||
- UnitFound("apache2.service")
|
||||
- OS.ID != "centos"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: journalctl
|
||||
journalctl_filter:
|
||||
- "_SYSTEMD_UNIT=mock-apache2.service"
|
||||
labels:
|
||||
type: apache2
|
||||
|
||||
apache2-systemd-centos:
|
||||
when:
|
||||
- UnitFound("httpd.service")
|
||||
- OS.ID == "centos"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: journalctl
|
||||
journalctl_filter:
|
||||
- "_SYSTEMD_UNIT=httpd.service"
|
||||
|
||||
ssh-systemd:
|
||||
when:
|
||||
- UnitFound("ssh.service") or UnitFound("ssh.socket")
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/apache2
|
||||
datasource:
|
||||
source: journalctl
|
||||
journalctl_filter:
|
||||
- "_SYSTEMD_UNIT=ssh.service"
|
||||
labels:
|
||||
type: syslog
|
||||
|
||||
linux:
|
||||
when:
|
||||
- OS.Family == "linux"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/linux
|
||||
datasource:
|
||||
source: file
|
||||
labels:
|
||||
type: syslog
|
||||
filenames:
|
||||
- /var/log/syslog
|
||||
- /var/log/kern.log
|
||||
- /var/log/messages
|
||||
|
||||
freebsd:
|
||||
when:
|
||||
- OS.Family == "freebsd"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/freebsd
|
||||
|
||||
windows:
|
||||
when:
|
||||
- OS.Family == "windows"
|
||||
install:
|
||||
collections:
|
||||
- crowdsecurity/windows
|
|
@ -63,6 +63,9 @@ config_generate() {
|
|||
|
||||
cp ../config/context.yaml "${CONFIG_DIR}/console/"
|
||||
|
||||
cp ../config/detect.yaml \
|
||||
"${HUB_DIR}"
|
||||
|
||||
# the default acquis file contains files that are not readable by everyone
|
||||
touch "$LOG_DIR/empty.log"
|
||||
cat <<-EOT >"$CONFIG_DIR/acquis.yaml"
|
||||
|
|
Loading…
Reference in a new issue