From 3682bbdcc71c9a7083d74d71fb5e6ba368e62220 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 5 Feb 2025 12:01:10 -0500 Subject: [PATCH 01/83] chore: update build workflow and add container build script (#70) * chore: update build workflow and add container build script * Add build dependencies to workflow * fix: remove unnecessary magellan installation path from goreleaser config --- .github/workflows/main.yml | 11 +++++-- .goreleaser.yaml | 60 ++++++++++++++++++-------------------- build-in-container.sh | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 build-in-container.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1136387..d5109a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,13 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 + + - name: Install dependencies + run: | + sudo apt update && sudo apt install -y curl git gcc g++ make \ + gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \ + libc6-dev-arm64-cross software-properties-common + - name: Checkout uses: actions/checkout@v4 @@ -53,6 +60,6 @@ jobs: args: release --clean id: goreleaser - name: Attest Binaries - uses: actions/attest-build-provenance@v1 + uses: actions/attest-build-provenance@v2 with: - subject-path: '${{ github.workspace }}/dist/magellan_linux_amd64_v3/magellan' + subject-checksums: dist/checksums.txt diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 24419e5..a4c3f37 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -27,18 +27,20 @@ builds: - version goos: - linux - - darwin - - windows goarch: - amd64 - arm64 goamd64: - v3 + goarm: + - 7 env: - - CGO_ENABLED=0 + - CGO_ENABLED=1 + - CC={{ if eq .Arch "arm64" }}aarch64-linux-gnu-gcc{{ else }}gcc{{ end }} + - CXX={{ if eq .Arch "arm64" }}aarch64-linux-gnu-g++{{ else }}g++{{ end }} archives: - - format: tar.gz + - formats: [ 'tar.gz' ] # this name template makes the OS and Arch compatible with the results of uname. name_template: >- {{ .ProjectName }}_ @@ -54,23 +56,22 @@ archives: - magellan.1 nfpms: - - id: magellan - formats: - - deb - - rpm - - apk - - archlinux - maintainer: "David J. Allen " - description: "Magellan is a discovery tool for BMCs." - homepage: "https://www.davidallendj.org" - license: MIT - section: utils - priority: optional - contents: - - src: dist/magellan_{{ .Os }}_{{ if eq .Arch "amd64" }}{{ .Arch }}_{{ .Amd64 }}{{ else }}{{ .Arch }}{{ end }}/magellan - dst: /usr/local/bin/magellan - - src: magellan.1 - dst: /usr/share/man/man1/ + - id: magellan + formats: + - deb + - rpm + - apk + - archlinux + maintainer: "David J. Allen " + description: "Magellan is a discovery tool for BMCs." + homepage: "https://www.openchami.org" + license: MIT + section: utils + priority: optional + contents: + - src: magellan.1 + dst: /usr/share/man/man1/ + dockers: @@ -93,9 +94,9 @@ dockers: - CHANGELOG.md - README.md - image_templates: - - &arm64v8_linux_image ghcr.io/davidallendj/{{.ProjectName}}:{{ .Tag }}-arm64 - - ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}-arm64 - - ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-arm64 + - &arm64v7_linux_image ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }}-arm64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}-arm64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-arm64 use: buildx build_flag_templates: - "--pull" @@ -114,25 +115,22 @@ docker_manifests: - name_template: "ghcr.io/davidallendj/{{.ProjectName}}:latest" image_templates: - *amd64_linux_image - - *arm64v8_linux_image + - *arm64v7_linux_image - name_template: "ghcr.io/davidallendj/{{.ProjectName}}:{{ .Tag }}" image_templates: - *amd64_linux_image - - *arm64v8_linux_image + - *arm64v7_linux_image - name_template: "ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}" image_templates: - *amd64_linux_image - - *arm64v8_linux_image + - *arm64v7_linux_image - name_template: "ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}" image_templates: - *amd64_linux_image - - *arm64v8_linux_image - - - + - *arm64v7_linux_image checksum: name_template: 'checksums.txt' diff --git a/build-in-container.sh b/build-in-container.sh new file mode 100644 index 0000000..ba07c82 --- /dev/null +++ b/build-in-container.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# This script uses the latest Ubuntu 24.04 container to build the project with GoReleaser. It emulates the GitHub Actions environment as closely as possible. +# Before submitting a PR for release/build. please run this script to ensure your PR will pass the build. + +# Name of the container +CONTAINER_NAME="goreleaser-build" + +# Directory where built binaries will be available +OUTPUT_DIR="$(pwd)/dist" + +export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) +export BUILD_HOST=$(hostname) +export GO_VERSION=$(go version | awk '{print $3}') +export BUILD_USER=$(whoami) + +# Start a new disposable Ubuntu 24.04 container with the current directory mounted +${CONTAINER_CMD:-docker} run --rm -it \ + --name "$CONTAINER_NAME" \ + -v "$(pwd)":/workspace \ + -w /workspace \ + ubuntu:24.04 bash -c " + + # Suppress timezone prompts + export DEBIAN_FRONTEND=noninteractive + export TZ=UTC + + + # Update package lists and install dependencies + apt update && apt install -y curl git gcc g++ make \ + gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \ + libc6-dev-arm64-cross software-properties-common + + # Install Go (match GitHub runner version) + curl -fsSL https://golang.org/dl/go1.21.5.linux-amd64.tar.gz | tar -C /usr/local -xz + export PATH=\$PATH:/usr/local/go/bin + go version # Verify Go installation + + # Set GOPATH and update PATH to include Go binaries + export GOPATH=\$(go env GOPATH) + export PATH=\$PATH:\$GOPATH/bin + echo \"GOPATH: \$GOPATH\" && echo \"PATH: \$PATH\"`` + + # Install Goreleaser + curl -sL https://github.com/goreleaser/goreleaser/releases/latest/download/goreleaser_Linux_x86_64.tar.gz | tar -xz -C /usr/local/bin + goreleaser --version # Verify Goreleaser installation + + # Set Build Environment Variables + export GIT_STATE="$GIT_STATE" + export BUILD_HOST="$BUILD_HOST" + export BUILD_USER="$BUILD_USER" + export GO_VERSION=$(go version | awk '{print $3}') + + # Run Goreleaser + goreleaser build --snapshot --clean +" + +# Notify user of success +echo "✅ Build complete! Check the output in: $OUTPUT_DIR" + From 4e7d011cd0fcff45533f7a11bad765c90a3d61a0 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 5 Feb 2025 12:34:13 -0500 Subject: [PATCH 02/83] Update prbuild.yml Signed-off-by: Alex Lovell-Troy --- .github/workflows/prbuild.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prbuild.yml b/.github/workflows/prbuild.yml index 476ba60..6621214 100644 --- a/.github/workflows/prbuild.yml +++ b/.github/workflows/prbuild.yml @@ -20,6 +20,13 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 + + - name: Install dependencies + run: | + sudo apt update && sudo apt install -y curl git gcc g++ make \ + gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \ + libc6-dev-arm64-cross software-properties-common + - name: Checkout uses: actions/checkout@v4 with: @@ -41,4 +48,4 @@ jobs: with: version: '~> v2' args: release --snapshot - id: goreleaser \ No newline at end of file + id: goreleaser From 7498aa5890bd0193a17939f23c928157b095cb5d Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 6 Feb 2025 12:59:24 -0700 Subject: [PATCH 03/83] fix: change db.MustExec to db.Exec and handle error --- internal/cache/sqlite/sqlite.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/cache/sqlite/sqlite.go b/internal/cache/sqlite/sqlite.go index fa0ae0b..896ba1a 100644 --- a/internal/cache/sqlite/sqlite.go +++ b/internal/cache/sqlite/sqlite.go @@ -28,7 +28,10 @@ func CreateScannedAssetIfNotExists(path string) (*sqlx.DB, error) { if err != nil { return nil, fmt.Errorf("failed to open database: %v", err) } - db.MustExec(schema) + _, err = db.Exec(schema) + if err != nil { + return nil, fmt.Errorf("failed to create scanned assets cache: %v", err) + } return db, nil } From 51c01df73a4a65158b2646be968a007e5def8bb4 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 6 Feb 2025 17:25:56 -0500 Subject: [PATCH 04/83] feat: enhance firmware update functionality and add BMC identification support --- README.md | 2 +- internal/update.go | 54 +++++++++++++++++++------------- pkg/crawler/identify.go | 69 +++++++++++++++++++++++++++++++++++++++++ pkg/crawler/main.go | 56 +++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 pkg/crawler/identify.go diff --git a/README.md b/README.md index 369f38f..65eba18 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Note: If the `cache` flag is not set, `magellan` will use `/tmp/$USER/magellan.d ### Updating Firmware -The `magellan` tool is capable of updating firmware with using the `update` subcommand via the Redfish API. This may sometimes necessary if some of the `collect` output is missing or is not including what is expected. The subcommand expects there to be a running HTTP/HTTPS server running that has an accessible URL path to the firmware download. Specify the URL with the `--firmware-path` flag and the firmware type with the `--component` flag with all the other usual arguments like in the example below: +The `magellan` tool is capable of updating firmware with using the `update` subcommand via the Redfish API. This may sometimes necessary if some of the `collect` output is missing or is not including what is expected. The subcommand expects there to be a running HTTP/HTTPS server running that has an accessible URL path to the firmware download. Specify the URL with the `--firmware-path` flag and the firmware type with the `--component` flag (optional) with all the other usual arguments like in the example below: ```bash ./magellan update 172.16.0.108:443 \ diff --git a/internal/update.go b/internal/update.go index 464b0b9..2928beb 100644 --- a/internal/update.go +++ b/internal/update.go @@ -1,12 +1,13 @@ package magellan import ( - "encoding/json" "fmt" "net/http" "net/url" - "github.com/davidallendj/magellan/pkg/client" + "github.com/OpenCHAMI/magellan/pkg/client" + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/redfish" ) type UpdateParams struct { @@ -20,38 +21,47 @@ type UpdateParams struct { // UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node. // The function expects the firmware URL, firmware version, and component flags to be // set from the CLI to perform a firmware update. +// Example: +// ./magellan update https://192.168.23.40 --username root --password 0penBmc +// --firmware-url http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar +// --scheme TFTP +// +// being: +// q.URI https://192.168.23.40 +// q.TransferProtocol TFTP +// q.FirmwarePath http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar func UpdateFirmwareRemote(q *UpdateParams) error { // parse URI to set up full address uri, err := url.ParseRequestURI(q.URI) if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } - uri.User = url.UserPassword(q.Username, q.Password) - // set up other vars - updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService/Actions/SimpleUpdate", uri.String()) - headers := map[string]string{ - "Content-Type": "application/json", - "cache-control": "no-cache", - } - b := map[string]any{ - "UpdateComponent": q.Component, // BMC, BIOS - "TransferProtocol": q.TransferProtocol, - "ImageURI": q.FirmwarePath, - } - data, err := json.Marshal(b) + // Connect to the Redfish service using gofish (using insecure connection for this example) + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true}) if err != nil { - return fmt.Errorf("failed to marshal data: %v", err) + return fmt.Errorf("failed to connect to Redfish service: %w", err) } - res, body, err := client.MakeRequest(nil, updateUrl, "POST", data, headers) + defer client.Logout() + + // Retrieve the UpdateService from the Redfish client + updateService, err := client.Service.UpdateService() if err != nil { - return fmt.Errorf("something went wrong: %v", err) - } else if res == nil { - return fmt.Errorf("no response returned (url: %s)", updateUrl) + return fmt.Errorf("failed to get update service: %w", err) } - if len(body) > 0 { - fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + + // Build the update request payload + req := redfish.SimpleUpdateParameters{ + ImageURI: q.FirmwarePath, + TransferProtocol: redfish.TransferProtocolType(q.TransferProtocol), } + + // Execute the SimpleUpdate action + err = updateService.SimpleUpdate(&req) + if err != nil { + return fmt.Errorf("firmware update failed: %w", err) + } + fmt.Println("Firmware update initiated successfully.") return nil } diff --git a/pkg/crawler/identify.go b/pkg/crawler/identify.go new file mode 100644 index 0000000..be5b788 --- /dev/null +++ b/pkg/crawler/identify.go @@ -0,0 +1,69 @@ +package crawler + +import ( + "fmt" + + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/redfish" +) + +// BMCInfo represents relevant information about a BMC +type BMCInfo struct { + Manufacturer string `json:"manufacturer"` + Model string `json:"model"` + SerialNumber string `json:"serial_number"` + FirmwareVersion string `json:"firmware_version"` + ManagerType string `json:"manager_type"` + UUID string `json:"uuid"` +} + +// IsBMC checks if a given Manager is a BMC based on its type and associations +func IsBMC(manager *redfish.Manager) bool { + if manager == nil { + return false + } + + // Valid BMC types in Redfish + bmcTypes := map[string]bool{ + "BMC": true, + "ManagementController": true, // Some BMCs use this type + } + + // Check if ManagerType matches a BMC type + if !bmcTypes[string(manager.ManagerType)] { + return false + } + + return false // Otherwise, it's likely a chassis manager or other device +} + +// GetBMCInfo retrieves details of all available BMCs +func GetBMCInfo(client *gofish.APIClient) ([]BMCInfo, error) { + var bmcList []BMCInfo + + // Retrieve all managers (BMCs and other managers) + managers, err := client.Service.Managers() + if err != nil { + return nil, fmt.Errorf("failed to retrieve managers: %v", err) + } + + // Iterate through each manager and collect BMC details + for _, manager := range managers { + if !IsBMC(manager) { + continue // Skip if it's not a BMC + } + + bmc := BMCInfo{ + Manufacturer: manager.Manufacturer, + Model: manager.Model, + SerialNumber: manager.SerialNumber, + FirmwareVersion: manager.FirmwareVersion, + ManagerType: string(manager.ManagerType), // Convert ManagerType to string + UUID: manager.UUID, + } + + bmcList = append(bmcList, bmc) + } + + return bmcList, nil +} diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 771efb9..f9526cd 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -130,6 +130,24 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) { } // CrawlBMCForSystems pulls BMC manager information. +// CrawlBMCForManagers connects to a BMC (Baseboard Management Controller) using the provided configuration, +// retrieves the ServiceRoot, and then fetches the list of managers from the ServiceRoot. +// +// Parameters: +// - config: A CrawlerConfig struct containing the URI, username, password, and other connection details. +// +// Returns: +// - []Manager: A slice of Manager structs representing the managers retrieved from the BMC. +// - error: An error object if any error occurs during the connection or retrieval process. +// +// The function performs the following steps: +// 1. Initializes a gofish client with the provided configuration. +// 2. Attempts to connect to the BMC using the gofish client. +// 3. Handles specific connection errors such as 404 (ServiceRoot not found) and 401 (authentication failed). +// 4. Logs out from the client after the operations are completed. +// 5. Retrieves the ServiceRoot from the connected BMC. +// 6. Fetches the list of managers from the ServiceRoot. +// 7. Returns the list of managers and any error encountered during the process. func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) { // initialize gofish client var managers []Manager @@ -165,6 +183,27 @@ func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) { return walkManagers(rf_managers, config.URI) } +// walkSystems processes a list of Redfish computer systems and their associated chassis, +// and returns a list of inventory details for each system. +// +// Parameters: +// - rf_systems: A slice of pointers to redfish.ComputerSystem objects representing the computer systems to be processed. +// - rf_chassis: A pointer to a redfish.Chassis object representing the chassis associated with the computer systems. +// - baseURI: A string representing the base URI for constructing resource URIs. +// +// Returns: +// - A slice of InventoryDetail objects containing detailed information about each computer system. +// - An error if any issues occur while processing the computer systems or their associated resources. +// +// The function performs the following steps: +// 1. Iterates over each computer system in rf_systems. +// 2. Constructs an InventoryDetail object for each computer system, populating fields such as URI, UUID, Name, Manufacturer, SystemType, Model, Serial, BiosVersion, PowerState, ProcessorCount, ProcessorType, and MemoryTotal. +// 3. If rf_chassis is not nil, populates additional chassis-related fields in the InventoryDetail object. +// 4. Retrieves and processes Ethernet interfaces for each computer system, adding them to the EthernetInterfaces field of the InventoryDetail object. +// 5. Retrieves and processes Network interfaces and their associated network adapters for each computer system, adding them to the NetworkInterfaces field of the InventoryDetail object. +// 6. Processes trusted modules for each computer system, adding them to the TrustedModules field of the InventoryDetail object. +// 7. Appends the populated InventoryDetail object to the systems slice. +// 8. Returns the systems slice and any error encountered during processing. func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chassis, baseURI string) ([]InventoryDetail, error) { systems := []InventoryDetail{} for _, rf_computersystem := range rf_systems { @@ -253,6 +292,23 @@ func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chass return systems, nil } +// walkManagers processes a list of Redfish managers and extracts relevant information +// to create a slice of Manager objects. +// +// Parameters: +// +// rf_managers - A slice of pointers to redfish.Manager objects representing the Redfish managers to be processed. +// baseURI - A string representing the base URI to be used for constructing URIs for the managers and their Ethernet interfaces. +// +// Returns: +// +// A slice of Manager objects containing the extracted information from the provided Redfish managers. +// An error if any issues occur while retrieving Ethernet interfaces from the managers. +// +// The function iterates over each Redfish manager, retrieves its Ethernet interfaces, +// and constructs a Manager object with the relevant details, including Ethernet interface information. +// If an error occurs while retrieving Ethernet interfaces, the function logs the error and returns the managers +// collected so far along with the error. func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, error) { var managers []Manager for _, rf_manager := range rf_managers { From 03bf2250a40808871d54e2ffa19e272f25b69302 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Feb 2025 09:52:21 -0500 Subject: [PATCH 05/83] refactor: improve Redfish service connection handling and update status retrieval --- internal/update.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/internal/update.go b/internal/update.go index 2928beb..183980b 100644 --- a/internal/update.go +++ b/internal/update.go @@ -2,10 +2,8 @@ package magellan import ( "fmt" - "net/http" "net/url" - "github.com/OpenCHAMI/magellan/pkg/client" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" ) @@ -37,7 +35,7 @@ func UpdateFirmwareRemote(q *UpdateParams) error { return fmt.Errorf("failed to parse URI: %w", err) } - // Connect to the Redfish service using gofish (using insecure connection for this example) + // Connect to the Redfish service using gofish (using insecure connection for this) client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true}) if err != nil { return fmt.Errorf("failed to connect to Redfish service: %w", err) @@ -71,18 +69,23 @@ func GetUpdateStatus(q *UpdateParams) error { if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } - uri.User = url.UserPassword(q.Username, q.Password) - updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService", uri.String()) - res, body, err := client.MakeRequest(nil, updateUrl, "GET", nil, nil) + + // Connect to the Redfish service using gofish (using insecure connection for this) + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true}) if err != nil { - return fmt.Errorf("something went wrong: %v", err) - } else if res == nil { - return fmt.Errorf("no response returned (url: %s)", updateUrl) - } else if res.StatusCode != http.StatusOK { - return fmt.Errorf("returned status code %d", res.StatusCode) + return fmt.Errorf("failed to connect to Redfish service: %w", err) } - if len(body) > 0 { - fmt.Printf("%v\n", string(body)) + defer client.Logout() + + // Retrieve the UpdateService from the Redfish client + updateService, err := client.Service.UpdateService() + if err != nil { + return fmt.Errorf("failed to get update service: %w", err) } + + // Get the update status + status := updateService.Status + fmt.Printf("Update Status: %v\n", status) + return nil } From b31ed136f6b8e360fd83cff08ae17b553b15261b Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Feb 2025 09:56:36 -0500 Subject: [PATCH 06/83] feat: add --insecure flag to allow insecure connections for firmware updates --- cmd/update.go | 39 ++++++++++++++++++++++----------------- internal/update.go | 9 +++++---- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index d0b050c..b8fe150 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -17,18 +17,19 @@ var ( component string transferProtocol string showStatus bool + Insecure bool ) // The `update` command provides an interface to easily update firmware // using Redfish. It also provides a simple way to check the status of // an update in-progress. -var UpdateCmd = &cobra.Command{ +var updateCmd = &cobra.Command{ Use: "update hosts...", Short: "Update BMC node firmware", Long: "Perform an firmware update using Redfish by providing a remote firmware URL and component.\n\n" + "Examples:\n" + - " magellan update 172.16.0.108:443 --username bmc_username --password bmc_password --firmware-url http://172.16.0.200:8005/firmware/bios/image.RBU --component BIOS\n" + - " magellan update 172.16.0.108:443 --status --username bmc_username --password bmc_password", + " magellan update 172.16.0.108:443 --insecure --username bmc_username --password bmc_password --firmware-url http://172.16.0.200:8005/firmware/bios/image.RBU --component BIOS\n" + + " magellan update 172.16.0.108:443 --insecure --status --username bmc_username --password bmc_password", Run: func(cmd *cobra.Command, args []string) { // check that we have at least one host if len(args) <= 0 { @@ -44,6 +45,7 @@ var UpdateCmd = &cobra.Command{ FirmwareVersion: firmwareVersion, Component: component, TransferProtocol: transferProtocol, + Insecure: Insecure, CollectParams: magellan.CollectParams{ URI: arg, Username: username, @@ -63,6 +65,7 @@ var UpdateCmd = &cobra.Command{ FirmwareVersion: firmwareVersion, Component: component, TransferProtocol: strings.ToUpper(transferProtocol), + Insecure: Insecure, CollectParams: magellan.CollectParams{ URI: host, Username: username, @@ -78,21 +81,23 @@ var UpdateCmd = &cobra.Command{ } func init() { - UpdateCmd.Flags().StringVar(&username, "username", "", "Set the BMC user") - UpdateCmd.Flags().StringVar(&password, "password", "", "Set the BMC password") - UpdateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") - UpdateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "Set the path to the firmware") - UpdateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "Set the version of firmware to be installed") - UpdateCmd.Flags().StringVar(&component, "component", "", "Set the component to upgrade (BMC|BIOS)") - UpdateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") + updateCmd.Flags().StringVar(&username, "username", "", "Set the BMC user") + updateCmd.Flags().StringVar(&password, "password", "", "Set the BMC password") + updateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") + updateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "Set the path to the firmware") + updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "Set the version of firmware to be installed") + updateCmd.Flags().StringVar(&component, "component", "", "Set the component to upgrade (BMC|BIOS)") + updateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") + updateCmd.Flags().BoolVar(&Insecure, "insecure", false, "Allow insecure connections to the server") - 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.status", UpdateCmd.Flags().Lookup("status"))) + 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.status", updateCmd.Flags().Lookup("status"))) + checkBindFlagError(viper.BindPFlag("update.insecure", updateCmd.Flags().Lookup("insecure"))) rootCmd.AddCommand(UpdateCmd) } diff --git a/internal/update.go b/internal/update.go index 183980b..ccbed6b 100644 --- a/internal/update.go +++ b/internal/update.go @@ -14,6 +14,7 @@ type UpdateParams struct { FirmwareVersion string Component string TransferProtocol string + Insecure bool } // UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node. @@ -35,8 +36,8 @@ func UpdateFirmwareRemote(q *UpdateParams) error { return fmt.Errorf("failed to parse URI: %w", err) } - // Connect to the Redfish service using gofish (using insecure connection for this) - client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true}) + // Connect to the Redfish service using gofish + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) if err != nil { return fmt.Errorf("failed to connect to Redfish service: %w", err) } @@ -70,8 +71,8 @@ func GetUpdateStatus(q *UpdateParams) error { return fmt.Errorf("failed to parse URI: %w", err) } - // Connect to the Redfish service using gofish (using insecure connection for this) - client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true}) + // Connect to the Redfish service using gofish + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) if err != nil { return fmt.Errorf("failed to connect to Redfish service: %w", err) } From 03c54cc7c1f55ad74ff31add656e5feb7c23a7dc Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Feb 2025 11:10:10 -0500 Subject: [PATCH 07/83] chore: update golang.org/x/crypto and golang.org/x/sys dependencies to latest versions --- go.mod | 6 +++--- go.sum | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 1a2e948..a65df98 100644 --- a/go.mod +++ b/go.mod @@ -49,9 +49,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index befbed6..9c85790 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -151,8 +151,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -164,8 +164,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From e19af0ce0c5704a6858c1899f05c693d2731d2e9 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Feb 2025 17:09:21 -0700 Subject: [PATCH 08/83] refactor: moved internal functions to pkg and updated refs --- cmd/collect.go | 4 ++++ cmd/scan.go | 4 ++-- cmd/update.go | 4 ++-- internal/cache/sqlite/sqlite.go | 4 ++-- {internal => pkg}/collect.go | 0 {internal => pkg}/scan.go | 0 {internal => pkg}/update.go | 0 tests/api_test.go | 6 +++--- 8 files changed, 13 insertions(+), 9 deletions(-) rename {internal => pkg}/collect.go (100%) rename {internal => pkg}/scan.go (100%) rename {internal => pkg}/update.go (100%) diff --git a/cmd/collect.go b/cmd/collect.go index 53807c3..6d8934e 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -4,6 +4,10 @@ import ( "fmt" "os/user" + "github.com/OpenCHAMI/magellan/internal/cache/sqlite" + urlx "github.com/OpenCHAMI/magellan/internal/url" + magellan "github.com/OpenCHAMI/magellan/pkg" + "github.com/OpenCHAMI/magellan/pkg/auth" "github.com/cznic/mathutil" magellan "github.com/davidallendj/magellan/internal" "github.com/davidallendj/magellan/internal/cache/sqlite" diff --git a/cmd/scan.go b/cmd/scan.go index d2cbaf8..261fad7 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -7,8 +7,8 @@ import ( "os" "path" - magellan "github.com/davidallendj/magellan/internal" - "github.com/davidallendj/magellan/internal/cache/sqlite" + "github.com/OpenCHAMI/magellan/internal/cache/sqlite" + magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/rs/zerolog/log" "github.com/cznic/mathutil" diff --git a/cmd/update.go b/cmd/update.go index b8fe150..83671e4 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -4,7 +4,7 @@ import ( "os" "strings" - magellan "github.com/davidallendj/magellan/internal" + magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -99,5 +99,5 @@ func init() { checkBindFlagError(viper.BindPFlag("update.status", updateCmd.Flags().Lookup("status"))) checkBindFlagError(viper.BindPFlag("update.insecure", updateCmd.Flags().Lookup("insecure"))) - rootCmd.AddCommand(UpdateCmd) + rootCmd.AddCommand(updateCmd) } diff --git a/internal/cache/sqlite/sqlite.go b/internal/cache/sqlite/sqlite.go index 896ba1a..12fc2bb 100644 --- a/internal/cache/sqlite/sqlite.go +++ b/internal/cache/sqlite/sqlite.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - magellan "github.com/davidallendj/magellan/internal" - "github.com/davidallendj/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/util" + magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/jmoiron/sqlx" ) diff --git a/internal/collect.go b/pkg/collect.go similarity index 100% rename from internal/collect.go rename to pkg/collect.go diff --git a/internal/scan.go b/pkg/scan.go similarity index 100% rename from internal/scan.go rename to pkg/scan.go diff --git a/internal/update.go b/pkg/update.go similarity index 100% rename from internal/update.go rename to pkg/update.go diff --git a/tests/api_test.go b/tests/api_test.go index d46a312..999f142 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -22,9 +22,9 @@ import ( "flag" - magellan "github.com/davidallendj/magellan/internal" - "github.com/davidallendj/magellan/internal/util" - "github.com/davidallendj/magellan/pkg/client" + "github.com/OpenCHAMI/magellan/internal/util" + magellan "github.com/OpenCHAMI/magellan/pkg" + "github.com/OpenCHAMI/magellan/pkg/client" "github.com/rs/zerolog/log" ) From ccce61694b2b3208a3c294d284b77c6a1dff516d Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 26 Feb 2025 16:18:42 -0700 Subject: [PATCH 09/83] fix: changed 'update' cmd to use gofish --- cmd/collect.go | 2 +- pkg/collect.go | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 6d8934e..9e39506 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -59,7 +59,7 @@ var CollectCmd = &cobra.Command{ if concurrency <= 0 { concurrency = mathutil.Clamp(len(scannedResults), 1, 10000) } - err = magellan.CollectInventory(&scannedResults, &magellan.CollectParams{ + _, err = magellan.CollectInventory(&scannedResults, &magellan.CollectParams{ URI: host, Username: username, Password: password, diff --git a/pkg/collect.go b/pkg/collect.go index 2f2c863..6de51a5 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -48,19 +48,20 @@ type CollectParams struct { // // Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency // property value between 1 and 10000. -func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { +func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[string]any, error) { // check for available remote assets found from scan if assets == nil { - return fmt.Errorf("no assets found") + return nil, fmt.Errorf("no assets found") } if len(*assets) <= 0 { - return fmt.Errorf("no assets found") + return nil, fmt.Errorf("no assets found") } // collect bmc information asynchronously var ( offset = 0 wg sync.WaitGroup + collection = make([]map[string]any, 0) found = make([]string, 0, len(*assets)) done = make(chan struct{}, params.Concurrency+1) chanAssets = make(chan RemoteAsset, params.Concurrency+1) @@ -73,7 +74,7 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { if params.CaCertPath != "" { cacert, err := os.ReadFile(params.CaCertPath) if err != nil { - return fmt.Errorf("failed to read CA cert path: %w", err) + return nil, fmt.Errorf("failed to read CA cert path: %w", err) } certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(cacert) @@ -169,6 +170,9 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { fmt.Printf("%v\n", string(body)) } + // add data output to collections + collection = append(collection, data) + // write JSON data to file if output path is set using hive partitioning strategy if outputPath != "" { var ( @@ -241,7 +245,7 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { wg.Wait() close(done) - return nil + return collection, nil } // FindMACAddressWithIP() returns the MAC address of an ethernet interface with From ee1fc327e2080f95aad691cc476084898f80741a Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Mar 2025 17:10:31 -0500 Subject: [PATCH 10/83] feat(secrets): implement SecretStore interface and StaticStore/LocalStore for credential management --- cmd/collect.go | 7 +- cmd/crawl.go | 16 ++-- go.mod | 5 +- pkg/collect.go | 23 +++-- pkg/crawler/main.go | 63 ++++++++++++-- pkg/scan.go | 2 +- pkg/secrets/encryption.go | 75 ++++++++++++++++ pkg/secrets/encryption_test.go | 41 +++++++++ pkg/secrets/localstore.go | 129 ++++++++++++++++++++++++++++ pkg/secrets/localstore_test.go | 151 +++++++++++++++++++++++++++++++++ pkg/secrets/main.go | 7 ++ pkg/secrets/staticstore.go | 28 ++++++ tests/compatibility_test.go | 18 ++-- 13 files changed, 531 insertions(+), 34 deletions(-) create mode 100644 pkg/secrets/encryption.go create mode 100644 pkg/secrets/encryption_test.go create mode 100644 pkg/secrets/localstore.go create mode 100644 pkg/secrets/localstore_test.go create mode 100644 pkg/secrets/main.go create mode 100644 pkg/secrets/staticstore.go diff --git a/cmd/collect.go b/cmd/collect.go index 9e39506..dbb465c 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -8,6 +8,7 @@ import ( urlx "github.com/OpenCHAMI/magellan/internal/url" magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/OpenCHAMI/magellan/pkg/auth" + "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/cznic/mathutil" magellan "github.com/davidallendj/magellan/internal" "github.com/davidallendj/magellan/internal/cache/sqlite" @@ -59,10 +60,10 @@ var CollectCmd = &cobra.Command{ 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{ URI: host, - Username: username, - Password: password, Timeout: timeout, Concurrency: concurrency, Verbose: verbose, @@ -70,7 +71,7 @@ var CollectCmd = &cobra.Command{ OutputPath: outputPath, ForceUpdate: forceUpdate, AccessToken: accessToken, - }) + }, secrets) if err != nil { log.Error().Err(err).Msgf("failed to collect data") } diff --git a/cmd/crawl.go b/cmd/crawl.go index a5bb56c..e9e91bd 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -5,8 +5,9 @@ import ( "fmt" "log" - urlx "github.com/davidallendj/magellan/internal/url" - "github.com/davidallendj/magellan/pkg/crawler" + urlx "github.com/OpenCHAMI/magellan/internal/url" + "github.com/OpenCHAMI/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -35,11 +36,14 @@ var CrawlCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { + staticStore := &secrets.StaticStore{ + Username: viper.GetString("crawl.username"), + Password: viper.GetString("crawl.password"), + } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ - URI: args[0], - Username: cmd.Flag("username").Value.String(), - Password: cmd.Flag("password").Value.String(), - Insecure: cmd.Flag("insecure").Value.String() == "true", + URI: args[0], + CredentialStore: staticStore, + Insecure: cmd.Flag("insecure").Value.String() == "true", }) if err != nil { log.Fatalf("Error crawling BMC: %v", err) diff --git a/go.mod b/go.mod index a65df98..cabcc22 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,10 @@ require ( golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 ) -require github.com/rs/zerolog v1.33.0 +require ( + github.com/rs/zerolog v1.33.0 + golang.org/x/crypto v0.32.0 +) require ( github.com/google/go-cmp v0.6.0 // indirect diff --git a/pkg/collect.go b/pkg/collect.go index 6de51a5..ccb1a67 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -15,8 +15,9 @@ import ( "sync" "time" - "github.com/davidallendj/magellan/pkg/client" - "github.com/davidallendj/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/client" + "github.com/OpenCHAMI/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" @@ -48,7 +49,7 @@ type CollectParams struct { // // Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency // property value between 1 and 10000. -func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[string]any, error) { +func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secrets.SecretStore) ([]map[string]any, error) { // check for available remote assets found from scan if assets == nil { return nil, fmt.Errorf("no assets found") @@ -117,10 +118,9 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[strin systems []crawler.InventoryDetail managers []crawler.Manager config = crawler.CrawlerConfig{ - URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), - Username: params.Username, - Password: params.Password, - Insecure: true, + URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), + CredentialStore: store, + Insecure: true, } ) systems, err := crawler.CrawlBMCForSystems(config) @@ -260,10 +260,15 @@ func FindMACAddressWithIP(config crawler.CrawlerConfig, targetIP net.IP) (string // gofish (at least for now). If there's a need for grabbing more // manager information in the future, we can move the logic into // the crawler. + bmc_creds, err := config.GetUserPass() + if err != nil { + return "", fmt.Errorf("failed to get credentials for URI: %s", config.URI) + } + client, err := gofish.Connect(gofish.ClientConfig{ Endpoint: config.URI, - Username: config.Username, - Password: config.Password, + Username: bmc_creds.Username, + Password: bmc_creds.Password, Insecure: config.Insecure, BasicAuth: true, }) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index f9526cd..ddf6357 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -1,19 +1,29 @@ package crawler import ( + "encoding/json" "fmt" "strings" + "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" ) type CrawlerConfig struct { - URI string // URI of the BMC - Username string // Username for the BMC - Password string // Password for the BMC - Insecure bool // Whether to ignore SSL errors + URI string // URI of the BMC + Insecure bool // Whether to ignore SSL errors + CredentialStore secrets.SecretStore +} + +func (cc *CrawlerConfig) GetUserPass() (BMCUsernamePassword, error) { + return loadBMCCreds(*cc) +} + +type BMCUsernamePassword struct { + Username string `json:"username"` + Password string `json:"password"` } type EthernetInterface struct { @@ -82,11 +92,20 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) { systems []InventoryDetail rf_systems []*redfish.ComputerSystem ) + // get username and password from secret store + bmc_creds, err := loadBMCCreds(config) + if err != nil { + event := log.Error() + event.Err(err) + event.Msg("failed to load BMC credentials") + return nil, err + } + // initialize gofish client client, err := gofish.Connect(gofish.ClientConfig{ Endpoint: config.URI, - Username: config.Username, - Password: config.Password, + Username: bmc_creds.Username, + Password: bmc_creds.Password, Insecure: config.Insecure, BasicAuth: true, }) @@ -149,12 +168,21 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) { // 6. Fetches the list of managers from the ServiceRoot. // 7. Returns the list of managers and any error encountered during the process. func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) { + + // get username and password from secret store + bmc_creds, err := loadBMCCreds(config) + if err != nil { + event := log.Error() + event.Err(err) + event.Msg("failed to load BMC credentials") + return nil, err + } // initialize gofish client var managers []Manager client, err := gofish.Connect(gofish.ClientConfig{ Endpoint: config.URI, - Username: config.Username, - Password: config.Password, + Username: bmc_creds.Username, + Password: bmc_creds.Password, Insecure: config.Insecure, BasicAuth: true, }) @@ -344,3 +372,22 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er } return managers, nil } + +func loadBMCCreds(config CrawlerConfig) (BMCUsernamePassword, error) { + creds, err := config.CredentialStore.GetSecretByID(config.URI) + if err != nil { + event := log.Error() + event.Err(err) + event.Msg("failed to get credentials from secret store") + return BMCUsernamePassword{}, err + } + var bmc_creds BMCUsernamePassword + err = json.Unmarshal([]byte(creds), &bmc_creds) + if err != nil { + event := log.Error() + event.Err(err) + event.Msg("failed to unmarshal credentials") + return BMCUsernamePassword{}, err + } + return bmc_creds, nil +} diff --git a/pkg/scan.go b/pkg/scan.go index 99b4904..c1b5f13 100644 --- a/pkg/scan.go +++ b/pkg/scan.go @@ -203,7 +203,7 @@ func rawConnect(address string, protocol string, timeoutSeconds int, keepOpenOnl ) // try to conntect to host (expects host in format [10.0.0.0]:443) - target := fmt.Sprintf("%s:%s", uri.Hostname(), uri.Port()) + target := net.JoinHostPort(uri.Hostname(), uri.Port()) conn, err := net.DialTimeout(protocol, target, timeoutDuration) if err != nil { asset.State = false diff --git a/pkg/secrets/encryption.go b/pkg/secrets/encryption.go new file mode 100644 index 0000000..6faa737 --- /dev/null +++ b/pkg/secrets/encryption.go @@ -0,0 +1,75 @@ +package secrets + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + + "golang.org/x/crypto/hkdf" +) + +// Derive a unique AES key per SecretID using HKDF +func deriveAESKey(masterKey []byte, secretID string) []byte { + salt := []byte(secretID) + hkdf := hkdf.New(sha256.New, masterKey, salt, nil) + derivedKey := make([]byte, 32) // AES-256 key + io.ReadFull(hkdf, derivedKey) + return derivedKey +} + +// Encrypt data using AES-GCM +func encryptAESGCM(key, plaintext []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonce := make([]byte, aesGCM.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return "", err + } + + ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil) + return hex.EncodeToString(ciphertext), nil +} + +// Decrypt data using AES-GCM +func decryptAESGCM(key []byte, encryptedData string) (string, error) { + data, err := hex.DecodeString(encryptedData) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonceSize := aesGCM.NonceSize() + if len(data) < nonceSize { + return "", fmt.Errorf("ciphertext too short") + } + + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) + if err != nil { + return "", err + } + + return string(plaintext), nil +} diff --git a/pkg/secrets/encryption_test.go b/pkg/secrets/encryption_test.go new file mode 100644 index 0000000..bc1919b --- /dev/null +++ b/pkg/secrets/encryption_test.go @@ -0,0 +1,41 @@ +package secrets + +import ( + "testing" +) + +func TestDeriveAESKey(t *testing.T) { + masterKey := []byte("testmasterkey") + secretID := "mySecretID" + key1 := deriveAESKey(masterKey, secretID) + key2 := deriveAESKey(masterKey, secretID) + + if len(key1) != 32 { + t.Errorf("derived key should be 32 bytes, got %d", len(key1)) + } + if string(key1) != string(key2) { + t.Errorf("keys derived from same secretID should match") + } +} + +func TestEncryptDecryptAESGCM(t *testing.T) { + masterKey := []byte("anotherTestMasterKey") + secretID := "testSecret" + plaintext := "Hello, secrets!" + + key := deriveAESKey(masterKey, secretID) + + encrypted, err := encryptAESGCM(key, []byte(plaintext)) + if err != nil { + t.Fatalf("encryption failed: %v", err) + } + + decrypted, err := decryptAESGCM(key, encrypted) + if err != nil { + t.Fatalf("decryption failed: %v", err) + } + + if decrypted != plaintext { + t.Errorf("expected %q, got %q", plaintext, decrypted) + } +} diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go new file mode 100644 index 0000000..76fd136 --- /dev/null +++ b/pkg/secrets/localstore.go @@ -0,0 +1,129 @@ +package secrets + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "sync" +) + +// Structure to store encrypted secrets in a JSON file +type LocalSecretStore struct { + mu sync.RWMutex + masterKey []byte + filename string + Secrets map[string]string `json:"secrets"` +} + +func NewLocalSecretStore(masterKeyHex, filename string, create bool) (*LocalSecretStore, error) { + var secrets map[string]string + + masterKey, err := hex.DecodeString(masterKeyHex) + if err != nil { + return nil, fmt.Errorf("unable to generate masterkey from hex representation: %v", err) + } + + if _, err := os.Stat(filename); os.IsNotExist(err) { + if !create { + return nil, fmt.Errorf("file %s does not exist", filename) + } + file, err := os.Create(filename) + if err != nil { + return nil, fmt.Errorf("unable to create file %s: %v", filename, err) + } + file.Close() + secrets = make(map[string]string) + } + + if secrets == nil { + secrets, err = loadSecrets(filename) + if err != nil { + return nil, fmt.Errorf("unable to load secrets from file: %v", err) + } + } + + return &LocalSecretStore{ + masterKey: masterKey, + filename: filename, + Secrets: secrets, + }, nil +} + +// GenerateMasterKey creates a 32-byte random key and returns it as a hex string. +func GenerateMasterKey() (string, error) { + key := make([]byte, 32) // 32 bytes for AES-256 + _, err := rand.Read(key) + if err != nil { + return "", err + } + return hex.EncodeToString(key), nil +} + +// GetSecretByID decrypts the secret using the master key and returns it +func (l *LocalSecretStore) GetSecretByID(secretID string) (string, error) { + l.mu.RLock() + encrypted, exists := l.Secrets[secretID] + l.mu.RUnlock() + if !exists { + return "", fmt.Errorf("no secret found for %s", secretID) + } + + derivedKey := deriveAESKey(l.masterKey, secretID) + return decryptAESGCM(derivedKey, encrypted) +} + +// StoreSecretByID encrypts the secret using the master key and stores it in the JSON file +func (l *LocalSecretStore) StoreSecretByID(secretID, secret string) error { + derivedKey := deriveAESKey(l.masterKey, secretID) + encryptedSecret, err := encryptAESGCM(derivedKey, []byte(secret)) + if err != nil { + return err + } + + l.mu.Lock() + l.Secrets[secretID] = encryptedSecret + err = saveSecrets(l.filename, l.Secrets) + l.mu.Unlock() + return err +} + +// ListSecrets returns a copy of secret IDs to secrets stored in memory +func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { + l.mu.RLock() + defer l.mu.RUnlock() + + secretsCopy := make(map[string]string) + for key, value := range l.Secrets { + secretsCopy[key] = value + } + return secretsCopy, nil +} + +// Saves secrets back to the JSON file +func saveSecrets(jsonFile string, store map[string]string) error { + file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + return encoder.Encode(store) +} + +// Loads the secrets JSON file +func loadSecrets(jsonFile string) (map[string]string, error) { + file, err := os.Open(jsonFile) + if err != nil { + return nil, fmt.Errorf("unable to open secret file %s:%v", jsonFile, err) + } + defer file.Close() + + store := make(map[string]string) + decoder := json.NewDecoder(file) + err = decoder.Decode(&store) + return store, err +} diff --git a/pkg/secrets/localstore_test.go b/pkg/secrets/localstore_test.go new file mode 100644 index 0000000..4009946 --- /dev/null +++ b/pkg/secrets/localstore_test.go @@ -0,0 +1,151 @@ +package secrets + +import ( + "encoding/hex" + "os" + "testing" +) + +func TestNewLocalSecretStore(t *testing.T) { + masterKey, err := GenerateMasterKey() + if err != nil { + t.Fatalf("Failed to generate master key: %v", err) + } + + filename := "test_secrets.json" + defer os.Remove(filename) + + store, err := NewLocalSecretStore(masterKey, filename, true) + if err != nil { + t.Fatalf("Failed to create LocalSecretStore: %v", err) + } + + if store.filename != filename { + t.Errorf("Expected filename %s, got %s", filename, store.filename) + } + + if hex.EncodeToString(store.masterKey) != masterKey { + t.Errorf("Expected master key %s, got %s", masterKey, hex.EncodeToString(store.masterKey)) + } +} + +func TestGenerateMasterKey(t *testing.T) { + key, err := GenerateMasterKey() + if err != nil { + t.Fatalf("Failed to generate master key: %v", err) + } + + if len(key) != 64 { // 32 bytes in hex representation + t.Errorf("Expected key length 64, got %d", len(key)) + } +} + +func TestStoreAndGetSecretByID(t *testing.T) { + masterKey, err := GenerateMasterKey() + if err != nil { + t.Fatalf("Failed to generate master key: %v", err) + } + + filename := "test_secrets.json" + defer os.Remove(filename) + + store, err := NewLocalSecretStore(masterKey, filename, true) + if err != nil { + t.Fatalf("Failed to create LocalSecretStore: %v", err) + } + + secretID := "test_secret" + secretValue := "my_secret_value" + + err = store.StoreSecretByID(secretID, secretValue) + if err != nil { + t.Fatalf("Failed to store secret: %v", err) + } + + retrievedSecret, err := store.GetSecretByID(secretID) + if err != nil { + t.Fatalf("Failed to get secret: %v", err) + } + + if retrievedSecret != secretValue { + t.Errorf("Expected secret value %s, got %s", secretValue, retrievedSecret) + } +} + +func TestStoreAndGetSecretJSON(t *testing.T) { + masterKey, err := GenerateMasterKey() + if err != nil { + t.Fatalf("Failed to generate master key: %v", err) + } + + filename := "test_secrets.json" + defer os.Remove(filename) + + store, err := NewLocalSecretStore(masterKey, filename, true) + if err != nil { + t.Fatalf("Failed to create LocalSecretStore: %v", err) + } + + secretID := "json_creds" + jsonSecret := `{"username":"testUser","password":"testPass"}` + + if err := store.StoreSecretByID(secretID, jsonSecret); err != nil { + t.Fatalf("Failed to store JSON secret: %v", err) + } + + retrieved, err := store.GetSecretByID(secretID) + if err != nil { + t.Fatalf("Failed to get JSON secret by ID: %v", err) + } + + if retrieved != jsonSecret { + t.Errorf("Expected %s, got %s", jsonSecret, retrieved) + } +} + +func TestListSecrets(t *testing.T) { + masterKey, err := GenerateMasterKey() + if err != nil { + t.Fatalf("Failed to generate master key: %v", err) + } + + filename := "test_secrets.json" + defer os.Remove(filename) + + store, err := NewLocalSecretStore(masterKey, filename, true) + if err != nil { + t.Fatalf("Failed to create LocalSecretStore: %v", err) + } + + secretID1 := "test_secret_1" + secretValue1 := "my_secret_value_1" + secretID2 := "test_secret_2" + secretValue2 := "my_secret_value_2" + + err = store.StoreSecretByID(secretID1, secretValue1) + if err != nil { + t.Fatalf("Failed to store secret: %v", err) + } + + err = store.StoreSecretByID(secretID2, secretValue2) + if err != nil { + t.Fatalf("Failed to store secret: %v", err) + } + + secrets, err := store.ListSecrets() + if err != nil { + t.Fatalf("Failed to list secrets: %v", err) + } + + if len(secrets) != 2 { + t.Errorf("Expected 2 secrets, got %d", len(secrets)) + } + + if secrets[secretID1] != store.Secrets[secretID1] { + t.Errorf("Expected secret value %s, got %s", store.Secrets[secretID1], secrets[secretID1]) + } + + if secrets[secretID2] != store.Secrets[secretID2] { + t.Errorf("Expected secret value %s, got %s", store.Secrets[secretID2], secrets[secretID2]) + } +} diff --git a/pkg/secrets/main.go b/pkg/secrets/main.go new file mode 100644 index 0000000..5925d53 --- /dev/null +++ b/pkg/secrets/main.go @@ -0,0 +1,7 @@ +package secrets + +type SecretStore interface { + GetSecretByID(secretID string) (string, error) + StoreSecretByID(secretID, secret string) error + ListSecrets() (map[string]string, error) +} diff --git a/pkg/secrets/staticstore.go b/pkg/secrets/staticstore.go new file mode 100644 index 0000000..3e77870 --- /dev/null +++ b/pkg/secrets/staticstore.go @@ -0,0 +1,28 @@ +package secrets + +import "fmt" + +type StaticStore struct { + Username string + Password string +} + +// NewStaticStore creates a new StaticStore with the given username and password. +func NewStaticStore(username, password string) *StaticStore { + return &StaticStore{ + Username: username, + Password: password, + } +} + +func (s *StaticStore) GetSecretByID(secretID string) (string, error) { + return fmt.Sprintf(`{"username":"%s","password":"%s"}`, s.Username, s.Password), nil +} +func (s *StaticStore) StoreSecretByID(secretID, secret string) error { + return nil +} +func (s *StaticStore) ListSecrets() (map[string]string, error) { + return map[string]string{ + "static_creds": fmt.Sprintf(`{"username":"%s","password":"%s"}`, s.Username, s.Password), + }, nil +} diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index c947e17..dfcc5e5 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -14,8 +14,9 @@ import ( "net/http" "testing" - "github.com/davidallendj/magellan/pkg/client" - "github.com/davidallendj/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/client" + "github.com/OpenCHAMI/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/secrets" ) var ( @@ -126,12 +127,17 @@ func TestExpectedOutput(t *testing.T) { t.Fatalf("failed while waiting for emulator: %v", err) } + // initialize a credential store + staticStore := &secrets.StaticStore{ + Username: *username, + Password: *password, + } + systems, err := crawler.CrawlBMCForSystems( crawler.CrawlerConfig{ - URI: *host, - Username: *username, - Password: *password, - Insecure: true, + URI: *host, + CredentialStore: staticStore, + Insecure: true, }, ) From abe0b5e27ac281ae3c3633be566ab3ec5a0f29d6 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 30 Jan 2025 08:43:42 -0700 Subject: [PATCH 11/83] bugfix: fixed URL param not being set for UpdateFirmwareRemote --- cmd/update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/update.go b/cmd/update.go index 83671e4..65e74c5 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -67,7 +67,7 @@ var updateCmd = &cobra.Command{ TransferProtocol: strings.ToUpper(transferProtocol), Insecure: Insecure, CollectParams: magellan.CollectParams{ - URI: host, + URI: arg, Username: username, Password: password, Timeout: timeout, From 14453bbbafb47e6ae4a2fbc44a120b04e3aff13e Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 6 Feb 2025 13:06:37 -0700 Subject: [PATCH 12/83] chore: fix critical dependabot issues by updating crypto --- go.mod | 5 +++++ go.sum | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/go.mod b/go.mod index cabcc22..3bfe1c5 100644 --- a/go.mod +++ b/go.mod @@ -52,8 +52,13 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect +<<<<<<< HEAD golang.org/x/crypto v0.31.0 // indirect golang.org/x/sys v0.28.0 // indirect +======= + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 9c85790..16443ce 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +<<<<<<< HEAD golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +======= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -151,8 +158,15 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +<<<<<<< HEAD golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +======= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -165,7 +179,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +<<<<<<< HEAD golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +======= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From b0ff7a8d387b98d39bd5fb6db0b0d1d2a0c25977 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Feb 2025 17:09:21 -0700 Subject: [PATCH 13/83] refactor: moved internal functions to pkg and updated refs --- pkg/update.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/pkg/update.go b/pkg/update.go index ccbed6b..b140a59 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -1,11 +1,20 @@ package magellan import ( +<<<<<<< HEAD "fmt" "net/url" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" +======= + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/OpenCHAMI/magellan/pkg/client" +>>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) ) type UpdateParams struct { @@ -14,12 +23,16 @@ type UpdateParams struct { FirmwareVersion string Component string TransferProtocol string +<<<<<<< HEAD Insecure bool +======= +>>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) } // UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node. // The function expects the firmware URL, firmware version, and component flags to be // set from the CLI to perform a firmware update. +<<<<<<< HEAD // Example: // ./magellan update https://192.168.23.40 --username root --password 0penBmc // --firmware-url http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar @@ -29,12 +42,15 @@ type UpdateParams struct { // q.URI https://192.168.23.40 // q.TransferProtocol TFTP // q.FirmwarePath http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar +======= +>>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) func UpdateFirmwareRemote(q *UpdateParams) error { // parse URI to set up full address uri, err := url.ParseRequestURI(q.URI) if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } +<<<<<<< HEAD // Connect to the Redfish service using gofish client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) @@ -61,6 +77,34 @@ func UpdateFirmwareRemote(q *UpdateParams) error { return fmt.Errorf("firmware update failed: %w", err) } fmt.Println("Firmware update initiated successfully.") +======= + uri.User = url.UserPassword(q.Username, q.Password) + + // set up other vars + updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService/Actions/SimpleUpdate", uri.String()) + headers := map[string]string{ + "Content-Type": "application/json", + "cache-control": "no-cache", + } + b := map[string]any{ + "UpdateComponent": q.Component, // BMC, BIOS + "TransferProtocol": q.TransferProtocol, + "ImageURI": q.FirmwarePath, + } + data, err := json.Marshal(b) + if err != nil { + return fmt.Errorf("failed to marshal data: %v", err) + } + res, body, err := client.MakeRequest(nil, updateUrl, "POST", data, headers) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", updateUrl) + } + if len(body) > 0 { + fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + } +>>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) return nil } @@ -70,6 +114,7 @@ func GetUpdateStatus(q *UpdateParams) error { if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } +<<<<<<< HEAD // Connect to the Redfish service using gofish client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) @@ -88,5 +133,20 @@ func GetUpdateStatus(q *UpdateParams) error { status := updateService.Status fmt.Printf("Update Status: %v\n", status) +======= + uri.User = url.UserPassword(q.Username, q.Password) + updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService", uri.String()) + res, body, err := client.MakeRequest(nil, updateUrl, "GET", nil, nil) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", updateUrl) + } else if res.StatusCode != http.StatusOK { + return fmt.Errorf("returned status code %d", res.StatusCode) + } + if len(body) > 0 { + fmt.Printf("%v\n", string(body)) + } +>>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) return nil } From 40a82d1c662f8fdbec0e48031529f60ca0246510 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 26 Feb 2025 16:18:42 -0700 Subject: [PATCH 14/83] collect: return collection output from CollectInventory() --- go.sum | 10 ++++++++++ pkg/update.go | 48 ------------------------------------------------ 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/go.sum b/go.sum index 16443ce..3bd122e 100644 --- a/go.sum +++ b/go.sum @@ -124,11 +124,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= ======= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +======= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= >>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) @@ -159,11 +162,14 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +======= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= >>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) @@ -180,9 +186,13 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= ======= >>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) +======= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/pkg/update.go b/pkg/update.go index b140a59..e031c75 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -32,7 +32,6 @@ type UpdateParams struct { // UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node. // The function expects the firmware URL, firmware version, and component flags to be // set from the CLI to perform a firmware update. -<<<<<<< HEAD // Example: // ./magellan update https://192.168.23.40 --username root --password 0penBmc // --firmware-url http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar @@ -42,15 +41,12 @@ type UpdateParams struct { // q.URI https://192.168.23.40 // q.TransferProtocol TFTP // q.FirmwarePath http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar -======= ->>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) func UpdateFirmwareRemote(q *UpdateParams) error { // parse URI to set up full address uri, err := url.ParseRequestURI(q.URI) if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } -<<<<<<< HEAD // Connect to the Redfish service using gofish client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) @@ -77,34 +73,6 @@ func UpdateFirmwareRemote(q *UpdateParams) error { return fmt.Errorf("firmware update failed: %w", err) } fmt.Println("Firmware update initiated successfully.") -======= - uri.User = url.UserPassword(q.Username, q.Password) - - // set up other vars - updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService/Actions/SimpleUpdate", uri.String()) - headers := map[string]string{ - "Content-Type": "application/json", - "cache-control": "no-cache", - } - b := map[string]any{ - "UpdateComponent": q.Component, // BMC, BIOS - "TransferProtocol": q.TransferProtocol, - "ImageURI": q.FirmwarePath, - } - data, err := json.Marshal(b) - if err != nil { - return fmt.Errorf("failed to marshal data: %v", err) - } - res, body, err := client.MakeRequest(nil, updateUrl, "POST", data, headers) - if err != nil { - return fmt.Errorf("something went wrong: %v", err) - } else if res == nil { - return fmt.Errorf("no response returned (url: %s)", updateUrl) - } - if len(body) > 0 { - fmt.Printf("%d: %v\n", res.StatusCode, string(body)) - } ->>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) return nil } @@ -114,7 +82,6 @@ func GetUpdateStatus(q *UpdateParams) error { if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } -<<<<<<< HEAD // Connect to the Redfish service using gofish client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) @@ -133,20 +100,5 @@ func GetUpdateStatus(q *UpdateParams) error { status := updateService.Status fmt.Printf("Update Status: %v\n", status) -======= - uri.User = url.UserPassword(q.Username, q.Password) - updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService", uri.String()) - res, body, err := client.MakeRequest(nil, updateUrl, "GET", nil, nil) - if err != nil { - return fmt.Errorf("something went wrong: %v", err) - } else if res == nil { - return fmt.Errorf("no response returned (url: %s)", updateUrl) - } else if res.StatusCode != http.StatusOK { - return fmt.Errorf("returned status code %d", res.StatusCode) - } - if len(body) > 0 { - fmt.Printf("%v\n", string(body)) - } ->>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) return nil } From 8b3f02b5b5ddab7cc83dea91c55676c5d60cc66d Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Mar 2025 17:10:31 -0500 Subject: [PATCH 15/83] feat(secrets): implement SecretStore interface and StaticStore/LocalStore for credential management --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index 3bfe1c5..e0f0aa5 100644 --- a/go.mod +++ b/go.mod @@ -52,13 +52,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect -<<<<<<< HEAD golang.org/x/crypto v0.31.0 // indirect golang.org/x/sys v0.28.0 // indirect -======= - golang.org/x/crypto v0.32.0 // indirect golang.org/x/sys v0.29.0 // indirect ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect From dd944245c7fab2486d0fe03748ca300f90a34b67 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 6 Feb 2025 17:25:56 -0500 Subject: [PATCH 16/83] feat: enhance firmware update functionality and add BMC identification support --- pkg/update.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pkg/update.go b/pkg/update.go index e031c75..757b455 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -1,20 +1,14 @@ package magellan import ( -<<<<<<< HEAD "fmt" "net/url" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" -======= - "encoding/json" - "fmt" - "net/http" - "net/url" - "github.com/OpenCHAMI/magellan/pkg/client" ->>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/redfish" ) type UpdateParams struct { @@ -23,10 +17,7 @@ type UpdateParams struct { FirmwareVersion string Component string TransferProtocol string -<<<<<<< HEAD Insecure bool -======= ->>>>>>> 81116ec (refactor: moved internal functions to pkg and updated refs) } // UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node. From 33c333a071ec089e0ab0808f08212d5762200f00 Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 6 Feb 2025 13:06:37 -0700 Subject: [PATCH 17/83] chore: fix critical dependabot issues by updating crypto --- go.mod | 3 +-- go.sum | 19 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/go.mod b/go.mod index e0f0aa5..e422da9 100644 --- a/go.mod +++ b/go.mod @@ -52,8 +52,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 3bd122e..5c0828e 100644 --- a/go.sum +++ b/go.sum @@ -130,11 +130,6 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ss ======= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -======= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -168,11 +163,6 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -======= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -185,15 +175,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -<<<<<<< HEAD -<<<<<<< HEAD -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -======= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) -======= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 0b16bf2ef6631b1b05e6eb54badf87878d79493b Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Feb 2025 17:09:21 -0700 Subject: [PATCH 18/83] refactor: moved internal functions to pkg and updated refs --- pkg/update.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/update.go b/pkg/update.go index 757b455..ed4aaa9 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -1,14 +1,12 @@ package magellan import ( + "encoding/json" "fmt" + "net/http" "net/url" - "github.com/stmcginnis/gofish" - "github.com/stmcginnis/gofish/redfish" - - "github.com/stmcginnis/gofish" - "github.com/stmcginnis/gofish/redfish" + "github.com/OpenCHAMI/magellan/pkg/client" ) type UpdateParams struct { From f47869069bdcef79d7ce6c3918adce5abdaf5340 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 26 Feb 2025 16:18:42 -0700 Subject: [PATCH 19/83] collect: return collection output from CollectInventory() --- go.sum | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/go.sum b/go.sum index 5c0828e..6261956 100644 --- a/go.sum +++ b/go.sum @@ -125,11 +125,16 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= ======= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +======= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +>>>>>>> 555990c (collect: return collection output from CollectInventory()) golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -158,11 +163,16 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +======= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +>>>>>>> 555990c (collect: return collection output from CollectInventory()) golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -175,6 +185,11 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +<<<<<<< HEAD +======= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +>>>>>>> 555990c (collect: return collection output from CollectInventory()) golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 9b1147d177dfd98dc894c7f7a4442d8449952086 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 7 Mar 2025 17:10:31 -0500 Subject: [PATCH 20/83] feat(secrets): implement SecretStore interface and StaticStore/LocalStore for credential management --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index e422da9..b39be0a 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.32.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect From 983985464bfa6f6bdaa2d2a131e0af7d5ab031ed Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 6 Feb 2025 13:06:37 -0700 Subject: [PATCH 21/83] chore: fix critical dependabot issues by updating crypto --- go.sum | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/go.sum b/go.sum index 6261956..dd4a29d 100644 --- a/go.sum +++ b/go.sum @@ -131,10 +131,15 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ss ======= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +<<<<<<< HEAD ======= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= >>>>>>> 555990c (collect: return collection output from CollectInventory()) +======= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -169,10 +174,15 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +<<<<<<< HEAD ======= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= >>>>>>> 555990c (collect: return collection output from CollectInventory()) +======= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -186,10 +196,14 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= <<<<<<< HEAD +<<<<<<< HEAD ======= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= >>>>>>> 555990c (collect: return collection output from CollectInventory()) +======= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +>>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 397571c441e7efd6b35f26fb4e836a428f756116 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 26 Feb 2025 16:18:42 -0700 Subject: [PATCH 22/83] collect: return collection output from CollectInventory() --- go.sum | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/go.sum b/go.sum index dd4a29d..8bbacc7 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,7 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= ======= @@ -133,6 +134,8 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= <<<<<<< HEAD ======= +======= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= >>>>>>> 555990c (collect: return collection output from CollectInventory()) @@ -169,6 +172,7 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= @@ -176,6 +180,8 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= <<<<<<< HEAD ======= +======= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= >>>>>>> 555990c (collect: return collection output from CollectInventory()) @@ -197,7 +203,10 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= +======= +>>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= >>>>>>> 555990c (collect: return collection output from CollectInventory()) From d45bfa333db975710e21cef24c9016fae63e37cd Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 13 Mar 2025 11:32:47 -0600 Subject: [PATCH 23/83] fix: update goimports in update.go --- pkg/update.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/update.go b/pkg/update.go index ed4aaa9..ccbed6b 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -1,12 +1,11 @@ package magellan import ( - "encoding/json" "fmt" - "net/http" "net/url" - "github.com/OpenCHAMI/magellan/pkg/client" + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/redfish" ) type UpdateParams struct { From 5aacfceb0d721e1e3c3a242bd47850c69ae87af0 Mon Sep 17 00:00:00 2001 From: David Allen <16520934+davidallendj@users.noreply.github.com> Date: Tue, 25 Mar 2025 13:37:42 -0600 Subject: [PATCH 24/83] Fix emulator image in `rf-emulator.yml` Signed-off-by: David Allen <16520934+davidallendj@users.noreply.github.com> --- emulator/rf-emulator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator/rf-emulator.yml b/emulator/rf-emulator.yml index eec8191..4b399ea 100644 --- a/emulator/rf-emulator.yml +++ b/emulator/rf-emulator.yml @@ -6,7 +6,7 @@ volumes: services: emulator: - image: davidallendj-rie:latest + image: ghcr.io/openchami/csm-rie:latest container_name: rf-emulator environment: BMC_PORT: 5000 From cdf380bd648d2b15744da08a02970fd54d7cd75a Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 14 Mar 2025 13:28:47 -0600 Subject: [PATCH 25/83] refactor: added function to open secrets store by checking env var --- pkg/secrets/example/main.go | 212 ++++++++++++++++++++++++++++++++++++ pkg/secrets/localstore.go | 15 +++ pkg/secrets/staticstore.go | 2 + 3 files changed, 229 insertions(+) create mode 100644 pkg/secrets/example/main.go diff --git a/pkg/secrets/example/main.go b/pkg/secrets/example/main.go new file mode 100644 index 0000000..52ab649 --- /dev/null +++ b/pkg/secrets/example/main.go @@ -0,0 +1,212 @@ +package main + +// This example demonstrates the usage of the LocalSecretStore to store and retrieve secrets. +// It provides a command-line interface to generate a master key, store secrets, and retrieve them. +// The master key is assumed to be stored in the environment variable MASTER_KEY and while it can +// anything you want, we recommend a 32 bit key for AES-256 encryption. The master key is used +// as part of a Key Derivation Function (KDF) to generate a unique AES key for each secret. +// The algorithm of choice is HMAC-based Extract-and-Expand Key Derivation Function (HKDF). +// Each secret is separately encrypted using AES-GCM and stored in a JSON file. +// The JSON file is loaded into memory when the LocalSecretStore is created and saved back to the file +// when a secret is stored or removed. +// + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/OpenCHAMI/magellan/pkg/secrets" +) + +func usage() { + fmt.Println("Usage:") + fmt.Println(" go run main.go generatekey") + fmt.Println(" - Generates a new 32-byte master key (in hex).") + fmt.Println() + fmt.Println(" Export MASTER_KEY= to use the same key in the next commands.") + fmt.Println() + fmt.Println(" go run main.go store [filename]") + fmt.Println(" - Stores the given string value under secretID.") + fmt.Println() + fmt.Println(" go run main.go storebase64 [filename]") + fmt.Println(" - Decodes the base64-encoded string before storing.") + fmt.Println() + fmt.Println(" go run main.go storejson [filename]") + fmt.Println(" - Stores the provided JSON for the specified secretID.") + fmt.Println() + fmt.Println(" go run main.go retrieve [filename]") + fmt.Println(" - Retrieves and prints the secret value for the given secretID.") + fmt.Println() + fmt.Println(" go run main.go list [filename]") + fmt.Println(" - Lists all the secret IDs and their values.") + fmt.Println() +} + +// openStore tries to create or open the LocalSecretStore based on the environment +// variable MASTER_KEY. If not found, it prints an error. +func openStore(filename string) (*secrets.LocalSecretStore, error) { + masterKey := os.Getenv("MASTER_KEY") + if masterKey == "" { + return nil, fmt.Errorf("MASTER_KEY environment variable not set") + } + + store, err := secrets.NewLocalSecretStore(masterKey, filename, true) + if err != nil { + return nil, fmt.Errorf("cannot open secrets store: %v", err) + } + return store, nil +} + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(1) + } + + cmd := os.Args[1] + + switch cmd { + case "generatekey": + key, err := secrets.GenerateMasterKey() + if err != nil { + fmt.Printf("Error generating master key: %v\n", err) + os.Exit(1) + } + fmt.Printf("%s\n", key) + + case "store": + if len(os.Args) < 4 { + fmt.Println("Not enough arguments. Usage: go run main.go store [filename]") + os.Exit(1) + } + secretID := os.Args[2] + secretValue := os.Args[3] + filename := "mysecrets.json" + if len(os.Args) == 5 { + filename = os.Args[4] + } + + store, err := openStore(filename) + 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.") + + case "storebase64": + 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 := 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.") + + case "storejson": + if len(os.Args) < 4 { + fmt.Println(`Not enough arguments. Usage: go run main.go storejson '{"key":"value"}' [filename]`) + os.Exit(1) + } + secretID := os.Args[2] + jsonValue := os.Args[3] + filename := "mysecrets.json" + if len(os.Args) == 5 { + filename = os.Args[4] + } + + store, err := openStore(filename) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := store.StoreSecretByID(secretID, jsonValue); err != nil { + fmt.Printf("Error storing JSON secret: %v\n", err) + os.Exit(1) + } + fmt.Println("JSON secret stored successfully.") + + case "retrieve": + if len(os.Args) < 3 { + fmt.Println("Not enough arguments. Usage: go run main.go retrieve [filename]") + os.Exit(1) + } + secretID := os.Args[2] + filename := "mysecrets.json" + if len(os.Args) == 4 { + filename = os.Args[3] + } + + store, err := openStore(filename) + 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) + + case "list": + if len(os.Args) < 2 { + fmt.Println("Not enough arguments. Usage: go run main.go list [filename]") + os.Exit(1) + } + + filename := "mysecrets.json" + if len(os.Args) == 3 { + filename = os.Args[2] + } + + store, err := openStore(filename) + 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) + } + + default: + usage() + } + +} diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 76fd136..553a63a 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -101,6 +101,21 @@ func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { return secretsCopy, nil } +// openStore tries to create or open the LocalSecretStore based on the environment +// variable MASTER_KEY. If not found, it prints an error. +func OpenStore(filename string) (SecretStore, error) { + masterKey := os.Getenv("MASTER_KEY") + if masterKey == "" { + return nil, fmt.Errorf("MASTER_KEY environment variable not set") + } + + store, err := NewLocalSecretStore(masterKey, filename, true) + if err != nil { + return nil, fmt.Errorf("cannot open secrets store: %v", err) + } + return store, nil +} + // Saves secrets back to the JSON file func saveSecrets(jsonFile string, store map[string]string) error { file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) diff --git a/pkg/secrets/staticstore.go b/pkg/secrets/staticstore.go index 3e77870..85c2d68 100644 --- a/pkg/secrets/staticstore.go +++ b/pkg/secrets/staticstore.go @@ -18,9 +18,11 @@ func NewStaticStore(username, password string) *StaticStore { func (s *StaticStore) GetSecretByID(secretID string) (string, error) { return fmt.Sprintf(`{"username":"%s","password":"%s"}`, s.Username, s.Password), nil } + func (s *StaticStore) StoreSecretByID(secretID, secret string) error { return nil } + func (s *StaticStore) ListSecrets() (map[string]string, error) { return map[string]string{ "static_creds": fmt.Sprintf(`{"username":"%s","password":"%s"}`, s.Username, s.Password), From 9396de11c25ac2fc765f862de12ecc4219ca93ae Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 10:14:36 -0600 Subject: [PATCH 26/83] chore: added pre-condition guards for secrets --- pkg/crawler/main.go | 5 ++++- pkg/secrets/localstore.go | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index ddf6357..bd29fcb 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -148,7 +148,6 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) { return walkSystems(rf_systems, nil, config.URI) } -// CrawlBMCForSystems pulls BMC manager information. // CrawlBMCForManagers connects to a BMC (Baseboard Management Controller) using the provided configuration, // retrieves the ServiceRoot, and then fetches the list of managers from the ServiceRoot. // @@ -374,6 +373,10 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er } func loadBMCCreds(config CrawlerConfig) (BMCUsernamePassword, error) { + // NOTE: it is possible for the SecretStore to be nil, so we need a check + if config.CredentialStore == nil { + return BMCUsernamePassword{}, fmt.Errorf("credential store is invalid") + } creds, err := config.CredentialStore.GetSecretByID(config.URI) if err != nil { event := log.Error() diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 553a63a..1cf862a 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -104,6 +104,10 @@ func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { // openStore tries to create or open the LocalSecretStore based on the environment // variable MASTER_KEY. If not found, it prints an error. func OpenStore(filename string) (SecretStore, error) { + if filename == "" { + return nil, fmt.Errorf("no path to secret store provided") + } + masterKey := os.Getenv("MASTER_KEY") if masterKey == "" { return nil, fmt.Errorf("MASTER_KEY environment variable not set") From f9059c50a13fda30e8d05afd1db2ac5961227d54 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 10:34:53 -0600 Subject: [PATCH 27/83] refactor: added optional secrets file parameter and lookup in collect --- pkg/collect.go | 55 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/pkg/collect.go b/pkg/collect.go index ccb1a67..07591d5 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -41,6 +41,7 @@ type CollectParams struct { OutputPath string // set the path to save output with 'output' flag ForceUpdate bool // set whether to force updating SMD with 'force-update' flag AccessToken string // set the access token to include in request with 'access-token' flag + SecretsFile string // set the path to secrets file } // This is the main function used to collect information from the BMC nodes via Redfish. @@ -60,15 +61,18 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret // collect bmc information asynchronously var ( - offset = 0 - wg sync.WaitGroup - collection = make([]map[string]any, 0) - found = make([]string, 0, len(*assets)) - done = make(chan struct{}, params.Concurrency+1) - chanAssets = make(chan RemoteAsset, params.Concurrency+1) - outputPath = path.Clean(params.OutputPath) - smdClient = &client.SmdClient{Client: &http.Client{}} + offset = 0 + wg sync.WaitGroup + collection = make([]map[string]any, 0) + found = make([]string, 0, len(*assets)) + done = make(chan struct{}, params.Concurrency+1) + chanAssets = make(chan RemoteAsset, params.Concurrency+1) + outputPath = path.Clean(params.OutputPath) + smdClient = &client.SmdClient{Client: &http.Client{}} + initialStore secrets.SecretStore = store + err error ) + // set the client's params from CLI // NOTE: temporary solution until client.NewClient() is fixed smdClient.URI = params.URI @@ -103,27 +107,46 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret return } + // use initial store to check for creds for specific node + store = initialStore + // generate custom xnames for bmcs // TODO: add xname customization via CLI - node := xnames.Node{ - Cabinet: 1000, - Chassis: 1, - ComputeModule: 7, - NodeBMC: offset, - } + var ( + uri = fmt.Sprintf("%s:%d", sr.Host, sr.Port) + node = xnames.Node{ + Cabinet: 1000, + Chassis: 1, + ComputeModule: 7, + NodeBMC: offset, + } + ) offset += 1 + // determine if local store exists and has credentials for + // the provided secretID... + // if it does not, create a static store and use the username + // and password provided instead + _, err = store.GetSecretByID(uri) + if store == nil || err != nil { + log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) + store = secrets.NewStaticStore(params.Username, params.Password) + } + // crawl BMC node to fetch inventory data via Redfish var ( systems []crawler.InventoryDetail managers []crawler.Manager config = crawler.CrawlerConfig{ - URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), + URI: uri, CredentialStore: store, Insecure: true, } + err error ) - systems, err := crawler.CrawlBMCForSystems(config) + + // crawl for node and BMC information + systems, err = crawler.CrawlBMCForSystems(config) if err != nil { log.Error().Err(err).Msg("failed to crawl BMC for systems") } From 842e86438410eb49067a8b322bcfa69b8920fe03 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 10:37:04 -0600 Subject: [PATCH 28/83] 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())) +} From 22af66f95c617af61d8ce6841e81c81b71eae8c2 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 10:37:18 -0600 Subject: [PATCH 29/83] chore: updated go deps --- go.sum | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/go.sum b/go.sum index 8bbacc7..cdc806e 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,7 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= ======= @@ -136,6 +137,8 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+ ======= ======= >>>>>>> 97a569d (collect: return collection output from CollectInventory()) +======= +>>>>>>> 73c3323 (chore: updated go deps) golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= >>>>>>> 555990c (collect: return collection output from CollectInventory()) @@ -173,6 +176,7 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ======= @@ -186,6 +190,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= >>>>>>> 555990c (collect: return collection output from CollectInventory()) ======= +======= +>>>>>>> 73c3323 (chore: updated go deps) golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= >>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) @@ -204,9 +210,12 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= ======= >>>>>>> 97a569d (collect: return collection output from CollectInventory()) +======= +>>>>>>> 73c3323 (chore: updated go deps) golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= >>>>>>> 555990c (collect: return collection output from CollectInventory()) From 51ff7b098c43736dc0d3aee4cdca30c475b07efb Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 11:39:23 -0600 Subject: [PATCH 30/83] feat: add 'secrets' command to root --- cmd/secrets.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/secrets.go b/cmd/secrets.go index 2ca718c..9fc05c4 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -165,6 +165,8 @@ func init() { secretsCmd.AddCommand(secretsRetrieveCmd) secretsCmd.AddCommand(secretsListCmd) + rootCmd.AddCommand(secretsCmd) + checkBindFlagError(viper.BindPFlags(secretsCmd.Flags())) checkBindFlagError(viper.BindPFlags(secretsGenerateKeyCmd.Flags())) checkBindFlagError(viper.BindPFlags(secretsStoreCmd.Flags())) From 9e831914dfcd8983063d2faecf81b5393ce9af9c Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 17 Mar 2025 18:34:08 -0600 Subject: [PATCH 31/83] refactor: updated secrets cmd implementation --- cmd/root.go | 1 - cmd/secrets.go | 152 +++++++++++++++++++++++++++++++------------------ 2 files changed, 97 insertions(+), 56 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3a470e9..778e37f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,7 +38,6 @@ 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 index 9fc05c4..581c332 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -2,14 +2,22 @@ package cmd import ( "encoding/base64" + "encoding/json" "fmt" "os" "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) +var ( + secretsFile string + secretsStoreFormat string + secretsStoreInputFile string +) + var secretsCmd = &cobra.Command{ Use: "secrets", Short: "Manage credentials for BMC nodes", @@ -33,6 +41,7 @@ var secretsCmd = &cobra.Command{ var secretsGenerateKeyCmd = &cobra.Command{ Use: "generatekey", + Args: cobra.NoArgs, Short: "Generates a new 32-byte master key (in hex).", Run: func(cmd *cobra.Command, args []string) { key, err := secrets.GenerateMasterKey() @@ -45,18 +54,73 @@ var secretsGenerateKeyCmd = &cobra.Command{ } var secretsStoreCmd = &cobra.Command{ - Use: "store secretID secretValue", - Args: cobra.ExactArgs(2), + Use: "store secretID ", + Args: cobra.MinimumNArgs(1), Short: "Stores the given string value under secretID.", Run: func(cmd *cobra.Command, args []string) { var ( - secretID = args[0] - secretValue = args[1] + secretID string = args[0] + secretValue string + store secrets.SecretStore + inputFileBytes []byte + err error ) - store, err := secrets.OpenStore(secretsFile) - if err != nil { - fmt.Println(err) + // require either the args or input file + if len(args) < 1 && secretsStoreInputFile == "" { + log.Error().Msg("no input data or file") + os.Exit(1) + } else if len(args) > 1 && secretsStoreInputFile == "" { + secretValue = args[1] + } + + switch secretsStoreFormat { + case "base64": + decoded, err := base64.StdEncoding.DecodeString(secretValue) + if err != nil { + fmt.Printf("Error decoding base64 data: %v\n", err) + os.Exit(1) + } + + // check the decoded string if it's a valid JSON and has creds + if !isValidCredsJSON(string(decoded)) { + log.Error().Msg("value is not a valid JSON or is missing credentials") + os.Exit(1) + } + + store, err = secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + secretValue = string(decoded) + case "json": + // read input from file if set and override + if secretsStoreInputFile != "" { + if secretValue != "" { + log.Error().Msg("cannot use -i/--input-file with positional argument") + os.Exit(1) + } + inputFileBytes, err = os.ReadFile(secretsStoreInputFile) + if err != nil { + log.Error().Err(err).Msg("failed to read input file") + os.Exit(1) + } + secretValue = string(inputFileBytes) + } + + // make sure we have valid JSON with "username" and "password" properties + if !isValidCredsJSON(string(secretValue)) { + log.Error().Err(err).Msg("not a valid JSON or creds") + os.Exit(1) + } + store, err = secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + default: + log.Error().Msg("no input format set") os.Exit(1) } @@ -68,58 +132,39 @@ var secretsStoreCmd = &cobra.Command{ }, } -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.") - }, +func isValidCredsJSON(val string) bool { + var ( + valid bool = !json.Valid([]byte(val)) + creds map[string]string + err error + ) + err = json.Unmarshal([]byte(val), &creds) + if err != nil { + return false + } + _, valid = creds["username"] + _, valid = creds["password"] + return valid } var secretsRetrieveCmd = &cobra.Command{ - Use: "retrieve secretID", + Use: "retrieve secretID", + Args: cobra.MinimumNArgs(1), 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] + var ( + secretID = args[0] + secretValue string + store secrets.SecretStore + err error + ) - store, err := secrets.OpenStore(secretsFile) + store, err = secrets.OpenStore(secretsFile) if err != nil { fmt.Println(err) os.Exit(1) } - secretValue, err := store.GetSecretByID(secretID) + secretValue, err = store.GetSecretByID(secretID) if err != nil { fmt.Printf("Error retrieving secret: %v\n", err) os.Exit(1) @@ -130,13 +175,9 @@ var secretsRetrieveCmd = &cobra.Command{ var secretsListCmd = &cobra.Command{ Use: "list", + Args: cobra.MinimumNArgs(1), 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) @@ -158,10 +199,11 @@ var secretsListCmd = &cobra.Command{ func init() { secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "") + secretsStoreCmd.Flags().StringVar(&secretsStoreFormat, "format", "json", "set the input format for the secrets file (json|base64)") + secretsStoreCmd.Flags().StringVarP(&secretsStoreInputFile, "input-file", "i", "", "set the file to read as input") secretsCmd.AddCommand(secretsGenerateKeyCmd) secretsCmd.AddCommand(secretsStoreCmd) - secretsCmd.AddCommand(secretsStoreBase64Cmd) secretsCmd.AddCommand(secretsRetrieveCmd) secretsCmd.AddCommand(secretsListCmd) From 35cf2222a0985427330eb8861a11075921ac56ad Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 19 Mar 2025 11:10:26 -0600 Subject: [PATCH 32/83] refactor: added func to remove secrets from store --- cmd/secrets.go | 18 ++++++++++++++++++ pkg/secrets/localstore.go | 5 +++++ pkg/secrets/main.go | 1 + 3 files changed, 24 insertions(+) diff --git a/cmd/secrets.go b/cmd/secrets.go index 581c332..ab838a2 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -197,6 +197,23 @@ var secretsListCmd = &cobra.Command{ }, } +var secretsRemoveCmd = &cobra.Command{ + Use: "remove secretIDs...", + Args: cobra.MinimumNArgs(2), + Short: "Remove secrets by IDs from secret store.", + Run: func(cmd *cobra.Command, args []string) { + for _, secretID := range args { + store, err := secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + store.RemoveSecretByID(secretID) + } + }, +} + func init() { secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "") secretsStoreCmd.Flags().StringVar(&secretsStoreFormat, "format", "json", "set the input format for the secrets file (json|base64)") @@ -206,6 +223,7 @@ func init() { secretsCmd.AddCommand(secretsStoreCmd) secretsCmd.AddCommand(secretsRetrieveCmd) secretsCmd.AddCommand(secretsListCmd) + secretsCmd.AddCommand(secretsRemoveCmd) rootCmd.AddCommand(secretsCmd) diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 1cf862a..e87a7cc 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -101,6 +101,11 @@ func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { return secretsCopy, nil } +// RemoveSecretByID removes the specified secretID stored locally +func (l *LocalSecretStore) RemoveSecretByID(secretID string) { + delete(l.Secrets, secretID) +} + // openStore tries to create or open the LocalSecretStore based on the environment // variable MASTER_KEY. If not found, it prints an error. func OpenStore(filename string) (SecretStore, error) { diff --git a/pkg/secrets/main.go b/pkg/secrets/main.go index 5925d53..6d27976 100644 --- a/pkg/secrets/main.go +++ b/pkg/secrets/main.go @@ -4,4 +4,5 @@ type SecretStore interface { GetSecretByID(secretID string) (string, error) StoreSecretByID(secretID, secret string) error ListSecrets() (map[string]string, error) + RemoveSecretByID(secretID string) } From b49b1f761d01691ecbf57984624bce669379a0d8 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 19 Mar 2025 11:36:29 -0600 Subject: [PATCH 33/83] fix: added missing funcs for secret store implementations --- pkg/secrets/localstore.go | 2 ++ pkg/secrets/staticstore.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index e87a7cc..1ae9b91 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -103,7 +103,9 @@ func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { // RemoveSecretByID removes the specified secretID stored locally func (l *LocalSecretStore) RemoveSecretByID(secretID string) { + l.mu.RLock() delete(l.Secrets, secretID) + l.mu.RUnlock() } // openStore tries to create or open the LocalSecretStore based on the environment diff --git a/pkg/secrets/staticstore.go b/pkg/secrets/staticstore.go index 85c2d68..405f58b 100644 --- a/pkg/secrets/staticstore.go +++ b/pkg/secrets/staticstore.go @@ -28,3 +28,7 @@ func (s *StaticStore) ListSecrets() (map[string]string, error) { "static_creds": fmt.Sprintf(`{"username":"%s","password":"%s"}`, s.Username, s.Password), }, nil } + +func (s *StaticStore) RemoveSecretByID(secretID string) { + // Nothing to do here, since nothing is being stored +} From a4928b9ebb3c4f84b8b0910f5d70ba1d1b2f7c0c Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 19 Mar 2025 15:31:24 -0600 Subject: [PATCH 34/83] refactor: minor changes to store in collect --- pkg/collect.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/collect.go b/pkg/collect.go index 07591d5..a83c730 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -70,7 +70,6 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret outputPath = path.Clean(params.OutputPath) smdClient = &client.SmdClient{Client: &http.Client{}} initialStore secrets.SecretStore = store - err error ) // set the client's params from CLI @@ -127,9 +126,14 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret // the provided secretID... // if it does not, create a static store and use the username // and password provided instead - _, err = store.GetSecretByID(uri) - if store == nil || err != nil { - log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) + if store != nil { + _, err := store.GetSecretByID(uri) + if err != nil { + log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) + store = secrets.NewStaticStore(params.Username, params.Password) + } + } else { + log.Warn().Msgf("invalid store...falling back to default provided credentials for %s", uri) store = secrets.NewStaticStore(params.Username, params.Password) } From d4443ac6c98ef6530357ddf59f71af86054940b5 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 19 Mar 2025 15:46:53 -0600 Subject: [PATCH 35/83] fix: added secrets file path to collect parameters --- cmd/collect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/collect.go b/cmd/collect.go index 68dad7b..f8804cc 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -71,6 +71,7 @@ var CollectCmd = &cobra.Command{ OutputPath: outputPath, ForceUpdate: forceUpdate, AccessToken: accessToken, + SecretsFile: secretsFile, } // show all of the 'collect' parameters being set from CLI if verbose @@ -85,7 +86,6 @@ var CollectCmd = &cobra.Command{ // Create a StaticSecretStore to hold the username and password fmt.Println(err) store = secrets.NewStaticStore(username, password) - } else { } _, err = magellan.CollectInventory(&scannedResults, params, store) From 056932952978f3d14d4c7f6153787c82a79feaf8 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 08:58:06 -0600 Subject: [PATCH 36/83] refactor: added exact number of args to list cmd --- cmd/list.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/list.go b/cmd/list.go index 24945ae..a8e6d64 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -21,6 +21,7 @@ var ( // is what is consumed by the `collect` command with the --cache flag. var ListCmd = &cobra.Command{ Use: "list", + Args: cobra.ExactArgs(0), Short: "List information stored in cache from a scan", Long: "Prints all of the host and associated data found from performing a scan.\n" + "See the 'scan' command on how to perform a scan.\n\n" + From 07e3d0eb7a97fd29053daf684faacfff058525e1 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 08:59:15 -0600 Subject: [PATCH 37/83] refactor: changed required number of args for secrets list --- cmd/secrets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index ab838a2..b75e4e7 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -175,7 +175,7 @@ var secretsRetrieveCmd = &cobra.Command{ var secretsListCmd = &cobra.Command{ Use: "list", - Args: cobra.MinimumNArgs(1), + Args: cobra.ExactArgs(0), Short: "Lists all the secret IDs and their values.", Run: func(cmd *cobra.Command, args []string) { store, err := secrets.OpenStore(secretsFile) From 149fcaec6dfd1d9ccf26eeb7669467168ad5b5cb Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 08:59:48 -0600 Subject: [PATCH 38/83] refactor: changed to use local store with static store fallback --- cmd/crawl.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 55757ce..3a3c4e5 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -36,13 +36,17 @@ var CrawlCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { - staticStore := &secrets.StaticStore{ - Username: viper.GetString("crawl.username"), - Password: viper.GetString("crawl.password"), + store, err := secrets.OpenStore(secretsFile) + if err != nil { + fmt.Println(err) + store = &secrets.StaticStore{ + Username: viper.GetString("crawl.username"), + Password: viper.GetString("crawl.password"), + } } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ URI: args[0], - CredentialStore: staticStore, + CredentialStore: store, Insecure: cmd.Flag("insecure").Value.String() == "true", }) if err != nil { From 8866dff3077cb7ce2a08beaaee01cf5c6731532f Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 09:11:21 -0600 Subject: [PATCH 39/83] refactor: added flag to set secrets file for crawl --- cmd/crawl.go | 1 + cmd/secrets.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 3a3c4e5..12203eb 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -68,6 +68,7 @@ func init() { CrawlCmd.Flags().StringP("username", "u", "", "Set the username for the BMC") CrawlCmd.Flags().StringP("password", "p", "", "Set the password for the BMC") CrawlCmd.Flags().BoolP("insecure", "i", false, "Ignore SSL errors") + CrawlCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "set the secrets file with BMC credentials") checkBindFlagError(viper.BindPFlag("crawl.username", CrawlCmd.Flags().Lookup("username"))) checkBindFlagError(viper.BindPFlag("crawl.password", CrawlCmd.Flags().Lookup("password"))) diff --git a/cmd/secrets.go b/cmd/secrets.go index b75e4e7..b4aaa43 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -215,7 +215,7 @@ var secretsRemoveCmd = &cobra.Command{ } func init() { - secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "") + secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "set the secrets file with BMC credentials") secretsStoreCmd.Flags().StringVar(&secretsStoreFormat, "format", "json", "set the input format for the secrets file (json|base64)") secretsStoreCmd.Flags().StringVarP(&secretsStoreInputFile, "input-file", "i", "", "set the file to read as input") From c3e1b40e3b124e2040f925722891aaaa2ff9c4fc Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 09:27:39 -0600 Subject: [PATCH 40/83] fix: changed number of minimum args for secrets list --- cmd/secrets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index b4aaa43..3ab09c5 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -199,7 +199,7 @@ var secretsListCmd = &cobra.Command{ var secretsRemoveCmd = &cobra.Command{ Use: "remove secretIDs...", - Args: cobra.MinimumNArgs(2), + Args: cobra.MinimumNArgs(1), Short: "Remove secrets by IDs from secret store.", Run: func(cmd *cobra.Command, args []string) { for _, secretID := range args { From 0333caa403d18546125c3401ce171c9228919857 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 09:28:24 -0600 Subject: [PATCH 41/83] refactor: changed removing secret from store returns error --- pkg/secrets/localstore.go | 8 +++++++- pkg/secrets/main.go | 2 +- pkg/secrets/staticstore.go | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 1ae9b91..08acd6c 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -102,10 +102,16 @@ func (l *LocalSecretStore) ListSecrets() (map[string]string, error) { } // RemoveSecretByID removes the specified secretID stored locally -func (l *LocalSecretStore) RemoveSecretByID(secretID string) { +func (l *LocalSecretStore) RemoveSecretByID(secretID string) error { l.mu.RLock() + // Let user know if there was nothing to delete + _, err := l.GetSecretByID(secretID) + if err != nil { + return err + } delete(l.Secrets, secretID) l.mu.RUnlock() + return nil } // openStore tries to create or open the LocalSecretStore based on the environment diff --git a/pkg/secrets/main.go b/pkg/secrets/main.go index 6d27976..5cb7f95 100644 --- a/pkg/secrets/main.go +++ b/pkg/secrets/main.go @@ -4,5 +4,5 @@ type SecretStore interface { GetSecretByID(secretID string) (string, error) StoreSecretByID(secretID, secret string) error ListSecrets() (map[string]string, error) - RemoveSecretByID(secretID string) + RemoveSecretByID(secretID string) error } diff --git a/pkg/secrets/staticstore.go b/pkg/secrets/staticstore.go index 405f58b..40d9049 100644 --- a/pkg/secrets/staticstore.go +++ b/pkg/secrets/staticstore.go @@ -29,6 +29,7 @@ func (s *StaticStore) ListSecrets() (map[string]string, error) { }, nil } -func (s *StaticStore) RemoveSecretByID(secretID string) { - // Nothing to do here, since nothing is being stored +func (s *StaticStore) RemoveSecretByID(secretID string) error { + // Nothing to do here, since nothing is being stored. With different implementations, we could return an error when no secret is found for a specific ID. + return nil } From 6ae0121af730d720909001331a6e926cb65d0320 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 10:17:33 -0600 Subject: [PATCH 42/83] fix: secrets remove not updating local store and return error when not found --- cmd/secrets.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index 3ab09c5..65f03db 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -203,13 +203,22 @@ var secretsRemoveCmd = &cobra.Command{ Short: "Remove secrets by IDs from secret store.", Run: func(cmd *cobra.Command, args []string) { for _, secretID := range args { + // open secret store from file store, err := secrets.OpenStore(secretsFile) if err != nil { fmt.Println(err) os.Exit(1) } - store.RemoveSecretByID(secretID) + // remove secret from store by it's ID + err = store.RemoveSecretByID(secretID) + if err != nil { + fmt.Println("failed to remove secret: ", err) + os.Exit(1) + } + + // update store by saving to original file + secrets.SaveSecrets(secretsFile, store.(*secrets.LocalSecretStore).Secrets) } }, } From 5c624de8211318e7f11351c5053cac78425031ac Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 20 Mar 2025 10:18:22 -0600 Subject: [PATCH 43/83] refactor: export function to save JSON secrets --- pkg/secrets/localstore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 08acd6c..5013d52 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -84,7 +84,7 @@ func (l *LocalSecretStore) StoreSecretByID(secretID, secret string) error { l.mu.Lock() l.Secrets[secretID] = encryptedSecret - err = saveSecrets(l.filename, l.Secrets) + err = SaveSecrets(l.filename, l.Secrets) l.mu.Unlock() return err } @@ -134,7 +134,7 @@ func OpenStore(filename string) (SecretStore, error) { } // Saves secrets back to the JSON file -func saveSecrets(jsonFile string, store map[string]string) error { +func SaveSecrets(jsonFile string, store map[string]string) error { file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err From 4dd01867f76f59f0f2b4b92da96c22e269fdc009 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 24 Mar 2025 11:35:37 -0600 Subject: [PATCH 44/83] refactor: change error message to warning --- cmd/collect.go | 2 +- cmd/secrets.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/collect.go b/cmd/collect.go index f8804cc..a22a29e 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -84,7 +84,7 @@ var CollectCmd = &cobra.Command{ if err != nil { // Something went wrong with the store so try using // Create a StaticSecretStore to hold the username and password - fmt.Println(err) + log.Warn().Err(err).Msg("failed to open local store") store = secrets.NewStaticStore(username, password) } diff --git a/cmd/secrets.go b/cmd/secrets.go index 65f03db..7e86170 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -74,6 +74,7 @@ var secretsStoreCmd = &cobra.Command{ secretValue = args[1] } + // handle input file format switch secretsStoreFormat { case "base64": decoded, err := base64.StdEncoding.DecodeString(secretValue) From da8b1a175671424e92bfdf5e59d4876aeb6901e8 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 24 Mar 2025 11:36:53 -0600 Subject: [PATCH 45/83] refactor: minor changes to error messages --- pkg/secrets/localstore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/secrets/localstore.go b/pkg/secrets/localstore.go index 5013d52..f426320 100644 --- a/pkg/secrets/localstore.go +++ b/pkg/secrets/localstore.go @@ -118,7 +118,7 @@ func (l *LocalSecretStore) RemoveSecretByID(secretID string) error { // variable MASTER_KEY. If not found, it prints an error. func OpenStore(filename string) (SecretStore, error) { if filename == "" { - return nil, fmt.Errorf("no path to secret store provided") + return nil, fmt.Errorf("path to secret store required") } masterKey := os.Getenv("MASTER_KEY") @@ -128,7 +128,7 @@ func OpenStore(filename string) (SecretStore, error) { store, err := NewLocalSecretStore(masterKey, filename, true) if err != nil { - return nil, fmt.Errorf("cannot open secrets store: %v", err) + return nil, fmt.Errorf("failed to create new local secret store: %v", err) } return store, nil } From 1ba78539fb9966e6de9075b84166b3722b27797e Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 24 Mar 2025 13:36:02 -0600 Subject: [PATCH 46/83] refactor: added basic input format and cleanup --- cmd/secrets.go | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index 7e86170..c6e1972 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "strings" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" @@ -54,7 +55,7 @@ var secretsGenerateKeyCmd = &cobra.Command{ } var secretsStoreCmd = &cobra.Command{ - Use: "store secretID ", + Use: "store secretID ", Args: cobra.MinimumNArgs(1), Short: "Stores the given string value under secretID.", Run: func(cmd *cobra.Command, args []string) { @@ -71,31 +72,59 @@ var secretsStoreCmd = &cobra.Command{ log.Error().Msg("no input data or file") os.Exit(1) } else if len(args) > 1 && secretsStoreInputFile == "" { + // use args[1] here because args[0] is the secretID secretValue = args[1] } // handle input file format switch secretsStoreFormat { - case "base64": + case "basic": // format: $username:$password + var ( + values []string + username string + password string + ) + // seperate username and password provided + values = strings.Split(secretValue, ":") + if len(values) != 2 { + log.Error().Msgf("expected 2 arguments in [username:password] format but got %d", len(values)) + os.Exit(1) + } + + // open secret store to save credentials + store, err = secrets.OpenStore(secretsFile) + if err != nil { + log.Error().Err(err).Msg("failed to open secrets store") + os.Exit(1) + } + + // extract username/password from input (for clarity) + username = values[0] + password = values[1] + + // create JSON formatted string from input + secretValue = fmt.Sprintf("{\"username\": \"%s\", \"password\": \"%s\"}", username, password) + + case "base64": // format: ($encoded_base64_string) decoded, err := base64.StdEncoding.DecodeString(secretValue) if err != nil { - fmt.Printf("Error decoding base64 data: %v\n", err) + log.Error().Err(err).Msg("error decoding base64 data") os.Exit(1) } // check the decoded string if it's a valid JSON and has creds if !isValidCredsJSON(string(decoded)) { - log.Error().Msg("value is not a valid JSON or is missing credentials") + log.Error().Err(err).Msg("value is not a valid JSON or is missing credentials") os.Exit(1) } store, err = secrets.OpenStore(secretsFile) if err != nil { - fmt.Println(err) + log.Error().Err(err).Msg("failed to open secrets store") os.Exit(1) } secretValue = string(decoded) - case "json": + case "json": // format: {"username": $username, "password": $password} // read input from file if set and override if secretsStoreInputFile != "" { if secretValue != "" { @@ -129,7 +158,6 @@ var secretsStoreCmd = &cobra.Command{ fmt.Printf("Error storing secret: %v\n", err) os.Exit(1) } - fmt.Println("Secret stored successfully.") }, } @@ -191,7 +219,6 @@ var secretsListCmd = &cobra.Command{ os.Exit(1) } - fmt.Println("Secrets:") for key, value := range secrets { fmt.Printf("%s: %s\n", key, value) } @@ -225,8 +252,8 @@ var secretsRemoveCmd = &cobra.Command{ } func init() { - secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "set the secrets file with BMC credentials") - secretsStoreCmd.Flags().StringVar(&secretsStoreFormat, "format", "json", "set the input format for the secrets file (json|base64)") + secretsCmd.Flags().StringVarP(&secretsFile, "output-file", "o", "nodes.json", "set the secrets file with BMC credentials") + secretsStoreCmd.Flags().StringVarP(&secretsStoreFormat, "format", "f", "basic", "set the input format for the secrets file (basic|json|base64)") secretsStoreCmd.Flags().StringVarP(&secretsStoreInputFile, "input-file", "i", "", "set the file to read as input") secretsCmd.AddCommand(secretsGenerateKeyCmd) From d1042d77aaa4538ef98e0c6d5d604ce816cc9906 Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 24 Mar 2025 14:29:16 -0600 Subject: [PATCH 47/83] refactor: changed short opts for secret store --- cmd/secrets.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index c6e1972..fb61168 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -252,8 +252,8 @@ var secretsRemoveCmd = &cobra.Command{ } func init() { - secretsCmd.Flags().StringVarP(&secretsFile, "output-file", "o", "nodes.json", "set the secrets file with BMC credentials") - secretsStoreCmd.Flags().StringVarP(&secretsStoreFormat, "format", "f", "basic", "set the input format for the secrets file (basic|json|base64)") + secretsCmd.Flags().StringVarP(&secretsFile, "file", "f", "nodes.json", "set the secrets file with BMC credentials") + secretsStoreCmd.Flags().StringVarP(&secretsStoreFormat, "format", "F", "basic", "set the input format for the secrets file (basic|json|base64)") secretsStoreCmd.Flags().StringVarP(&secretsStoreInputFile, "input-file", "i", "", "set the file to read as input") secretsCmd.AddCommand(secretsGenerateKeyCmd) From 5c4ca3497640368e2b2e68d97b062a5a4207b092 Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 24 Mar 2025 14:29:47 -0600 Subject: [PATCH 48/83] refactor: use vars for cred flags --- cmd/crawl.go | 10 +++++----- cmd/root.go | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 12203eb..db9821c 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -40,8 +40,8 @@ var CrawlCmd = &cobra.Command{ if err != nil { fmt.Println(err) store = &secrets.StaticStore{ - Username: viper.GetString("crawl.username"), - Password: viper.GetString("crawl.password"), + Username: username, + Password: password, } } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ @@ -65,9 +65,9 @@ var CrawlCmd = &cobra.Command{ } func init() { - CrawlCmd.Flags().StringP("username", "u", "", "Set the username for the BMC") - CrawlCmd.Flags().StringP("password", "p", "", "Set the password for the BMC") - CrawlCmd.Flags().BoolP("insecure", "i", false, "Ignore SSL errors") + 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") checkBindFlagError(viper.BindPFlag("crawl.username", CrawlCmd.Flags().Lookup("username"))) diff --git a/cmd/root.go b/cmd/root.go index 778e37f..4e713fe 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,6 +44,7 @@ var ( verbose bool debug bool forceUpdate bool + insecure bool ) // The `root` command doesn't do anything on it's own except display From cc112e72e4917152dd4c46a6b94b55b1c8b8dcc4 Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 24 Mar 2025 14:43:34 -0600 Subject: [PATCH 49/83] refactor: changed logging to use consistent JSON format --- cmd/crawl.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index db9821c..9192d59 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -3,7 +3,8 @@ package cmd import ( "encoding/json" "fmt" - "log" + + "github.com/rs/zerolog/log" urlx "github.com/OpenCHAMI/magellan/internal/url" "github.com/OpenCHAMI/magellan/pkg/crawler" @@ -36,9 +37,11 @@ var CrawlCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { + // try and load credentials from local store first store, err := secrets.OpenStore(secretsFile) if err != nil { - fmt.Println(err) + log.Error().Err(err).Msg("failed to open store") + // try and use the `username` and `password` arguments instead store = &secrets.StaticStore{ Username: username, Password: password, @@ -47,15 +50,15 @@ var CrawlCmd = &cobra.Command{ systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ URI: args[0], CredentialStore: store, - Insecure: cmd.Flag("insecure").Value.String() == "true", + Insecure: insecure, }) if err != nil { - log.Fatalf("Error crawling BMC: %v", err) + log.Error().Err(err).Msg("error crawling BMC") } // Marshal the inventory details to JSON jsonData, err := json.MarshalIndent(systems, "", " ") if err != nil { - fmt.Println("Error marshalling to JSON:", err) + log.Error().Err(err).Msg("error marshalling to JSON:") return } From 2b0245e17bde09d6475ed66858c5f8dc3eb8a85c Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 24 Mar 2025 15:32:47 -0600 Subject: [PATCH 50/83] refactor: added check for secretID in secrets store --- cmd/crawl.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 9192d59..0e2984d 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -37,28 +37,37 @@ var CrawlCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { + var ( + uri = args[0] + store secrets.SecretStore + err error + ) // try and load credentials from local store first - store, err := secrets.OpenStore(secretsFile) + store, err = secrets.OpenStore(secretsFile) if err != nil { - log.Error().Err(err).Msg("failed to open store") + log.Warn().Err(err).Msg("failed to open local store...falling back to default provided arguments") // try and use the `username` and `password` arguments instead - store = &secrets.StaticStore{ - Username: username, - Password: password, - } + store = secrets.NewStaticStore(username, password) } + + // found the store so try to load the creds + _, err = store.GetSecretByID(uri) + if err != nil { + store = secrets.NewStaticStore(username, password) + } + systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ - URI: args[0], + URI: uri, CredentialStore: store, Insecure: insecure, }) if err != nil { - log.Error().Err(err).Msg("error crawling BMC") + log.Error().Err(err).Msg("failed to crawl BMC") } // Marshal the inventory details to JSON jsonData, err := json.MarshalIndent(systems, "", " ") if err != nil { - log.Error().Err(err).Msg("error marshalling to JSON:") + log.Error().Err(err).Msg("failed to marshal JSON") return } From b7cf7233a899f226a5e714db5cb7b243bb5ad384 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 09:53:58 -0600 Subject: [PATCH 51/83] fix: collect not falling back to CLI args correctly --- pkg/collect.go | 57 ++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/pkg/collect.go b/pkg/collect.go index a83c730..1a2552e 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -61,15 +61,14 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret // collect bmc information asynchronously var ( - offset = 0 - wg sync.WaitGroup - collection = make([]map[string]any, 0) - found = make([]string, 0, len(*assets)) - done = make(chan struct{}, params.Concurrency+1) - chanAssets = make(chan RemoteAsset, params.Concurrency+1) - outputPath = path.Clean(params.OutputPath) - smdClient = &client.SmdClient{Client: &http.Client{}} - initialStore secrets.SecretStore = store + offset = 0 + wg sync.WaitGroup + collection = make([]map[string]any, 0) + found = make([]string, 0, len(*assets)) + done = make(chan struct{}, params.Concurrency+1) + chanAssets = make(chan RemoteAsset, params.Concurrency+1) + outputPath = path.Clean(params.OutputPath) + smdClient = &client.SmdClient{Client: &http.Client{}} ) // set the client's params from CLI @@ -106,9 +105,6 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret return } - // use initial store to check for creds for specific node - store = initialStore - // generate custom xnames for bmcs // TODO: add xname customization via CLI var ( @@ -122,26 +118,12 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret ) offset += 1 - // determine if local store exists and has credentials for - // the provided secretID... - // if it does not, create a static store and use the username - // and password provided instead - if store != nil { - _, err := store.GetSecretByID(uri) - if err != nil { - log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) - store = secrets.NewStaticStore(params.Username, params.Password) - } - } else { - log.Warn().Msgf("invalid store...falling back to default provided credentials for %s", uri) - store = secrets.NewStaticStore(params.Username, params.Password) - } - // crawl BMC node to fetch inventory data via Redfish var ( - systems []crawler.InventoryDetail - managers []crawler.Manager - config = crawler.CrawlerConfig{ + fallbackStore = secrets.NewStaticStore(params.Username, params.Password) + systems []crawler.InventoryDetail + managers []crawler.Manager + config = crawler.CrawlerConfig{ URI: uri, CredentialStore: store, Insecure: true, @@ -149,6 +131,21 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret err error ) + // determine if local store exists and has credentials for + // the provided secretID... + // if it does not, use the fallback static store instead with + // the username and password provided as arguments + if store != nil { + _, err := store.GetSecretByID(uri) + if err != nil { + log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) + config.CredentialStore = fallbackStore + } + } else { + log.Warn().Msgf("invalid store...falling back to default provided credentials for %s", uri) + config.CredentialStore = fallbackStore + } + // crawl for node and BMC information systems, err = crawler.CrawlBMCForSystems(config) if err != nil { From 6c5e9588631a6bba5142e7116397a9093032dbdf Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 14:15:39 -0600 Subject: [PATCH 52/83] fix: added username/password to collect params --- cmd/collect.go | 2 ++ cmd/secrets.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index a22a29e..6af6bf5 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -72,6 +72,8 @@ var CollectCmd = &cobra.Command{ ForceUpdate: forceUpdate, AccessToken: accessToken, SecretsFile: secretsFile, + Username: username, + Password: password, } // show all of the 'collect' parameters being set from CLI if verbose diff --git a/cmd/secrets.go b/cmd/secrets.go index fb61168..3766de9 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -219,8 +219,8 @@ var secretsListCmd = &cobra.Command{ os.Exit(1) } - for key, value := range secrets { - fmt.Printf("%s: %s\n", key, value) + for key := range secrets { + fmt.Printf("%s\n", key) } }, } From 94a339e39e9d3dd39f8928f53c03883f255179a9 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 14:16:19 -0600 Subject: [PATCH 53/83] refactor: changed var name for clarity and added logging details --- pkg/collect.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/collect.go b/pkg/collect.go index 1a2552e..31288fc 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -50,7 +50,7 @@ type CollectParams struct { // // Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency // property value between 1 and 10000. -func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secrets.SecretStore) ([]map[string]any, error) { +func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore secrets.SecretStore) ([]map[string]any, error) { // check for available remote assets found from scan if assets == nil { return nil, fmt.Errorf("no assets found") @@ -125,7 +125,7 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret managers []crawler.Manager config = crawler.CrawlerConfig{ URI: uri, - CredentialStore: store, + CredentialStore: localStore, Insecure: true, } err error @@ -135,14 +135,14 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, store secret // the provided secretID... // if it does not, use the fallback static store instead with // the username and password provided as arguments - if store != nil { - _, err := store.GetSecretByID(uri) + if localStore != nil { + _, err := localStore.GetSecretByID(uri) if err != nil { - log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials", uri) + log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials for user '%s'", uri, params.Username) config.CredentialStore = fallbackStore } } else { - log.Warn().Msgf("invalid store...falling back to default provided credentials for %s", uri) + log.Warn().Msgf("invalid store for %s...falling back to default provided credentials for user '%s'", uri, params.Username) config.CredentialStore = fallbackStore } From 69abd2041df80ea6cd971a15b93a1a90c3c15a8d Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 14:52:58 -0600 Subject: [PATCH 54/83] makefile: updated golangci-lint version --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5495893..1b55064 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ mod: ## go mod tidy inst: ## go install tools $(call print-target) go install github.com/client9/misspell/cmd/misspell@v0.3.4 - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.0.1 go install github.com/goreleaser/goreleaser/v2@v2.3.2 go install github.com/cpuguy83/go-md2man/v2@latest @@ -71,7 +71,7 @@ build: ## go build container: ## docker build container: $(call print-target) - docker build . --build-arg REGISTRY_HOST=${REGISTRY_HOST} --no-cache --pull --tag '${NAME}:${VERSION}' + docker build . --build-arg REGISTRY_HOST=${REGISTRY_HOST} --no-cache --pull --tag '${NAME}:${VERSION}' .PHONY: spell spell: ## misspell From 5e200edab5379d81ae5c0f57f589610e69d55815 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 14:54:09 -0600 Subject: [PATCH 55/83] lint: apply changes from golint --- cmd/secrets.go | 4 ++-- pkg/collect.go | 2 +- tests/compatibility_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index 3766de9..cb6a191 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -60,7 +60,7 @@ var secretsStoreCmd = &cobra.Command{ Short: "Stores the given string value under secretID.", Run: func(cmd *cobra.Command, args []string) { var ( - secretID string = args[0] + secretID = args[0] secretValue string store secrets.SecretStore inputFileBytes []byte @@ -163,7 +163,7 @@ var secretsStoreCmd = &cobra.Command{ func isValidCredsJSON(val string) bool { var ( - valid bool = !json.Valid([]byte(val)) + valid = !json.Valid([]byte(val)) creds map[string]string err error ) diff --git a/pkg/collect.go b/pkg/collect.go index 31288fc..40ec851 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -81,7 +81,7 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s } certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(cacert) - smdClient.Client.Transport = &http.Transport{ + smdClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: certPool, InsecureSkipVerify: true, diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index dfcc5e5..f428f6a 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -78,7 +78,7 @@ func TestRedfishV1ServiceRootAvailability(t *testing.T) { // Simple test to ensure an expected Redfish version minimum requirement. func TestRedfishV1Version(t *testing.T) { var ( - url string = fmt.Sprintf("%s/redfish/v1/", *host) + url = fmt.Sprintf("%s/redfish/v1/", *host) body client.HTTPBody = []byte{} headers client.HTTPHeader = map[string]string{} testClient = &http.Client{ From e20b6a3b8e368c74d10684fd00f0915bdaf84428 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 15:26:14 -0600 Subject: [PATCH 56/83] makefile: corrected golangci-lint install string --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1b55064..a28a664 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ mod: ## go mod tidy inst: ## go install tools $(call print-target) go install github.com/client9/misspell/cmd/misspell@v0.3.4 - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.0.1 + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.1 go install github.com/goreleaser/goreleaser/v2@v2.3.2 go install github.com/cpuguy83/go-md2man/v2@latest From 5b28ea4575c70af33b33620e6650923dde01d076 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 16:34:04 -0600 Subject: [PATCH 57/83] cmd: allow short opts for username/password --- cmd/collect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 6af6bf5..0888908 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -100,8 +100,8 @@ 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 master BMC username") - CollectCmd.PersistentFlags().StringVar(&password, "password", "", "Set the master BMC password") + CollectCmd.PersistentFlags().StringVarP(&username, "username", "u", "", "Set the master BMC username") + CollectCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "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") From b7761c2cbf9c19d16c90b50a6d67cb646b4a8d72 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 25 Mar 2025 16:48:41 -0600 Subject: [PATCH 58/83] readme: update with secrets and emulator sections --- README.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 65eba18..ee4f252 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,32 @@ The `magellan` CLI tool is a Redfish-based, board management controller (BMC) discovery tool designed to scan networks and is written in Go. The tool collects information from BMC nodes using the provided Redfish RESTful API with [`gofish`](https://github.com/stmcginnis/gofish) and loads the queried data into an [SMD](https://github.com/davidallendj/smd/tree/master) instance. The tool strives to be more flexible by implementing multiple methods of discovery to work for a wider range of systems (WIP) and is capable of using independently of other tools or services. -**Note: `magellan` v0.1.0 is incompatible with SMD v2.15.3 and earlier.** +> [!NOTE] +> The v0.1.0 version of `magellan` is incompatible with `smd` v2.15.3 and earlier due to `smd` lacking the inventory parsing code used with `magellan`'s output.** + + + + * [Main Features](#main-features) + * [Getting Started](#getting-started) + * [Building the Executable](#building-the-executable) + + [Building on Debian 12 (Bookworm)](#building-on-debian-12-bookworm) + + [Docker](#docker) + + [Arch Linux (AUR)](#arch-linux-aur) + * [Usage](#usage) + + [Checking for Redfish](#checking-for-redfish) + + [Running the Tool](#running-the-tool) + + [Managing Secrets](#managing-secrets) + + [Starting the Emulator](#starting-the-emulator) + + [Updating Firmware](#updating-firmware) + + [Getting an Access Token (WIP)](#getting-an-access-token-wip) + + [Running with Docker](#running-with-docker) + * [How It Works](#how-it-works) + * [TODO](#todo) + * [Copyright](#copyright) + + + + ## Main Features @@ -13,6 +38,7 @@ The `magellan` tool comes packed with a handleful of features for doing discover - Redfish-based firmware updating - Integration with davidallendj SMD - Write inventory data to JSON +- Store and manage BMC secrets See the [TODO](#todo) section for a list of soon-ish goals planned. @@ -40,7 +66,7 @@ Getting the `magellan` tool to work with Go 1.21 on Debian 12 may require instal apt install gcc golang-1.21/bookworm-backport ``` -The binary executable for the `golang-1.21` executable can then be found using `dpkg`. +The binary executable for the `golang-1.21` executable can then be found using `dpkg`.v2.0.1 ```bash dpkg -L golang-1.21-go @@ -49,7 +75,7 @@ dpkg -L golang-1.21-go Using the correct binary, set the `CGO_ENABLED` environment variable and build the executable with `cgo` enabled: ```bash -export GOBIN=/usr/bin/golang-1.21/bin/go +export GOBIN=/usr/bin/golang-1.21/bin/go go env -w CGO_ENABLED=1 go mod tidy && go build ``` @@ -66,6 +92,17 @@ docker pull ghcr.io/davidallendj/magellan:latest See the ["Running with Docker"](#running-with-docker) section below about running with the Docker container. + +### Arch Linux (AUR) + +The `magellan` tool is in the AUR as a binary package and can be installed via your favorite AUR helper. + +```bash +yay -S magellan-bin +``` +> [!NOTE] +> The AUR package may not always be in sync with the latest release. It is recommended to install `magellan` from source for the latest version. + ## Usage The sections below assume that the BMC nodes have an IP address available to query Redfish. Currently, `magellan` does not support discovery with MAC addresses although that may change in the future. @@ -173,14 +210,95 @@ This will initiate a crawler that will find as much inventory data as possible. Note: If the `cache` flag is not set, `magellan` will use `/tmp/$USER/magellan.db` by default. +### Managing Secrets + +When connecting to an array of BMC nodes, some nodes may have different secret credentials than the rest. These secrets can be stored and used automatically by `magellan` when performing a `collect` or a `crawl`. All secrets are encrypted and are only accessible using the same `MASTER_KEY` as when stored originally. + +To store secrets using `magellan`: + +1. Set the `MASTER_KEY` environment variable. This can be generated using `magellan secrets generatekey`. + +```bash +export MASTER_KEY=$(magellan secrets generatekey) +``` + +2. Store secret credentials for hosts shown by `magellan list`: + +```bash +export bmc_host=https://172.16.0.105:443 +magellan secrets store $bmc_host $bmc_username:$bmc_password +``` + +There should be no output unless an error occurred. + +3. Print the list of hosts to confirm secrets are stored. + +```bash +magellan secrets list +``` + +If you see your `bmc_host` listed in the output, that means that your secrets were stored successfully. + +Additionally, if you want to see the actually contents, make sure the `MASTER_KEY` environment variable is correctly set and do the following: + +```bash +magellan secrets retrieve $bmc_host +``` + +4. Run either a `crawl` or `collect` and `magellan` should be a do find the credentials for each host. + +```bash +magellan crawl -i $bmc_host +magellan collect \ + --username $default_bmc_username \ + --password $default_bmc_password +``` + +If you pass agruments with the `--username/--password` flags, they will be used as a fallback if no credentials are found in the store. However, the secret store credentials are always used first if they exists. + +> [!NOTE] +> Make sure that the `secretID` is EXACTLY as show with `magellan list`. Otherwise, `magellan` will not be able to do the lookup from the secret store correctly. + +### Starting the Emulator + +This repository includes a quick and dirty way to test `magellan` using a Redfish emulator with little to no effort to get running. + +1. Make sure you have `docker` with Docker compose and optionally `make`. + +2. Run the `emulator/setup.sh` script or alternatively `make emulator`. + +This will start a flask server that you can make requests to using `curl`. + +```bash +export emulator_host=https://172.21.0.2:5000 +export emulator_username=root # set in the `rf_emulator.yml` file +export emulator_password=root_password # set in the `rf_emulator.yml` file +curl -k $emulator_host/redfish/v1 -u $emulator_username:$emulator_password +``` + +...or with `magellan` using the secret store... + +```bash +magellan scan --subnet 172.21.0.0/24 +magellan secrets store \ + $emulator_host \ + $emulator_username:$emulator_password +magellan collect --host https://smd.openchami.cluster +``` + +This example should work just like running on real hardware. + +> [!NOTE] +> The emulator host may be different from the one in the README. Make sure to double-check the host! + ### Updating Firmware The `magellan` tool is capable of updating firmware with using the `update` subcommand via the Redfish API. This may sometimes necessary if some of the `collect` output is missing or is not including what is expected. The subcommand expects there to be a running HTTP/HTTPS server running that has an accessible URL path to the firmware download. Specify the URL with the `--firmware-path` flag and the firmware type with the `--component` flag (optional) with all the other usual arguments like in the example below: ```bash ./magellan update 172.16.0.108:443 \ - --username $USERNAME \ - --password $PASSWORD \ + --username $bmc_username \ + --password $bmc_password \ --firmware-path http://172.16.0.255:8005/firmware/bios/image.RBU \ --component BIOS ``` @@ -188,9 +306,12 @@ The `magellan` tool is capable of updating firmware with using the `update` subc Then, the update status can be viewed by including the `--status` flag along with the other usual arguments or with the `watch` command: ```bash -./magellan update 172.16.0.110 --status --username $USERNAME --pass $PASSWORD | jq '.' +./magellan update 172.16.0.110 \ + --status \ + --username $bmc_username \ + --password $bmc_password | jq '.' # ...or... -watch -n 1 "./magellan update 172.16.0.110 --status --username $USERNAME --password $PASSWORD | jq '.'" +watch -n 1 "./magellan update 172.16.0.110 --status --username $bmc_username --password $bmc_password | jq '.'" ``` ### Getting an Access Token (WIP) @@ -258,8 +379,10 @@ See the [issue list](https://github.com/davidallendj/magellan/issues) for plans * [ ] Separate `collect` subcommand with making request to endpoint * [X] Support logging in with `opaal` to get access token * [X] Support using CA certificates with HTTP requests to SMD -* [ ] Add tests for the regressions and compatibility +* [X] Add tests for the regressions and compatibility * [X] Clean up, remove unused, and tidy code (first round) +* [X] Add `secrets` command to manage secret credentials +* [ ] Add server component to make `magellan` a micro-service ## Copyright From 92b05a81c7a436e61de92753109662c34a2ec204 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 28 Mar 2025 13:12:38 -0600 Subject: [PATCH 59/83] refactor: improvements to CLI and update pkg --- cmd/collect.go | 24 ++++++++++++------------ cmd/crawl.go | 12 ++++-------- cmd/list.go | 11 ++++++----- cmd/root.go | 8 ++++---- cmd/scan.go | 40 +++++++++++++++++++++++----------------- cmd/secrets.go | 32 ++++++++++++++++++-------------- cmd/update.go | 40 +++++++++++++++++----------------------- pkg/update.go | 6 ++---- 8 files changed, 86 insertions(+), 87 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 0888908..42d8c60 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -23,17 +23,19 @@ import ( // This command should be ran after the `scan` to find available hosts // on a subnet. var CollectCmd = &cobra.Command{ - Use: "collect", + Use: "collect", + Example: ` // basic collect after scan without making a follow-up request + magellan collect --cache ./assets.db --cacert ochami.pem -o ./logs -t 30 + + // set username and password for all nodes and make request to specified host + magellan collect --host https://smd.openchami.cluster -u $bmc_username -p $bmc_password + + // run a collect using secrets manager with fallback username and password + export MASTER_KEY=$(magellan secrets generatekey) + magellan secrets store $node_creds_json -f nodes.json + magellan collect --host https://smd.openchami.cluster -u $fallback_bmc_username -p $fallback_bmc_password`, Short: "Collect system information by interrogating BMC node", - Long: "Send request(s) to a collection of hosts running Redfish services found stored from the 'scan' in cache.\n" + - "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\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", + Long: "Send request(s) to a collection of hosts running Redfish services found stored from the 'scan' in cache.\nSee the 'scan' command on how to perform a scan.", Run: func(cmd *cobra.Command, args []string) { // get probe states stored in db from scan scannedResults, err := sqlite.GetScannedAssets(cachePath) @@ -114,8 +116,6 @@ func init() { // bind flags to config properties checkBindFlagError(viper.BindPFlag("collect.host", CollectCmd.Flags().Lookup("host"))) - checkBindFlagError(viper.BindPFlag("collect.username", CollectCmd.Flags().Lookup("username"))) - checkBindFlagError(viper.BindPFlag("collect.password", CollectCmd.Flags().Lookup("password"))) checkBindFlagError(viper.BindPFlag("collect.scheme", CollectCmd.Flags().Lookup("scheme"))) checkBindFlagError(viper.BindPFlag("collect.protocol", CollectCmd.Flags().Lookup("protocol"))) checkBindFlagError(viper.BindPFlag("collect.output", CollectCmd.Flags().Lookup("output"))) diff --git a/cmd/crawl.go b/cmd/crawl.go index 0e2984d..e059663 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -17,13 +17,11 @@ import ( // specfic inventory detail. This command only expects host names and does // not require a scan to be performed beforehand. var CrawlCmd = &cobra.Command{ - Use: "crawl [uri]", + Use: "crawl [uri]", + Example: ` magellan crawl https://bmc.example.com + magellan crawl https://bmc.example.com -i -u username -p password`, Short: "Crawl a single BMC for inventory information", - 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", + Long: "Crawl a single BMC for inventory information with URI.\n\n NOTE: This command does not scan subnets, store scan information in cache, nor make a request to a specified host. It is used only to retrieve inventory data directly. Otherwise, use 'scan' and 'collect' instead.", Args: func(cmd *cobra.Command, args []string) error { // Validate that the only argument is a valid URI var err error @@ -82,8 +80,6 @@ func init() { 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") - checkBindFlagError(viper.BindPFlag("crawl.username", CrawlCmd.Flags().Lookup("username"))) - checkBindFlagError(viper.BindPFlag("crawl.password", CrawlCmd.Flags().Lookup("password"))) checkBindFlagError(viper.BindPFlag("crawl.insecure", CrawlCmd.Flags().Lookup("insecure"))) rootCmd.AddCommand(CrawlCmd) diff --git a/cmd/list.go b/cmd/list.go index a8e6d64..7d5b781 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -20,14 +20,15 @@ var ( // and stored in a cache database from a scan. The data that's stored // is what is consumed by the `collect` command with the --cache flag. var ListCmd = &cobra.Command{ - Use: "list", + Use: "list", + Example: ` magellan list + magellan list --cache ./assets.db + magellan list --cache-info + `, Args: cobra.ExactArgs(0), Short: "List information stored in cache from a scan", Long: "Prints all of the host and associated data found from performing a scan.\n" + - "See the 'scan' command on how to perform a scan.\n\n" + - "Examples:\n" + - " magellan list\n" + - " magellan list --cache ./assets.db", + "See the 'scan' command on how to perform a scan.", Run: func(cmd *cobra.Command, args []string) { // check if we just want to show cache-related info and exit if showCache { diff --git a/cmd/root.go b/cmd/root.go index 4e713fe..fc15150 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -52,7 +52,7 @@ var ( var rootCmd = &cobra.Command{ Use: "magellan", Short: "Redfish-based BMC discovery tool", - Long: "", + Long: "Redfish-based BMC discovery tool with dynamic discovery features.", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { err := cmd.Help() @@ -75,8 +75,8 @@ func Execute() { func init() { currentUser, _ = user.Current() cobra.OnInitialize(InitializeConfig) - rootCmd.PersistentFlags().IntVar(&concurrency, "concurrency", -1, "Set the number of concurrent processes") - rootCmd.PersistentFlags().IntVar(&timeout, "timeout", 5, "Set the timeout for requests") + rootCmd.PersistentFlags().IntVarP(&concurrency, "concurrency", "j", -1, "Set the number of concurrent processes") + rootCmd.PersistentFlags().IntVarP(&timeout, "timeout", "t", 5, "Set the timeout for requests") rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Set the config file path") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Set to enable/disable verbose output") rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Set to enable/disable debug messages") @@ -94,7 +94,7 @@ func init() { func checkBindFlagError(err error) { if err != nil { - log.Error().Err(err).Msg("failed to bind flag") + log.Error().Err(err).Msg("failed to bind cobra/viper flag") } } diff --git a/cmd/scan.go b/cmd/scan.go index 261fad7..8549eb1 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -33,7 +33,28 @@ var ( // See the `ScanForAssets()` function in 'internal/scan.go' for details // related to the implementation. var ScanCmd = &cobra.Command{ - Use: "scan urls...", + Use: "scan urls...", + Example: ` + // assumes host https://10.0.0.101:443 + magellan scan 10.0.0.101 + + // assumes subnet using HTTPS and port 443 except for specified host + magellan scan http://10.0.0.101:80 https://user:password@10.0.0.102:443 http://172.16.0.105:8080 --subnet 172.16.0.0/24 + + // assumes hosts http://10.0.0.101:8080 and http://10.0.0.102:8080 + magellan scan 10.0.0.101 10.0.0.102 https://172.16.0.10:443 --port 8080 --protocol tcp + + // assumes subnet using default unspecified subnet-masks + magellan scan --subnet 10.0.0.0 + + // assumes subnet using HTTPS and port 443 with specified CIDR + magellan scan --subnet 10.0.0.0/16 + + // assumes subnet using HTTP and port 5000 similar to 192.168.0.0/16 + magellan scan --subnet 192.168.0.0 --protocol tcp --scheme https --port 5000 --subnet-mask 255.255.0.0 + + // assumes subnet without CIDR has a subnet-mask of 255.255.0.0 + magellan scan --subnet 10.0.0.0/24 --subnet 172.16.0.0 --subnet-mask 255.255.0.0 --cache ./assets.db`, Short: "Scan to discover BMC nodes on a network", Long: "Perform a net scan by attempting to connect to each host and port specified and getting a response.\n" + "Each host is passed *with a full URL* including the protocol and port. Additional subnets can be added\n" + @@ -46,22 +67,7 @@ var ScanCmd = &cobra.Command{ "'--protocol' flag.\n\n" + "If the '--disable-probe` flag is used, the tool will not send another request to probe for available.\n" + "Redfish services. This is not recommended, since the extra request makes the scan a bit more reliable\n" + - "for determining which hosts to collect inventory data.\n\n" + - "Examples:\n" + - // assumes host https://10.0.0.101:443 - " magellan scan 10.0.0.101\n" + - // assumes subnet using HTTPS and port 443 except for specified host - " magellan scan http://10.0.0.101:80 https://user:password@10.0.0.102:443 http://172.16.0.105:8080 --subnet 172.16.0.0/24\n" + - // assumes hosts http://10.0.0.101:8080 and http://10.0.0.102:8080 - " magellan scan 10.0.0.101 10.0.0.102 https://172.16.0.10:443 --port 8080 --protocol tcp\n" + - // assumes subnet using default unspecified subnet-masks - " magellan scan --subnet 10.0.0.0\n" + - // assumes subnet using HTTPS and port 443 with specified CIDR - " magellan scan --subnet 10.0.0.0/16\n" + - // assumes subnet using HTTP and port 5000 similar to 192.168.0.0/16 - " magellan scan --subnet 192.168.0.0 --protocol tcp --scheme https --port 5000 --subnet-mask 255.255.0.0\n" + - // assumes subnet without CIDR has a subnet-mask of 255.255.0.0 - " magellan scan --subnet 10.0.0.0/24 --subnet 172.16.0.0 --subnet-mask 255.255.0.0 --cache ./assets.db\n", + "for determining which hosts to collect inventory data.\n\n", Run: func(cmd *cobra.Command, args []string) { // add default ports for hosts if none are specified with flag if len(ports) == 0 { diff --git a/cmd/secrets.go b/cmd/secrets.go index cb6a191..9d18d2e 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -20,17 +20,21 @@ var ( ) var secretsCmd = &cobra.Command{ - Use: "secrets", + Use: "secrets", + Example: ` + // generate new key and set environment variable + export MASTER_KEY=$(magellan secrets generatekey) + + // 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`, 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", + 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.", Run: func(cmd *cobra.Command, args []string) { // show command help and exit if len(args) < 1 { @@ -60,7 +64,7 @@ var secretsStoreCmd = &cobra.Command{ Short: "Stores the given string value under secretID.", Run: func(cmd *cobra.Command, args []string) { var ( - secretID = args[0] + secretID string = args[0] secretValue string store secrets.SecretStore inputFileBytes []byte @@ -163,7 +167,7 @@ var secretsStoreCmd = &cobra.Command{ func isValidCredsJSON(val string) bool { var ( - valid = !json.Valid([]byte(val)) + valid bool = !json.Valid([]byte(val)) creds map[string]string err error ) @@ -219,8 +223,8 @@ var secretsListCmd = &cobra.Command{ os.Exit(1) } - for key := range secrets { - fmt.Printf("%s\n", key) + for key, value := range secrets { + fmt.Printf("%s: %s\n", key, value) } }, } diff --git a/cmd/update.go b/cmd/update.go index 65e74c5..42461d5 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -12,7 +12,7 @@ import ( var ( host string - firmwareUrl string + firmwareUri string firmwareVersion string component string transferProtocol string @@ -24,12 +24,16 @@ var ( // using Redfish. It also provides a simple way to check the status of // an update in-progress. var updateCmd = &cobra.Command{ - Use: "update hosts...", + Use: "update hosts...", + Example: ` // perform an firmware update + magellan update 172.16.0.108:443 -i -u $bmc_username -p $bmc_password \ + --firmware-url http://172.16.0.200:8005/firmware/bios/image.RBU \ + --component BIOS + + // check update status + magellan update 172.16.0.108:443 -i -u $bmc_username -p $bmc_password --status`, Short: "Update BMC node firmware", - Long: "Perform an firmware update using Redfish by providing a remote firmware URL and component.\n\n" + - "Examples:\n" + - " magellan update 172.16.0.108:443 --insecure --username bmc_username --password bmc_password --firmware-url http://172.16.0.200:8005/firmware/bios/image.RBU --component BIOS\n" + - " magellan update 172.16.0.108:443 --insecure --status --username bmc_username --password bmc_password", + Long: "Perform an firmware update using Redfish by providing a remote firmware URL and component.", Run: func(cmd *cobra.Command, args []string) { // check that we have at least one host if len(args) <= 0 { @@ -41,9 +45,7 @@ var updateCmd = &cobra.Command{ for _, arg := range args { if showStatus { err := magellan.GetUpdateStatus(&magellan.UpdateParams{ - FirmwarePath: firmwareUrl, - FirmwareVersion: firmwareVersion, - Component: component, + FirmwareURI: firmwareUri, TransferProtocol: transferProtocol, Insecure: Insecure, CollectParams: magellan.CollectParams{ @@ -61,9 +63,7 @@ var updateCmd = &cobra.Command{ // initiate a remote update err := magellan.UpdateFirmwareRemote(&magellan.UpdateParams{ - FirmwarePath: firmwareUrl, - FirmwareVersion: firmwareVersion, - Component: component, + FirmwareURI: firmwareUri, TransferProtocol: strings.ToUpper(transferProtocol), Insecure: Insecure, CollectParams: magellan.CollectParams{ @@ -81,21 +81,15 @@ var updateCmd = &cobra.Command{ } func init() { - updateCmd.Flags().StringVar(&username, "username", "", "Set the BMC user") - updateCmd.Flags().StringVar(&password, "password", "", "Set the BMC password") + updateCmd.Flags().StringVarP(&username, "username", "u", "", "Set the BMC user") + updateCmd.Flags().StringVarP(&password, "password", "p", "", "Set the BMC password") updateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") - updateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "Set the path to the firmware") - updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "Set the version of firmware to be installed") - updateCmd.Flags().StringVar(&component, "component", "", "Set the component to upgrade (BMC|BIOS)") + updateCmd.Flags().StringVar(&firmwareUri, "firmware-uri", "", "Set the URI to retrieve the firmware") updateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") - updateCmd.Flags().BoolVar(&Insecure, "insecure", false, "Allow insecure connections to the server") + updateCmd.Flags().BoolVarP(&Insecure, "insecure", "i", false, "Allow insecure connections to the server") - 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"))) checkBindFlagError(viper.BindPFlag("update.insecure", updateCmd.Flags().Lookup("insecure"))) diff --git a/pkg/update.go b/pkg/update.go index ccbed6b..fea41b8 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -10,9 +10,7 @@ import ( type UpdateParams struct { CollectParams - FirmwarePath string - FirmwareVersion string - Component string + FirmwareURI string TransferProtocol string Insecure bool } @@ -51,7 +49,7 @@ func UpdateFirmwareRemote(q *UpdateParams) error { // Build the update request payload req := redfish.SimpleUpdateParameters{ - ImageURI: q.FirmwarePath, + ImageURI: q.FirmwareURI, TransferProtocol: redfish.TransferProtocolType(q.TransferProtocol), } From 38e22ff24c4ccfac19ba103862aaac28fc96b893 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 31 Mar 2025 15:35:15 -0600 Subject: [PATCH 60/83] feat: add default secret to local store --- cmd/collect.go | 31 ++++++++++++++++++++++++++++--- cmd/crawl.go | 21 ++++++++++++++++++++- pkg/collect.go | 10 ++++++++-- pkg/crawler/main.go | 15 ++++++++++++++- pkg/secrets/main.go | 2 ++ 5 files changed, 72 insertions(+), 7 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 42d8c60..6302fcd 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "os/user" @@ -8,6 +9,7 @@ import ( urlx "github.com/OpenCHAMI/magellan/internal/url" magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/OpenCHAMI/magellan/pkg/auth" + "github.com/OpenCHAMI/magellan/pkg/crawler" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/cznic/mathutil" magellan "github.com/davidallendj/magellan/internal" @@ -86,12 +88,35 @@ var CollectCmd = &cobra.Command{ // 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 { - // Something went wrong with the store so try using - // Create a StaticSecretStore to hold the username and password - log.Warn().Err(err).Msg("failed to open local store") + log.Warn().Err(err).Msg("failed to open local store...falling back to default provided arguments") + // try and use the `username` and `password` arguments instead store = secrets.NewStaticStore(username, password) } + // found the store so try to load the creds + _, err = store.GetSecretByID(host) + if err != nil { + // if we have CLI flags set, then we want to override default stored creds + if username != "" && password != "" { + // finally, use the CLI arguments passed instead + store = secrets.NewStaticStore(username, password) + } else { + // try and get a default *stored* username/password + secret, err := store.GetSecretByID("default") + if err != nil { + // no default found, so use CLI arguments + log.Warn().Err(err).Msg("no default credentials found") + } else { + // found default values in local store so use them + var creds crawler.BMCUsernamePassword + err = json.Unmarshal([]byte(secret), &creds) + if err != nil { + log.Warn().Err(err).Msg("failed to unmarshal default store credentials") + } + } + } + } + _, err = magellan.CollectInventory(&scannedResults, params, store) if err != nil { log.Error().Err(err).Msg("failed to collect data") diff --git a/cmd/crawl.go b/cmd/crawl.go index e059663..558f372 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -51,13 +51,32 @@ var CrawlCmd = &cobra.Command{ // found the store so try to load the creds _, err = store.GetSecretByID(uri) if err != nil { - store = secrets.NewStaticStore(username, password) + // if we have CLI flags set, then we want to override default stored creds + if username != "" && password != "" { + // finally, use the CLI arguments passed instead + store = secrets.NewStaticStore(username, password) + } else { + // try and get a default *stored* username/password + secret, err := store.GetSecretByID(secrets.DEFAULT_KEY) + if err != nil { + // no default found, so use CLI arguments + log.Warn().Err(err).Msg("no default credentials found") + } else { + // found default values in local store so use them + var creds crawler.BMCUsernamePassword + err = json.Unmarshal([]byte(secret), &creds) + if err != nil { + log.Warn().Err(err).Msg("failed to unmarshal default store credentials") + } + } + } } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ URI: uri, CredentialStore: store, Insecure: insecure, + UseDefault: true, }) if err != nil { log.Error().Err(err).Msg("failed to crawl BMC") diff --git a/pkg/collect.go b/pkg/collect.go index 40ec851..ec980ba 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -127,6 +127,7 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s URI: uri, CredentialStore: localStore, Insecure: true, + UseDefault: true, } err error ) @@ -138,8 +139,13 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s if localStore != nil { _, err := localStore.GetSecretByID(uri) if err != nil { - log.Warn().Err(err).Msgf("could not retrieve secrets for %s...falling back to default provided credentials for user '%s'", uri, params.Username) - config.CredentialStore = fallbackStore + log.Warn().Err(err).Msgf("could not retrieve secrets for '%s'...falling back to credentials provided with flags -u/-p for user '%s'", uri, params.Username) + if params.Username != "" && params.Password != "" { + config.CredentialStore = fallbackStore + } else if !config.UseDefault { + log.Warn().Msgf("no fallback credentials provided for '%s'", params.Username) + continue + } } } else { log.Warn().Msgf("invalid store for %s...falling back to default provided credentials for user '%s'", uri, params.Username) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index bd29fcb..2eb9932 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -15,6 +15,7 @@ type CrawlerConfig struct { URI string // URI of the BMC Insecure bool // Whether to ignore SSL errors CredentialStore secrets.SecretStore + UseDefault bool } func (cc *CrawlerConfig) GetUserPass() (BMCUsernamePassword, error) { @@ -382,7 +383,19 @@ func loadBMCCreds(config CrawlerConfig) (BMCUsernamePassword, error) { event := log.Error() event.Err(err) event.Msg("failed to get credentials from secret store") - return BMCUsernamePassword{}, err + // try to get default if parameter is set + if config.UseDefault { + creds, err = config.CredentialStore.GetSecretByID(secrets.DEFAULT_KEY) + // no default credentials + if err != nil { + event := log.Error() + event.Err(err) + event.Msg("failed to get default credentials from secret store") + return BMCUsernamePassword{}, err + } + } else { + return BMCUsernamePassword{}, err + } } var bmc_creds BMCUsernamePassword err = json.Unmarshal([]byte(creds), &bmc_creds) diff --git a/pkg/secrets/main.go b/pkg/secrets/main.go index 5cb7f95..983f159 100644 --- a/pkg/secrets/main.go +++ b/pkg/secrets/main.go @@ -1,5 +1,7 @@ package secrets +const DEFAULT_KEY = "default" + type SecretStore interface { GetSecretByID(secretID string) (string, error) StoreSecretByID(secretID, secret string) error From 3074e7323aa0544a117c435ba4dfaef06dfea6f4 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 31 Mar 2025 16:06:16 -0600 Subject: [PATCH 61/83] readme: added tip about default secrets --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ee4f252..32d083a 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,12 @@ If you pass agruments with the `--username/--password` flags, they will be used > [!NOTE] > Make sure that the `secretID` is EXACTLY as show with `magellan list`. Otherwise, `magellan` will not be able to do the lookup from the secret store correctly. +> [!TIP] +> You can set default fallback credentials by storing a secret with the `secretID` of "default". This is used if no `secretID` is found in the local store for the specified host. Otherwise, the `--username/--password` arguments are used. +> ```bash +> magellan secrets default $username:$password +> + ### Starting the Emulator This repository includes a quick and dirty way to test `magellan` using a Redfish emulator with little to no effort to get running. From ee99d6e06dbef8baa9620153ab3c2036b8c7d3e4 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 14 Apr 2025 14:11:00 -0600 Subject: [PATCH 62/83] chore: updated error/warn messages to be more informative --- cmd/collect.go | 6 ++++-- cmd/crawl.go | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 6302fcd..166c7d0 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -99,15 +99,17 @@ var CollectCmd = &cobra.Command{ // if we have CLI flags set, then we want to override default stored creds if username != "" && password != "" { // finally, use the CLI arguments passed instead + log.Info().Msg("...using provided arguments for credentials") store = secrets.NewStaticStore(username, password) } else { // try and get a default *stored* username/password - secret, err := store.GetSecretByID("default") + secret, err := store.GetSecretByID(secrets.DEFAULT_KEY) if err != nil { // no default found, so use CLI arguments - log.Warn().Err(err).Msg("no default credentials found") + log.Warn().Err(err).Msg("failed to get default credentials...") } else { // found default values in local store so use them + log.Info().Msg("...using default store for credentials") var creds crawler.BMCUsernamePassword err = json.Unmarshal([]byte(secret), &creds) if err != nil { diff --git a/cmd/crawl.go b/cmd/crawl.go index 558f372..68f01cc 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -43,7 +43,7 @@ var CrawlCmd = &cobra.Command{ // try and load credentials from local store first store, err = secrets.OpenStore(secretsFile) if err != nil { - log.Warn().Err(err).Msg("failed to open local store...falling back to default provided arguments") + log.Warn().Err(err).Msg("failed to open local store...falling back to provided arguments") // try and use the `username` and `password` arguments instead store = secrets.NewStaticStore(username, password) } @@ -51,12 +51,15 @@ var CrawlCmd = &cobra.Command{ // found the store so try to load the creds _, err = store.GetSecretByID(uri) if err != nil { + log.Warn().Err(err).Msgf("failed to get secrets for '%s'...", uri) // if we have CLI flags set, then we want to override default stored creds if username != "" && password != "" { // finally, use the CLI arguments passed instead + log.Info().Msg("...using provided arguments for credentials") store = secrets.NewStaticStore(username, password) } else { // try and get a default *stored* username/password + log.Info().Msg("...using default stored secrets for credentials") secret, err := store.GetSecretByID(secrets.DEFAULT_KEY) if err != nil { // no default found, so use CLI arguments From a6dadfcdb541409be5acd9ac39cbd2b5a0ba2757 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Mon, 14 Apr 2025 16:37:20 -0600 Subject: [PATCH 63/83] chore: run gofmt --- cmd/secrets.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/secrets.go b/cmd/secrets.go index 9d18d2e..f4ea00d 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -64,7 +64,7 @@ var secretsStoreCmd = &cobra.Command{ Short: "Stores the given string value under secretID.", Run: func(cmd *cobra.Command, args []string) { var ( - secretID string = args[0] + secretID = args[0] secretValue string store secrets.SecretStore inputFileBytes []byte @@ -167,7 +167,7 @@ var secretsStoreCmd = &cobra.Command{ func isValidCredsJSON(val string) bool { var ( - valid bool = !json.Valid([]byte(val)) + valid = !json.Valid([]byte(val)) creds map[string]string err error ) From e62a38183f218b34aaac3ea7dfabadfd75dfc05a Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Mon, 14 Apr 2025 16:37:34 -0600 Subject: [PATCH 64/83] feat(crawl): improved logs; add partial credential overriding The order of precedence is: 1. --username/--password 2. URI-specific credentials in store 3. Default credentials in store The "partial" overriding means that specifying only one of --username or --password will cause the crawl command to fetch the specific node credentials for the BMC (falling back to the default) but override the result with the value of the passed flag. For instance, magellan crawl --username foo will fetch the username and password for in the secret store, but override the username being sent to 'foo'. This does not change the username stored in the secret store. --- cmd/crawl.go | 80 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 68f01cc..9b44285 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -40,39 +40,67 @@ var CrawlCmd = &cobra.Command{ store secrets.SecretStore err error ) - // try and load credentials from local store first - store, err = secrets.OpenStore(secretsFile) - if err != nil { - log.Warn().Err(err).Msg("failed to open local store...falling back to provided arguments") - // try and use the `username` and `password` arguments instead - store = secrets.NewStaticStore(username, password) - } - // found the store so try to load the creds - _, err = store.GetSecretByID(uri) - if err != nil { - log.Warn().Err(err).Msgf("failed to get secrets for '%s'...", uri) - // if we have CLI flags set, then we want to override default stored creds - if username != "" && password != "" { - // finally, use the CLI arguments passed instead - log.Info().Msg("...using provided arguments for credentials") - store = secrets.NewStaticStore(username, password) - } else { - // try and get a default *stored* username/password - log.Info().Msg("...using default stored secrets for credentials") - secret, err := store.GetSecretByID(secrets.DEFAULT_KEY) + if username != "" && password != "" { + // First, try and load credentials from --username and --password if both are set. + log.Debug().Str("uri", 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("uri", 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("uri", 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. + // + // Attempt to get URI-specific credentials. + var nodeCreds secrets.StaticStore + if uriCreds, err := store.GetSecretByID(uri); err != nil { + // Specific credentials for URI not found, fetch default. + log.Warn().Str("uri", uri).Msg("specific credentials not found, falling back to default") + defaultSecret, err := store.GetSecretByID(secrets.DEFAULT_KEY) if err != nil { - // no default found, so use CLI arguments - log.Warn().Err(err).Msg("no default credentials found") + // We've exhausted all options, the credentials will be blank unless + // overridden by a CLI flag. + log.Warn().Str("uri", uri).Err(err).Msg("no default credentials were set, they will be blank unless overridden by CLI flags") } else { - // found default values in local store so use them + // Default credentials found, use them. var creds crawler.BMCUsernamePassword - err = json.Unmarshal([]byte(secret), &creds) - if err != nil { - log.Warn().Err(err).Msg("failed to unmarshal default store credentials") + if err = json.Unmarshal([]byte(defaultSecret), &creds); err != nil { + log.Warn().Str("uri", uri).Err(err).Msg("failed to unmarshal default secrets store credentials") + } else { + log.Info().Str("uri", uri).Msg("default credentials found, using") + nodeCreds.Username = creds.Username + nodeCreds.Password = creds.Password } } + } else { + // Specific URI credentials found, use them. + var creds crawler.BMCUsernamePassword + if err = json.Unmarshal([]byte(uriCreds), &creds); err != nil { + log.Warn().Str("uri", uri).Err(err).Msg("failed to unmarshal uri credentials") + } else { + nodeCreds.Username = creds.Username + nodeCreds.Password = creds.Password + log.Info().Str("uri", uri).Msg("specific credentials found, using") + } } + + // If either of the flags were passed, override the fetched + // credentials with them. + if username != "" { + log.Info().Str("uri", uri).Msg("--username was set, overriding username for this BMC") + nodeCreds.Username = username + } + if password != "" { + log.Info().Str("uri", uri).Msg("--password was set, overriding password for this BMC") + nodeCreds.Password = password + } + + store = &nodeCreds } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ From 93010587c631c68b7ff699e1b98ce1cee12c7764 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 16:29:20 -0600 Subject: [PATCH 65/83] refactor: split BMC data structures into pkg/bmc package --- cmd/crawl.go | 47 ++++++++++----------------------------------- pkg/bmc/bmc.go | 45 +++++++++++++++++++++++++++++++++++++++++++ pkg/crawler/main.go | 20 ++++++++----------- pkg/update.go | 17 ++++++++++++++-- 4 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 pkg/bmc/bmc.go diff --git a/cmd/crawl.go b/cmd/crawl.go index 9b44285..5e4a038 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -7,6 +7,7 @@ import ( "github.com/rs/zerolog/log" urlx "github.com/OpenCHAMI/magellan/internal/url" + "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/crawler" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/spf13/cobra" @@ -43,60 +44,32 @@ var CrawlCmd = &cobra.Command{ if username != "" && password != "" { // First, try and load credentials from --username and --password if both are set. - log.Debug().Str("uri", uri).Msgf("--username and --password specified, using them for BMC credentials") + 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("uri", uri).Msgf("one or both of --username and --password NOT passed, attempting to obtain missing credentials from secret store at %s", secretsFile) + 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("uri", uri).Err(err).Msg("failed to open local secrets store") + 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. - // - // Attempt to get URI-specific credentials. - var nodeCreds secrets.StaticStore - if uriCreds, err := store.GetSecretByID(uri); err != nil { - // Specific credentials for URI not found, fetch default. - log.Warn().Str("uri", uri).Msg("specific credentials not found, falling back to default") - defaultSecret, err := store.GetSecretByID(secrets.DEFAULT_KEY) - if err != nil { - // We've exhausted all options, the credentials will be blank unless - // overridden by a CLI flag. - log.Warn().Str("uri", uri).Err(err).Msg("no default credentials were set, they will be blank unless overridden by CLI flags") - } else { - // Default credentials found, use them. - var creds crawler.BMCUsernamePassword - if err = json.Unmarshal([]byte(defaultSecret), &creds); err != nil { - log.Warn().Str("uri", uri).Err(err).Msg("failed to unmarshal default secrets store credentials") - } else { - log.Info().Str("uri", uri).Msg("default credentials found, using") - nodeCreds.Username = creds.Username - nodeCreds.Password = creds.Password - } - } - } else { - // Specific URI credentials found, use them. - var creds crawler.BMCUsernamePassword - if err = json.Unmarshal([]byte(uriCreds), &creds); err != nil { - log.Warn().Str("uri", uri).Err(err).Msg("failed to unmarshal uri credentials") - } else { - nodeCreds.Username = creds.Username - nodeCreds.Password = creds.Password - log.Info().Str("uri", uri).Msg("specific credentials found, using") - } + 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("uri", uri).Msg("--username was set, overriding username for this BMC") + log.Info().Str("id", uri).Msg("--username was set, overriding username for this BMC") nodeCreds.Username = username } if password != "" { - log.Info().Str("uri", uri).Msg("--password was set, overriding password for this BMC") + log.Info().Str("id", uri).Msg("--password was set, overriding password for this BMC") nodeCreds.Password = password } diff --git a/pkg/bmc/bmc.go b/pkg/bmc/bmc.go new file mode 100644 index 0000000..9948e0d --- /dev/null +++ b/pkg/bmc/bmc.go @@ -0,0 +1,45 @@ +package bmc + +import ( + "encoding/json" + + "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/rs/zerolog/log" +) + +type BMCCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func GetBMCCredentials(store secrets.SecretStore, id string) (BMCCredentials, error) { + var creds BMCCredentials + if uriCreds, err := store.GetSecretByID(id); err != nil { + // Specific credentials for URI not found, fetch default. + log.Warn().Str("id", id).Msg("specific credentials not found, falling back to default") + defaultSecret, err := store.GetSecretByID(secrets.DEFAULT_KEY) + if err != nil { + // We've exhausted all options, the credentials will be blank unless + // overridden by a CLI flag. + log.Warn().Str("id", id).Err(err).Msg("no default credentials were set, they will be blank unless overridden by CLI flags") + } else { + // Default credentials found, use them. + if err = json.Unmarshal([]byte(defaultSecret), &creds); err != nil { + log.Warn().Str("id", id).Err(err).Msg("failed to unmarshal default secrets store credentials") + return creds, err + } else { + log.Info().Str("id", id).Msg("default credentials found, using") + } + } + } else { + // Specific URI credentials found, use them. + if err = json.Unmarshal([]byte(uriCreds), &creds); err != nil { + log.Warn().Str("id", id).Err(err).Msg("failed to unmarshal specific credentials") + return creds, err + } else { + log.Info().Str("id", id).Msg("specific credentials found, using") + } + } + + return creds, nil +} diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 2eb9932..8b732f4 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/stmcginnis/gofish" @@ -18,15 +19,10 @@ type CrawlerConfig struct { UseDefault bool } -func (cc *CrawlerConfig) GetUserPass() (BMCUsernamePassword, error) { +func (cc *CrawlerConfig) GetUserPass() (bmc.BMCCredentials, error) { return loadBMCCreds(*cc) } -type BMCUsernamePassword struct { - Username string `json:"username"` - Password string `json:"password"` -} - type EthernetInterface struct { URI string `json:"uri,omitempty"` // URI of the interface MAC string `json:"mac,omitempty"` // MAC address of the interface @@ -373,10 +369,10 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er return managers, nil } -func loadBMCCreds(config CrawlerConfig) (BMCUsernamePassword, error) { +func loadBMCCreds(config CrawlerConfig) (bmc.BMCCredentials, error) { // NOTE: it is possible for the SecretStore to be nil, so we need a check if config.CredentialStore == nil { - return BMCUsernamePassword{}, fmt.Errorf("credential store is invalid") + return bmc.BMCCredentials{}, fmt.Errorf("credential store is invalid") } creds, err := config.CredentialStore.GetSecretByID(config.URI) if err != nil { @@ -391,19 +387,19 @@ func loadBMCCreds(config CrawlerConfig) (BMCUsernamePassword, error) { event := log.Error() event.Err(err) event.Msg("failed to get default credentials from secret store") - return BMCUsernamePassword{}, err + return bmc.BMCCredentials{}, err } } else { - return BMCUsernamePassword{}, err + return bmc.BMCCredentials{}, err } } - var bmc_creds BMCUsernamePassword + var bmc_creds bmc.BMCCredentials err = json.Unmarshal([]byte(creds), &bmc_creds) if err != nil { event := log.Error() event.Err(err) event.Msg("failed to unmarshal credentials") - return BMCUsernamePassword{}, err + return bmc.BMCCredentials{}, err } return bmc_creds, nil } diff --git a/pkg/update.go b/pkg/update.go index fea41b8..411c28e 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" + "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" ) @@ -34,8 +35,14 @@ func UpdateFirmwareRemote(q *UpdateParams) error { return fmt.Errorf("failed to parse URI: %w", err) } + // Get BMC credentials from secret store in update parameters + bmcCreds, err := bmc.GetBMCCredentials(q.SecretStore, q.URI) + if err != nil { + return fmt.Errorf("failed to get BMC credentials: %w", err) + } + // Connect to the Redfish service using gofish - client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: bmcCreds.Username, Password: bmcCreds.Password, Insecure: q.Insecure}) if err != nil { return fmt.Errorf("failed to connect to Redfish service: %w", err) } @@ -69,8 +76,14 @@ func GetUpdateStatus(q *UpdateParams) error { return fmt.Errorf("failed to parse URI: %w", err) } + // Get BMC credentials from secret store in update parameters + bmcCreds, err := bmc.GetBMCCredentials(q.SecretStore, q.URI) + if err != nil { + return fmt.Errorf("failed to get BMC credentials: %w", err) + } + // Connect to the Redfish service using gofish - client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: q.Insecure}) + client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: bmcCreds.Username, Password: bmcCreds.Password, Insecure: q.Insecure}) if err != nil { return fmt.Errorf("failed to connect to Redfish service: %w", err) } From 09092545507c681b250918a9edd86f48fae96797 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 16:31:23 -0600 Subject: [PATCH 66/83] feat: add secret store support to update command --- cmd/update.go | 56 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index 42461d5..f07a0bb 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -5,6 +5,8 @@ import ( "strings" magellan "github.com/OpenCHAMI/magellan/pkg" + "github.com/OpenCHAMI/magellan/pkg/bmc" + "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -41,6 +43,46 @@ var updateCmd = &cobra.Command{ os.Exit(1) } + // use secret store for BMC credentials, and/or credential CLI flags + var ( + store secrets.SecretStore + uri = args[0] + 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 + } + // get status if flag is set and exit for _, arg := range args { if showStatus { @@ -49,10 +91,9 @@ var updateCmd = &cobra.Command{ TransferProtocol: transferProtocol, Insecure: Insecure, CollectParams: magellan.CollectParams{ - URI: arg, - Username: username, - Password: password, - Timeout: timeout, + URI: arg, + SecretStore: store, + Timeout: timeout, }, }) if err != nil { @@ -67,10 +108,9 @@ var updateCmd = &cobra.Command{ TransferProtocol: strings.ToUpper(transferProtocol), Insecure: Insecure, CollectParams: magellan.CollectParams{ - URI: arg, - Username: username, - Password: password, - Timeout: timeout, + URI: arg, + SecretStore: store, + Timeout: timeout, }, }) if err != nil { From 541fb6ebb07607752103707cbe5314e695533fda Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 16:32:15 -0600 Subject: [PATCH 67/83] fix: adjust secret store precedence in collect command --- cmd/collect.go | 89 ++++++++++++++++++++++++++++---------------------- pkg/collect.go | 60 +++++++++++++--------------------- 2 files changed, 72 insertions(+), 77 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 166c7d0..fba5bf5 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -9,7 +9,7 @@ import ( urlx "github.com/OpenCHAMI/magellan/internal/url" magellan "github.com/OpenCHAMI/magellan/pkg" "github.com/OpenCHAMI/magellan/pkg/auth" - "github.com/OpenCHAMI/magellan/pkg/crawler" + "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/cznic/mathutil" magellan "github.com/davidallendj/magellan/internal" @@ -65,6 +65,53 @@ var CollectCmd = &cobra.Command{ concurrency = mathutil.Clamp(len(scannedResults), 1, 10000) } + // use secret store for BMC credentials, and/or credential CLI flags + var store secrets.SecretStore + if username != "" && password != "" { + // First, try and load credentials from --username and --password if both are set. + log.Debug().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().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().Err(err).Msg("failed to open local secrets store") + } + + // Temporarily override username/password of each BMC if one of those + // flags is passed. The expectation is that if the flag is specified + // on the command line, it should be used. + switch s := store.(type) { + case *secrets.StaticStore: + if username != "" { + s.Username = username + } + if password != "" { + s.Password = password + } + case *secrets.LocalSecretStore: + for k, _ := range s.Secrets { + if creds, err := bmc.GetBMCCredentials(store, k); err != nil { + log.Error().Str("id", k).Err(err).Msg("failed to get BMC credentials from secret store") + } else { + if username != "" { + creds.Username = username + } + if password != "" { + creds.Password = password + } + + if newCreds, err := json.Marshal(creds); err != nil { + log.Error().Str("id", k).Err(err).Msg("failed to marshal updated BMC credentials") + } else { + s.Secrets[k] = string(newCreds) + } + } + } + } + } + // set the collect parameters from CLI params params := &magellan.CollectParams{ URI: host, @@ -75,9 +122,7 @@ var CollectCmd = &cobra.Command{ OutputPath: outputPath, ForceUpdate: forceUpdate, AccessToken: accessToken, - SecretsFile: secretsFile, - Username: username, - Password: password, + SecretStore: store, } // show all of the 'collect' parameters being set from CLI if verbose @@ -85,41 +130,7 @@ var CollectCmd = &cobra.Command{ 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.Warn().Err(err).Msg("failed to open local store...falling back to default provided arguments") - // try and use the `username` and `password` arguments instead - store = secrets.NewStaticStore(username, password) - } - - // found the store so try to load the creds - _, err = store.GetSecretByID(host) - if err != nil { - // if we have CLI flags set, then we want to override default stored creds - if username != "" && password != "" { - // finally, use the CLI arguments passed instead - log.Info().Msg("...using provided arguments for credentials") - store = secrets.NewStaticStore(username, password) - } else { - // try and get a default *stored* username/password - secret, err := store.GetSecretByID(secrets.DEFAULT_KEY) - if err != nil { - // no default found, so use CLI arguments - log.Warn().Err(err).Msg("failed to get default credentials...") - } else { - // found default values in local store so use them - log.Info().Msg("...using default store for credentials") - var creds crawler.BMCUsernamePassword - err = json.Unmarshal([]byte(secret), &creds) - if err != nil { - log.Warn().Err(err).Msg("failed to unmarshal default store credentials") - } - } - } - } - - _, err = magellan.CollectInventory(&scannedResults, params, store) + _, err = magellan.CollectInventory(&scannedResults, params) if err != nil { log.Error().Err(err).Msg("failed to collect data") } diff --git a/pkg/collect.go b/pkg/collect.go index ec980ba..80feff5 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -15,6 +15,7 @@ import ( "sync" "time" + "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/client" "github.com/OpenCHAMI/magellan/pkg/crawler" "github.com/OpenCHAMI/magellan/pkg/secrets" @@ -31,17 +32,15 @@ import ( // CollectParams is a collection of common parameters passed to the CLI // for the 'collect' subcommand. type CollectParams struct { - URI string // set by the 'host' flag - Username string // set the BMC username with the 'username' flag - Password string // set the BMC password with the 'password' flag - Concurrency int // set the of concurrent jobs with the 'concurrency' flag - Timeout int // set the timeout with the 'timeout' flag - CaCertPath string // set the cert path with the 'cacert' flag - Verbose bool // set whether to include verbose output with 'verbose' flag - OutputPath string // set the path to save output with 'output' flag - ForceUpdate bool // set whether to force updating SMD with 'force-update' flag - AccessToken string // set the access token to include in request with 'access-token' flag - SecretsFile string // set the path to secrets file + URI string // set by the 'host' flag + Concurrency int // set the of concurrent jobs with the 'concurrency' flag + Timeout int // set the timeout with the 'timeout' flag + CaCertPath string // set the cert path with the 'cacert' flag + Verbose bool // set whether to include verbose output with 'verbose' flag + OutputPath string // set the path to save output with 'output' flag + ForceUpdate bool // set whether to force updating SMD with 'force-update' flag + AccessToken string // set the access token to include in request with 'access-token' flag + SecretStore secrets.SecretStore // set BMC credentials } // This is the main function used to collect information from the BMC nodes via Redfish. @@ -50,7 +49,7 @@ type CollectParams struct { // // Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency // property value between 1 and 10000. -func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore secrets.SecretStore) ([]map[string]any, error) { +func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[string]any, error) { // check for available remote assets found from scan if assets == nil { return nil, fmt.Errorf("no assets found") @@ -120,38 +119,17 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s // crawl BMC node to fetch inventory data via Redfish var ( - fallbackStore = secrets.NewStaticStore(params.Username, params.Password) - systems []crawler.InventoryDetail - managers []crawler.Manager - config = crawler.CrawlerConfig{ + systems []crawler.InventoryDetail + managers []crawler.Manager + config = crawler.CrawlerConfig{ URI: uri, - CredentialStore: localStore, + CredentialStore: params.SecretStore, Insecure: true, UseDefault: true, } err error ) - // determine if local store exists and has credentials for - // the provided secretID... - // if it does not, use the fallback static store instead with - // the username and password provided as arguments - if localStore != nil { - _, err := localStore.GetSecretByID(uri) - if err != nil { - log.Warn().Err(err).Msgf("could not retrieve secrets for '%s'...falling back to credentials provided with flags -u/-p for user '%s'", uri, params.Username) - if params.Username != "" && params.Password != "" { - config.CredentialStore = fallbackStore - } else if !config.UseDefault { - log.Warn().Msgf("no fallback credentials provided for '%s'", params.Username) - continue - } - } - } else { - log.Warn().Msgf("invalid store for %s...falling back to default provided credentials for user '%s'", uri, params.Username) - config.CredentialStore = fallbackStore - } - // crawl for node and BMC information systems, err = crawler.CrawlBMCForSystems(config) if err != nil { @@ -162,13 +140,19 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s log.Error().Err(err).Msg("failed to crawl BMC for managers") } + // get BMC username to send + bmcCreds, err := bmc.GetBMCCredentials(params.SecretStore, config.URI) + if err != nil { + log.Error().Str("id", config.URI).Msg("username will be blank") + } + // data to be sent to smd data := map[string]any{ "ID": fmt.Sprintf("%v", node.String()[:len(node.String())-2]), "Type": "", "Name": "", "FQDN": sr.Host, - "User": params.Username, + "User": bmcCreds.Username, "MACRequired": true, "RediscoverOnUpdate": false, "Systems": systems, From 0ed861e3a76df1e6cd8723183baa79da346bfba4 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 17:31:04 -0600 Subject: [PATCH 68/83] fix(collect): make sure secret store is set --- cmd/collect.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/collect.go b/cmd/collect.go index fba5bf5..67e6971 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -91,6 +91,7 @@ var CollectCmd = &cobra.Command{ s.Password = password } case *secrets.LocalSecretStore: + tmpSecrets := make(map[string]string) for k, _ := range s.Secrets { if creds, err := bmc.GetBMCCredentials(store, k); err != nil { log.Error().Str("id", k).Err(err).Msg("failed to get BMC credentials from secret store") @@ -105,10 +106,11 @@ var CollectCmd = &cobra.Command{ if newCreds, err := json.Marshal(creds); err != nil { log.Error().Str("id", k).Err(err).Msg("failed to marshal updated BMC credentials") } else { - s.Secrets[k] = string(newCreds) + tmpSecrets[k] = string(newCreds) } } } + store.(*secrets.LocalSecretStore).Secrets = tmpSecrets } } From 7bcd2f94620fbba1d323248acbd4e96c5686688c Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 17:31:20 -0600 Subject: [PATCH 69/83] fix(collect): don;t require both creds flags --- cmd/collect.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 67e6971..e2225ba 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -151,9 +151,6 @@ func init() { CollectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", false, "Set flag to force update data sent to SMD") 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") - // bind flags to config properties checkBindFlagError(viper.BindPFlag("collect.host", CollectCmd.Flags().Lookup("host"))) checkBindFlagError(viper.BindPFlag("collect.scheme", CollectCmd.Flags().Lookup("scheme"))) From e4a521971ae035163995a7f3aa6f7fdb4ca16f88 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 17:41:46 -0600 Subject: [PATCH 70/83] fix(bmc): check for default key --- pkg/bmc/bmc.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/bmc/bmc.go b/pkg/bmc/bmc.go index 9948e0d..012ffca 100644 --- a/pkg/bmc/bmc.go +++ b/pkg/bmc/bmc.go @@ -2,6 +2,7 @@ package bmc import ( "encoding/json" + "fmt" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" @@ -14,6 +15,23 @@ type BMCCredentials struct { func GetBMCCredentials(store secrets.SecretStore, id string) (BMCCredentials, error) { var creds BMCCredentials + if id == secrets.DEFAULT_KEY { + log.Info().Msg("fetching default credentials") + if uriCreds, err := store.GetSecretByID(id); err != nil { + log.Warn().Err(err).Msg("failed to get default credentials") + return creds, fmt.Errorf("get default credentials: %w", err) + } else { + if err := json.Unmarshal([]byte(uriCreds), &creds); err != nil { + log.Error().Err(err).Msg("failed to unmarshal default credentials") + return creds, fmt.Errorf("unmarshal default credentials: %w", err) + } else { + log.Info().Msg("default credentials found, using") + } + } + + return creds, nil + } + if uriCreds, err := store.GetSecretByID(id); err != nil { // Specific credentials for URI not found, fetch default. log.Warn().Str("id", id).Msg("specific credentials not found, falling back to default") From 939be12da7d53b47450782d40021518ab61c9f4a Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 21:43:59 -0600 Subject: [PATCH 71/83] fix(collect): properly set secret when overriding with flags --- cmd/collect.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index e2225ba..5649900 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -91,7 +91,6 @@ var CollectCmd = &cobra.Command{ s.Password = password } case *secrets.LocalSecretStore: - tmpSecrets := make(map[string]string) for k, _ := range s.Secrets { if creds, err := bmc.GetBMCCredentials(store, k); err != nil { log.Error().Str("id", k).Err(err).Msg("failed to get BMC credentials from secret store") @@ -106,11 +105,10 @@ var CollectCmd = &cobra.Command{ if newCreds, err := json.Marshal(creds); err != nil { log.Error().Str("id", k).Err(err).Msg("failed to marshal updated BMC credentials") } else { - tmpSecrets[k] = string(newCreds) + s.StoreSecretByID(k, string(newCreds)) } } } - store.(*secrets.LocalSecretStore).Secrets = tmpSecrets } } From 5d9afebcb171e5ec06cab9189cc4927dbae2a65a Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Wed, 16 Apr 2025 22:42:19 -0600 Subject: [PATCH 72/83] fix: move BMC credentials getter that logs to util func --- cmd/collect.go | 6 ++-- internal/util/bmc.go | 47 +++++++++++++++++++++++++++ pkg/bmc/bmc.go | 76 +++++++++++++++++++++++--------------------- pkg/collect.go | 4 +-- pkg/crawler/main.go | 33 +++---------------- 5 files changed, 97 insertions(+), 69 deletions(-) create mode 100644 internal/util/bmc.go diff --git a/cmd/collect.go b/cmd/collect.go index 5649900..7db23d8 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -85,15 +85,17 @@ var CollectCmd = &cobra.Command{ switch s := store.(type) { case *secrets.StaticStore: if username != "" { + log.Info().Msg("--username passed, overriding all usernames with value") s.Username = username } if password != "" { + log.Info().Msg("--password passed, overriding all passwords with value") s.Password = password } case *secrets.LocalSecretStore: for k, _ := range s.Secrets { if creds, err := bmc.GetBMCCredentials(store, k); err != nil { - log.Error().Str("id", k).Err(err).Msg("failed to get BMC credentials from secret store") + log.Error().Str("id", k).Err(err).Msg("failed to override BMC credentials") } else { if username != "" { creds.Username = username @@ -103,7 +105,7 @@ var CollectCmd = &cobra.Command{ } if newCreds, err := json.Marshal(creds); err != nil { - log.Error().Str("id", k).Err(err).Msg("failed to marshal updated BMC credentials") + log.Error().Str("id", k).Err(err).Msg("failed to override BMC credentials: marshal error") } else { s.StoreSecretByID(k, string(newCreds)) } diff --git a/internal/util/bmc.go b/internal/util/bmc.go new file mode 100644 index 0000000..76f61a8 --- /dev/null +++ b/internal/util/bmc.go @@ -0,0 +1,47 @@ +package util + +import ( + "github.com/OpenCHAMI/magellan/pkg/bmc" + "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/rs/zerolog/log" +) + +func GetBMCCredentials(store secrets.SecretStore, id string) bmc.BMCCredentials { + var ( + creds bmc.BMCCredentials + err error + ) + + if id == "" { + log.Error().Msg("failed to get BMC credentials: id was empty") + return creds + } + + if id == secrets.DEFAULT_KEY { + log.Info().Msg("fetching default credentials") + if creds, err = bmc.GetBMCCredentialsDefault(store); err != nil { + log.Warn().Err(err).Msg("failed to get default credentials") + } else { + log.Info().Msg("default credentials found, using") + } + return creds + } + + if creds, err = bmc.GetBMCCredentials(store, id); err != nil { + // Specific credentials for URI not found, fetch default. + log.Warn().Str("id", id).Msg("specific credentials not found, falling back to default") + if defaultSecret, err := bmc.GetBMCCredentialsDefault(store); err != nil { + // We've exhausted all options, the credentials will be blank unless + // overridden by a CLI flag. + log.Warn().Str("id", id).Err(err).Msg("no default credentials were set, they will be blank unless overridden by CLI flags") + } else { + // Default credentials found, use them. + log.Info().Str("id", id).Msg("default credentials found, using") + creds = defaultSecret + } + } else { + log.Info().Str("id", id).Msg("specific credentials found, using") + } + + return creds +} diff --git a/pkg/bmc/bmc.go b/pkg/bmc/bmc.go index 012ffca..387100a 100644 --- a/pkg/bmc/bmc.go +++ b/pkg/bmc/bmc.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/OpenCHAMI/magellan/pkg/secrets" - "github.com/rs/zerolog/log" ) type BMCCredentials struct { @@ -13,51 +12,54 @@ type BMCCredentials struct { Password string `json:"password"` } -func GetBMCCredentials(store secrets.SecretStore, id string) (BMCCredentials, error) { +func GetBMCCredentialsDefault(store secrets.SecretStore) (BMCCredentials, error) { var creds BMCCredentials - if id == secrets.DEFAULT_KEY { - log.Info().Msg("fetching default credentials") - if uriCreds, err := store.GetSecretByID(id); err != nil { - log.Warn().Err(err).Msg("failed to get default credentials") - return creds, fmt.Errorf("get default credentials: %w", err) - } else { - if err := json.Unmarshal([]byte(uriCreds), &creds); err != nil { - log.Error().Err(err).Msg("failed to unmarshal default credentials") - return creds, fmt.Errorf("unmarshal default credentials: %w", err) - } else { - log.Info().Msg("default credentials found, using") - } + if strCreds, err := store.GetSecretByID(secrets.DEFAULT_KEY); err != nil { + return creds, fmt.Errorf("get default BMC credentials from secret store: %w", err) + } else { + // Default URI credentials found, use them. + if err = json.Unmarshal([]byte(strCreds), &creds); err != nil { + return creds, fmt.Errorf("get default BMC credentials from secret store: failed to unmarshal: %w", err) } - return creds, nil } +} - if uriCreds, err := store.GetSecretByID(id); err != nil { - // Specific credentials for URI not found, fetch default. - log.Warn().Str("id", id).Msg("specific credentials not found, falling back to default") - defaultSecret, err := store.GetSecretByID(secrets.DEFAULT_KEY) - if err != nil { - // We've exhausted all options, the credentials will be blank unless - // overridden by a CLI flag. - log.Warn().Str("id", id).Err(err).Msg("no default credentials were set, they will be blank unless overridden by CLI flags") - } else { - // Default credentials found, use them. - if err = json.Unmarshal([]byte(defaultSecret), &creds); err != nil { - log.Warn().Str("id", id).Err(err).Msg("failed to unmarshal default secrets store credentials") - return creds, err - } else { - log.Info().Str("id", id).Msg("default credentials found, using") - } - } +func GetBMCCredentials(store secrets.SecretStore, id string) (BMCCredentials, error) { + var creds BMCCredentials + if strCreds, err := store.GetSecretByID(id); err != nil { + return creds, fmt.Errorf("get BMC credentials from secret store: %w", err) } else { // Specific URI credentials found, use them. - if err = json.Unmarshal([]byte(uriCreds), &creds); err != nil { - log.Warn().Str("id", id).Err(err).Msg("failed to unmarshal specific credentials") - return creds, err - } else { - log.Info().Str("id", id).Msg("specific credentials found, using") + if err = json.Unmarshal([]byte(strCreds), &creds); err != nil { + return creds, fmt.Errorf("get BMC credentials from secret store: failed to unmarshal: %w", err) } } return creds, nil } + +func GetBMCCredentialsOrDefault(store secrets.SecretStore, id string) BMCCredentials { + var ( + creds BMCCredentials + err error + ) + + if id == "" { + return creds + } + + if id == secrets.DEFAULT_KEY { + creds, _ = GetBMCCredentialsDefault(store) + return creds + } + + if creds, err = GetBMCCredentials(store, id); err != nil { + if defaultSecret, err := GetBMCCredentialsDefault(store); err == nil { + // Default credentials found, use them. + creds = defaultSecret + } + } + + return creds +} diff --git a/pkg/collect.go b/pkg/collect.go index 80feff5..f46f6fa 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -141,8 +141,8 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[strin } // get BMC username to send - bmcCreds, err := bmc.GetBMCCredentials(params.SecretStore, config.URI) - if err != nil { + bmcCreds := bmc.GetBMCCredentialsOrDefault(params.SecretStore, config.URI) + if bmcCreds == (bmc.BMCCredentials{}) { log.Error().Str("id", config.URI).Msg("username will be blank") } diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 8b732f4..16658d6 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -1,10 +1,10 @@ package crawler import ( - "encoding/json" "fmt" "strings" + "github.com/OpenCHAMI/magellan/internal/util" "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/rs/zerolog/log" @@ -374,32 +374,9 @@ func loadBMCCreds(config CrawlerConfig) (bmc.BMCCredentials, error) { if config.CredentialStore == nil { return bmc.BMCCredentials{}, fmt.Errorf("credential store is invalid") } - creds, err := config.CredentialStore.GetSecretByID(config.URI) - if err != nil { - event := log.Error() - event.Err(err) - event.Msg("failed to get credentials from secret store") - // try to get default if parameter is set - if config.UseDefault { - creds, err = config.CredentialStore.GetSecretByID(secrets.DEFAULT_KEY) - // no default credentials - if err != nil { - event := log.Error() - event.Err(err) - event.Msg("failed to get default credentials from secret store") - return bmc.BMCCredentials{}, err - } - } else { - return bmc.BMCCredentials{}, err - } + if creds := util.GetBMCCredentials(config.CredentialStore, config.URI); creds == (bmc.BMCCredentials{}) { + return creds, fmt.Errorf("%s: credentials blank for BNC", config.URI) + } else { + return creds, nil } - var bmc_creds bmc.BMCCredentials - err = json.Unmarshal([]byte(creds), &bmc_creds) - if err != nil { - event := log.Error() - event.Err(err) - event.Msg("failed to unmarshal credentials") - return bmc.BMCCredentials{}, err - } - return bmc_creds, nil } From 26e39d777ba02c9e16b9f29fddc94a828b3bcc55 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Thu, 17 Apr 2025 09:39:52 -0600 Subject: [PATCH 73/83] fix: log override for local store too --- cmd/collect.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/collect.go b/cmd/collect.go index 7db23d8..a79e6d2 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -98,9 +98,11 @@ var CollectCmd = &cobra.Command{ log.Error().Str("id", k).Err(err).Msg("failed to override BMC credentials") } else { if username != "" { + log.Info().Msg("--username passed, overriding all usernames with value") creds.Username = username } if password != "" { + log.Info().Msg("--password passed, overriding all passwords with value") creds.Password = password } From ed9db6d943b2120331edef8f06ea5b5f829afd73 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Thu, 17 Apr 2025 09:43:03 -0600 Subject: [PATCH 74/83] fix: only do it once --- cmd/collect.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index a79e6d2..d816567 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -82,14 +82,18 @@ var CollectCmd = &cobra.Command{ // Temporarily override username/password of each BMC if one of those // flags is passed. The expectation is that if the flag is specified // on the command line, it should be used. + if username != "" { + log.Info().Msg("--username passed, overriding all usernames with value") + } + if password != "" { + log.Info().Msg("--password passed, overriding all passwords with value") + } switch s := store.(type) { case *secrets.StaticStore: if username != "" { - log.Info().Msg("--username passed, overriding all usernames with value") s.Username = username } if password != "" { - log.Info().Msg("--password passed, overriding all passwords with value") s.Password = password } case *secrets.LocalSecretStore: @@ -98,11 +102,9 @@ var CollectCmd = &cobra.Command{ log.Error().Str("id", k).Err(err).Msg("failed to override BMC credentials") } else { if username != "" { - log.Info().Msg("--username passed, overriding all usernames with value") creds.Username = username } if password != "" { - log.Info().Msg("--password passed, overriding all passwords with value") creds.Password = password } From 979841d76245b3f4200804de1201b0874b54cee3 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Thu, 17 Apr 2025 09:47:29 -0600 Subject: [PATCH 75/83] fix: clarify that override is temporary --- cmd/collect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index d816567..175680b 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -83,10 +83,10 @@ var CollectCmd = &cobra.Command{ // flags is passed. The expectation is that if the flag is specified // on the command line, it should be used. if username != "" { - log.Info().Msg("--username passed, overriding all usernames with value") + log.Info().Msg("--username passed, temporarily overriding all usernames from secret store with value") } if password != "" { - log.Info().Msg("--password passed, overriding all passwords with value") + log.Info().Msg("--password passed, temporarily overriding all passwords from secret store with value") } switch s := store.(type) { case *secrets.StaticStore: From 5d9b382921605eb0bf002eabb2810aed869ed481 Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 21 Apr 2025 12:17:28 -0600 Subject: [PATCH 76/83] fix: changed persistent flags in collect to fix binding --- cmd/collect.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index 175680b..4e55956 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -145,15 +145,15 @@ var CollectCmd = &cobra.Command{ func init() { currentUser, _ = user.Current() - CollectCmd.PersistentFlags().StringVar(&host, "host", "", "Set the URI to the SMD root endpoint") - CollectCmd.PersistentFlags().StringVarP(&username, "username", "u", "", "Set the master BMC username") - CollectCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "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", "", "Set the path to CA cert file. (defaults to system CAs when blank)") + CollectCmd.Flags().StringVar(&host, "host", "", "Set the URI to the SMD root endpoint") + CollectCmd.Flags().StringVarP(&username, "username", "u", "", "Set the master BMC username") + CollectCmd.Flags().StringVarP(&password, "password", "p", "", "Set the master BMC password") + CollectCmd.Flags().StringVar(&secretsFile, "secrets-file", "", "Set path to the node secrets file") + CollectCmd.Flags().StringVar(&scheme, "scheme", "https", "Set the default scheme used to query when not included in URI") + CollectCmd.Flags().StringVar(&protocol, "protocol", "tcp", "Set the protocol used to query") + CollectCmd.Flags().StringVarP(&outputPath, "output", "o", fmt.Sprintf("/tmp/%smagellan/inventory/", currentUser.Username+"/"), "Set the path to store collection data") + CollectCmd.Flags().BoolVar(&forceUpdate, "force-update", false, "Set flag to force update data sent to SMD") + CollectCmd.Flags().StringVar(&cacertPath, "cacert", "", "Set the path to CA cert file. (defaults to system CAs when blank)") // bind flags to config properties checkBindFlagError(viper.BindPFlag("collect.host", CollectCmd.Flags().Lookup("host"))) From ad30eb297d3013dc2b142fedc38991bf154ffcce Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 21 Apr 2025 15:37:15 -0600 Subject: [PATCH 77/83] fix: added check to stop collect on error --- pkg/collect.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/collect.go b/pkg/collect.go index f46f6fa..69fe951 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -133,17 +133,22 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) ([]map[strin // crawl for node and BMC information systems, err = crawler.CrawlBMCForSystems(config) if err != nil { - log.Error().Err(err).Msg("failed to crawl BMC for systems") + log.Error().Err(err).Str("uri", uri).Msg("failed to crawl BMC for systems") } managers, err = crawler.CrawlBMCForManagers(config) if err != nil { - log.Error().Err(err).Msg("failed to crawl BMC for managers") + log.Error().Err(err).Str("uri", uri).Msg("failed to crawl BMC for managers") + } + + // we didn't find anything so do not proceed + if len(systems) == 0 && len(managers) == 0 { + continue } // get BMC username to send bmcCreds := bmc.GetBMCCredentialsOrDefault(params.SecretStore, config.URI) if bmcCreds == (bmc.BMCCredentials{}) { - log.Error().Str("id", config.URI).Msg("username will be blank") + log.Warn().Str("id", config.URI).Msg("username will be blank") } // data to be sent to smd From 738685095f1cdadd4152c8925850f29101a54aac Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 21 Apr 2025 15:57:19 -0600 Subject: [PATCH 78/83] readme: updated information about default secrets --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32d083a..a71f0a0 100644 --- a/README.md +++ b/README.md @@ -254,16 +254,16 @@ magellan collect \ --password $default_bmc_password ``` -If you pass agruments with the `--username/--password` flags, they will be used as a fallback if no credentials are found in the store. However, the secret store credentials are always used first if they exists. +If you pass arguments with the `--username/--password` flags, the arguments will override all credentials set in the secret store for each flag. However, it is possible only override a single flag (e.g. `magellan collect --username`). > [!NOTE] > Make sure that the `secretID` is EXACTLY as show with `magellan list`. Otherwise, `magellan` will not be able to do the lookup from the secret store correctly. > [!TIP] -> You can set default fallback credentials by storing a secret with the `secretID` of "default". This is used if no `secretID` is found in the local store for the specified host. Otherwise, the `--username/--password` arguments are used. +> You can set default fallback credentials by storing a secret with the `secretID` of "default". This is used if no `secretID` is found in the local store for the specified host. This is useful when you want to set a username and password that is the same for all BMCs with the exception of the ones specified. > ```bash > magellan secrets default $username:$password -> +> ``` ### Starting the Emulator From 0f811543f5e0ad1ae18488f28e44ce7d2e04c2f0 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 18 Sep 2024 14:46:52 -0600 Subject: [PATCH 79/83] Changed DeleteScannedAssets to work correct and added db tag --- internal/cache/sqlite/sqlite.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/internal/cache/sqlite/sqlite.go b/internal/cache/sqlite/sqlite.go index 12fc2bb..21ee4b8 100644 --- a/internal/cache/sqlite/sqlite.go +++ b/internal/cache/sqlite/sqlite.go @@ -2,7 +2,6 @@ package sqlite import ( "fmt" - "strings" "github.com/OpenCHAMI/magellan/internal/util" magellan "github.com/OpenCHAMI/magellan/pkg" @@ -78,22 +77,7 @@ func DeleteScannedAssets(path string, assets ...magellan.RemoteAsset) error { } tx = db.MustBegin() for _, asset := range assets { - // skip if neither host nor port are specified - if asset.Host == "" && asset.Port <= 0 { - continue - } - sql := fmt.Sprintf(`DELETE FROM %s`, TABLE_NAME) - where := []string{} - if asset.Port > 0 { - where = append(where, "port=:port") - } - if asset.Host != "" { - where = append(where, "host=:host") - } - if len(where) <= 0 { - continue - } - sql += fmt.Sprintf(" WHERE %s;", strings.Join(where, " AND ")) + sql := fmt.Sprintf(`DELETE FROM %s WHERE host=:host AND port=:port;`, TABLE_NAME) _, err := tx.NamedExec(sql, &asset) if err != nil { fmt.Printf("failed to execute DELETE transaction: %v\n", err) From f3ede4117f3c735739246dbe89bf57a942794513 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Sun, 3 Nov 2024 19:53:48 -0700 Subject: [PATCH 80/83] updated all references --- README.md | 2 +- cmd/collect.go | 4 ---- tests/api_test.go | 6 +++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a71f0a0..77c8483 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ The `magellan` tool has a `login` subcommand that works with the [`opaal`](https export ACCESS_TOKEN=eyJhbGciOiJIUzI1NiIs... ``` -Alternatively, if you are running the davidallendj quickstart in the [deployment recipes](https://github.com/davidallendj/deployment-recipes), you can run the provided script to generate a token and set the environment variable that way. +Alternatively, if you are running the OpenCHAMI quickstart in the [deployment recipes](https://github.com/davidallendj/deployment-recipes), you can run the provided script to generate a token and set the environment variable that way. ```bash quickstart_dir=path/to/deployment/recipes/quickstart diff --git a/cmd/collect.go b/cmd/collect.go index 4e55956..d5e29c3 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -5,10 +5,6 @@ import ( "fmt" "os/user" - "github.com/OpenCHAMI/magellan/internal/cache/sqlite" - urlx "github.com/OpenCHAMI/magellan/internal/url" - magellan "github.com/OpenCHAMI/magellan/pkg" - "github.com/OpenCHAMI/magellan/pkg/auth" "github.com/OpenCHAMI/magellan/pkg/bmc" "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/cznic/mathutil" diff --git a/tests/api_test.go b/tests/api_test.go index 999f142..d46a312 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -22,9 +22,9 @@ import ( "flag" - "github.com/OpenCHAMI/magellan/internal/util" - magellan "github.com/OpenCHAMI/magellan/pkg" - "github.com/OpenCHAMI/magellan/pkg/client" + magellan "github.com/davidallendj/magellan/internal" + "github.com/davidallendj/magellan/internal/util" + "github.com/davidallendj/magellan/pkg/client" "github.com/rs/zerolog/log" ) From a9a3ebee207efabf83ec6f742d84225b12bd730e Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 11 Dec 2024 10:57:42 -0700 Subject: [PATCH 81/83] refactor: updated references --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77c8483..a71f0a0 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ The `magellan` tool has a `login` subcommand that works with the [`opaal`](https export ACCESS_TOKEN=eyJhbGciOiJIUzI1NiIs... ``` -Alternatively, if you are running the OpenCHAMI quickstart in the [deployment recipes](https://github.com/davidallendj/deployment-recipes), you can run the provided script to generate a token and set the environment variable that way. +Alternatively, if you are running the davidallendj quickstart in the [deployment recipes](https://github.com/davidallendj/deployment-recipes), you can run the provided script to generate a token and set the environment variable that way. ```bash quickstart_dir=path/to/deployment/recipes/quickstart From 665cd4bc14ef5fe38a726f893474652c93132644 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 11 Dec 2024 14:13:51 -0700 Subject: [PATCH 82/83] refactor: exported more cmd variables --- cmd/update.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index f07a0bb..f299c80 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -25,7 +25,7 @@ var ( // The `update` command provides an interface to easily update firmware // using Redfish. It also provides a simple way to check the status of // an update in-progress. -var updateCmd = &cobra.Command{ +var UpdateCmd = &cobra.Command{ Use: "update hosts...", Example: ` // perform an firmware update magellan update 172.16.0.108:443 -i -u $bmc_username -p $bmc_password \ @@ -121,17 +121,21 @@ var updateCmd = &cobra.Command{ } func init() { - updateCmd.Flags().StringVarP(&username, "username", "u", "", "Set the BMC user") - updateCmd.Flags().StringVarP(&password, "password", "p", "", "Set the BMC password") - updateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") - updateCmd.Flags().StringVar(&firmwareUri, "firmware-uri", "", "Set the URI to retrieve the firmware") - updateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") - updateCmd.Flags().BoolVarP(&Insecure, "insecure", "i", false, "Allow insecure connections to the server") + UpdateCmd.Flags().StringVar(&username, "username", "", "Set the BMC user") + UpdateCmd.Flags().StringVar(&password, "password", "", "Set the BMC password") + UpdateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") + UpdateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "Set the path to the firmware") + UpdateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "Set the version of firmware to be installed") + UpdateCmd.Flags().StringVar(&component, "component", "", "Set the component to upgrade (BMC|BIOS)") + UpdateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") - checkBindFlagError(viper.BindPFlag("update.scheme", updateCmd.Flags().Lookup("scheme"))) - checkBindFlagError(viper.BindPFlag("update.firmware-uri", updateCmd.Flags().Lookup("firmware-uri"))) - checkBindFlagError(viper.BindPFlag("update.status", updateCmd.Flags().Lookup("status"))) - checkBindFlagError(viper.BindPFlag("update.insecure", updateCmd.Flags().Lookup("insecure"))) + 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.status", UpdateCmd.Flags().Lookup("status"))) - rootCmd.AddCommand(updateCmd) + rootCmd.AddCommand(UpdateCmd) } From d34ba3f754e5552fdaf7df16c2e13fd905ba1179 Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 26 Apr 2025 18:12:21 -0600 Subject: [PATCH 83/83] chore: update references and imports --- .goreleaser.yaml | 6 ++-- cmd/cache.go | 2 +- cmd/collect.go | 6 ++-- cmd/crawl.go | 8 ++--- cmd/scan.go | 4 +-- cmd/secrets.go | 2 +- cmd/update.go | 12 +++---- go.mod | 2 +- go.sum | 55 --------------------------------- internal/cache/sqlite/sqlite.go | 4 +-- internal/util/bmc.go | 4 +-- pkg/bmc/bmc.go | 2 +- pkg/collect.go | 8 ++--- pkg/crawler/main.go | 6 ++-- pkg/secrets/example/main.go | 2 +- pkg/update.go | 2 +- tests/api_test.go | 2 +- tests/compatibility_test.go | 6 ++-- 18 files changed, 37 insertions(+), 96 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a4c3f37..04df1f4 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -94,9 +94,9 @@ dockers: - CHANGELOG.md - README.md - image_templates: - - &arm64v7_linux_image ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }}-arm64 - - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}-arm64 - - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-arm64 + - &arm64v7_linux_image ghcr.io/davidallendj/{{.ProjectName}}:{{ .Tag }}-arm64 + - ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}-arm64 + - ghcr.io/davidallendj/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-arm64 use: buildx build_flag_templates: - "--pull" diff --git a/cmd/cache.go b/cmd/cache.go index 11c87a5..f39bffd 100644 --- a/cmd/cache.go +++ b/cmd/cache.go @@ -6,8 +6,8 @@ import ( "os" "strconv" - magellan "github.com/davidallendj/magellan/internal" "github.com/davidallendj/magellan/internal/cache/sqlite" + magellan "github.com/davidallendj/magellan/pkg" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) diff --git a/cmd/collect.go b/cmd/collect.go index d5e29c3..c3fb35c 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -5,13 +5,13 @@ import ( "fmt" "os/user" - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/secrets" "github.com/cznic/mathutil" - magellan "github.com/davidallendj/magellan/internal" "github.com/davidallendj/magellan/internal/cache/sqlite" urlx "github.com/davidallendj/magellan/internal/url" + magellan "github.com/davidallendj/magellan/pkg" "github.com/davidallendj/magellan/pkg/auth" + "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" diff --git a/cmd/crawl.go b/cmd/crawl.go index 5e4a038..c1acc3a 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -6,10 +6,10 @@ import ( "github.com/rs/zerolog/log" - urlx "github.com/OpenCHAMI/magellan/internal/url" - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/crawler" - "github.com/OpenCHAMI/magellan/pkg/secrets" + urlx "github.com/davidallendj/magellan/internal/url" + "github.com/davidallendj/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/crawler" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/cmd/scan.go b/cmd/scan.go index 8549eb1..f25150d 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -7,8 +7,8 @@ import ( "os" "path" - "github.com/OpenCHAMI/magellan/internal/cache/sqlite" - magellan "github.com/OpenCHAMI/magellan/pkg" + "github.com/davidallendj/magellan/internal/cache/sqlite" + magellan "github.com/davidallendj/magellan/pkg" "github.com/rs/zerolog/log" "github.com/cznic/mathutil" diff --git a/cmd/secrets.go b/cmd/secrets.go index f4ea00d..59523e6 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/cmd/update.go b/cmd/update.go index f299c80..c2cc3a4 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -4,9 +4,9 @@ import ( "os" "strings" - magellan "github.com/OpenCHAMI/magellan/pkg" - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/secrets" + magellan "github.com/davidallendj/magellan/pkg" + "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" @@ -15,8 +15,6 @@ import ( var ( host string firmwareUri string - firmwareVersion string - component string transferProtocol string showStatus bool Insecure bool @@ -124,9 +122,7 @@ func init() { UpdateCmd.Flags().StringVar(&username, "username", "", "Set the BMC user") UpdateCmd.Flags().StringVar(&password, "password", "", "Set the BMC password") UpdateCmd.Flags().StringVar(&transferProtocol, "scheme", "https", "Set the transfer protocol") - UpdateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "Set the path to the firmware") - UpdateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "Set the version of firmware to be installed") - UpdateCmd.Flags().StringVar(&component, "component", "", "Set the component to upgrade (BMC|BIOS)") + UpdateCmd.Flags().StringVar(&firmwareUri, "firmware-uri", "", "Set the path to the firmware") UpdateCmd.Flags().BoolVar(&showStatus, "status", false, "Get the status of the update") checkBindFlagError(viper.BindPFlag("update.username", UpdateCmd.Flags().Lookup("username"))) diff --git a/go.mod b/go.mod index b39be0a..1224cc7 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( ) require ( - github.com/google/go-cmp v0.6.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect ) @@ -31,6 +30,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect diff --git a/go.sum b/go.sum index cdc806e..be886fb 100644 --- a/go.sum +++ b/go.sum @@ -123,29 +123,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -======= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -<<<<<<< HEAD -======= -======= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) -======= ->>>>>>> 73c3323 (chore: updated go deps) golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= ->>>>>>> 555990c (collect: return collection output from CollectInventory()) -======= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -172,29 +151,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -======= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -<<<<<<< HEAD -======= -======= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ->>>>>>> 555990c (collect: return collection output from CollectInventory()) -======= -======= ->>>>>>> 73c3323 (chore: updated go deps) -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -207,21 +165,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= -======= ->>>>>>> 97a569d (collect: return collection output from CollectInventory()) -======= ->>>>>>> 73c3323 (chore: updated go deps) golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= ->>>>>>> 555990c (collect: return collection output from CollectInventory()) -======= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= ->>>>>>> 3b85dd3 (chore: fix critical dependabot issues by updating crypto) golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/internal/cache/sqlite/sqlite.go b/internal/cache/sqlite/sqlite.go index 21ee4b8..bbbdff3 100644 --- a/internal/cache/sqlite/sqlite.go +++ b/internal/cache/sqlite/sqlite.go @@ -3,8 +3,8 @@ package sqlite import ( "fmt" - "github.com/OpenCHAMI/magellan/internal/util" - magellan "github.com/OpenCHAMI/magellan/pkg" + "github.com/davidallendj/magellan/internal/util" + magellan "github.com/davidallendj/magellan/pkg" "github.com/jmoiron/sqlx" ) diff --git a/internal/util/bmc.go b/internal/util/bmc.go index 76f61a8..d2aaebf 100644 --- a/internal/util/bmc.go +++ b/internal/util/bmc.go @@ -1,8 +1,8 @@ package util import ( - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/rs/zerolog/log" ) diff --git a/pkg/bmc/bmc.go b/pkg/bmc/bmc.go index 387100a..90dcadf 100644 --- a/pkg/bmc/bmc.go +++ b/pkg/bmc/bmc.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/secrets" ) type BMCCredentials struct { diff --git a/pkg/collect.go b/pkg/collect.go index 69fe951..d5b8855 100644 --- a/pkg/collect.go +++ b/pkg/collect.go @@ -15,10 +15,10 @@ import ( "sync" "time" - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/client" - "github.com/OpenCHAMI/magellan/pkg/crawler" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/client" + "github.com/davidallendj/magellan/pkg/crawler" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/rs/zerolog/log" diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 16658d6..3aa4900 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/OpenCHAMI/magellan/internal/util" - "github.com/OpenCHAMI/magellan/pkg/bmc" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/internal/util" + "github.com/davidallendj/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/secrets" "github.com/rs/zerolog/log" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" diff --git a/pkg/secrets/example/main.go b/pkg/secrets/example/main.go index 52ab649..d5739b8 100644 --- a/pkg/secrets/example/main.go +++ b/pkg/secrets/example/main.go @@ -16,7 +16,7 @@ import ( "fmt" "os" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/secrets" ) func usage() { diff --git a/pkg/update.go b/pkg/update.go index 411c28e..838ba0a 100644 --- a/pkg/update.go +++ b/pkg/update.go @@ -4,7 +4,7 @@ import ( "fmt" "net/url" - "github.com/OpenCHAMI/magellan/pkg/bmc" + "github.com/davidallendj/magellan/pkg/bmc" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" ) diff --git a/tests/api_test.go b/tests/api_test.go index d46a312..572b941 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -22,8 +22,8 @@ import ( "flag" - magellan "github.com/davidallendj/magellan/internal" "github.com/davidallendj/magellan/internal/util" + magellan "github.com/davidallendj/magellan/pkg" "github.com/davidallendj/magellan/pkg/client" "github.com/rs/zerolog/log" ) diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index f428f6a..766b96b 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -14,9 +14,9 @@ import ( "net/http" "testing" - "github.com/OpenCHAMI/magellan/pkg/client" - "github.com/OpenCHAMI/magellan/pkg/crawler" - "github.com/OpenCHAMI/magellan/pkg/secrets" + "github.com/davidallendj/magellan/pkg/client" + "github.com/davidallendj/magellan/pkg/crawler" + "github.com/davidallendj/magellan/pkg/secrets" ) var (