cscli: restyle table titles; autocomplete "cscli metrics show" (#3391)

* cscli: restyle table titles; autocomplete "cscli metrics show"
* lint
This commit is contained in:
mmetc 2025-01-02 16:58:03 +01:00 committed by GitHub
parent 90f7c56aab
commit 5c7b957a34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 185 additions and 130 deletions

View file

@ -86,7 +86,7 @@ func alertDecisionsTable(out io.Writer, wantColor string, alert *models.Alert) {
}
if foundActive {
fmt.Printf(" - Active Decisions :\n")
t.Writer.SetTitle("Active Decisions")
t.Render() // Send output
}
}

View file

@ -47,7 +47,7 @@ func (cli *cliBouncers) inspectHuman(out io.Writer, bouncer *ent.Bouncer) {
t.AppendRow(table.Row{"Feature Flags", ff})
}
io.WriteString(out, t.Render()+"\n")
fmt.Fprint(out, t.Render())
}
func (cli *cliBouncers) inspect(bouncer *ent.Bouncer) error {

View file

@ -37,7 +37,7 @@ func (cli *cliBouncers) listHuman(out io.Writer, bouncers ent.Bouncers) {
t.AppendRow(table.Row{b.Name, b.IPAddress, revoked, lastPull, b.Type, b.Version, b.AuthType})
}
io.WriteString(out, t.Render()+"\n")
fmt.Fprintln(out, t.Render())
}
func (cli *cliBouncers) listCSV(out io.Writer, bouncers ent.Bouncers) error {
@ -71,7 +71,6 @@ func (cli *cliBouncers) listCSV(out io.Writer, bouncers ent.Bouncers) error {
func (cli *cliBouncers) List(ctx context.Context, out io.Writer, db *database.Client) error {
// XXX: must use the provided db object, the one in the struct might be nil
// (calling List directly skips the PersistentPreRunE)
bouncers, err := db.ListBouncers(ctx)
if err != nil {
return fmt.Errorf("unable to list bouncers: %w", err)

View file

@ -105,7 +105,7 @@ func ListItems(out io.Writer, wantColor string, itemTypes []string, items map[st
return fmt.Errorf("failed to parse: %w", err)
}
out.Write(x)
fmt.Fprint(out, string(x))
case "raw":
csvwriter := csv.NewWriter(out)

View file

@ -20,6 +20,6 @@ func listHubItemTable(out io.Writer, wantColor string, title string, items []*cw
t.AppendRow(table.Row{item.Name, status, item.State.LocalVersion, item.State.LocalPath})
}
io.WriteString(out, title+"\n")
io.WriteString(out, t.Render()+"\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}

View file

@ -1,8 +1,8 @@
package cliitem
import (
"fmt"
"encoding/json"
"fmt"
"os"
"path/filepath"

View file

@ -10,7 +10,6 @@ import (
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cstable"
)
func appsecMetricsTable(out io.Writer, wantColor string, itemName string, metrics map[string]int) {
t := cstable.NewLight(out, wantColor).Writer
t.AppendHeader(table.Row{"Inband Hits", "Outband Hits"})
@ -20,8 +19,8 @@ func appsecMetricsTable(out io.Writer, wantColor string, itemName string, metric
strconv.Itoa(metrics["outband_hits"]),
})
io.WriteString(out, fmt.Sprintf("\n - (AppSec Rule) %s:\n", itemName))
io.WriteString(out, t.Render()+"\n")
t.SetTitle("(AppSec) " + itemName)
fmt.Fprintln(out, t.Render())
}
func scenarioMetricsTable(out io.Writer, wantColor string, itemName string, metrics map[string]int) {
@ -40,8 +39,8 @@ func scenarioMetricsTable(out io.Writer, wantColor string, itemName string, metr
strconv.Itoa(metrics["underflow"]),
})
io.WriteString(out, fmt.Sprintf("\n - (Scenario) %s:\n", itemName))
io.WriteString(out, t.Render()+"\n")
t.SetTitle("(Scenario) " + itemName)
fmt.Fprintln(out, t.Render())
}
func parserMetricsTable(out io.Writer, wantColor string, itemName string, metrics map[string]map[string]int) {
@ -65,7 +64,7 @@ func parserMetricsTable(out io.Writer, wantColor string, itemName string, metric
}
if showTable {
io.WriteString(out, fmt.Sprintf("\n - (Parser) %s:\n", itemName))
io.WriteString(out, t.Render()+"\n")
t.SetTitle("(Parser) " + itemName)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -44,7 +44,7 @@ func (cli *cliMachines) inspectHubHuman(out io.Writer, machine *ent.Machine) {
t.AppendHeader(table.Row{"Name", "Status", "Version"})
t.SetTitle(itemType)
t.AppendRows(rows)
io.WriteString(out, t.Render()+"\n")
fmt.Fprintln(out, t.Render())
}
}
@ -80,7 +80,7 @@ func (cli *cliMachines) inspectHuman(out io.Writer, machine *ent.Machine) {
t.AppendRow(table.Row{"Collections", coll.Name})
}
io.WriteString(out, t.Render()+"\n")
fmt.Fprintln(out, t.Render())
}
func (cli *cliMachines) inspect(machine *ent.Machine) error {

View file

@ -55,7 +55,7 @@ func (cli *cliMachines) listHuman(out io.Writer, machines ent.Machines) {
t.AppendRow(table.Row{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, clientinfo.GetOSNameAndVersion(m), m.AuthType, hb})
}
io.WriteString(out, t.Render()+"\n")
fmt.Fprintln(out, t.Render())
}
func (cli *cliMachines) listCSV(out io.Writer, machines ent.Machines) error {
@ -90,7 +90,6 @@ func (cli *cliMachines) listCSV(out io.Writer, machines ent.Machines) error {
func (cli *cliMachines) List(ctx context.Context, out io.Writer, db *database.Client) error {
// XXX: must use the provided db object, the one in the struct might be nil
// (calling List directly skips the PersistentPreRunE)
machines, err := db.ListMachines(ctx)
if err != nil {
return fmt.Errorf("unable to list machines: %w", err)

View file

@ -3,7 +3,6 @@ package climetrics
import (
"encoding/json"
"fmt"
"io"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
@ -64,7 +63,7 @@ func (cli *cliMetrics) list() error {
t.AppendRow(table.Row{metric.Type, metric.Title, metric.Description})
}
io.WriteString(out, t.Render()+"\n")
fmt.Fprintln(out, t.Render())
case "json":
x, err := json.MarshalIndent(allMetrics, "", " ")
if err != nil {

View file

@ -4,11 +4,15 @@ import (
"context"
"errors"
"fmt"
"slices"
"strings"
"github.com/fatih/color"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/go-cs-lib/maptools"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
)
@ -99,6 +103,17 @@ cscli metrics list; cscli metrics list -o json
cscli metrics show acquisition parsers scenarios stash -o json`,
// Positional args are optional
DisableAutoGenTag: true,
ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ms := NewMetricStore()
ret := []string{}
for _, section := range maptools.SortedKeys(ms) {
if !slices.Contains(args, section) && strings.Contains(section, toComplete) {
ret = append(ret, section)
}
}
return ret, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
args = expandAlias(args)
return cli.show(cmd.Context(), args, url, noUnit)

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -37,8 +38,7 @@ func (s statAcquis) Table(out io.Writer, wantColor string, noUnit bool, showEmpt
log.Warningf("while collecting acquis stats: %s", err)
} else if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"strconv"
@ -38,8 +39,7 @@ func (s statAlert) Table(out io.Writer, wantColor string, noUnit bool, showEmpty
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -34,8 +35,7 @@ func (s statAppsecEngine) Table(out io.Writer, wantColor string, noUnit bool, sh
log.Warningf("while collecting appsec stats: %s", err)
} else if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -40,9 +40,8 @@ func (s statAppsecRule) Table(out io.Writer, wantColor string, noUnit bool, show
if numRows, err := metricsToTable(t, appsecEngineRulesStats, keys, noUnit); err != nil {
log.Warningf("while collecting appsec rules stats: %s", err)
} else if numRows > 0 || showEmpty {
io.WriteString(out, fmt.Sprintf("Appsec '%s' Rules Metrics:\n", appsecEngine))
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(fmt.Sprintf("Appsec '%s' Rules Metrics", appsecEngine))
fmt.Fprintln(out, t.Render())
}
}
}

View file

@ -176,17 +176,20 @@ func (*statBouncer) extractRawMetrics(metrics []*ent.Metric) ([]bouncerMetricIte
if item.Name == nil {
logWarningOnce(warningsLogged, "missing 'name' field in metrics reported by "+bouncerName)
// no continue - keep checking the rest
valid = false
}
if item.Unit == nil {
logWarningOnce(warningsLogged, "missing 'unit' field in metrics reported by "+bouncerName)
valid = false
}
if item.Value == nil {
logWarningOnce(warningsLogged, "missing 'value' field in metrics reported by "+bouncerName)
valid = false
}
@ -439,11 +442,8 @@ func (s *statBouncer) bouncerTable(out io.Writer, bouncerName string, wantColor
title = fmt.Sprintf("%s since %s", title, s.oldestTS[bouncerName].String())
}
// don't use SetTitle() because it draws the title inside table box
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
// empty line between tables
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
// Table displays a table of metrics for each bouncer
@ -452,10 +452,11 @@ func (s *statBouncer) Table(out io.Writer, wantColor string, noUnit bool, showEm
for _, bouncerName := range maptools.SortedKeys(s.aggOverOrigin) {
s.bouncerTable(out, bouncerName, wantColor, noUnit)
found = true
}
if !found && showEmpty {
io.WriteString(out, "No bouncer metrics found.\n\n")
fmt.Fprintln(out, "No bouncer metrics found.")
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -35,8 +36,7 @@ func (s statBucket) Table(out io.Writer, wantColor string, noUnit bool, showEmpt
log.Warningf("while collecting scenario stats: %s", err)
} else if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"strconv"
@ -53,8 +54,7 @@ func (s statDecision) Table(out io.Writer, wantColor string, noUnit bool, showEm
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"strconv"
@ -49,8 +50,7 @@ func (s statLapi) Table(out io.Writer, wantColor string, noUnit bool, showEmpty
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -35,8 +36,7 @@ func (s statLapiBouncer) Table(out io.Writer, wantColor string, noUnit bool, sho
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"strconv"
@ -57,8 +58,7 @@ func (s statLapiDecision) Table(out io.Writer, wantColor string, noUnit bool, sh
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -35,8 +36,7 @@ func (s statLapiMachine) Table(out io.Writer, wantColor string, noUnit bool, sho
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -36,8 +37,7 @@ func (s statParser) Table(out io.Writer, wantColor string, noUnit bool, showEmpt
log.Warningf("while collecting parsers stats: %s", err)
} else if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"strconv"
@ -52,8 +53,7 @@ func (s statStash) Table(out io.Writer, wantColor string, noUnit bool, showEmpty
if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -1,6 +1,7 @@
package climetrics
import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
@ -36,8 +37,7 @@ func (s statWhitelist) Table(out io.Writer, wantColor string, noUnit bool, showE
log.Warningf("while collecting parsers stats: %s", err)
} else if numRows > 0 || showEmpty {
title, _ := s.Description()
io.WriteString(out, title+":\n")
io.WriteString(out, t.Render()+"\n")
io.WriteString(out, "\n")
t.SetTitle(title)
fmt.Fprintln(out, t.Render())
}
}

View file

@ -262,7 +262,8 @@ func (ms metricStore) Format(out io.Writer, wantColor string, sections []string,
if err != nil {
return fmt.Errorf("failed to serialize metrics: %w", err)
}
out.Write(x)
fmt.Fprint(out, string(x))
default:
return fmt.Errorf("output format '%s' not supported for this command", outputFormat)
}

View file

@ -3,13 +3,15 @@ package appsecacquisition
import (
"testing"
"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
)
func TestAppsecRuleLoad(t *testing.T) {
log.SetLevel(log.TraceLevel)
tests := []appsecRuleTest{
{
name: "simple rule load",
@ -105,21 +107,22 @@ func TestAppsecRuleLoad(t *testing.T) {
Or: []appsec_rule.CustomRule{
{
//Name: "rule1",
// Name: "rule1",
Zones: []string{"ARGS"},
Match: appsec_rule.Match{Type: "equals", Value: "toto"},
},
{
//Name: "rule1",
// Name: "rule1",
Zones: []string{"ARGS"},
Match: appsec_rule.Match{Type: "equals", Value: "tutu"},
},
{
//Name: "rule1",
// Name: "rule1",
Zones: []string{"ARGS"},
Match: appsec_rule.Match{Type: "equals", Value: "tata"},
}, {
//Name: "rule1",
},
{
// Name: "rule1",
Zones: []string{"ARGS"},
Match: appsec_rule.Match{Type: "equals", Value: "titi"},
},

View file

@ -16,7 +16,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"gopkg.in/tomb.v2"
"gopkg.in/yaml.v3"
@ -36,8 +35,8 @@ var linesRead = prometheus.NewCounterVec(
[]string{"path", "src"})
type HttpConfiguration struct {
//IPFilter []string `yaml:"ip_filter"`
//ChunkSize *int64 `yaml:"chunk_size"`
// IPFilter []string `yaml:"ip_filter"`
// ChunkSize *int64 `yaml:"chunk_size"`
ListenAddr string `yaml:"listen_addr"`
Path string `yaml:"path"`
AuthType string `yaml:"auth_type"`
@ -76,6 +75,7 @@ func (h *HTTPSource) GetUuid() string {
func (h *HTTPSource) UnmarshalConfig(yamlConfig []byte) error {
h.Config = HttpConfiguration{}
err := yaml.Unmarshal(yamlConfig, &h.Config)
if err != nil {
return fmt.Errorf("cannot parse %s datasource configuration: %w", dataSourceName, err)
@ -96,6 +96,7 @@ func (hc *HttpConfiguration) Validate() error {
if hc.Path == "" {
hc.Path = "/"
}
if hc.Path[0] != '/' {
return errors.New("path must start with /")
}
@ -106,9 +107,11 @@ func (hc *HttpConfiguration) Validate() error {
if hc.BasicAuth == nil {
return errors.New(baseErr + " basic_auth is not provided")
}
if hc.BasicAuth.Username == "" {
return errors.New(baseErr + " username is not provided")
}
if hc.BasicAuth.Password == "" {
return errors.New(baseErr + " password is not provided")
}
@ -128,6 +131,7 @@ func (hc *HttpConfiguration) Validate() error {
if hc.TLS.ServerCert == "" {
return errors.New("server_cert is required")
}
if hc.TLS.ServerKey == "" {
return errors.New("server_key is required")
}
@ -156,6 +160,7 @@ func (hc *HttpConfiguration) Validate() error {
func (h *HTTPSource) Configure(yamlConfig []byte, logger *log.Entry, MetricsLevel int) error {
h.logger = logger
h.metricsLevel = MetricsLevel
err := h.UnmarshalConfig(yamlConfig)
if err != nil {
return err
@ -210,6 +215,7 @@ func (hc *HttpConfiguration) NewTLSConfig() (*tls.Config, error) {
if err != nil {
return nil, fmt.Errorf("failed to load server cert/key: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
@ -227,6 +233,7 @@ func (hc *HttpConfiguration) NewTLSConfig() (*tls.Config, error) {
if caCertPool == nil {
caCertPool = x509.NewCertPool()
}
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.ClientCAs = caCertPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
@ -241,10 +248,12 @@ func authorizeRequest(r *http.Request, hc *HttpConfiguration) error {
if !ok {
return errors.New("missing basic auth")
}
if username != hc.BasicAuth.Username || password != hc.BasicAuth.Password {
return errors.New("invalid basic auth")
}
}
if hc.AuthType == "headers" {
for key, value := range *hc.Headers {
if r.Header.Get(key) != value {
@ -252,6 +261,7 @@ func authorizeRequest(r *http.Request, hc *HttpConfiguration) error {
}
}
}
return nil
}
@ -280,6 +290,7 @@ func (h *HTTPSource) processRequest(w http.ResponseWriter, r *http.Request, hc *
}
decoder := json.NewDecoder(reader)
for {
var message json.RawMessage
@ -287,7 +298,9 @@ func (h *HTTPSource) processRequest(w http.ResponseWriter, r *http.Request, hc *
if err == io.EOF {
break
}
w.WriteHeader(http.StatusBadRequest)
return fmt.Errorf("failed to decode: %w", err)
}
@ -328,11 +341,13 @@ func (h *HTTPSource) RunServer(out chan types.Event, t *tomb.Tomb) error {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
if err := authorizeRequest(r, &h.Config); err != nil {
h.logger.Errorf("failed to authorize request from '%s': %s", r.RemoteAddr, err)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
err := h.processRequest(w, r, &h.Config, out)
if err != nil {
h.logger.Errorf("failed to process request from '%s': %s", r.RemoteAddr, err)
@ -344,6 +359,7 @@ func (h *HTTPSource) RunServer(out chan types.Event, t *tomb.Tomb) error {
w.Header().Set(key, value)
}
}
if h.Config.CustomStatusCode != nil {
w.WriteHeader(*h.Config.CustomStatusCode)
} else {
@ -367,25 +383,30 @@ func (h *HTTPSource) RunServer(out chan types.Event, t *tomb.Tomb) error {
if err != nil {
return fmt.Errorf("failed to create tls config: %w", err)
}
h.logger.Tracef("tls config: %+v", tlsConfig)
h.Server.TLSConfig = tlsConfig
}
t.Go(func() error {
defer trace.CatchPanic("crowdsec/acquis/http/server")
if h.Config.TLS != nil {
h.logger.Infof("start https server on %s", h.Config.ListenAddr)
err := h.Server.ListenAndServeTLS(h.Config.TLS.ServerCert, h.Config.TLS.ServerKey)
if err != nil && err != http.ErrServerClosed {
return fmt.Errorf("https server failed: %w", err)
}
} else {
h.logger.Infof("start http server on %s", h.Config.ListenAddr)
err := h.Server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
return fmt.Errorf("http server failed: %w", err)
}
}
return nil
})

View file

@ -14,13 +14,15 @@ import (
"testing"
"time"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/tomb.v2"
"github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
const (
@ -241,7 +243,7 @@ func SetupAndRunHTTPSource(t *testing.T, h *HTTPSource, config []byte, metricLev
func TestStreamingAcquisitionWrongHTTPMethod(t *testing.T) {
h := &HTTPSource{}
_, _, tomb:= SetupAndRunHTTPSource(t, h, []byte(`
_, _, tomb := SetupAndRunHTTPSource(t, h, []byte(`
source: http
listen_addr: 127.0.0.1:8080
path: /test

View file

@ -8,9 +8,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/crowdsecurity/go-cs-lib/ptr"
)
func TestNewAlertContext(t *testing.T) {
@ -229,6 +230,7 @@ func TestValidateContextExpr(t *testing.T) {
}
for _, test := range tests {
fmt.Printf("Running test '%s'\n", test.name)
err := ValidateContextExpr(test.key, test.exprs)
if test.expectedErr == nil {
require.NoError(t, err)
@ -348,13 +350,13 @@ func TestAppsecEventToContext(t *testing.T) {
}
for _, test := range tests {
//reset cache
// reset cache
alertContext = Context{}
//compile
// compile
if err := NewAlertContext(test.contextToSend, 100); err != nil {
t.Fatalf("failed to compile %s: %s", test.name, err)
}
//run
// run
metas, errors := AppsecEventToContext(test.match, test.req)
assert.Len(t, errors, test.expectedErrLen)

View file

@ -114,37 +114,38 @@ type Dependencies struct {
// a group of items of the same type
type itemgroup struct {
typeName string
itemNames []string
typeName string
itemNames []string
}
func (d Dependencies) byType() []itemgroup {
return []itemgroup{
{PARSERS, d.Parsers},
{POSTOVERFLOWS, d.PostOverflows},
{SCENARIOS, d.Scenarios},
{CONTEXTS, d.Contexts},
{APPSEC_CONFIGS, d.AppsecConfigs},
{APPSEC_RULES, d.AppsecRules},
{COLLECTIONS, d.Collections},
}
return []itemgroup{
{PARSERS, d.Parsers},
{POSTOVERFLOWS, d.PostOverflows},
{SCENARIOS, d.Scenarios},
{CONTEXTS, d.Contexts},
{APPSEC_CONFIGS, d.AppsecConfigs},
{APPSEC_RULES, d.AppsecRules},
{COLLECTIONS, d.Collections},
}
}
// SubItems iterates over the sub-items in the struct, excluding the ones that were not found in the hub.
func (d Dependencies) SubItems(hub *Hub) func(func(*Item) bool) {
return func(yield func(*Item) bool) {
for _, typeGroup := range d.byType() {
for _, name := range typeGroup.itemNames {
s := hub.GetItem(typeGroup.typeName, name)
if s == nil {
continue
}
if !yield(s) {
return
}
}
}
}
return func(yield func(*Item) bool) {
for _, typeGroup := range d.byType() {
for _, name := range typeGroup.itemNames {
s := hub.GetItem(typeGroup.typeName, name)
if s == nil {
continue
}
if !yield(s) {
return
}
}
}
}
}
// Item is created from an index file and enriched with local info.
@ -272,6 +273,7 @@ func (i *Item) CurrentDependencies() Dependencies {
if errors.Is(err, fs.ErrNotExist) {
return i.Dependencies
}
if err != nil {
// a file might be corrupted, or in development
i.hub.logger.Warningf("can't read dependencies for %s, using index", i.FQName())
@ -285,11 +287,10 @@ func (i *Item) CurrentDependencies() Dependencies {
i.hub.logger.Warningf("can't parse dependencies for %s, using index", i.FQName())
return i.Dependencies
}
return d
}
func (i *Item) logMissingSubItems() {
if !i.HasSubItems() {
return
@ -405,7 +406,6 @@ func (i *Item) SafeToRemoveDeps() ([]*Item, error) {
return ret, nil
}
// descendants returns a list of all (direct or indirect) dependencies of the item's current version.
func (i *Item) descendants() ([]*Item, error) {
var collectSubItems func(item *Item, visited map[*Item]bool, result *[]*Item) error

View file

@ -8,8 +8,8 @@ import (
"strings"
"github.com/AlecAivazis/survey/v2"
isatty "github.com/mattn/go-isatty"
"github.com/fatih/color"
isatty "github.com/mattn/go-isatty"
"github.com/crowdsecurity/go-cs-lib/slicetools"
@ -59,13 +59,13 @@ func UniqueKey(c Command) string {
type ActionPlan struct {
// hold the list of Commands to be executed as part of the action plan.
// If a command is skipped (i.e. calling Prepare() returned false), it won't be included in the slice.
commands []Command
commands []Command
// Tracks unique commands
commandsTracker map[string]struct{}
// A reference to the Hub instance, required for dependency lookup.
hub *cwhub.Hub
hub *cwhub.Hub
// Indicates whether a reload of the CrowdSec service is required after executing the action plan.
ReloadNeeded bool
@ -73,7 +73,7 @@ type ActionPlan struct {
func NewActionPlan(hub *cwhub.Hub) *ActionPlan {
return &ActionPlan{
hub: hub,
hub: hub,
commandsTracker: make(map[string]struct{}),
}
}

View file

@ -300,7 +300,7 @@ func (t *HubTestItem) RunWithNucleiTemplate() error {
crowdsecDaemon.Start()
// wait for the appsec port to be available
if _, err := IsAlive(t.AppSecHost); err != nil {
if _, err = IsAlive(t.AppSecHost); err != nil {
crowdsecLog, err2 := os.ReadFile(crowdsecLogFile)
if err2 != nil {
log.Errorf("unable to read crowdsec log file '%s': %s", crowdsecLogFile, err)
@ -319,7 +319,7 @@ func (t *HubTestItem) RunWithNucleiTemplate() error {
}
nucleiTargetHost := nucleiTargetParsedURL.Host
if _, err := IsAlive(nucleiTargetHost); err != nil {
if _, err = IsAlive(nucleiTargetHost); err != nil {
return fmt.Errorf("target is down: %w", err)
}
@ -631,8 +631,11 @@ func (t *HubTestItem) Run(patternDir string) error {
if t.Config.LogFile != "" {
return t.RunWithLogFile(patternDir)
} else if t.Config.NucleiTemplate != "" {
}
if t.Config.NucleiTemplate != "" {
return t.RunWithNucleiTemplate()
}
return fmt.Errorf("log file or nuclei template must be set in '%s'", t.Name)
}

View file

@ -76,5 +76,5 @@ teardown() {
rune -0 cscli metrics
assert_output --partial "Route"
assert_output --partial '/v1/watchers/login'
assert_output --partial "Local API Metrics:"
assert_output --partial "Local API Metrics"
}

View file

@ -66,7 +66,7 @@ teardown() {
rune -0 cscli metrics
assert_output --partial "Route"
assert_output --partial '/v1/watchers/login'
assert_output --partial "Local API Metrics:"
assert_output --partial "Local API Metrics"
rune -0 cscli metrics -o json
rune -0 jq 'keys' <(output)
@ -93,7 +93,7 @@ teardown() {
assert_stderr --partial "unknown metrics type: foobar"
rune -0 cscli metrics show lapi
assert_output --partial "Local API Metrics:"
assert_output --partial "Local API Metrics"
assert_output --regexp "Route.*Method.*Hits"
assert_output --regexp "/v1/watchers/login.*POST"

View file

@ -136,7 +136,10 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (testbouncer) since 2024-02-08 13:35:16 +0000 UTC:
+--------------------------+
| Bouncer Metrics (testbou |
| ncer) since 2024-02-08 1 |
| 3:35:16 +0000 UTC |
+--------+-----------------+
| Origin | foo |
| | dogyear | pound |
@ -226,7 +229,8 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (testbouncer) since 2024-02-08 13:35:16 +0000 UTC:
+-------------------------------------------------------------------------------------------+
| Bouncer Metrics (testbouncer) since 2024-02-08 13:35:16 +0000 UTC |
+----------------------------------+------------------+-------------------+-----------------+
| Origin | active_decisions | dropped | foo |
| | IPs | bytes | packets | dogyear | pound |
@ -309,7 +313,8 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (testbouncer) since 2024-02-08 13:35:16 +0000 UTC:
+-------------------------------------------------------------------------------------------+
| Bouncer Metrics (testbouncer) since 2024-02-08 13:35:16 +0000 UTC |
+----------------------------------+------------------+-------------------+-----------------+
| Origin | active_decisions | dropped | foo |
| | IPs | bytes | packets | dogyear | pound |
@ -365,7 +370,9 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (testbouncer) since 2024-02-09 03:40:00 +0000 UTC:
+-----------------------------------------------+
| Bouncer Metrics (testbouncer) since 2024-02-0 |
| 9 03:40:00 +0000 UTC |
+--------------------------+--------+-----------+
| Origin | ima | notagauge |
| | second | inch |
@ -417,7 +424,9 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (testbouncer) since 2024-02-09 03:40:00 +0000 UTC:
+---------------------------------------------+
| Bouncer Metrics (testbouncer) since 2024-02 |
| -09 03:40:00 +0000 UTC |
+--------------------------+------------------+
| Origin | active_decisions |
| | IPs |
@ -502,7 +511,9 @@ teardown() {
rune -0 cscli metrics show bouncers
assert_output - <<-EOT
Bouncer Metrics (bouncer1) since 2024-02-08 13:35:16 +0000 UTC:
+--------------------------------------------------------------+
| Bouncer Metrics (bouncer1) since 2024-02-08 13:35:16 +0000 U |
| TC |
+----------------------------+---------+-----------------------+
| Origin | dropped | processed |
| | bytes | bytes | packets |
@ -512,8 +523,9 @@ teardown() {
+----------------------------+---------+-----------+-----------+
| Total | 1.80k | 12.34k | 100 |
+----------------------------+---------+-----------+-----------+
Bouncer Metrics (bouncer2) since 2024-02-08 10:48:36 +0000 UTC:
+------------------------------------------------+
| Bouncer Metrics (bouncer2) since 2024-02-08 10 |
| :48:36 +0000 UTC |
+----------------------------+-------------------+
| Origin | dropped |
| | bytes | packets |

View file

@ -89,7 +89,7 @@ teardown() {
assert_line --regexp "^ - AS *: *$"
assert_line --regexp "^ - Begin *: .*$"
assert_line --regexp "^ - End *: .*$"
assert_line --regexp "^ - Active Decisions *:$"
assert_line --regexp "^\| Active Decisions *\|$"
assert_line --regexp "^.* ID .* scope:value .* action .* expiration .* created_at .*$"
assert_line --regexp "^.* Ip:10.20.30.40 .* ban .*$"

View file

@ -80,7 +80,7 @@ teardown() {
# the list should be the same in all formats, and sorted (not case sensitive)
list_raw=$(cscli parsers list -o raw -a | tail -n +2 | cut -d, -f1)
list_human=$(cscli parsers list -o human -a | tail -n +6 | head -n -1 | cut -d' ' -f2)
list_human=$(cscli parsers list -o human -a | tail -n +7 | head -n -1 | cut -d' ' -f2)
list_json=$(cscli parsers list -o json -a | jq -r '.parsers[].name')
# use python to sort because it handles "_" like go