diff --git a/cmd/collect.go b/cmd/collect.go index c3fb35c..05ffd4a 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -7,7 +7,7 @@ import ( "github.com/cznic/mathutil" "github.com/davidallendj/magellan/internal/cache/sqlite" - urlx "github.com/davidallendj/magellan/internal/url" + urlx "github.com/davidallendj/magellan/internal/urlx" magellan "github.com/davidallendj/magellan/pkg" "github.com/davidallendj/magellan/pkg/auth" "github.com/davidallendj/magellan/pkg/bmc" diff --git a/cmd/crawl.go b/cmd/crawl.go index c1acc3a..dffb901 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog/log" - urlx "github.com/davidallendj/magellan/internal/url" + urlx "github.com/davidallendj/magellan/internal/urlx" "github.com/davidallendj/magellan/pkg/bmc" "github.com/davidallendj/magellan/pkg/crawler" "github.com/davidallendj/magellan/pkg/secrets" @@ -101,7 +101,7 @@ func init() { CrawlCmd.Flags().StringVarP(&username, "username", "u", "", "Set the username for the BMC") CrawlCmd.Flags().StringVarP(&password, "password", "p", "", "Set the password for the BMC") CrawlCmd.Flags().BoolVarP(&insecure, "insecure", "i", false, "Ignore SSL errors") - CrawlCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "set the secrets file with BMC credentials") + CrawlCmd.Flags().StringVarP(&secretsFile, "secrets-file", "f", "secrets.json", "set the secrets file with BMC credentials") checkBindFlagError(viper.BindPFlag("crawl.insecure", CrawlCmd.Flags().Lookup("insecure"))) diff --git a/cmd/root.go b/cmd/root.go index fc15150..3014b19 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,8 @@ import ( "os/user" magellan "github.com/davidallendj/magellan/internal" + "github.com/davidallendj/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -147,3 +149,44 @@ func SetDefaults() { viper.SetDefault("update.status", false) } + +func initSecretsStore(uri string) secrets.SecretStore { + var ( + store secrets.SecretStore + err error + ) + if username != "" && password != "" { + // First, try and load credentials from --username and --password if both are set. + log.Debug().Str("id", uri).Msgf("--username and --password specified, using them for BMC credentials") + store = secrets.NewStaticStore(username, password) + } else { + // Alternatively, locate specific credentials (falling back to default) and override those + // with --username or --password if either are passed. + log.Debug().Str("id", uri).Msgf("one or both of --username and --password NOT passed, attempting to obtain missing credentials from secret store at %s", secretsFile) + if store, err = secrets.OpenStore(secretsFile); err != nil { + log.Error().Str("id", uri).Err(err).Msg("failed to open local secrets store") + } + + // Either none of the flags were passed or only one of them were; get + // credentials from secrets store to fill in the gaps. + bmcCreds, _ := bmc.GetBMCCredentials(store, uri) + nodeCreds := secrets.StaticStore{ + Username: bmcCreds.Username, + Password: bmcCreds.Password, + } + + // If either of the flags were passed, override the fetched + // credentials with them. + if username != "" { + log.Info().Str("id", uri).Msg("--username was set, overriding username for this BMC") + nodeCreds.Username = username + } + if password != "" { + log.Info().Str("id", uri).Msg("--password was set, overriding password for this BMC") + nodeCreds.Password = password + } + + store = &nodeCreds + } + return store +} diff --git a/cmd/scan.go b/cmd/scan.go index f25150d..f110914 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -12,7 +12,7 @@ import ( "github.com/rs/zerolog/log" "github.com/cznic/mathutil" - urlx "github.com/davidallendj/magellan/internal/url" + urlx "github.com/davidallendj/magellan/internal/urlx" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/cmd/update.go b/cmd/update.go index c2cc3a4..c537471 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -128,9 +128,7 @@ func init() { checkBindFlagError(viper.BindPFlag("update.username", UpdateCmd.Flags().Lookup("username"))) checkBindFlagError(viper.BindPFlag("update.password", UpdateCmd.Flags().Lookup("password"))) checkBindFlagError(viper.BindPFlag("update.scheme", UpdateCmd.Flags().Lookup("scheme"))) - checkBindFlagError(viper.BindPFlag("update.firmware-url", UpdateCmd.Flags().Lookup("firmware-url"))) - checkBindFlagError(viper.BindPFlag("update.firmware-version", UpdateCmd.Flags().Lookup("firmware-version"))) - checkBindFlagError(viper.BindPFlag("update.component", UpdateCmd.Flags().Lookup("component"))) + checkBindFlagError(viper.BindPFlag("update.firmware-uri", UpdateCmd.Flags().Lookup("firmware-uri"))) checkBindFlagError(viper.BindPFlag("update.status", UpdateCmd.Flags().Lookup("status"))) rootCmd.AddCommand(UpdateCmd) diff --git a/internal/cache/edit/modify.go b/internal/cache/edit/modify.go new file mode 100644 index 0000000..08bf029 --- /dev/null +++ b/internal/cache/edit/modify.go @@ -0,0 +1 @@ +package cache diff --git a/internal/cache/edit/table.go b/internal/cache/edit/table.go new file mode 100644 index 0000000..7759bd7 --- /dev/null +++ b/internal/cache/edit/table.go @@ -0,0 +1,48 @@ +package cache + +import ( + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var baseStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + +type Model struct { + selected int + Table table.Model +} + +func (m Model) Init() tea.Cmd { return nil } + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + // m.Table = m.Table.Width(msg.Width) + // m.Table = m.Table.Height(msg.Height) + case tea.KeyMsg: + switch msg.String() { + case "esc": + if m.Table.Focused() { + m.Table.Blur() + } else { + m.Table.Focus() + } + case "q", "ctrl+c": + return m, tea.Quit + case "enter": + return m, tea.Batch( + tea.Printf("Selected host '%s'", m.Table.SelectedRow()[0]), + ) + } + } + m.Table, cmd = m.Table.Update(msg) + return m, cmd +} + +func (m Model) View() string { + return baseStyle.Render(m.Table.View()) + "\n" +} diff --git a/internal/url/url.go b/internal/url/url.go deleted file mode 100644 index 38f0eed..0000000 --- a/internal/url/url.go +++ /dev/null @@ -1,116 +0,0 @@ -package url - -import ( - "fmt" - "net/url" - "strings" - - "github.com/rs/zerolog/log" -) - -func Sanitize(uri string) (string, error) { - // URL sanitanization for host argument - parsedURI, err := url.ParseRequestURI(uri) - if err != nil { - return "", fmt.Errorf("failed to parse URI: %w", err) - } - // Remove any trailing slashes - parsedURI.Path = strings.TrimSuffix(parsedURI.Path, "/") - // Collapse any doubled slashes - parsedURI.Path = strings.ReplaceAll(parsedURI.Path, "//", "/") - return parsedURI.String(), nil -} - -// FormatHosts() takes a list of hosts and ports and builds full URLs in the -// form of scheme://host:port. If no scheme is provided, it will use "https" by -// default. -// -// Returns a 2D string slice where each slice contains URL host strings for each -// port. The intention is to have all of the URLs for a single host combined into -// a single slice to initiate one goroutine per host, but making request to multiple -// ports. -func FormatHosts(hosts []string, ports []int, scheme string, verbose bool) [][]string { - // format each positional arg as a complete URL - var formattedHosts [][]string - for _, host := range hosts { - uri, err := url.ParseRequestURI(host) - if err != nil { - if verbose { - log.Warn().Msgf("invalid URI parsed: %s", host) - } - continue - } - - // check if scheme is set, if not set it with flag or default value ('https' if flag is not set) - if uri.Scheme == "" { - if scheme != "" { - uri.Scheme = scheme - } else { - // hardcoded assumption - uri.Scheme = "https" - } - } - - // tidy up slashes and update arg with new value - uri.Path = strings.TrimSuffix(uri.Path, "/") - uri.Path = strings.ReplaceAll(uri.Path, "//", "/") - - // for hosts with unspecified ports, add ports to scan from flag - if uri.Port() == "" { - var tmp []string - for _, port := range ports { - uri.Host += fmt.Sprintf(":%d", port) - tmp = append(tmp, uri.String()) - } - formattedHosts = append(formattedHosts, tmp) - } else { - formattedHosts = append(formattedHosts, []string{uri.String()}) - } - - } - return formattedHosts -} - -// FormatIPs() takes a list of IP addresses and ports and builds full URLs in the -// form of scheme://host:port. If no scheme is provided, it will use "https" by -// default. -// -// Returns a 2D string slice where each slice contains URL host strings for each -// port. The intention is to have all of the URLs for a single host combined into -// a single slice to initiate one goroutine per host, but making request to multiple -// ports. -func FormatIPs(ips []string, ports []int, scheme string, verbose bool) [][]string { - // format each positional arg as a complete URL - var formattedHosts [][]string - for _, ip := range ips { - if scheme == "" { - scheme = "https" - } - // make an entirely new object since we're expecting just IPs - uri := &url.URL{ - Scheme: scheme, - Host: ip, - } - - // tidy up slashes and update arg with new value - uri.Path = strings.ReplaceAll(uri.Path, "//", "/") - uri.Path = strings.TrimSuffix(uri.Path, "/") - - // for hosts with unspecified ports, add ports to scan from flag - if uri.Port() == "" { - if len(ports) == 0 { - ports = append(ports, 443) - } - var tmp []string - for _, port := range ports { - uri.Host += fmt.Sprintf(":%d", port) - tmp = append(tmp, uri.String()) - } - formattedHosts = append(formattedHosts, tmp) - } else { - formattedHosts = append(formattedHosts, []string{uri.String()}) - } - - } - return formattedHosts -} diff --git a/pkg/bmc/bmc.go b/pkg/bmc/bmc.go index 90dcadf..c451543 100644 --- a/pkg/bmc/bmc.go +++ b/pkg/bmc/bmc.go @@ -14,6 +14,9 @@ type BMCCredentials struct { func GetBMCCredentialsDefault(store secrets.SecretStore) (BMCCredentials, error) { var creds BMCCredentials + if store == nil { + return creds, fmt.Errorf("invalid secrets store") + } if strCreds, err := store.GetSecretByID(secrets.DEFAULT_KEY); err != nil { return creds, fmt.Errorf("get default BMC credentials from secret store: %w", err) } else { @@ -27,6 +30,9 @@ func GetBMCCredentialsDefault(store secrets.SecretStore) (BMCCredentials, error) func GetBMCCredentials(store secrets.SecretStore, id string) (BMCCredentials, error) { var creds BMCCredentials + if store == nil { + return creds, fmt.Errorf("invalid secrets store") + } if strCreds, err := store.GetSecretByID(id); err != nil { return creds, fmt.Errorf("get BMC credentials from secret store: %w", err) } else { diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 3aa4900..57311ba 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -375,7 +375,7 @@ func loadBMCCreds(config CrawlerConfig) (bmc.BMCCredentials, error) { return bmc.BMCCredentials{}, fmt.Errorf("credential store is invalid") } if creds := util.GetBMCCredentials(config.CredentialStore, config.URI); creds == (bmc.BMCCredentials{}) { - return creds, fmt.Errorf("%s: credentials blank for BNC", config.URI) + return creds, fmt.Errorf("%s: credentials blank for BMC", config.URI) } else { return creds, nil } diff --git a/pkg/scan.go b/pkg/scan.go index c1b5f13..032e75f 100644 --- a/pkg/scan.go +++ b/pkg/scan.go @@ -10,7 +10,7 @@ import ( "sync" "time" - urlx "github.com/davidallendj/magellan/internal/url" + urlx "github.com/davidallendj/magellan/internal/urlx" "github.com/davidallendj/magellan/pkg/client" "github.com/rs/zerolog/log" )