From 842e86438410eb49067a8b322bcfa69b8920fe03 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 10:37:04 -0600 Subject: [PATCH] refactor: updated description/example and added 'secrets-file' flag to cmd --- cmd/collect.go | 48 +++++++++----- cmd/crawl.go | 4 +- cmd/root.go | 1 + cmd/secrets.go | 174 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 cmd/secrets.go diff --git a/cmd/collect.go b/cmd/collect.go index dbb465c..68dad7b 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -29,7 +29,11 @@ var CollectCmd = &cobra.Command{ "See the 'scan' command on how to perform a scan.\n\n" + "Examples:\n" + " magellan collect --cache ./assets.db --output ./logs --timeout 30 --cacert cecert.pem\n" + - " magellan collect --host smd.example.com --port 27779 --username username --password password", + " magellan collect --host smd.example.com --port 27779 --username $username --password $password\n\n" + + // example using `collect` + " export MASTER_KEY=$(magellan secrets generatekey)\n" + + " magellan secrets store $node_creds_json -f nodes.json" + + " magellan collect --host openchami.cluster --username $username --password $password \\\n", Run: func(cmd *cobra.Command, args []string) { // get probe states stored in db from scan scannedResults, err := sqlite.GetScannedAssets(cachePath) @@ -52,17 +56,13 @@ var CollectCmd = &cobra.Command{ } } - if verbose { - log.Debug().Str("Access Token", accessToken) - } - - // + // set the minimum/maximum number of concurrent processes if concurrency <= 0 { concurrency = mathutil.Clamp(len(scannedResults), 1, 10000) } - // Create a StaticSecretStore to hold the username and password - secrets := secrets.NewStaticStore(username, password) - _, err = magellan.CollectInventory(&scannedResults, &magellan.CollectParams{ + + // set the collect parameters from CLI params + params := &magellan.CollectParams{ URI: host, Timeout: timeout, Concurrency: concurrency, @@ -71,9 +71,26 @@ var CollectCmd = &cobra.Command{ OutputPath: outputPath, ForceUpdate: forceUpdate, AccessToken: accessToken, - }, secrets) + } + + // show all of the 'collect' parameters being set from CLI if verbose + if verbose { + log.Debug().Any("params", params) + } + + // load the secrets file to get node credentials by ID (i.e. the BMC node's URI) + store, err := secrets.OpenStore(params.SecretsFile) if err != nil { - log.Error().Err(err).Msgf("failed to collect data") + // Something went wrong with the store so try using + // Create a StaticSecretStore to hold the username and password + fmt.Println(err) + store = secrets.NewStaticStore(username, password) + } else { + } + + _, err = magellan.CollectInventory(&scannedResults, params, store) + if err != nil { + log.Error().Err(err).Msg("failed to collect data") } }, } @@ -81,13 +98,14 @@ var CollectCmd = &cobra.Command{ func init() { currentUser, _ = user.Current() CollectCmd.PersistentFlags().StringVar(&host, "host", "", "Set the URI to the SMD root endpoint") - CollectCmd.PersistentFlags().StringVar(&username, "username", "", "Set the BMC user") - CollectCmd.PersistentFlags().StringVar(&password, "password", "", "Set the BMC password") - CollectCmd.PersistentFlags().StringVar(&scheme, "scheme", "https", "Set the scheme used to query") + CollectCmd.PersistentFlags().StringVar(&username, "username", "", "Set the master BMC username") + CollectCmd.PersistentFlags().StringVar(&password, "password", "", "Set the master BMC password") + CollectCmd.PersistentFlags().StringVar(&secretsFile, "secrets-file", "", "Set path to the node secrets file") + CollectCmd.PersistentFlags().StringVar(&scheme, "scheme", "https", "Set the default scheme used to query when not included in URI") CollectCmd.PersistentFlags().StringVar(&protocol, "protocol", "tcp", "Set the protocol used to query") CollectCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", fmt.Sprintf("/tmp/%smagellan/inventory/", currentUser.Username+"/"), "Set the path to store collection data") CollectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", false, "Set flag to force update data sent to SMD") - CollectCmd.PersistentFlags().StringVar(&cacertPath, "cacert", "", "Path to CA cert. (defaults to system CAs)") + CollectCmd.PersistentFlags().StringVar(&cacertPath, "cacert", "", "Set the path to CA cert file. (defaults to system CAs when blank)") // set flags to only be used together CollectCmd.MarkFlagsRequiredTogether("username", "password") diff --git a/cmd/crawl.go b/cmd/crawl.go index e9e91bd..55757ce 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -18,8 +18,8 @@ import ( var CrawlCmd = &cobra.Command{ Use: "crawl [uri]", Short: "Crawl a single BMC for inventory information", - Long: "Crawl a single BMC for inventory information. This command does NOT store information\n" + - "about the scan into cache after completion. To do so, use the 'collect' command instead\n\n" + + Long: "Crawl a single BMC for inventory information with URI. This command does NOT scan subnets nor store scan information\n" + + "in cache after completion. To do so, use the 'collect' command instead\n\n" + "Examples:\n" + " magellan crawl https://bmc.example.com\n" + " magellan crawl https://bmc.example.com -i -u username -p password", diff --git a/cmd/root.go b/cmd/root.go index 778e37f..3a470e9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,6 +38,7 @@ var ( cacertPath string username string password string + secretsFile string cachePath string outputPath string configPath string diff --git a/cmd/secrets.go b/cmd/secrets.go new file mode 100644 index 0000000..2ca718c --- /dev/null +++ b/cmd/secrets.go @@ -0,0 +1,174 @@ +package cmd + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var secretsCmd = &cobra.Command{ + Use: "secrets", + Short: "Manage credentials for BMC nodes", + Long: "Manage credentials for BMC nodes to for querying information through redfish. This requires generating a key and setting the 'MASTER_KEY' environment variable for the secrets store.\n" + + "Examples:\n\n" + + " export MASTER_KEY=$(magellan secrets generatekey)\n" + + // store specific BMC node creds for `collect` and `crawl` in default secrets store (`--file/-f`` flag not set) + " magellan secrets store $bmc_host $bmc_creds" + + // retrieve creds from secrets store + " magellan secrets retrieve $bmc_host -f nodes.json" + + // list creds from specific secrets + " magellan secrets list -f nodes.json", + Run: func(cmd *cobra.Command, args []string) { + // show command help and exit + if len(args) < 1 { + cmd.Help() + os.Exit(0) + } + }, +} + +var secretsGenerateKeyCmd = &cobra.Command{ + Use: "generatekey", + Short: "Generates a new 32-byte master key (in hex).", + Run: func(cmd *cobra.Command, args []string) { + key, err := secrets.GenerateMasterKey() + if err != nil { + fmt.Printf("Error generating master key: %v\n", err) + os.Exit(1) + } + fmt.Printf("%s\n", key) + }, +} + +var secretsStoreCmd = &cobra.Command{ + Use: "store secretID secretValue", + Args: cobra.ExactArgs(2), + Short: "Stores the given string value under secretID.", + Run: func(cmd *cobra.Command, args []string) { + var ( + secretID = args[0] + secretValue = args[1] + ) + + store, err := secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := store.StoreSecretByID(secretID, secretValue); err != nil { + fmt.Printf("Error storing secret: %v\n", err) + os.Exit(1) + } + fmt.Println("Secret stored successfully.") + }, +} + +var secretsStoreBase64Cmd = &cobra.Command{ + Use: "storebase64 base64String", + Args: cobra.ExactArgs(1), + Short: "Decodes the base64-encoded string before storing.", + Run: func(cmd *cobra.Command, args []string) { + if len(os.Args) < 4 { + fmt.Println("Not enough arguments. Usage: go run main.go storebase64 [filename]") + os.Exit(1) + } + secretID := os.Args[2] + base64Value := os.Args[3] + filename := "mysecrets.json" + if len(os.Args) == 5 { + filename = os.Args[4] + } + + decoded, err := base64.StdEncoding.DecodeString(base64Value) + if err != nil { + fmt.Printf("Error decoding base64 data: %v\n", err) + os.Exit(1) + } + + store, err := secrets.OpenStore(filename) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := store.StoreSecretByID(secretID, string(decoded)); err != nil { + fmt.Printf("Error storing base64-decoded secret: %v\n", err) + os.Exit(1) + } + fmt.Println("Base64-decoded secret stored successfully.") + }, +} + +var secretsRetrieveCmd = &cobra.Command{ + Use: "retrieve secretID", + Run: func(cmd *cobra.Command, args []string) { + if len(os.Args) < 3 { + fmt.Println("Not enough arguments. Usage: go run main.go retrieve [filename]") + os.Exit(1) + } + secretID := os.Args[2] + + store, err := secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + secretValue, err := store.GetSecretByID(secretID) + if err != nil { + fmt.Printf("Error retrieving secret: %v\n", err) + os.Exit(1) + } + fmt.Printf("Secret for %s: %s\n", secretID, secretValue) + }, +} + +var secretsListCmd = &cobra.Command{ + Use: "list", + Short: "Lists all the secret IDs and their values.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + fmt.Println("Not enough arguments. Usage: go run main.go list [filename]") + os.Exit(1) + } + + store, err := secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + secrets, err := store.ListSecrets() + if err != nil { + fmt.Printf("Error listing secrets: %v\n", err) + os.Exit(1) + } + + fmt.Println("Secrets:") + for key, value := range secrets { + fmt.Printf("%s: %s\n", key, value) + } + }, +} + +func init() { + secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "") + + secretsCmd.AddCommand(secretsGenerateKeyCmd) + secretsCmd.AddCommand(secretsStoreCmd) + secretsCmd.AddCommand(secretsStoreBase64Cmd) + secretsCmd.AddCommand(secretsRetrieveCmd) + secretsCmd.AddCommand(secretsListCmd) + + checkBindFlagError(viper.BindPFlags(secretsCmd.Flags())) + checkBindFlagError(viper.BindPFlags(secretsGenerateKeyCmd.Flags())) + checkBindFlagError(viper.BindPFlags(secretsStoreCmd.Flags())) + checkBindFlagError(viper.BindPFlags(secretsGenerateKeyCmd.Flags())) + checkBindFlagError(viper.BindPFlags(secretsGenerateKeyCmd.Flags())) + checkBindFlagError(viper.BindPFlags(secretsGenerateKeyCmd.Flags())) +}