-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
656 additions
and
585 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package climachine | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"github.com/go-openapi/strfmt" | ||
"github.com/spf13/cobra" | ||
"gopkg.in/yaml.v3" | ||
|
||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/idgen" | ||
"github.com/crowdsecurity/crowdsec/pkg/csconfig" | ||
"github.com/crowdsecurity/crowdsec/pkg/types" | ||
) | ||
|
||
func (cli *cliMachines) add(ctx context.Context, args []string, machinePassword string, dumpFile string, apiURL string, interactive bool, autoAdd bool, force bool) error { | ||
var ( | ||
err error | ||
machineID string | ||
) | ||
|
||
// create machineID if not specified by user | ||
if len(args) == 0 { | ||
if !autoAdd { | ||
return errors.New("please specify a machine name to add, or use --auto") | ||
} | ||
|
||
machineID, err = idgen.GenerateMachineID("") | ||
if err != nil { | ||
return fmt.Errorf("unable to generate machine id: %w", err) | ||
} | ||
} else { | ||
machineID = args[0] | ||
} | ||
|
||
clientCfg := cli.cfg().API.Client | ||
serverCfg := cli.cfg().API.Server | ||
|
||
/*check if file already exists*/ | ||
if dumpFile == "" && clientCfg != nil && clientCfg.CredentialsFilePath != "" { | ||
credFile := clientCfg.CredentialsFilePath | ||
// use the default only if the file does not exist | ||
_, err = os.Stat(credFile) | ||
|
||
switch { | ||
case os.IsNotExist(err) || force: | ||
dumpFile = credFile | ||
case err != nil: | ||
return fmt.Errorf("unable to stat '%s': %w", credFile, err) | ||
default: | ||
return fmt.Errorf(`credentials file '%s' already exists: please remove it, use "--force" or specify a different file with "-f" ("-f -" for standard output)`, credFile) | ||
} | ||
} | ||
|
||
if dumpFile == "" { | ||
return errors.New(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`) | ||
} | ||
|
||
// create a password if it's not specified by user | ||
if machinePassword == "" && !interactive { | ||
if !autoAdd { | ||
return errors.New("please specify a password with --password or use --auto") | ||
} | ||
|
||
machinePassword = idgen.GeneratePassword(idgen.PasswordLength) | ||
} else if machinePassword == "" && interactive { | ||
qs := &survey.Password{ | ||
Message: "Please provide a password for the machine:", | ||
} | ||
survey.AskOne(qs, &machinePassword) | ||
} | ||
|
||
password := strfmt.Password(machinePassword) | ||
|
||
_, err = cli.db.CreateMachine(ctx, &machineID, &password, "", true, force, types.PasswordAuthType) | ||
if err != nil { | ||
return fmt.Errorf("unable to create machine: %w", err) | ||
} | ||
|
||
fmt.Fprintf(os.Stderr, "Machine '%s' successfully added to the local API.\n", machineID) | ||
|
||
if apiURL == "" { | ||
if clientCfg != nil && clientCfg.Credentials != nil && clientCfg.Credentials.URL != "" { | ||
apiURL = clientCfg.Credentials.URL | ||
} else if serverCfg.ClientURL() != "" { | ||
apiURL = serverCfg.ClientURL() | ||
} else { | ||
return errors.New("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") | ||
} | ||
} | ||
|
||
apiCfg := csconfig.ApiCredentialsCfg{ | ||
Login: machineID, | ||
Password: password.String(), | ||
URL: apiURL, | ||
} | ||
|
||
apiConfigDump, err := yaml.Marshal(apiCfg) | ||
if err != nil { | ||
return fmt.Errorf("unable to serialize api credentials: %w", err) | ||
} | ||
|
||
if dumpFile != "" && dumpFile != "-" { | ||
if err = os.WriteFile(dumpFile, apiConfigDump, 0o600); err != nil { | ||
return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err) | ||
} | ||
|
||
fmt.Fprintf(os.Stderr, "API credentials written to '%s'.\n", dumpFile) | ||
} else { | ||
fmt.Print(string(apiConfigDump)) | ||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by an access to Password Error loading related location Loading |
||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cli *cliMachines) newAddCmd() *cobra.Command { | ||
var ( | ||
password MachinePassword | ||
dumpFile string | ||
apiURL string | ||
interactive bool | ||
autoAdd bool | ||
force bool | ||
) | ||
|
||
cmd := &cobra.Command{ | ||
Use: "add", | ||
Short: "add a single machine to the database", | ||
DisableAutoGenTag: true, | ||
Long: `Register a new machine in the database. cscli should be on the same machine as LAPI.`, | ||
Example: `cscli machines add --auto | ||
cscli machines add MyTestMachine --auto | ||
cscli machines add MyTestMachine --password MyPassword | ||
cscli machines add -f- --auto > /tmp/mycreds.yaml`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return cli.add(cmd.Context(), args, string(password), dumpFile, apiURL, interactive, autoAdd, force) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
flags.VarP(&password, "password", "p", "machine password to login to the API") | ||
flags.StringVarP(&dumpFile, "file", "f", "", "output file destination (defaults to "+csconfig.DefaultConfigPath("local_api_credentials.yaml")+")") | ||
flags.StringVarP(&apiURL, "url", "u", "", "URL of the local API") | ||
flags.BoolVarP(&interactive, "interactive", "i", false, "interfactive mode to enter the password") | ||
flags.BoolVarP(&autoAdd, "auto", "a", false, "automatically generate password (and username if not provided)") | ||
flags.BoolVar(&force, "force", false, "will force add the machine if it already exist") | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package climachine | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
log "github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/crowdsecurity/crowdsec/pkg/database" | ||
) | ||
|
||
func (cli *cliMachines) delete(ctx context.Context, machines []string, ignoreMissing bool) error { | ||
for _, machineID := range machines { | ||
if err := cli.db.DeleteWatcher(ctx, machineID); err != nil { | ||
var notFoundErr *database.MachineNotFoundError | ||
if ignoreMissing && errors.As(err, ¬FoundErr) { | ||
return nil | ||
} | ||
|
||
log.Errorf("unable to delete machine: %s", err) | ||
|
||
return nil | ||
} | ||
|
||
log.Infof("machine '%s' deleted successfully", machineID) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cli *cliMachines) newDeleteCmd() *cobra.Command { | ||
var ignoreMissing bool | ||
|
||
cmd := &cobra.Command{ | ||
Use: "delete [machine_name]...", | ||
Short: "delete machine(s) by name", | ||
Example: `cscli machines delete "machine1" "machine2"`, | ||
Args: cobra.MinimumNArgs(1), | ||
Aliases: []string{"remove"}, | ||
DisableAutoGenTag: true, | ||
ValidArgsFunction: cli.validMachineID, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return cli.delete(cmd.Context(), args, ignoreMissing) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
flags.BoolVar(&ignoreMissing, "ignore-missing", false, "don't print errors if one or more machines don't exist") | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package climachine | ||
|
||
import ( | ||
"encoding/csv" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/fatih/color" | ||
"github.com/jedib0t/go-pretty/v6/table" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clientinfo" | ||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cstable" | ||
"github.com/crowdsecurity/crowdsec/pkg/cwhub" | ||
"github.com/crowdsecurity/crowdsec/pkg/database/ent" | ||
) | ||
|
||
func (cli *cliMachines) inspectHubHuman(out io.Writer, machine *ent.Machine) { | ||
state := machine.Hubstate | ||
|
||
if len(state) == 0 { | ||
fmt.Println("No hub items found for this machine") | ||
return | ||
} | ||
|
||
// group state rows by type for multiple tables | ||
rowsByType := make(map[string][]table.Row) | ||
|
||
for itemType, items := range state { | ||
for _, item := range items { | ||
if _, ok := rowsByType[itemType]; !ok { | ||
rowsByType[itemType] = make([]table.Row, 0) | ||
} | ||
|
||
row := table.Row{item.Name, item.Status, item.Version} | ||
rowsByType[itemType] = append(rowsByType[itemType], row) | ||
} | ||
} | ||
|
||
for itemType, rows := range rowsByType { | ||
t := cstable.New(out, cli.cfg().Cscli.Color).Writer | ||
t.AppendHeader(table.Row{"Name", "Status", "Version"}) | ||
t.SetTitle(itemType) | ||
t.AppendRows(rows) | ||
io.WriteString(out, t.Render()+"\n") | ||
} | ||
} | ||
|
||
func (cli *cliMachines) inspectHuman(out io.Writer, machine *ent.Machine) { | ||
t := cstable.New(out, cli.cfg().Cscli.Color).Writer | ||
|
||
t.SetTitle("Machine: " + machine.MachineId) | ||
|
||
t.SetColumnConfigs([]table.ColumnConfig{ | ||
{Number: 1, AutoMerge: true}, | ||
}) | ||
|
||
t.AppendRows([]table.Row{ | ||
{"IP Address", machine.IpAddress}, | ||
{"Created At", machine.CreatedAt}, | ||
{"Last Update", machine.UpdatedAt}, | ||
{"Last Heartbeat", machine.LastHeartbeat}, | ||
{"Validated?", machine.IsValidated}, | ||
{"CrowdSec version", machine.Version}, | ||
{"OS", clientinfo.GetOSNameAndVersion(machine)}, | ||
{"Auth type", machine.AuthType}, | ||
}) | ||
|
||
for dsName, dsCount := range machine.Datasources { | ||
t.AppendRow(table.Row{"Datasources", fmt.Sprintf("%s: %d", dsName, dsCount)}) | ||
} | ||
|
||
for _, ff := range clientinfo.GetFeatureFlagList(machine) { | ||
t.AppendRow(table.Row{"Feature Flags", ff}) | ||
} | ||
|
||
for _, coll := range machine.Hubstate[cwhub.COLLECTIONS] { | ||
t.AppendRow(table.Row{"Collections", coll.Name}) | ||
} | ||
|
||
io.WriteString(out, t.Render()+"\n") | ||
} | ||
|
||
func (cli *cliMachines) inspect(machine *ent.Machine) error { | ||
out := color.Output | ||
outputFormat := cli.cfg().Cscli.Output | ||
|
||
switch outputFormat { | ||
case "human": | ||
cli.inspectHuman(out, machine) | ||
case "json": | ||
enc := json.NewEncoder(out) | ||
enc.SetIndent("", " ") | ||
|
||
if err := enc.Encode(newMachineInfo(machine)); err != nil { | ||
return errors.New("failed to serialize") | ||
} | ||
|
||
return nil | ||
default: | ||
return fmt.Errorf("output format '%s' not supported for this command", outputFormat) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cli *cliMachines) inspectHub(machine *ent.Machine) error { | ||
out := color.Output | ||
|
||
switch cli.cfg().Cscli.Output { | ||
case "human": | ||
cli.inspectHubHuman(out, machine) | ||
case "json": | ||
enc := json.NewEncoder(out) | ||
enc.SetIndent("", " ") | ||
|
||
if err := enc.Encode(machine.Hubstate); err != nil { | ||
return errors.New("failed to serialize") | ||
} | ||
|
||
return nil | ||
case "raw": | ||
csvwriter := csv.NewWriter(out) | ||
|
||
err := csvwriter.Write([]string{"type", "name", "status", "version"}) | ||
if err != nil { | ||
return fmt.Errorf("failed to write header: %w", err) | ||
} | ||
|
||
rows := make([][]string, 0) | ||
|
||
for itemType, items := range machine.Hubstate { | ||
for _, item := range items { | ||
rows = append(rows, []string{itemType, item.Name, item.Status, item.Version}) | ||
} | ||
} | ||
|
||
for _, row := range rows { | ||
if err := csvwriter.Write(row); err != nil { | ||
return fmt.Errorf("failed to write raw output: %w", err) | ||
} | ||
} | ||
|
||
csvwriter.Flush() | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cli *cliMachines) newInspectCmd() *cobra.Command { | ||
var showHub bool | ||
|
||
cmd := &cobra.Command{ | ||
Use: "inspect [machine_name]", | ||
Short: "inspect a machine by name", | ||
Example: `cscli machines inspect "machine1"`, | ||
Args: cobra.ExactArgs(1), | ||
DisableAutoGenTag: true, | ||
ValidArgsFunction: cli.validMachineID, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
ctx := cmd.Context() | ||
machineID := args[0] | ||
|
||
machine, err := cli.db.QueryMachineByID(ctx, machineID) | ||
if err != nil { | ||
return fmt.Errorf("unable to read machine data '%s': %w", machineID, err) | ||
} | ||
|
||
if showHub { | ||
return cli.inspectHub(machine) | ||
} | ||
|
||
return cli.inspect(machine) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
|
||
flags.BoolVarP(&showHub, "hub", "H", false, "show hub state") | ||
|
||
return cmd | ||
} |
Oops, something went wrong.