Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ config.yaml) each time the container is run.
| `ENROLL_INSTANCE_NAME` | | To set an instance name and see it on [the console](https://app.crowdsec.net/) |
| `ENROLL_TAGS` | | Tags of the enrolled instance, for search and filter |
| `ENABLE_CONSOLE_ALL` | | Enable all console options |
| `ENABLE_CONSOLE_MANAGEMENT` | | Enable console management (this option is ignored since 1.6.9) |
| `ENABLE_CONSOLE_MANAGEMENT` | | Deprecated and ignored: decision management is enabled automatically based on your console plan |
| `ENABLE_CONSOLE_CONTEXT` | | Send alert context to the console (automatically enabled for enrolled instances) |
| `ENABLE_CONSOLE_TAINTED` | | Send tainted alerts (from modified scenarios) to the console (automatically enabled for enrolled instances) |
| `ENABLE_CONSOLE_MANUAL` | | Send manual alerts (`cscli decisions add`) to the console (automatically enabled for enrolled instances) |
Expand Down
2 changes: 1 addition & 1 deletion build/docker/docker_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ if [ "$ENABLE_CONSOLE_ALL" != "" ]; then
else
CONSOLE_FLAGS=""
if [ "$ENABLE_CONSOLE_MANAGEMENT" != "" ]; then
CONSOLE_FLAGS="$CONSOLE_FLAGS console_management"
echo "WARNING: ENABLE_CONSOLE_MANAGEMENT is deprecated and ignored; decision management is enabled automatically based on your console plan." >&2
fi
if [ "$ENABLE_CONSOLE_CONTEXT" != "" ]; then
CONSOLE_FLAGS="$CONSOLE_FLAGS context"
Expand Down
65 changes: 5 additions & 60 deletions cmd/crowdsec-cli/clicapi/capi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import (
"gopkg.in/yaml.v3"

"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/args"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/consolestatus"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/idgen"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/reload"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/require"
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/database"
"github.com/crowdsecurity/crowdsec/pkg/models"
)

var CAPIBaseURL = "https://api.crowdsec.net/"
Expand Down Expand Up @@ -158,61 +158,6 @@ func (cli *cliCapi) newRegisterCmd() *cobra.Command {
return cmd
}

type capiStatus struct {
authenticated bool
enrolled bool
subscriptionType string
}

// queryCAPIStatus checks if the Central API is reachable, and if the credentials are correct. It then checks if the instance is enrolled in the console.
func queryCAPIStatus(ctx context.Context, db *database.Client, hub *cwhub.Hub, credURL string, login string, password string) (capiStatus, error) {
apiURL, err := url.Parse(credURL)
if err != nil {
return capiStatus{}, err
}

itemsForAPI := hub.GetInstalledListForAPI()

passwd := strfmt.Password(password)

client := apiclient.NewClient(&apiclient.Config{
MachineID: login,
Password: passwd,
URL: apiURL,
// I don't believe papi is needed to check enrollement
// PapiURL: papiURL,
VersionPrefix: "v3",
UpdateScenario: func(_ context.Context) ([]string, error) {
return itemsForAPI, nil
},
})

pw := strfmt.Password(password)

t := models.WatcherAuthRequest{
MachineID: &login,
Password: &pw,
Scenarios: itemsForAPI,
}

authResp, _, err := client.Auth.AuthenticateWatcher(ctx, t)
if err != nil {
return capiStatus{}, err
}

if err := db.SaveAPICToken(ctx, authResp.Token); err != nil {
return capiStatus{}, err
}

client.GetClient().Transport.(*apiclient.JWTTransport).Token = authResp.Token

if client.IsEnrolled() {
return capiStatus{true, true, client.GetSubscriptionType()}, nil
}

return capiStatus{true, false, ""}, nil
}

func (cli *cliCapi) Status(ctx context.Context, db *database.Client, out io.Writer, hub *cwhub.Hub) error {
cfg := cli.cfg()

Expand All @@ -225,18 +170,18 @@ func (cli *cliCapi) Status(ctx context.Context, db *database.Client, out io.Writ
fmt.Fprintf(out, "Loaded credentials from %s\n", cfg.API.Server.OnlineClient.CredentialsFilePath)
fmt.Fprintf(out, "Trying to authenticate with username %s on %s\n", cred.Login, cred.URL)

status, err := queryCAPIStatus(ctx, db, hub, cred.URL, cred.Login, cred.Password)
status, err := consolestatus.QueryCAPIStatus(ctx, db, hub, cred.URL, cred.Login, cred.Password)
if err != nil {
return fmt.Errorf("failed to authenticate to Central API (CAPI): %w", err)
}

if status.authenticated {
if status.Authenticated {
fmt.Fprint(out, "You can successfully interact with Central API (CAPI)\n")
}

if status.enrolled {
if status.Enrolled {
fmt.Fprint(out, "Your instance is enrolled in the console\n")
fmt.Fprintf(out, "Subscription type: %s\n", status.subscriptionType)
fmt.Fprintf(out, "Subscription type: %s\n", status.SubscriptionType)
}

switch *cfg.API.Server.OnlineClient.Sharing {
Expand Down
158 changes: 117 additions & 41 deletions cmd/crowdsec-cli/cliconsole/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/crowdsecurity/go-cs-lib/slicetools"

"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/args"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/consolestatus"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/reload"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/require"
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
Expand Down Expand Up @@ -140,6 +141,11 @@ func optionFilterEnable(opts []string, enableOpts []string) ([]string, error) {
continue
}

if opt == csconfig.CONSOLE_MANAGEMENT {
log.Warnf("'%s' is deprecated and has no effect: decision management is enabled automatically based on your console plan", csconfig.CONSOLE_MANAGEMENT)
continue
}

if !slices.Contains(csconfig.CONSOLE_CONFIGS, opt) {
return nil, fmt.Errorf("option %s doesn't exist", opt)
}
Expand All @@ -164,6 +170,11 @@ func optionFilterDisable(opts []string, disableOpts []string) ([]string, error)
continue
}

if opt == csconfig.CONSOLE_MANAGEMENT {
log.Warnf("'%s' is deprecated and has no effect: decision management is enabled automatically based on your console plan", csconfig.CONSOLE_MANAGEMENT)
continue
}

if !slices.Contains(csconfig.CONSOLE_CONFIGS, opt) {
return nil, fmt.Errorf("option %s doesn't exist", opt)
}
Expand Down Expand Up @@ -216,7 +227,6 @@ cscli console enroll --quick
cscli console enroll --quick --name [instance_name]
cscli console enroll --name [instance_name] YOUR-ENROLL-KEY
cscli console enroll --name [instance_name] --tags [tag_1] --tags [tag_2] YOUR-ENROLL-KEY
cscli console enroll --enable console_management YOUR-ENROLL-KEY
cscli console enroll --disable context YOUR-ENROLL-KEY

valid options are : %s,all (see 'cscli console status' for details)`, strings.Join(csconfig.CONSOLE_CONFIGS, ",")),
Expand Down Expand Up @@ -344,27 +354,124 @@ Disable given information push to the central API.`,
return cmd
}

type liveConsoleStatus struct {
capi consolestatus.CAPIStatus
registered bool
reachable bool
decisionManagement bool
papi *consolestatus.PAPIInfo
}

// fetchConsoleStatus queries CAPI (and PAPI when enrolled) for the live console link.
func (*cliConsole) fetchConsoleStatus(ctx context.Context, cfg *csconfig.Config) liveConsoleStatus {
st := liveConsoleStatus{}

online := cfg.API.Server.OnlineClient

// load credz here to gracefully handle missing/invalid file.
if online == nil || online.CredentialsFilePath == "" {
return st
}

if err := online.Load(); err != nil {
log.Warnf("could not load CAPI credentials: %s", err)
return st
}

if online.Credentials == nil {
return st
}

st.registered = true

hub, err := require.Hub(cfg, nil)
if err != nil {
log.Warnf("could not load hub, skipping live console status: %s", err)
return st
}

db, err := require.DBClient(ctx, cfg.DbConfig)
if err != nil {
log.Warnf("could not connect to database, skipping live console status: %s", err)
return st
}

cred := online.Credentials

capi, err := consolestatus.QueryCAPIStatus(ctx, db, hub, cred.URL, cred.Login, cred.Password)
if err != nil {
log.Warnf("could not reach Central API (CAPI): %s", err)
return st
}
Comment thread
buixor marked this conversation as resolved.

st.capi = capi
st.reachable = true

if !capi.Enrolled {
return st
}

st.decisionManagement = consolestatus.DecisionManagementActive(capi.SubscriptionType)

papi, err := consolestatus.QueryPAPIInfo(ctx, cfg.API.Server, db)
if err != nil {
log.Debugf("could not reach Polling API (PAPI): %s", err)
return st
}

st.papi = &papi

return st
}

func (cli *cliConsole) newStatusCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Shows status of the console options",
Example: `sudo cscli console status`,
Args: args.NoArgs,
DisableAutoGenTag: true,
RunE: func(_ *cobra.Command, _ []string) error {
// Unlike the other console subcommands, status must run even when the engine is not
// registered against CAPI or can't reach it.
// We skip loading online credentials here (they're loaded best-effort in
// fetchConsoleStatus). This overrides the parent's stricter PersistentPreRunE.
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
return require.LAPINoOnlineCreds(cli.cfg())
},
RunE: func(cmd *cobra.Command, _ []string) error {
cfg := cli.cfg()
ctx := cmd.Context()
consoleCfg := cfg.API.Server.ConsoleConfig

switch cfg.Cscli.Output {
case "human":
st := cli.fetchConsoleStatus(ctx, cfg)
cmdConsoleConnectionTable(color.Output, cfg.Cscli.Color, st)
cmdConsoleStatusTable(color.Output, cfg.Cscli.Color, *consoleCfg)
case "json":
out := map[string]*bool{
csconfig.SEND_MANUAL_SCENARIOS: consoleCfg.ShareManualDecisions,
csconfig.SEND_CUSTOM_SCENARIOS: consoleCfg.ShareCustomScenarios,
csconfig.SEND_TAINTED_SCENARIOS: consoleCfg.ShareTaintedScenarios,
csconfig.SEND_CONTEXT: consoleCfg.ShareContext,
csconfig.CONSOLE_MANAGEMENT: consoleCfg.ConsoleManagement,
st := cli.fetchConsoleStatus(ctx, cfg)

console := map[string]any{
"registered": st.registered,
"authenticated": st.reachable,
"enrolled": st.capi.Enrolled,
"plan": st.capi.SubscriptionType,
"decision_management": st.decisionManagement,
}

if st.papi != nil {
console["last_order_received"] = st.papi.LastOrder
console["papi_categories"] = st.papi.Categories
}

out := map[string]any{
"sharing_options": map[string]*bool{
csconfig.SEND_MANUAL_SCENARIOS: consoleCfg.ShareManualDecisions,
csconfig.SEND_CUSTOM_SCENARIOS: consoleCfg.ShareCustomScenarios,
csconfig.SEND_TAINTED_SCENARIOS: consoleCfg.ShareTaintedScenarios,
csconfig.SEND_CONTEXT: consoleCfg.ShareContext,
},
"console": console,
}

data, err := json.MarshalIndent(out, "", " ")
Expand All @@ -386,7 +493,6 @@ func (cli *cliConsole) newStatusCmd() *cobra.Command {
{csconfig.SEND_CUSTOM_SCENARIOS, strconv.FormatBool(*consoleCfg.ShareCustomScenarios)},
{csconfig.SEND_TAINTED_SCENARIOS, strconv.FormatBool(*consoleCfg.ShareTaintedScenarios)},
{csconfig.SEND_CONTEXT, strconv.FormatBool(*consoleCfg.ShareContext)},
{csconfig.CONSOLE_MANAGEMENT, strconv.FormatBool(*consoleCfg.ConsoleManagement)},
}
for _, row := range rows {
err = csvwriter.Write(row)
Expand Down Expand Up @@ -432,38 +538,8 @@ func (cli *cliConsole) setConsoleOpts(args []string, wanted bool) error {
for _, arg := range args {
switch arg {
case csconfig.CONSOLE_MANAGEMENT:
// for each flag check if it's already set before setting it
if consoleCfg.ConsoleManagement != nil && *consoleCfg.ConsoleManagement == wanted {
log.Debugf("%s already set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
} else {
log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
consoleCfg.ConsoleManagement = new(wanted)
}

if cfg.API.Server.OnlineClient.Credentials != nil {
changed := false
if wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL == "" {
changed = true
cfg.API.Server.OnlineClient.Credentials.PapiURL = csconfig.PAPIBaseURL
} else if !wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL != "" {
changed = true
cfg.API.Server.OnlineClient.Credentials.PapiURL = ""
}

if changed {
fileContent, err := yaml.Marshal(cfg.API.Server.OnlineClient.Credentials)
if err != nil {
return fmt.Errorf("cannot serialize credentials: %w", err)
}

log.Infof("Updating credentials file: %s", cfg.API.Server.OnlineClient.CredentialsFilePath)

err = os.WriteFile(cfg.API.Server.OnlineClient.CredentialsFilePath, fileContent, 0o600)
if err != nil {
return fmt.Errorf("cannot write credentials file: %w", err)
}
}
}
// deprecated no-op: decision management is now enabled automatically based on the plan
log.Warnf("'%s' is deprecated and has no effect: decision management is enabled automatically based on your console plan", csconfig.CONSOLE_MANAGEMENT)
case csconfig.SEND_CUSTOM_SCENARIOS:
// for each flag check if it's already set before setting it
if consoleCfg.ShareCustomScenarios != nil && *consoleCfg.ShareCustomScenarios == wanted {
Expand Down
Loading
Loading