279 lines
7 KiB
Go
279 lines
7 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type detect struct {
|
|
LogFiles []string `json:"log_files"`
|
|
JournalCTL []string `json:"journalctl"`
|
|
ServiceName []string `json:"services_name"`
|
|
Collections []string `json:"collections"`
|
|
Parsers []string `json:"parsers"`
|
|
Scenarios []string `json:"scenarios"`
|
|
PostOverflows []string `json:"postoverflows"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type State struct {
|
|
Service string
|
|
Collections []string
|
|
Parsers []string
|
|
Scenarios []string
|
|
PostOverflows []string
|
|
Type string
|
|
LogsPath []string
|
|
JournalCTLFilters []string
|
|
ServicesName []string // for windows
|
|
}
|
|
|
|
type detectionManager struct {
|
|
Filepath string
|
|
OS map[string]*detect `json:"os"`
|
|
Services map[string]*detect `json:"services"`
|
|
State map[string]*State
|
|
}
|
|
|
|
// 1 month ago
|
|
var fileOldLimit = time.Now().AddDate(0, -1, 0)
|
|
|
|
const journalCTlOldLimit = "1 month ago"
|
|
const journalctlCmd = "journalctl"
|
|
|
|
func NewServiceDetect(filepath string) (*detectionManager, error) {
|
|
sd := &detectionManager{Filepath: filepath, State: make(map[string]*State)}
|
|
jsonFile, err := ioutil.ReadFile(filepath)
|
|
if err != nil {
|
|
return sd, err
|
|
}
|
|
err = json.Unmarshal(jsonFile, sd)
|
|
return sd, err
|
|
}
|
|
|
|
func isFileOlder(t time.Time, limit time.Time) bool {
|
|
return t.Before(limit)
|
|
}
|
|
|
|
func detectService(serviceName string, detection *detect) (*State, bool, error) {
|
|
detected := false
|
|
state := &State{
|
|
Service: serviceName,
|
|
Type: detection.Type,
|
|
LogsPath: make([]string, 0),
|
|
JournalCTLFilters: make([]string, 0),
|
|
Collections: detection.Collections,
|
|
Parsers: detection.Parsers,
|
|
Scenarios: detection.Scenarios,
|
|
PostOverflows: detection.PostOverflows,
|
|
}
|
|
|
|
for _, filePath := range detection.LogFiles {
|
|
files, err := filepath.Glob(filePath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(files) == 0 {
|
|
log.Debugf("No matching files for pattern %s", filePath)
|
|
continue
|
|
}
|
|
|
|
// check if at least one file is younger than 1 month
|
|
ok := false
|
|
for _, file := range files {
|
|
fileInfo, err := os.Stat(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if isFileOlder(fileInfo.ModTime(), fileOldLimit) {
|
|
continue
|
|
}
|
|
ok = true
|
|
break
|
|
}
|
|
if ok {
|
|
detected = true
|
|
state.LogsPath = append(state.LogsPath, filePath)
|
|
}
|
|
}
|
|
|
|
for _, journalCTLFilter := range detection.JournalCTL {
|
|
args := []string{journalCTLFilter, "--since", journalCTlOldLimit}
|
|
cmd := exec.CommandContext(context.Background(), journalctlCmd, args...)
|
|
stdout, err := cmd.Output()
|
|
if err != nil {
|
|
log.Errorf("checking journalctl filter '%s' return error: %s", journalCTLFilter, err)
|
|
continue
|
|
}
|
|
scanner := bufio.NewScanner(bytes.NewReader(stdout))
|
|
ok := true
|
|
for scanner.Scan() {
|
|
if strings.Contains(scanner.Text(), "No entries") {
|
|
log.Debugf("no entries since '%s' in journalctl '%s' filter, continue", journalCTlOldLimit, journalCTLFilter)
|
|
ok = false
|
|
break
|
|
}
|
|
}
|
|
if ok {
|
|
detected = true
|
|
state.JournalCTLFilters = append(state.JournalCTLFilters, journalCTLFilter)
|
|
}
|
|
}
|
|
|
|
return state, detected, nil
|
|
}
|
|
|
|
func (d *detectionManager) Detect() error {
|
|
var err error
|
|
|
|
log.Debugf("Detected OS: %s", runtime.GOOS)
|
|
err = d.DetectOS(runtime.GOOS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for serviceName := range d.Services {
|
|
err = d.DetectService(serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *detectionManager) Apply(config *csconfig.CrowdsecServiceCfg) error {
|
|
for serviceName, service := range d.State {
|
|
log.Printf("service: %s|%+v", serviceName, service)
|
|
// check if service already configured
|
|
dataSourceExist := false
|
|
for _, dataSource := range dataSources {
|
|
for logFile := range service.LogsPath {
|
|
for filenames := range dataSource.
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *detectionManager) DetectOS(osName string) error {
|
|
var ok bool
|
|
var err error
|
|
var detectedOS *detect
|
|
|
|
if detectedOS, ok = d.OS[osName]; !ok {
|
|
return fmt.Errorf("os '%s' not supported", osName)
|
|
}
|
|
state, detected, err := detectService(osName, detectedOS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if detected {
|
|
d.State[osName] = state
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *detectionManager) Print() {
|
|
for serviceName, service := range d.State {
|
|
fmt.Printf("%s detected\n", strings.Title(serviceName))
|
|
if len(service.LogsPath) > 0 {
|
|
fmt.Printf(" * logs files found:\n")
|
|
for _, logPath := range service.LogsPath {
|
|
fmt.Printf(" - %s\n", logPath)
|
|
}
|
|
}
|
|
if len(service.JournalCTLFilters) > 0 {
|
|
fmt.Printf(" * journalctl found:\n")
|
|
for _, journalctl := range service.JournalCTLFilters {
|
|
fmt.Printf(" - %s\n", journalctl)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *detectionManager) DetectService(serviceName string) error {
|
|
var ok bool
|
|
var err error
|
|
var service *detect
|
|
|
|
if service, ok = d.Services[serviceName]; !ok {
|
|
return fmt.Errorf("service '%s' not found or can't be detected", service)
|
|
}
|
|
|
|
state, detected, err := detectService(serviceName, service)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if detected {
|
|
d.State[serviceName] = state
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewWizardCmd() *cobra.Command {
|
|
var cmdWizard = &cobra.Command{
|
|
Use: "wizard [action]",
|
|
Short: "Help to configure crowdsec",
|
|
Args: cobra.MinimumNArgs(1),
|
|
DisableAutoGenTag: true,
|
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
|
if err := csConfig.LoadCrowdsec(); err != nil || csConfig.DisableAgent {
|
|
log.Fatal("CrowdSec agent is not configured or disabled, can't run the wizard")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var apply bool
|
|
var reconfigure bool
|
|
var serviceDetectionFile string
|
|
var service string
|
|
|
|
var cmdWizardDetect = &cobra.Command{
|
|
Use: "detect",
|
|
Short: "Detect running services, generate acquisitions and install collections",
|
|
DisableAutoGenTag: true,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
serviceDetector, err := NewServiceDetect(serviceDetectionFile)
|
|
if err != nil {
|
|
log.Fatalf("unable to load detection services: %s", err)
|
|
}
|
|
|
|
if err := serviceDetector.Detect(); err != nil {
|
|
log.Fatalf("unable to detect services: %s", err)
|
|
}
|
|
serviceDetector.Print()
|
|
if apply {
|
|
if err := serviceDetector.Apply(csConfig.Crowdsec); err != nil {
|
|
log.Fatalf("unable to apply configuration: %s", err)
|
|
}
|
|
}
|
|
|
|
},
|
|
}
|
|
cmdWizardDetect.Flags().StringVarP(&serviceDetectionFile, "file", "f", "/etc/crowdsec/service_detection.json", "File to detect services")
|
|
cmdWizardDetect.Flags().BoolVarP(&apply, "apply", "a", false, "Apply the detection")
|
|
cmdWizardDetect.Flags().StringVarP(&service, "service", "s", "", "Service to detect")
|
|
cmdWizardDetect.Flags().BoolVarP(&reconfigure, "reconfigure", "r", false, "Reconfigure all the acquisitions/collections")
|
|
cmdWizard.AddCommand(cmdWizardDetect)
|
|
|
|
return cmdWizard
|
|
}
|