mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 03:27:03 -07:00
commit
7e8011ce29
2 changed files with 96 additions and 34 deletions
64
cmd/scan.go
64
cmd/scan.go
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/OpenCHAMI/magellan/internal/cache/sqlite"
|
"github.com/OpenCHAMI/magellan/internal/cache/sqlite"
|
||||||
magellan "github.com/OpenCHAMI/magellan/pkg"
|
magellan "github.com/OpenCHAMI/magellan/pkg"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
urlx "github.com/OpenCHAMI/magellan/internal/url"
|
urlx "github.com/OpenCHAMI/magellan/internal/url"
|
||||||
"github.com/cznic/mathutil"
|
"github.com/cznic/mathutil"
|
||||||
|
|
@ -24,6 +25,8 @@ var (
|
||||||
targetHosts [][]string
|
targetHosts [][]string
|
||||||
disableProbing bool
|
disableProbing bool
|
||||||
disableCache bool
|
disableCache bool
|
||||||
|
format string
|
||||||
|
include []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// The `scan` command is usually the first step to using the CLI tool.
|
// The `scan` command is usually the first step to using the CLI tool.
|
||||||
|
|
@ -66,7 +69,7 @@ var ScanCmd = &cobra.Command{
|
||||||
"specified. The `--scheme` flag works similarly and the default value is 'https' in the host URL or with the\n" +
|
"specified. The `--scheme` flag works similarly and the default value is 'https' in the host URL or with the\n" +
|
||||||
"'--protocol' flag.\n\n" +
|
"'--protocol' flag.\n\n" +
|
||||||
"If the '--disable-probe` flag is used, the tool will not send another request to probe for available.\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" +
|
"Redfish and JAWS 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",
|
"for determining which hosts to collect inventory data.\n\n",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// add default ports for hosts if none are specified with flag
|
// add default ports for hosts if none are specified with flag
|
||||||
|
|
@ -138,32 +141,61 @@ var ScanCmd = &cobra.Command{
|
||||||
DisableProbing: disableProbing,
|
DisableProbing: disableProbing,
|
||||||
Verbose: verbose,
|
Verbose: verbose,
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
|
Insecure: insecure,
|
||||||
|
Include: include,
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(foundAssets) > 0 && debug {
|
if len(foundAssets) > 0 && debug {
|
||||||
log.Info().Any("assets", foundAssets).Msgf("found assets from scan")
|
log.Info().Any("assets", foundAssets).Msgf("found assets from scan")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !disableCache && cachePath != "" {
|
if len(foundAssets) == 0 {
|
||||||
// make the cache directory path if needed
|
log.Warn().Msg("Scan complete. No responsive assets were found.")
|
||||||
err := os.MkdirAll(path.Dir(cachePath), 0755)
|
return
|
||||||
if err != nil {
|
}
|
||||||
log.Printf("failed to make cache directory: %v", err)
|
|
||||||
|
switch format {
|
||||||
|
case "json", "yaml":
|
||||||
|
var output []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if format == "json" {
|
||||||
|
output, err = json.MarshalIndent(foundAssets, "", " ")
|
||||||
|
} else {
|
||||||
|
output, err = yaml.Marshal(foundAssets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change this to use an extensible plugin system for storage solutions
|
if err != nil {
|
||||||
// (i.e. something like cache.InsertScannedAssets(path, assets) which implements a Cache interface)
|
log.Error().Err(err).Msgf("Failed to marshal output to %s", format)
|
||||||
if len(foundAssets) > 0 {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputPath != "" {
|
||||||
|
err := os.WriteFile(outputPath, output, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Failed to write to file: %s", outputPath)
|
||||||
|
} else {
|
||||||
|
log.Info().Msgf("Scan results successfully written to %s", outputPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
case "db":
|
||||||
|
if !disableCache && cachePath != "" {
|
||||||
|
err := os.MkdirAll(path.Dir(cachePath), 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to make cache directory: %v", err)
|
||||||
|
}
|
||||||
err = sqlite.InsertScannedAssets(cachePath, foundAssets...)
|
err = sqlite.InsertScannedAssets(cachePath, foundAssets...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to write scanned assets to cache")
|
log.Error().Err(err).Msg("failed to write scanned assets to cache")
|
||||||
|
} else if verbose {
|
||||||
|
log.Info().Msgf("Saved assets to cache: %s", cachePath)
|
||||||
}
|
}
|
||||||
if verbose {
|
|
||||||
log.Info().Msgf("saved assets to cache: %s", cachePath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Warn().Msg("no assets found to save")
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
log.Error().Msgf("unknown format specified: %s. Please use 'db', 'json', or 'yaml'.", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
@ -177,6 +209,10 @@ func init() {
|
||||||
ScanCmd.Flags().IPMaskVar(&subnetMask, "subnet-mask", net.IPv4Mask(255, 255, 255, 0), "Set the default subnet mask to use for with all subnets not using CIDR notation.")
|
ScanCmd.Flags().IPMaskVar(&subnetMask, "subnet-mask", net.IPv4Mask(255, 255, 255, 0), "Set the default subnet mask to use for with all subnets not using CIDR notation.")
|
||||||
ScanCmd.Flags().BoolVar(&disableProbing, "disable-probing", false, "Disable probing found assets for Redfish service(s) running on BMC nodes")
|
ScanCmd.Flags().BoolVar(&disableProbing, "disable-probing", false, "Disable probing found assets for Redfish service(s) running on BMC nodes")
|
||||||
ScanCmd.Flags().BoolVar(&disableCache, "disable-cache", false, "Disable saving found assets to a cache database specified with 'cache' flag")
|
ScanCmd.Flags().BoolVar(&disableCache, "disable-cache", false, "Disable saving found assets to a cache database specified with 'cache' flag")
|
||||||
|
ScanCmd.Flags().BoolVar(&insecure, "insecure", true, "Skip TLS certificate verification during probe")
|
||||||
|
ScanCmd.Flags().StringVarP(&format, "format", "F", "db", "Output format (db, json, yaml)")
|
||||||
|
ScanCmd.Flags().StringVarP(&outputPath, "output", "o", "", "Output file path (for json/yaml formats)")
|
||||||
|
ScanCmd.Flags().StringSliceVar(&include, "include", []string{"bmcs"}, "Asset types to scan for (bmcs, pdus)")
|
||||||
|
|
||||||
checkBindFlagError(viper.BindPFlag("scan.ports", ScanCmd.Flags().Lookup("port")))
|
checkBindFlagError(viper.BindPFlag("scan.ports", ScanCmd.Flags().Lookup("port")))
|
||||||
checkBindFlagError(viper.BindPFlag("scan.scheme", ScanCmd.Flags().Lookup("scheme")))
|
checkBindFlagError(viper.BindPFlag("scan.scheme", ScanCmd.Flags().Lookup("scheme")))
|
||||||
|
|
|
||||||
66
pkg/scan.go
66
pkg/scan.go
|
|
@ -1,6 +1,7 @@
|
||||||
package magellan
|
package magellan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -16,11 +17,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RemoteAsset struct {
|
type RemoteAsset struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
State bool `json:"state"`
|
State bool `json:"state"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
ServiceType string `json:"service_type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanParams is a collection of commom parameters passed to the CLI
|
// ScanParams is a collection of commom parameters passed to the CLI
|
||||||
|
|
@ -33,6 +35,8 @@ type ScanParams struct {
|
||||||
DisableProbing bool
|
DisableProbing bool
|
||||||
Verbose bool
|
Verbose bool
|
||||||
Debug bool
|
Debug bool
|
||||||
|
Insecure bool
|
||||||
|
Include []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanForAssets() performs a net scan on a network to find available services
|
// ScanForAssets() performs a net scan on a network to find available services
|
||||||
|
|
@ -45,7 +49,7 @@ type ScanParams struct {
|
||||||
// to be made concurrently.
|
// to be made concurrently.
|
||||||
//
|
//
|
||||||
// If the "disableProbing" flag is set, then the function will skip the extra
|
// If the "disableProbing" flag is set, then the function will skip the extra
|
||||||
// HTTP request made to check if the response was from a Redfish service.
|
// HTTP request made to check if the response was from a Redfish or JAWS service.
|
||||||
// Otherwise, not receiving a 200 OK response code from the HTTP request will
|
// Otherwise, not receiving a 200 OK response code from the HTTP request will
|
||||||
// remove the service from being stored in the list of scanned results.
|
// remove the service from being stored in the list of scanned results.
|
||||||
//
|
//
|
||||||
|
|
@ -61,6 +65,26 @@ func ScanForAssets(params *ScanParams) []RemoteAsset {
|
||||||
log.Info().Any("args", params).Msg("starting scan...")
|
log.Info().Any("args", params).Msg("starting scan...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
probesToRun := []struct {
|
||||||
|
Type, Path string
|
||||||
|
}{}
|
||||||
|
for _, item := range params.Include {
|
||||||
|
if item == "bmcs" {
|
||||||
|
probesToRun = append(probesToRun, struct{ Type, Path string }{Type: "Redfish", Path: "/redfish/v1/"})
|
||||||
|
}
|
||||||
|
if item == "pdus" {
|
||||||
|
probesToRun = append(probesToRun, struct{ Type, Path string }{Type: "JAWS", Path: "/jaws/monitor/outlets"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: params.Insecure},
|
||||||
|
}
|
||||||
|
probeClient := &http.Client{
|
||||||
|
Timeout: time.Duration(params.Timeout) * time.Second,
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(params.Concurrency)
|
wg.Add(params.Concurrency)
|
||||||
for i := 0; i < params.Concurrency; i++ {
|
for i := 0; i < params.Concurrency; i++ {
|
||||||
|
|
@ -78,26 +102,28 @@ func ScanForAssets(params *ScanParams) []RemoteAsset {
|
||||||
if params.Verbose {
|
if params.Verbose {
|
||||||
log.Debug().Err(err).Msgf("failed to connect to host")
|
log.Debug().Err(err).Msgf("failed to connect to host")
|
||||||
}
|
}
|
||||||
wg.Done()
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if !params.DisableProbing {
|
if !params.DisableProbing {
|
||||||
assetsToAdd := []RemoteAsset{}
|
assetsToAdd := []RemoteAsset{}
|
||||||
for _, foundAsset := range foundAssets {
|
for _, foundAsset := range foundAssets {
|
||||||
url := fmt.Sprintf("%s:%d/redfish/v1/", foundAsset.Host, foundAsset.Port)
|
for _, probe := range probesToRun {
|
||||||
res, _, err := client.MakeRequest(nil, url, http.MethodGet, nil, nil)
|
probeURL := fmt.Sprintf("%s:%d%s", foundAsset.Host, foundAsset.Port, probe.Path)
|
||||||
if err != nil || res == nil {
|
req, err := http.NewRequest("GET", probeURL, nil)
|
||||||
if params.Verbose {
|
if err != nil {
|
||||||
log.Printf("failed to make request: %v\n", err)
|
continue
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
} else if res.StatusCode != http.StatusOK {
|
res, err := probeClient.Do(req)
|
||||||
if params.Verbose {
|
if err == nil && res != nil && res.StatusCode == http.StatusOK {
|
||||||
log.Printf("request returned code: %v\n", res.StatusCode)
|
res.Body.Close()
|
||||||
|
foundAsset.ServiceType = probe.Type
|
||||||
|
assetsToAdd = append(assetsToAdd, foundAsset)
|
||||||
|
break // Found a valid service, no need to probe other types
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
res.Body.Close()
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
assetsToAdd = append(assetsToAdd, foundAsset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results = append(results, assetsToAdd...)
|
results = append(results, assetsToAdd...)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue