diff --git a/cmd/crowdsec-cli/clihub/hub.go b/cmd/crowdsec-cli/clihub/hub.go index 36e851d1b74..0d1e625f715 100644 --- a/cmd/crowdsec-cli/clihub/hub.go +++ b/cmd/crowdsec-cli/clihub/hub.go @@ -213,7 +213,11 @@ func (cli *cliHub) newUpgradeCmd() *cobra.Command { Long: ` Upgrade all configs installed from Crowdsec Hub. Run 'sudo cscli hub update' if you want the latest versions available. `, - // TODO: Example + Example: `# Upgrade all the collections, scenarios etc. to the latest version in the downloaded index. Update data files too. +cscli hub upgrade + +# Upgrade tainted items as well; force re-download of data files. +cscli hub upgrade --force`, Args: cobra.NoArgs, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, _ []string) error { diff --git a/cmd/crowdsec-cli/cliitem/appsec.go b/cmd/crowdsec-cli/cliitem/appsec.go deleted file mode 100644 index 44afa2133bd..00000000000 --- a/cmd/crowdsec-cli/cliitem/appsec.go +++ /dev/null @@ -1,123 +0,0 @@ -package cliitem - -import ( - "fmt" - "os" - - "golang.org/x/text/cases" - "golang.org/x/text/language" - "gopkg.in/yaml.v3" - - "github.com/crowdsecurity/crowdsec/pkg/appsec" - "github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule" - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func NewAppsecConfig(cfg configGetter) *cliItem { - return &cliItem{ - cfg: cfg, - name: cwhub.APPSEC_CONFIGS, - singular: "appsec-config", - oneOrMore: "appsec-config(s)", - help: cliHelp{ - example: `cscli appsec-configs list -a -cscli appsec-configs install crowdsecurity/vpatch -cscli appsec-configs inspect crowdsecurity/vpatch -cscli appsec-configs upgrade crowdsecurity/vpatch -cscli appsec-configs remove crowdsecurity/vpatch -`, - }, - installHelp: cliHelp{ - example: `cscli appsec-configs install crowdsecurity/vpatch`, - }, - removeHelp: cliHelp{ - example: `cscli appsec-configs remove crowdsecurity/vpatch`, - }, - upgradeHelp: cliHelp{ - example: `cscli appsec-configs upgrade crowdsecurity/vpatch`, - }, - inspectHelp: cliHelp{ - example: `cscli appsec-configs inspect crowdsecurity/vpatch`, - }, - listHelp: cliHelp{ - example: `cscli appsec-configs list -cscli appsec-configs list -a -cscli appsec-configs list crowdsecurity/vpatch`, - }, - } -} - -func NewAppsecRule(cfg configGetter) *cliItem { - inspectDetail := func(item *cwhub.Item) error { - // Only show the converted rules in human mode - if cfg().Cscli.Output != "human" { - return nil - } - - appsecRule := appsec.AppsecCollectionConfig{} - - yamlContent, err := os.ReadFile(item.State.LocalPath) - if err != nil { - return fmt.Errorf("unable to read file %s: %w", item.State.LocalPath, err) - } - - if err := yaml.Unmarshal(yamlContent, &appsecRule); err != nil { - return fmt.Errorf("unable to parse yaml file %s: %w", item.State.LocalPath, err) - } - - for _, ruleType := range appsec_rule.SupportedTypes() { - fmt.Printf("\n%s format:\n", cases.Title(language.Und, cases.NoLower).String(ruleType)) - - for _, rule := range appsecRule.Rules { - convertedRule, _, err := rule.Convert(ruleType, appsecRule.Name) - if err != nil { - return fmt.Errorf("unable to convert rule %s: %w", rule.Name, err) - } - - fmt.Println(convertedRule) - } - - switch ruleType { //nolint:gocritic - case appsec_rule.ModsecurityRuleType: - for _, rule := range appsecRule.SecLangRules { - fmt.Println(rule) - } - } - } - - return nil - } - - return &cliItem{ - cfg: cfg, - name: "appsec-rules", - singular: "appsec-rule", - oneOrMore: "appsec-rule(s)", - help: cliHelp{ - example: `cscli appsec-rules list -a -cscli appsec-rules install crowdsecurity/crs -cscli appsec-rules inspect crowdsecurity/crs -cscli appsec-rules upgrade crowdsecurity/crs -cscli appsec-rules remove crowdsecurity/crs -`, - }, - installHelp: cliHelp{ - example: `cscli appsec-rules install crowdsecurity/crs`, - }, - removeHelp: cliHelp{ - example: `cscli appsec-rules remove crowdsecurity/crs`, - }, - upgradeHelp: cliHelp{ - example: `cscli appsec-rules upgrade crowdsecurity/crs`, - }, - inspectHelp: cliHelp{ - example: `cscli appsec-rules inspect crowdsecurity/crs`, - }, - inspectDetail: inspectDetail, - listHelp: cliHelp{ - example: `cscli appsec-rules list -cscli appsec-rules list -a -cscli appsec-rules list crowdsecurity/crs`, - }, - } -} diff --git a/cmd/crowdsec-cli/cliitem/cmdinspect.go b/cmd/crowdsec-cli/cliitem/cmdinspect.go new file mode 100644 index 00000000000..b5ee0816d72 --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/cmdinspect.go @@ -0,0 +1,236 @@ +package cliitem + +import ( + "cmp" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func (cli cliItem) inspect(ctx context.Context, args []string, url string, diff bool, rev bool, noMetrics bool) error { + cfg := cli.cfg() + + if rev && !diff { + return errors.New("--rev can only be used with --diff") + } + + if url != "" { + cfg.Cscli.PrometheusUrl = url + } + + var contentProvider cwhub.ContentProvider + + if diff { + contentProvider = require.HubDownloader(ctx, cfg) + } + + hub, err := require.Hub(cfg, log.StandardLogger()) + if err != nil { + return err + } + + for _, name := range args { + item := hub.GetItem(cli.name, name) + if item == nil { + return fmt.Errorf("can't find '%s' in %s", name, cli.name) + } + + if diff { + fmt.Println(cli.whyTainted(ctx, hub, contentProvider, item, rev)) + + continue + } + + if err = inspectItem(hub, item, !noMetrics, cfg.Cscli.Output, cfg.Cscli.PrometheusUrl, cfg.Cscli.Color); err != nil { + return err + } + + if cli.inspectDetail != nil { + if err = cli.inspectDetail(item); err != nil { + return err + } + } + } + + return nil +} + +// return the diff between the installed version and the latest version +func (cli cliItem) itemDiff(ctx context.Context, item *cwhub.Item, contentProvider cwhub.ContentProvider, reverse bool) (string, error) { + if !item.State.Installed { + return "", fmt.Errorf("'%s' is not installed", item.FQName()) + } + + dest, err := os.CreateTemp("", "cscli-diff-*") + if err != nil { + return "", fmt.Errorf("while creating temporary file: %w", err) + } + defer os.Remove(dest.Name()) + + _, remoteURL, err := item.FetchContentTo(ctx, contentProvider, dest.Name()) + if err != nil { + return "", err + } + + latestContent, err := os.ReadFile(dest.Name()) + if err != nil { + return "", fmt.Errorf("while reading %s: %w", dest.Name(), err) + } + + localContent, err := os.ReadFile(item.State.LocalPath) + if err != nil { + return "", fmt.Errorf("while reading %s: %w", item.State.LocalPath, err) + } + + file1 := item.State.LocalPath + file2 := remoteURL + content1 := string(localContent) + content2 := string(latestContent) + + if reverse { + file1, file2 = file2, file1 + content1, content2 = content2, content1 + } + + edits := myers.ComputeEdits(span.URIFromPath(file1), content1, content2) + diff := gotextdiff.ToUnified(file1, file2, content1, edits) + + return fmt.Sprintf("%s", diff), nil +} + +func (cli cliItem) whyTainted(ctx context.Context, hub *cwhub.Hub, contentProvider cwhub.ContentProvider, item *cwhub.Item, reverse bool) string { + if !item.State.Installed { + return fmt.Sprintf("# %s is not installed", item.FQName()) + } + + if !item.State.Tainted { + return fmt.Sprintf("# %s is not tainted", item.FQName()) + } + + if len(item.State.TaintedBy) == 0 { + return fmt.Sprintf("# %s is tainted but we don't know why. please report this as a bug", item.FQName()) + } + + ret := []string{ + fmt.Sprintf("# Let's see why %s is tainted.", item.FQName()), + } + + for _, fqsub := range item.State.TaintedBy { + ret = append(ret, fmt.Sprintf("\n-> %s\n", fqsub)) + + sub, err := hub.GetItemFQ(fqsub) + if err != nil { + ret = append(ret, err.Error()) + } + + diff, err := cli.itemDiff(ctx, sub, contentProvider, reverse) + if err != nil { + ret = append(ret, err.Error()) + } + + if diff != "" { + ret = append(ret, diff) + } else if len(sub.State.TaintedBy) > 0 { + taintList := strings.Join(sub.State.TaintedBy, ", ") + if sub.FQName() == taintList { + // hack: avoid message "item is tainted by itself" + continue + } + + ret = append(ret, fmt.Sprintf("# %s is tainted by %s", sub.FQName(), taintList)) + } + } + + return strings.Join(ret, "\n") +} + +func (cli cliItem) newInspectCmd() *cobra.Command { + var ( + url string + diff bool + rev bool + noMetrics bool + ) + + cmd := &cobra.Command{ + Use: cmp.Or(cli.inspectHelp.use, "inspect [item]..."), + Short: cmp.Or(cli.inspectHelp.short, "Inspect given "+cli.oneOrMore), + Long: cmp.Or(cli.inspectHelp.long, "Inspect the state of one or more "+cli.name), + Example: cli.inspectHelp.example, + Args: cobra.MinimumNArgs(1), + DisableAutoGenTag: true, + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cli.name, args, toComplete, cli.cfg) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return cli.inspect(cmd.Context(), args, url, diff, rev, noMetrics) + }, + } + + flags := cmd.Flags() + flags.StringVarP(&url, "url", "u", "", "Prometheus url") + flags.BoolVar(&diff, "diff", false, "Show diff with latest version (for tainted items)") + flags.BoolVar(&rev, "rev", false, "Reverse diff output") + flags.BoolVar(&noMetrics, "no-metrics", false, "Don't show metrics (when cscli.output=human)") + + return cmd +} + +func inspectItem(hub *cwhub.Hub, item *cwhub.Item, wantMetrics bool, output string, prometheusURL string, wantColor string) error { + // This is dirty... + // We want to show current dependencies (from content), not latest (from index). + // The item is modifed but after this function the whole hub should be thrown away. + // A cleaner way would be to copy the struct first. + item.Dependencies = item.CurrentDependencies() + + switch output { + case "human", "raw": + enc := yaml.NewEncoder(os.Stdout) + enc.SetIndent(2) + + if err := enc.Encode(item); err != nil { + return fmt.Errorf("unable to encode item: %w", err) + } + case "json": + b, err := json.MarshalIndent(*item, "", " ") + if err != nil { + return fmt.Errorf("unable to serialize item: %w", err) + } + + fmt.Print(string(b)) + } + + if output != "human" { + return nil + } + + if item.State.Tainted { + fmt.Println() + fmt.Printf(`This item is tainted. Use "%s %s inspect --diff %s" to see why.`, filepath.Base(os.Args[0]), item.Type, item.Name) + fmt.Println() + } + + if wantMetrics { + fmt.Printf("\nCurrent metrics: \n") + + if err := showMetrics(prometheusURL, hub, item, wantColor); err != nil { + return err + } + } + + return nil +} diff --git a/cmd/crowdsec-cli/cliitem/cmdinstall.go b/cmd/crowdsec-cli/cliitem/cmdinstall.go new file mode 100644 index 00000000000..daddbe84a4b --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/cmdinstall.go @@ -0,0 +1,150 @@ +package cliitem + +import ( + "cmp" + "context" + "errors" + "fmt" + "slices" + "strings" + + "github.com/agext/levenshtein" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/reload" + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" + "github.com/crowdsecurity/crowdsec/pkg/hubops" +) + +// suggestNearestMessage returns a message with the most similar item name, if one is found +func suggestNearestMessage(hub *cwhub.Hub, itemType string, itemName string) string { + const maxDistance = 7 + + score := 100 + nearest := "" + + for _, item := range hub.GetItemsByType(itemType, false) { + d := levenshtein.Distance(itemName, item.Name, nil) + if d < score { + score = d + nearest = item.Name + } + } + + msg := fmt.Sprintf("can't find '%s' in %s", itemName, itemType) + + if score < maxDistance { + msg += fmt.Sprintf(", did you mean '%s'?", nearest) + } + + return msg +} + +func (cli cliItem) install(ctx context.Context, args []string, yes bool, dryRun bool, downloadOnly bool, force bool, ignoreError bool) error { + cfg := cli.cfg() + + hub, err := require.Hub(cfg, log.StandardLogger()) + if err != nil { + return err + } + + plan := hubops.NewActionPlan(hub) + + contentProvider := require.HubDownloader(ctx, cfg) + + for _, name := range args { + item := hub.GetItem(cli.name, name) + if item == nil { + msg := suggestNearestMessage(hub, cli.name, name) + if !ignoreError { + return errors.New(msg) + } + + log.Error(msg) + + continue + } + + if err = plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { + return err + } + + if !downloadOnly { + if err = plan.AddCommand(hubops.NewEnableCommand(item, force)); err != nil { + return err + } + } + } + + verbose := (cfg.Cscli.Output == "raw") + + if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { + if !ignoreError { + return err + } + + log.Error(err) + } + + if plan.ReloadNeeded { + fmt.Println("\n" + reload.Message) + } + + return nil +} + +func compAllItems(itemType string, args []string, toComplete string, cfg configGetter) ([]string, cobra.ShellCompDirective) { + hub, err := require.Hub(cfg(), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + + comp := make([]string, 0) + + for _, item := range hub.GetItemsByType(itemType, false) { + if !slices.Contains(args, item.Name) && strings.Contains(item.Name, toComplete) { + comp = append(comp, item.Name) + } + } + + cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true) + + return comp, cobra.ShellCompDirectiveNoFileComp +} + +func (cli cliItem) newInstallCmd() *cobra.Command { + var ( + yes bool + dryRun bool + downloadOnly bool + force bool + ignoreError bool + ) + + cmd := &cobra.Command{ + Use: cmp.Or(cli.installHelp.use, "install [item]..."), + Short: cmp.Or(cli.installHelp.short, "Install given "+cli.oneOrMore), + Long: cmp.Or(cli.installHelp.long, fmt.Sprintf("Fetch and install one or more %s from the hub", cli.name)), + Example: cli.installHelp.example, + Args: cobra.MinimumNArgs(1), + DisableAutoGenTag: true, + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compAllItems(cli.name, args, toComplete, cli.cfg) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return cli.install(cmd.Context(), args, yes, dryRun, downloadOnly, force, ignoreError) + }, + } + + flags := cmd.Flags() + flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") + flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") + flags.BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable") + flags.BoolVar(&force, "force", false, "Force install: overwrite tainted and outdated files") + flags.BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple "+cli.name) + cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") + + return cmd +} diff --git a/cmd/crowdsec-cli/cliitem/cmdremove.go b/cmd/crowdsec-cli/cliitem/cmdremove.go new file mode 100644 index 00000000000..ac9410c047d --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/cmdremove.go @@ -0,0 +1,151 @@ +package cliitem + +import ( + "cmp" + "context" + "errors" + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/reload" + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" + "github.com/crowdsecurity/crowdsec/pkg/hubops" +) + +func (cli cliItem) removePlan(hub *cwhub.Hub, args []string, purge bool, force bool, all bool) (*hubops.ActionPlan, error) { + plan := hubops.NewActionPlan(hub) + + if all { + itemGetter := hub.GetInstalledByType + if purge { + itemGetter = hub.GetItemsByType + } + + for _, item := range itemGetter(cli.name, true) { + if err := plan.AddCommand(hubops.NewDisableCommand(item, force)); err != nil { + return nil, err + } + + if purge { + if err := plan.AddCommand(hubops.NewPurgeCommand(item, force)); err != nil { + return nil, err + } + } + } + + return plan, nil + } + + if len(args) == 0 { + return nil, fmt.Errorf("specify at least one %s to remove or '--all'", cli.singular) + } + + for _, itemName := range args { + item := hub.GetItem(cli.name, itemName) + if item == nil { + return nil, fmt.Errorf("can't find '%s' in %s", itemName, cli.name) + } + + parents := installedParentNames(item) + + if !force && len(parents) > 0 { + log.Warningf("%s belongs to collections: %s", item.Name, parents) + log.Warningf("Run 'sudo cscli %s remove %s --force' if you want to force remove this %s", item.Type, item.Name, cli.singular) + + continue + } + + if err := plan.AddCommand(hubops.NewDisableCommand(item, force)); err != nil { + return nil, err + } + + if purge { + if err := plan.AddCommand(hubops.NewPurgeCommand(item, force)); err != nil { + return nil, err + } + } + } + + return plan, nil +} + +// return the names of the installed parents of an item, used to check if we can remove it +func installedParentNames(item *cwhub.Item) []string { + ret := make([]string, 0) + + for _, parent := range item.Ancestors() { + if parent.State.Installed { + ret = append(ret, parent.Name) + } + } + + return ret +} + +func (cli cliItem) remove(ctx context.Context, args []string, yes bool, dryRun bool, purge bool, force bool, all bool) error { + cfg := cli.cfg() + + hub, err := require.Hub(cli.cfg(), log.StandardLogger()) + if err != nil { + return err + } + + plan, err := cli.removePlan(hub, args, purge, force, all) + if err != nil { + return err + } + + verbose := (cfg.Cscli.Output == "raw") + + if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { + return err + } + + if plan.ReloadNeeded { + fmt.Println("\n" + reload.Message) + } + + return nil +} + +func (cli cliItem) newRemoveCmd() *cobra.Command { + var ( + yes bool + dryRun bool + purge bool + force bool + all bool + ) + + cmd := &cobra.Command{ + Use: cmp.Or(cli.removeHelp.use, "remove [item]..."), + Short: cmp.Or(cli.removeHelp.short, "Remove given "+cli.oneOrMore), + Long: cmp.Or(cli.removeHelp.long, "Remove one or more "+cli.name), + Example: cli.removeHelp.example, + Aliases: []string{"delete"}, + DisableAutoGenTag: true, + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cli.name, args, toComplete, cli.cfg) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 && all { + return errors.New("can't specify items and '--all' at the same time") + } + + return cli.remove(cmd.Context(), args, yes, dryRun, purge, force, all) + }, + } + + flags := cmd.Flags() + flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") + flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") + flags.BoolVar(&purge, "purge", false, "Delete source file too") + flags.BoolVar(&force, "force", false, "Force remove: remove tainted and outdated files") + flags.BoolVar(&all, "all", false, "Remove all the "+cli.name) + cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") + + return cmd +} diff --git a/cmd/crowdsec-cli/cliitem/cmdupgrade.go b/cmd/crowdsec-cli/cliitem/cmdupgrade.go new file mode 100644 index 00000000000..1ddd07485d4 --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/cmdupgrade.go @@ -0,0 +1,106 @@ +package cliitem + +import ( + "cmp" + "context" + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/reload" + "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" + "github.com/crowdsecurity/crowdsec/pkg/hubops" +) + +func (cli cliItem) upgradePlan(hub *cwhub.Hub, contentProvider cwhub.ContentProvider, args []string, force bool, all bool) (*hubops.ActionPlan, error) { + plan := hubops.NewActionPlan(hub) + + if all { + for _, item := range hub.GetInstalledByType(cli.name, true) { + if err := plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { + return nil, err + } + } + + return plan, nil + } + + if len(args) == 0 { + return nil, fmt.Errorf("specify at least one %s to upgrade or '--all'", cli.singular) + } + + for _, itemName := range args { + item := hub.GetItem(cli.name, itemName) + if item == nil { + return nil, fmt.Errorf("can't find '%s' in %s", itemName, cli.name) + } + + if err := plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { + return nil, err + } + } + + return plan, nil +} + +func (cli cliItem) upgrade(ctx context.Context, args []string, yes bool, dryRun bool, force bool, all bool) error { + cfg := cli.cfg() + + hub, err := require.Hub(cfg, log.StandardLogger()) + if err != nil { + return err + } + + contentProvider := require.HubDownloader(ctx, cfg) + + plan, err := cli.upgradePlan(hub, contentProvider, args, force, all) + if err != nil { + return err + } + + verbose := (cfg.Cscli.Output == "raw") + + if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { + return err + } + + if plan.ReloadNeeded { + fmt.Println("\n" + reload.Message) + } + + return nil +} + +func (cli cliItem) newUpgradeCmd() *cobra.Command { + var ( + yes bool + dryRun bool + all bool + force bool + ) + + cmd := &cobra.Command{ + Use: cmp.Or(cli.upgradeHelp.use, "upgrade [item]..."), + Short: cmp.Or(cli.upgradeHelp.short, "Upgrade given "+cli.oneOrMore), + Long: cmp.Or(cli.upgradeHelp.long, fmt.Sprintf("Fetch and upgrade one or more %s from the hub", cli.name)), + Example: cli.upgradeHelp.example, + DisableAutoGenTag: true, + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cli.name, args, toComplete, cli.cfg) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return cli.upgrade(cmd.Context(), args, yes, dryRun, force, all) + }, + } + + flags := cmd.Flags() + flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") + flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") + flags.BoolVarP(&all, "all", "a", false, "Upgrade all the "+cli.name) + flags.BoolVar(&force, "force", false, "Force upgrade: overwrite tainted and outdated files") + cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") + + return cmd +} diff --git a/cmd/crowdsec-cli/cliitem/collection.go b/cmd/crowdsec-cli/cliitem/collection.go deleted file mode 100644 index ea91c1e537a..00000000000 --- a/cmd/crowdsec-cli/cliitem/collection.go +++ /dev/null @@ -1,41 +0,0 @@ -package cliitem - -import ( - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func NewCollection(cfg configGetter) *cliItem { - return &cliItem{ - cfg: cfg, - name: cwhub.COLLECTIONS, - singular: "collection", - oneOrMore: "collection(s)", - help: cliHelp{ - example: `cscli collections list -a -cscli collections install crowdsecurity/http-cve crowdsecurity/iptables -cscli collections inspect crowdsecurity/http-cve crowdsecurity/iptables -cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables -cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables -`, - }, - installHelp: cliHelp{ - example: `cscli collections install crowdsecurity/http-cve crowdsecurity/iptables`, - }, - removeHelp: cliHelp{ - example: `cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables`, - }, - upgradeHelp: cliHelp{ - example: `cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables`, - }, - inspectHelp: cliHelp{ - example: `cscli collections inspect crowdsecurity/http-cve crowdsecurity/iptables`, - }, - listHelp: cliHelp{ - example: `cscli collections list -cscli collections list -a -cscli collections list crowdsecurity/http-cve crowdsecurity/iptables - -List only enabled collections unless "-a" or names are specified.`, - }, - } -} diff --git a/cmd/crowdsec-cli/cliitem/context.go b/cmd/crowdsec-cli/cliitem/context.go deleted file mode 100644 index 7d110b8203d..00000000000 --- a/cmd/crowdsec-cli/cliitem/context.go +++ /dev/null @@ -1,41 +0,0 @@ -package cliitem - -import ( - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func NewContext(cfg configGetter) *cliItem { - return &cliItem{ - cfg: cfg, - name: cwhub.CONTEXTS, - singular: "context", - oneOrMore: "context(s)", - help: cliHelp{ - example: `cscli contexts list -a -cscli contexts install crowdsecurity/yyy crowdsecurity/zzz -cscli contexts inspect crowdsecurity/yyy crowdsecurity/zzz -cscli contexts upgrade crowdsecurity/yyy crowdsecurity/zzz -cscli contexts remove crowdsecurity/yyy crowdsecurity/zzz -`, - }, - installHelp: cliHelp{ - example: `cscli contexts install crowdsecurity/yyy crowdsecurity/zzz`, - }, - removeHelp: cliHelp{ - example: `cscli contexts remove crowdsecurity/yyy crowdsecurity/zzz`, - }, - upgradeHelp: cliHelp{ - example: `cscli contexts upgrade crowdsecurity/yyy crowdsecurity/zzz`, - }, - inspectHelp: cliHelp{ - example: `cscli contexts inspect crowdsecurity/yyy crowdsecurity/zzz`, - }, - listHelp: cliHelp{ - example: `cscli contexts list -cscli contexts list -a -cscli contexts list crowdsecurity/yyy crowdsecurity/zzz - -List only enabled contexts unless "-a" or names are specified.`, - }, - } -} diff --git a/cmd/crowdsec-cli/cliitem/hubappsec.go b/cmd/crowdsec-cli/cliitem/hubappsec.go new file mode 100644 index 00000000000..7f9143d35b8 --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/hubappsec.go @@ -0,0 +1,255 @@ +package cliitem + +import ( + "fmt" + "os" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + "gopkg.in/yaml.v3" + + "github.com/crowdsecurity/crowdsec/pkg/appsec" + "github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func NewAppsecConfig(cfg configGetter) *cliItem { + return &cliItem{ + cfg: cfg, + name: cwhub.APPSEC_CONFIGS, + singular: "appsec-config", + oneOrMore: "appsec-config(s)", + help: cliHelp{ + example: `cscli appsec-configs list -a +cscli appsec-configs install crowdsecurity/virtual-patching +cscli appsec-configs inspect crowdsecurity/virtual-patching +cscli appsec-configs upgrade crowdsecurity/virtual-patching +cscli appsec-configs remove crowdsecurity/virtual-patching +`, + }, + installHelp: cliHelp{ + example: `# Install some appsec-configs. +cscli appsec-configs install crowdsecurity/virtual-patching + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-configs install crowdsecurity/virtual-patching --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-configs install crowdsecurity/virtual-patching --dry-run -o raw + +# Download only, to be installed later. +cscli appsec-configs install crowdsecurity/virtual-patching --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli appsec-configs install crowdsecurity/virtual-patching --force + +# Proceed without prompting. +cscli appsec-configs install crowdsecurity/virtual-patching --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some appsec-configs. +cscli appsec-configs remove crowdsecurity/virtual-patching + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-configs remove crowdsecurity/virtual-patching --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-configs remove crowdsecurity/virtual-patching --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli appsec-configs remove crowdsecurity/virtual-patching --purge + +# Remove tainted items. +cscli appsec-configs remove crowdsecurity/virtual-patching --force + +# Proceed without prompting. +cscli appsec-configs remove crowdsecurity/virtual-patching --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some appsec-configs. If they are not currently installed, they are downloaded but not installed. +cscli appsec-configs upgrade crowdsecurity/virtual-patching + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-configs upgrade crowdsecurity/virtual-patching --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-configs upgrade crowdsecurity/virtual-patching --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli appsec-configs upgrade crowdsecurity/virtual-patching --force + +# Proceed without prompting. +cscli appsec-configs upgrade crowdsecurity/virtual-patching --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state, metrics and ancestor collections of appsec-configs (installed or not). +cscli appsec-configs inspect crowdsecurity/virtual-patching + +# Don't collect metrics (avoid error if crowdsec is not running). +cscli appsec-configs inspect crowdsecurity/virtual-patching --no-metrics + +# Display difference between a tainted item and the latest one. +cscli appsec-configs inspect crowdsecurity/virtual-patching --diff + +# Reverse the above diff +cscli appsec-configs inspect crowdsecurity/virtual-patching --diff --rev`, + }, + listHelp: cliHelp{ + example: `# List enabled (installed) appsec-configs. +cscli appsec-configs list + +# List all available appsec-configs (installed or not). +cscli appsec-configs list -a + +# List specific appsec-configs (installed or not). +cscli appsec-configs list crowdsecurity/virtual-patching crowdsecurity/generic-rules`, + }, + } +} + +func NewAppsecRule(cfg configGetter) *cliItem { + inspectDetail := func(item *cwhub.Item) error { + // Only show the converted rules in human mode + if cfg().Cscli.Output != "human" { + return nil + } + + appsecRule := appsec.AppsecCollectionConfig{} + + yamlContent, err := os.ReadFile(item.State.LocalPath) + if err != nil { + return fmt.Errorf("unable to read file %s: %w", item.State.LocalPath, err) + } + + if err := yaml.Unmarshal(yamlContent, &appsecRule); err != nil { + return fmt.Errorf("unable to parse yaml file %s: %w", item.State.LocalPath, err) + } + + for _, ruleType := range appsec_rule.SupportedTypes() { + fmt.Printf("\n%s format:\n", cases.Title(language.Und, cases.NoLower).String(ruleType)) + + for _, rule := range appsecRule.Rules { + convertedRule, _, err := rule.Convert(ruleType, appsecRule.Name) + if err != nil { + return fmt.Errorf("unable to convert rule %s: %w", rule.Name, err) + } + + fmt.Println(convertedRule) + } + + switch ruleType { //nolint:gocritic + case appsec_rule.ModsecurityRuleType: + for _, rule := range appsecRule.SecLangRules { + fmt.Println(rule) + } + } + } + + return nil + } + + return &cliItem{ + cfg: cfg, + name: "appsec-rules", + singular: "appsec-rule", + oneOrMore: "appsec-rule(s)", + help: cliHelp{ + example: `cscli appsec-rules list -a +cscli appsec-rules install crowdsecurity/crs +cscli appsec-rules inspect crowdsecurity/crs +cscli appsec-rules upgrade crowdsecurity/crs +cscli appsec-rules remove crowdsecurity/crs +`, + }, + installHelp: cliHelp{ + example: `# Install some appsec-rules. +cscli appsec-rules install crowdsecurity/crs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-rules install crowdsecurity/crs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-rules install crowdsecurity/crs --dry-run -o raw + +# Download only, to be installed later. +cscli appsec-rules install crowdsecurity/crs --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli appsec-rules install crowdsecurity/crs --force + +# Proceed without prompting. +cscli appsec-rules install crowdsecurity/crs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some appsec-rules. +cscli appsec-rules remove crowdsecurity/crs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-rules remove crowdsecurity/crs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-rules remove crowdsecurity/crs --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli appsec-rules remove crowdsecurity/crs --purge + +# Remove tainted items. +cscli appsec-rules remove crowdsecurity/crs --force + +# Proceed without prompting. +cscli appsec-rules remove crowdsecurity/crs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some appsec-rules. If they are not currently installed, they are downloaded but not installed. +cscli appsec-rules upgrade crowdsecurity/crs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli appsec-rules upgrade crowdsecurity/crs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli appsec-rules upgrade crowdsecurity/crs --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli appsec-rules upgrade crowdsecurity/crs --force + +# Proceed without prompting. +cscli appsec-rules upgrade crowdsecurity/crs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state, metrics and ancestor collections of appsec-rules (installed or not). +cscli appsec-rules inspect crowdsecurity/crs + +# Don't collect metrics (avoid error if crowdsec is not running). +cscli appsec-configs inspect crowdsecurity/crs --no-metrics + +# Display difference between a tainted item and the latest one. +cscli appsec-rules inspect crowdsecurity/crs --diff + +# Reverse the above diff +cscli appsec-rules inspect crowdsecurity/crs --diff --rev`, + }, + inspectDetail: inspectDetail, + listHelp: cliHelp{ + example: `# List enabled (installed) appsec-rules. +cscli appsec-rules list + +# List all available appsec-rules (installed or not). +cscli appsec-rules list -a + +# List specific appsec-rules (installed or not). +cscli appsec-rules list crowdsecurity/crs crowdsecurity/vpatch-git-config`, + }, + } +} diff --git a/cmd/crowdsec-cli/cliitem/hubcollection.go b/cmd/crowdsec-cli/cliitem/hubcollection.go new file mode 100644 index 00000000000..b45f956e0ac --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/hubcollection.go @@ -0,0 +1,105 @@ +package cliitem + +import ( + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func NewCollection(cfg configGetter) *cliItem { + return &cliItem{ + cfg: cfg, + name: cwhub.COLLECTIONS, + singular: "collection", + oneOrMore: "collection(s)", + help: cliHelp{ + example: `cscli collections list -a +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables +cscli collections inspect crowdsecurity/http-cve crowdsecurity/iptables +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables +`, + }, + installHelp: cliHelp{ + example: `# Install some collections. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables --dry-run -o raw + +# Download only, to be installed later. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables --force + +# Proceed without prompting. +cscli collections install crowdsecurity/http-cve crowdsecurity/iptables --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some collections. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables --purge + +# Remove tainted items. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables --force + +# Proceed without prompting. +cscli collections remove crowdsecurity/http-cve crowdsecurity/iptables --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some collections. If they are not currently installed, they are downloaded but not installed. +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables --force + +# Proceed without prompting. +cscli collections upgrade crowdsecurity/http-cve crowdsecurity/iptables --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state, metrics and dependencies of collections (installed or not). +cscli collections inspect crowdsecurity/http-cve crowdsecurity/iptables + +# Don't collect metrics (avoid error if crowdsec is not running). +cscli collections inspect crowdsecurity/http-cve crowdsecurity/iptables --no-metrics + +# Display difference between a tainted item and the latest one, or the reason for the taint if it's a dependency. +cscli collections inspect crowdsecurity/http-cve --diff + +# Reverse the above diff +cscli collections inspect crowdsecurity/http-cve --diff --rev`, + }, + listHelp: cliHelp{ + example: `# List enabled (installed) collections. +cscli collections list + +# List all available collections (installed or not). +cscli collections list -a + +# List specific collections (installed or not). +cscli collections list crowdsecurity/http-cve crowdsecurity/iptables`, + }, + } +} diff --git a/cmd/crowdsec-cli/cliitem/hubcontext.go b/cmd/crowdsec-cli/cliitem/hubcontext.go new file mode 100644 index 00000000000..3a94687843d --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/hubcontext.go @@ -0,0 +1,102 @@ +package cliitem + +import ( + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func NewContext(cfg configGetter) *cliItem { + return &cliItem{ + cfg: cfg, + name: cwhub.CONTEXTS, + singular: "context", + oneOrMore: "context(s)", + help: cliHelp{ + example: `cscli contexts list -a +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet +cscli contexts inspect crowdsecurity/bf_base crowdsecurity/fortinet +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet +`, + }, + installHelp: cliHelp{ + example: `# Install some contexts. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet --dry-run -o raw + +# Download only, to be installed later. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet --force + +# Proceed without prompting. +cscli contexts install crowdsecurity/bf_base crowdsecurity/fortinet --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some contexts. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet --purge + +# Remove tainted items. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet --force + +# Proceed without prompting. +cscli contexts remove crowdsecurity/bf_base crowdsecurity/fortinet --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some contexts. If they are not currently installed, they are downloaded but not installed. +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet --force + +# Proceed without prompting. +cscli contexts upgrade crowdsecurity/bf_base crowdsecurity/fortinet --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state and ancestor collections of contexts (installed or not). +cscli contexts inspect crowdsecurity/bf_base crowdsecurity/fortinet + +# Display difference between a tainted item and the latest one. +cscli contexts inspect crowdsecurity/bf_base --diff + +# Reverse the above diff +cscli contexts inspect crowdsecurity/bf_base --diff --rev`, + }, + listHelp: cliHelp{ + example: `# List enabled (installed) contexts. +cscli contexts list + +# List all available contexts (installed or not). +cscli contexts list -a + +# List specific contexts (installed or not). +cscli contexts list crowdsecurity/bf_base crowdsecurity/fortinet`, + }, + } +} diff --git a/cmd/crowdsec-cli/cliitem/hubparser.go b/cmd/crowdsec-cli/cliitem/hubparser.go new file mode 100644 index 00000000000..440cb61204f --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/hubparser.go @@ -0,0 +1,105 @@ +package cliitem + +import ( + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func NewParser(cfg configGetter) *cliItem { + return &cliItem{ + cfg: cfg, + name: cwhub.PARSERS, + singular: "parser", + oneOrMore: "parser(s)", + help: cliHelp{ + example: `cscli parsers list -a +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs +cscli parsers inspect crowdsecurity/caddy-logs crowdsecurity/sshd-logs +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs +`, + }, + installHelp: cliHelp{ + example: `# Install some parsers. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run -o raw + +# Download only, to be installed later. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs --force + +# Proceed without prompting. +cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some parsers. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs --purge + +# Remove tainted items. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs --force + +# Proceed without prompting. +cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some parsers. If they are not currently installed, they are downloaded but not installed. +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs --force + +# Proceed without prompting. +cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state, metrics and ancestor collections of parsers (installed or not). +cscli parsers inspect crowdsecurity/httpd-logs crowdsecurity/sshd-logs + +# Don't collect metrics (avoid error if crowdsec is not running). +cscli parsers inspect crowdsecurity/httpd-logs --no-metrics + +# Display difference between a tainted item and the latest one. +cscli parsers inspect crowdsecurity/httpd-logs --diff + +# Reverse the above diff +cscli parsers inspect crowdsecurity/httpd-logs --diff --rev`, + }, + listHelp: cliHelp{ + example: `# List enabled (installed) parsers. +cscli parsers list + +# List all available parsers (installed or not). +cscli parsers list -a + +# List specific parsers (installed or not). +cscli parsers list crowdsecurity/caddy-logs crowdsecurity/sshd-logs`, + }, + } +} diff --git a/cmd/crowdsec-cli/cliitem/hubpostoverflow.go b/cmd/crowdsec-cli/cliitem/hubpostoverflow.go new file mode 100644 index 00000000000..cfd5f7c95aa --- /dev/null +++ b/cmd/crowdsec-cli/cliitem/hubpostoverflow.go @@ -0,0 +1,102 @@ +package cliitem + +import ( + "github.com/crowdsecurity/crowdsec/pkg/cwhub" +) + +func NewPostOverflow(cfg configGetter) *cliItem { + return &cliItem{ + cfg: cfg, + name: cwhub.POSTOVERFLOWS, + singular: "postoverflow", + oneOrMore: "postoverflow(s)", + help: cliHelp{ + example: `cscli postoverflows list -a +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns +cscli postoverflows inspect crowdsecurity/cdn-whitelist crowdsecurity/rdns +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdns +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns +`, + }, + installHelp: cliHelp{ + example: `# Install some postoverflows. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns --dry-run -o raw + +# Download only, to be installed later. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns --force + +# Proceed without prompting. +cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + removeHelp: cliHelp{ + example: `# Uninstall some postoverflows. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns --purge + +# Remove tainted items. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns --force + +# Proceed without prompting. +cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + upgradeHelp: cliHelp{ + example: `# Upgrade some postoverflows. If they are not currently installed, they are downloaded but not installed. +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdnss + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdnss --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdnss --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdnss --force + +# Proceed without prompting. +cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdnss --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, + }, + inspectHelp: cliHelp{ + example: `# Display metadata, state and ancestor collections of postoverflows (installed or not). +cscli postoverflows inspect crowdsecurity/cdn-whitelist + +# Display difference between a tainted item and the latest one. +cscli postoverflows inspect crowdsecurity/cdn-whitelist --diff + +# Reverse the above diff +cscli postoverflows inspect crowdsecurity/cdn-whitelist --diff --rev`, + }, + listHelp: cliHelp{ + example: `# List enabled (installed) postoverflows. +cscli postoverflows list + +# List all available postoverflows (installed or not). +cscli postoverflows list -a + +# List specific postoverflows (installed or not). +cscli postoverflows list crowdsecurity/cdn-whitelists crowdsecurity/rdns`, + }, + } +} diff --git a/cmd/crowdsec-cli/cliitem/hubscenario.go b/cmd/crowdsec-cli/cliitem/hubscenario.go index a5e854b3c82..5dee3323f6f 100644 --- a/cmd/crowdsec-cli/cliitem/hubscenario.go +++ b/cmd/crowdsec-cli/cliitem/hubscenario.go @@ -19,23 +19,87 @@ cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing `, }, installHelp: cliHelp{ - example: `cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing`, + example: `# Install some scenarios. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run -o raw + +# Download only, to be installed later. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing --download-only + +# Install over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing --force + +# Proceed without prompting. +cscli scenarios install crowdsecurity/ssh-bf crowdsecurity/http-probing --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, }, removeHelp: cliHelp{ - example: `cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing`, + example: `# Uninstall some scenarios. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run -o raw + +# Uninstall and also remove the downloaded files. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing --purge + +# Remove tainted items. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing --force + +# Proceed without prompting. +cscli scenarios remove crowdsecurity/ssh-bf crowdsecurity/http-probing --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, }, upgradeHelp: cliHelp{ - example: `cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing`, + example: `# Upgrade some scenarios. If they are not currently installed, they are downloaded but not installed. +cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing + +# Show the execution plan without changing anything - compact output sorted by type and name. +cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run + +# Show the execution plan without changing anything - verbose output sorted by execution order. +cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing --dry-run -o raw + +# Upgrade over tainted items. Can be used to restore or repair after local modifications or missing dependencies. +cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing --force + +# Proceed without prompting. +cscli scenarios upgrade crowdsecurity/ssh-bf crowdsecurity/http-probing --yes + +# The "--yes" parameter is implied when the command is not connected to a terminal, like pipes or scripts.`, }, inspectHelp: cliHelp{ - example: `cscli scenarios inspect crowdsecurity/ssh-bf crowdsecurity/http-probing`, + example: `# Display metadata, state, metrics and ancestor collections of scenarios (installed or not). +cscli scenarios inspect crowdsecurity/ssh-bf crowdsecurity/http-probing + +# Don't collect metrics (avoid error if crowdsec is not running). +cscli scenarios inspect crowdsecurity/ssh-bf --no-metrics + +# Display difference between a tainted item and the latest one. +cscli scenarios inspect crowdsecurity/ssh-bf --diff + +# Reverse the above diff +cscli scenarios inspect crowdsecurity/ssh-bf --diff --rev`, }, listHelp: cliHelp{ - example: `cscli scenarios list + example: `# List enabled (installed) scenarios. +cscli scenarios list + +# List all available scenarios (installed or not). cscli scenarios list -a -cscli scenarios list crowdsecurity/ssh-bf crowdsecurity/http-probing -List only enabled scenarios unless "-a" or names are specified.`, +# List specific scenarios (installed or not). +cscli scenarios list crowdsecurity/ssh-bf crowdsecurity/http-probing`, }, } } diff --git a/cmd/crowdsec-cli/cliitem/inspect.go b/cmd/crowdsec-cli/cliitem/inspect.go deleted file mode 100644 index 9939de1810e..00000000000 --- a/cmd/crowdsec-cli/cliitem/inspect.go +++ /dev/null @@ -1,57 +0,0 @@ -package cliitem - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "gopkg.in/yaml.v3" - - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func inspectItem(hub *cwhub.Hub, item *cwhub.Item, wantMetrics bool, output string, prometheusURL string, wantColor string) error { - // This is dirty... - // We want to show current dependencies (from content), not latest (from index). - // The item is modifed but after this function the whole hub should be thrown away. - // A cleaner way would be to copy the struct first. - item.Dependencies = item.CurrentDependencies() - - switch output { - case "human", "raw": - enc := yaml.NewEncoder(os.Stdout) - enc.SetIndent(2) - - if err := enc.Encode(item); err != nil { - return fmt.Errorf("unable to encode item: %w", err) - } - case "json": - b, err := json.MarshalIndent(*item, "", " ") - if err != nil { - return fmt.Errorf("unable to serialize item: %w", err) - } - - fmt.Print(string(b)) - } - - if output != "human" { - return nil - } - - if item.State.Tainted { - fmt.Println() - fmt.Printf(`This item is tainted. Use "%s %s inspect --diff %s" to see why.`, filepath.Base(os.Args[0]), item.Type, item.Name) - fmt.Println() - } - - if wantMetrics { - fmt.Printf("\nCurrent metrics: \n") - - if err := showMetrics(prometheusURL, hub, item, wantColor); err != nil { - return err - } - } - - return nil -} diff --git a/cmd/crowdsec-cli/cliitem/item.go b/cmd/crowdsec-cli/cliitem/item.go index 637bd3023cf..3dcc0665a89 100644 --- a/cmd/crowdsec-cli/cliitem/item.go +++ b/cmd/crowdsec-cli/cliitem/item.go @@ -2,25 +2,17 @@ package cliitem import ( "cmp" - "context" - "errors" "fmt" - "os" "strings" "github.com/fatih/color" - "github.com/hexops/gotextdiff" - "github.com/hexops/gotextdiff/myers" - "github.com/hexops/gotextdiff/span" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clihub" - "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/reload" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/cwhub" - "github.com/crowdsecurity/crowdsec/pkg/hubops" ) type cliHelp struct { @@ -68,400 +60,6 @@ func (cli cliItem) NewCommand() *cobra.Command { return cmd } -func (cli cliItem) install(ctx context.Context, args []string, yes bool, dryRun bool, downloadOnly bool, force bool, ignoreError bool) error { - cfg := cli.cfg() - - hub, err := require.Hub(cfg, log.StandardLogger()) - if err != nil { - return err - } - - plan := hubops.NewActionPlan(hub) - - contentProvider := require.HubDownloader(ctx, cfg) - - for _, name := range args { - item := hub.GetItem(cli.name, name) - if item == nil { - msg := suggestNearestMessage(hub, cli.name, name) - if !ignoreError { - return errors.New(msg) - } - - log.Error(msg) - - continue - } - - if err = plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { - return err - } - - if !downloadOnly { - if err = plan.AddCommand(hubops.NewEnableCommand(item, force)); err != nil { - return err - } - } - } - - verbose := (cfg.Cscli.Output == "raw") - - if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { - if !ignoreError { - return err - } - - log.Error(err) - } - - if plan.ReloadNeeded { - fmt.Println("\n" + reload.Message) - } - - return nil -} - -func (cli cliItem) newInstallCmd() *cobra.Command { - var ( - yes bool - dryRun bool - downloadOnly bool - force bool - ignoreError bool - ) - - cmd := &cobra.Command{ - Use: cmp.Or(cli.installHelp.use, "install [item]..."), - Short: cmp.Or(cli.installHelp.short, "Install given "+cli.oneOrMore), - Long: cmp.Or(cli.installHelp.long, fmt.Sprintf("Fetch and install one or more %s from the hub", cli.name)), - Example: cli.installHelp.example, - Args: cobra.MinimumNArgs(1), - DisableAutoGenTag: true, - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compAllItems(cli.name, args, toComplete, cli.cfg) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return cli.install(cmd.Context(), args, yes, dryRun, downloadOnly, force, ignoreError) - }, - } - - flags := cmd.Flags() - flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") - flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") - flags.BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable") - flags.BoolVar(&force, "force", false, "Force install: overwrite tainted and outdated files") - flags.BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple "+cli.name) - cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") - - return cmd -} - -// return the names of the installed parents of an item, used to check if we can remove it -func installedParentNames(item *cwhub.Item) []string { - ret := make([]string, 0) - - for _, parent := range item.Ancestors() { - if parent.State.Installed { - ret = append(ret, parent.Name) - } - } - - return ret -} - -func (cli cliItem) removePlan(hub *cwhub.Hub, args []string, purge bool, force bool, all bool) (*hubops.ActionPlan, error) { - plan := hubops.NewActionPlan(hub) - - if all { - itemGetter := hub.GetInstalledByType - if purge { - itemGetter = hub.GetItemsByType - } - - for _, item := range itemGetter(cli.name, true) { - if err := plan.AddCommand(hubops.NewDisableCommand(item, force)); err != nil { - return nil, err - } - - if purge { - if err := plan.AddCommand(hubops.NewPurgeCommand(item, force)); err != nil { - return nil, err - } - } - } - - return plan, nil - } - - if len(args) == 0 { - return nil, fmt.Errorf("specify at least one %s to remove or '--all'", cli.singular) - } - - for _, itemName := range args { - item := hub.GetItem(cli.name, itemName) - if item == nil { - return nil, fmt.Errorf("can't find '%s' in %s", itemName, cli.name) - } - - parents := installedParentNames(item) - - if !force && len(parents) > 0 { - log.Warningf("%s belongs to collections: %s", item.Name, parents) - log.Warningf("Run 'sudo cscli %s remove %s --force' if you want to force remove this %s", item.Type, item.Name, cli.singular) - - continue - } - - if err := plan.AddCommand(hubops.NewDisableCommand(item, force)); err != nil { - return nil, err - } - - if purge { - if err := plan.AddCommand(hubops.NewPurgeCommand(item, force)); err != nil { - return nil, err - } - } - } - - return plan, nil -} - -func (cli cliItem) remove(ctx context.Context, args []string, yes bool, dryRun bool, purge bool, force bool, all bool) error { - cfg := cli.cfg() - - hub, err := require.Hub(cli.cfg(), log.StandardLogger()) - if err != nil { - return err - } - - plan, err := cli.removePlan(hub, args, purge, force, all) - if err != nil { - return err - } - - verbose := (cfg.Cscli.Output == "raw") - - if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { - return err - } - - if plan.ReloadNeeded { - fmt.Println("\n" + reload.Message) - } - - return nil -} - -func (cli cliItem) newRemoveCmd() *cobra.Command { - var ( - yes bool - dryRun bool - purge bool - force bool - all bool - ) - - cmd := &cobra.Command{ - Use: cmp.Or(cli.removeHelp.use, "remove [item]..."), - Short: cmp.Or(cli.removeHelp.short, "Remove given "+cli.oneOrMore), - Long: cmp.Or(cli.removeHelp.long, "Remove one or more "+cli.name), - Example: cli.removeHelp.example, - Aliases: []string{"delete"}, - DisableAutoGenTag: true, - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cli.name, args, toComplete, cli.cfg) - }, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 && all { - return errors.New("can't specify items and '--all' at the same time") - } - - return cli.remove(cmd.Context(), args, yes, dryRun, purge, force, all) - }, - } - - flags := cmd.Flags() - flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") - flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") - flags.BoolVar(&purge, "purge", false, "Delete source file too") - flags.BoolVar(&force, "force", false, "Force remove: remove tainted and outdated files") - flags.BoolVar(&all, "all", false, "Remove all the "+cli.name) - cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") - - return cmd -} - -func (cli cliItem) upgradePlan(hub *cwhub.Hub, contentProvider cwhub.ContentProvider, args []string, force bool, all bool) (*hubops.ActionPlan, error) { - plan := hubops.NewActionPlan(hub) - - if all { - for _, item := range hub.GetInstalledByType(cli.name, true) { - if err := plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { - return nil, err - } - } - - return plan, nil - } - - if len(args) == 0 { - return nil, fmt.Errorf("specify at least one %s to upgrade or '--all'", cli.singular) - } - - for _, itemName := range args { - item := hub.GetItem(cli.name, itemName) - if item == nil { - return nil, fmt.Errorf("can't find '%s' in %s", itemName, cli.name) - } - - if err := plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, force)); err != nil { - return nil, err - } - } - - return plan, nil -} - -func (cli cliItem) upgrade(ctx context.Context, args []string, yes bool, dryRun bool, force bool, all bool) error { - cfg := cli.cfg() - - hub, err := require.Hub(cfg, log.StandardLogger()) - if err != nil { - return err - } - - contentProvider := require.HubDownloader(ctx, cfg) - - plan, err := cli.upgradePlan(hub, contentProvider, args, force, all) - if err != nil { - return err - } - - verbose := (cfg.Cscli.Output == "raw") - - if err := plan.Execute(ctx, yes, dryRun, verbose); err != nil { - return err - } - - if plan.ReloadNeeded { - fmt.Println("\n" + reload.Message) - } - - return nil -} - -func (cli cliItem) newUpgradeCmd() *cobra.Command { - var ( - yes bool - dryRun bool - all bool - force bool - ) - - cmd := &cobra.Command{ - Use: cmp.Or(cli.upgradeHelp.use, "upgrade [item]..."), - Short: cmp.Or(cli.upgradeHelp.short, "Upgrade given "+cli.oneOrMore), - Long: cmp.Or(cli.upgradeHelp.long, fmt.Sprintf("Fetch and upgrade one or more %s from the hub", cli.name)), - Example: cli.upgradeHelp.example, - DisableAutoGenTag: true, - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cli.name, args, toComplete, cli.cfg) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return cli.upgrade(cmd.Context(), args, yes, dryRun, force, all) - }, - } - - flags := cmd.Flags() - flags.BoolVarP(&yes, "yes", "y", false, "Confirm execution without prompt") - flags.BoolVar(&dryRun, "dry-run", false, "Don't install or remove anything; print the execution plan") - flags.BoolVarP(&all, "all", "a", false, "Upgrade all the "+cli.name) - flags.BoolVar(&force, "force", false, "Force upgrade: overwrite tainted and outdated files") - cmd.MarkFlagsMutuallyExclusive("yes", "dry-run") - - return cmd -} - -func (cli cliItem) inspect(ctx context.Context, args []string, url string, diff bool, rev bool, noMetrics bool) error { - cfg := cli.cfg() - - if rev && !diff { - return errors.New("--rev can only be used with --diff") - } - - if url != "" { - cfg.Cscli.PrometheusUrl = url - } - - var contentProvider cwhub.ContentProvider - - if diff { - contentProvider = require.HubDownloader(ctx, cfg) - } - - hub, err := require.Hub(cfg, log.StandardLogger()) - if err != nil { - return err - } - - for _, name := range args { - item := hub.GetItem(cli.name, name) - if item == nil { - return fmt.Errorf("can't find '%s' in %s", name, cli.name) - } - - if diff { - fmt.Println(cli.whyTainted(ctx, hub, contentProvider, item, rev)) - - continue - } - - if err = inspectItem(hub, item, !noMetrics, cfg.Cscli.Output, cfg.Cscli.PrometheusUrl, cfg.Cscli.Color); err != nil { - return err - } - - if cli.inspectDetail != nil { - if err = cli.inspectDetail(item); err != nil { - return err - } - } - } - - return nil -} - -func (cli cliItem) newInspectCmd() *cobra.Command { - var ( - url string - diff bool - rev bool - noMetrics bool - ) - - cmd := &cobra.Command{ - Use: cmp.Or(cli.inspectHelp.use, "inspect [item]..."), - Short: cmp.Or(cli.inspectHelp.short, "Inspect given "+cli.oneOrMore), - Long: cmp.Or(cli.inspectHelp.long, "Inspect the state of one or more "+cli.name), - Example: cli.inspectHelp.example, - Args: cobra.MinimumNArgs(1), - DisableAutoGenTag: true, - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cli.name, args, toComplete, cli.cfg) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return cli.inspect(cmd.Context(), args, url, diff, rev, noMetrics) - }, - } - - flags := cmd.Flags() - flags.StringVarP(&url, "url", "u", "", "Prometheus url") - flags.BoolVar(&diff, "diff", false, "Show diff with latest version (for tainted items)") - flags.BoolVar(&rev, "rev", false, "Reverse diff output") - flags.BoolVar(&noMetrics, "no-metrics", false, "Don't show metrics (when cscli.output=human)") - - return cmd -} - func (cli cliItem) list(args []string, all bool) error { cfg := cli.cfg() @@ -500,91 +98,23 @@ func (cli cliItem) newListCmd() *cobra.Command { return cmd } -// return the diff between the installed version and the latest version -func (cli cliItem) itemDiff(ctx context.Context, item *cwhub.Item, contentProvider cwhub.ContentProvider, reverse bool) (string, error) { - if !item.State.Installed { - return "", fmt.Errorf("'%s' is not installed", item.FQName()) - } - - dest, err := os.CreateTemp("", "cscli-diff-*") +func compInstalledItems(itemType string, args []string, toComplete string, cfg configGetter) ([]string, cobra.ShellCompDirective) { + hub, err := require.Hub(cfg(), nil) if err != nil { - return "", fmt.Errorf("while creating temporary file: %w", err) + return nil, cobra.ShellCompDirectiveDefault } - defer os.Remove(dest.Name()) - _, remoteURL, err := item.FetchContentTo(ctx, contentProvider, dest.Name()) - if err != nil { - return "", err - } + items := hub.GetInstalledByType(itemType, true) - latestContent, err := os.ReadFile(dest.Name()) - if err != nil { - return "", fmt.Errorf("while reading %s: %w", dest.Name(), err) - } - - localContent, err := os.ReadFile(item.State.LocalPath) - if err != nil { - return "", fmt.Errorf("while reading %s: %w", item.State.LocalPath, err) - } - - file1 := item.State.LocalPath - file2 := remoteURL - content1 := string(localContent) - content2 := string(latestContent) - - if reverse { - file1, file2 = file2, file1 - content1, content2 = content2, content1 - } - - edits := myers.ComputeEdits(span.URIFromPath(file1), content1, content2) - diff := gotextdiff.ToUnified(file1, file2, content1, edits) - - return fmt.Sprintf("%s", diff), nil -} - -func (cli cliItem) whyTainted(ctx context.Context, hub *cwhub.Hub, contentProvider cwhub.ContentProvider, item *cwhub.Item, reverse bool) string { - if !item.State.Installed { - return fmt.Sprintf("# %s is not installed", item.FQName()) - } + comp := make([]string, 0) - if !item.State.Tainted { - return fmt.Sprintf("# %s is not tainted", item.FQName()) - } - - if len(item.State.TaintedBy) == 0 { - return fmt.Sprintf("# %s is tainted but we don't know why. please report this as a bug", item.FQName()) - } - - ret := []string{ - fmt.Sprintf("# Let's see why %s is tainted.", item.FQName()), - } - - for _, fqsub := range item.State.TaintedBy { - ret = append(ret, fmt.Sprintf("\n-> %s\n", fqsub)) - - sub, err := hub.GetItemFQ(fqsub) - if err != nil { - ret = append(ret, err.Error()) - } - - diff, err := cli.itemDiff(ctx, sub, contentProvider, reverse) - if err != nil { - ret = append(ret, err.Error()) - } - - if diff != "" { - ret = append(ret, diff) - } else if len(sub.State.TaintedBy) > 0 { - taintList := strings.Join(sub.State.TaintedBy, ", ") - if sub.FQName() == taintList { - // hack: avoid message "item is tainted by itself" - continue - } - - ret = append(ret, fmt.Sprintf("# %s is tainted by %s", sub.FQName(), taintList)) + for _, item := range items { + if strings.Contains(item.Name, toComplete) { + comp = append(comp, item.Name) } } - return strings.Join(ret, "\n") + cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true) + + return comp, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/crowdsec-cli/cliitem/parser.go b/cmd/crowdsec-cli/cliitem/parser.go deleted file mode 100644 index bc1d96bdaf0..00000000000 --- a/cmd/crowdsec-cli/cliitem/parser.go +++ /dev/null @@ -1,41 +0,0 @@ -package cliitem - -import ( - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func NewParser(cfg configGetter) *cliItem { - return &cliItem{ - cfg: cfg, - name: cwhub.PARSERS, - singular: "parser", - oneOrMore: "parser(s)", - help: cliHelp{ - example: `cscli parsers list -a -cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs -cscli parsers inspect crowdsecurity/caddy-logs crowdsecurity/sshd-logs -cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs -cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs -`, - }, - installHelp: cliHelp{ - example: `cscli parsers install crowdsecurity/caddy-logs crowdsecurity/sshd-logs`, - }, - removeHelp: cliHelp{ - example: `cscli parsers remove crowdsecurity/caddy-logs crowdsecurity/sshd-logs`, - }, - upgradeHelp: cliHelp{ - example: `cscli parsers upgrade crowdsecurity/caddy-logs crowdsecurity/sshd-logs`, - }, - inspectHelp: cliHelp{ - example: `cscli parsers inspect crowdsecurity/httpd-logs crowdsecurity/sshd-logs`, - }, - listHelp: cliHelp{ - example: `cscli parsers list -cscli parsers list -a -cscli parsers list crowdsecurity/caddy-logs crowdsecurity/sshd-logs - -List only enabled parsers unless "-a" or names are specified.`, - }, - } -} diff --git a/cmd/crowdsec-cli/cliitem/postoverflow.go b/cmd/crowdsec-cli/cliitem/postoverflow.go deleted file mode 100644 index ea53aef327d..00000000000 --- a/cmd/crowdsec-cli/cliitem/postoverflow.go +++ /dev/null @@ -1,41 +0,0 @@ -package cliitem - -import ( - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -func NewPostOverflow(cfg configGetter) *cliItem { - return &cliItem{ - cfg: cfg, - name: cwhub.POSTOVERFLOWS, - singular: "postoverflow", - oneOrMore: "postoverflow(s)", - help: cliHelp{ - example: `cscli postoverflows list -a -cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns -cscli postoverflows inspect crowdsecurity/cdn-whitelist crowdsecurity/rdns -cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdns -cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns -`, - }, - installHelp: cliHelp{ - example: `cscli postoverflows install crowdsecurity/cdn-whitelist crowdsecurity/rdns`, - }, - removeHelp: cliHelp{ - example: `cscli postoverflows remove crowdsecurity/cdn-whitelist crowdsecurity/rdns`, - }, - upgradeHelp: cliHelp{ - example: `cscli postoverflows upgrade crowdsecurity/cdn-whitelist crowdsecurity/rdns`, - }, - inspectHelp: cliHelp{ - example: `cscli postoverflows inspect crowdsecurity/cdn-whitelist crowdsecurity/rdns`, - }, - listHelp: cliHelp{ - example: `cscli postoverflows list -cscli postoverflows list -a -cscli postoverflows list crowdsecurity/cdn-whitelist crowdsecurity/rdns - -List only enabled postoverflows unless "-a" or names are specified.`, - }, - } -} diff --git a/cmd/crowdsec-cli/cliitem/suggest.go b/cmd/crowdsec-cli/cliitem/suggest.go deleted file mode 100644 index b0f19b6993c..00000000000 --- a/cmd/crowdsec-cli/cliitem/suggest.go +++ /dev/null @@ -1,77 +0,0 @@ -package cliitem - -import ( - "fmt" - "slices" - "strings" - - "github.com/agext/levenshtein" - "github.com/spf13/cobra" - - "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" - "github.com/crowdsecurity/crowdsec/pkg/cwhub" -) - -// suggestNearestMessage returns a message with the most similar item name, if one is found -func suggestNearestMessage(hub *cwhub.Hub, itemType string, itemName string) string { - const maxDistance = 7 - - score := 100 - nearest := "" - - for _, item := range hub.GetItemsByType(itemType, false) { - d := levenshtein.Distance(itemName, item.Name, nil) - if d < score { - score = d - nearest = item.Name - } - } - - msg := fmt.Sprintf("can't find '%s' in %s", itemName, itemType) - - if score < maxDistance { - msg += fmt.Sprintf(", did you mean '%s'?", nearest) - } - - return msg -} - -func compAllItems(itemType string, args []string, toComplete string, cfg configGetter) ([]string, cobra.ShellCompDirective) { - hub, err := require.Hub(cfg(), nil) - if err != nil { - return nil, cobra.ShellCompDirectiveDefault - } - - comp := make([]string, 0) - - for _, item := range hub.GetItemsByType(itemType, false) { - if !slices.Contains(args, item.Name) && strings.Contains(item.Name, toComplete) { - comp = append(comp, item.Name) - } - } - - cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true) - - return comp, cobra.ShellCompDirectiveNoFileComp -} - -func compInstalledItems(itemType string, args []string, toComplete string, cfg configGetter) ([]string, cobra.ShellCompDirective) { - hub, err := require.Hub(cfg(), nil) - if err != nil { - return nil, cobra.ShellCompDirectiveDefault - } - - items := hub.GetInstalledByType(itemType, true) - - comp := make([]string, 0) - - for _, item := range items { - if strings.Contains(item.Name, toComplete) { - comp = append(comp, item.Name) - } - } - - cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true) - - return comp, cobra.ShellCompDirectiveNoFileComp -}