diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3d0cc49 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- [ ] Logger seems to be putting everything from plugins out as DEBUG from the core logger diff --git a/cmd/approved-raids.go b/cmd/approved-raids.go index 8d84043..e442c31 100644 --- a/cmd/approved-raids.go +++ b/cmd/approved-raids.go @@ -33,10 +33,6 @@ var ( // StartApprovedRaid will run a single raid after ensuring it is installed // Approved raids are listed in run/approved-raids.go func StartApprovedRaid(raidName string) (err error) { - configVar := make(map[string]interface{}, 1) - configVar[raidName] = make(map[string]interface{}, 1) - viper.Set("Raids", configVar) - err = installIfNotPresent(raidName) if err != nil { logger.Error(fmt.Sprintf( @@ -51,9 +47,9 @@ func StartApprovedRaid(raidName string) (err error) { func installIfNotPresent(raidName string) (err error) { installed := false - raids := GetAvailableRaids() + raids := GetRaids() for _, raid := range raids { - if raid == raidName { + if raid.Name == raidName { installed = true logger.Trace("Raid already installed.") } diff --git a/cmd/errors.go b/cmd/errors.go deleted file mode 100644 index 45f9a78..0000000 --- a/cmd/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import "fmt" - -// RaidError retains an error object and the name of the pack that generated it -type RaidError struct { - Raid string - Err error -} - -// RaidErrors holds a list of errors and an Error() method -// so it adheres to the standard Error interface -type RaidErrors struct { - Errors []RaidError -} - -func (e *RaidErrors) Error() string { - return fmt.Sprintf("Service Pack Errors: %v", e.Errors) -} diff --git a/cmd/generate-raid.go b/cmd/generate-raid.go index 3448484..c893499 100644 --- a/cmd/generate-raid.go +++ b/cmd/generate-raid.go @@ -9,17 +9,17 @@ import ( "strings" "github.com/go-git/go-git/v5" - "github.com/labstack/gommon/log" "github.com/spf13/cobra" "github.com/spf13/viper" "gopkg.in/yaml.v2" ) + type ControlCatalog struct { CategoryIDFriendly string ServiceName string Tactics map[string][]string - Metadata Metadata `yaml:"metadata"` + Metadata Metadata `yaml:"metadata"` Controls []Control `yaml:"controls"` Features []Feature `yaml:"features"` @@ -30,10 +30,10 @@ type ControlCatalog struct { // Metadata is a struct that represents the metadata.yaml file type Metadata struct { - Title string `yaml:"title"` - ID string `yaml:"id"` - Description string `yaml:"description"` - ReleaseDetails []ReleaseDetails `yaml:"release_details"` + Title string `yaml:"title"` + ID string `yaml:"id"` + Description string `yaml:"description"` + ReleaseDetails []ReleaseDetails `yaml:"release_details"` } type ReleaseDetails struct { @@ -48,10 +48,10 @@ type ReleaseDetails struct { } type ReleaseManager struct { - Name string `yaml:"name"` - GithubId string `yaml:"github_id"` - Company string `yaml:"company"` - Summary string `yaml:"summary"` + Name string `yaml:"name"` + GithubId string `yaml:"github_id"` + Company string `yaml:"company"` + Summary string `yaml:"summary"` } type Feature struct { @@ -70,22 +70,22 @@ type Threat struct { type Control struct { IDFriendly string - ID string `yaml:"id"` - Title string `yaml:"title"` - Objective string `yaml:"objective"` - ControlFamily string `yaml:"control_family"` - Threats []string `yaml:"threats"` - NISTCSF string `yaml:"nist_csf"` - MITREATTACK string `yaml:"mitre_attack"` - ControlMappings map[string]interface{} `yaml:"control_mappings"` - TestRequirements []TestRequirement `yaml:"test_requirements"` + ID string `yaml:"id"` + Title string `yaml:"title"` + Objective string `yaml:"objective"` + ControlFamily string `yaml:"control_family"` + Threats []string `yaml:"threats"` + NISTCSF string `yaml:"nist_csf"` + MITREATTACK string `yaml:"mitre_attack"` + ControlMappings map[string]interface{} `yaml:"control_mappings"` + TestRequirements []TestRequirement `yaml:"test_requirements"` } type TestRequirement struct { - IDFriendly string - ID string `yaml:"id"` - Text string `yaml:"text"` - TLPLevels []string `yaml:"tlp_levels"` + IDFriendly string + ID string `yaml:"id"` + Text string `yaml:"text"` + TLPLevels []string `yaml:"tlp_levels"` } var TemplatesDir string @@ -123,12 +123,12 @@ func generateRaid() { } data, err := readData() if err != nil { - log.Error(err.Error()) + logger.Error(err.Error()) return } data.ServiceName = viper.GetString("service-name") if data.ServiceName == "" { - log.Error(fmt.Errorf("--service-name is required to generate a raid.")) + logger.Error("--service-name is required to generate a raid.") return } @@ -156,7 +156,7 @@ func generateRaid() { func setupTemplatingEnvironment() error { SourcePath = viper.GetString("source-path") if SourcePath == "" { - return fmt.Errorf("--source-path is required to generate a raid from a control set from local file or URL.") + return fmt.Errorf("--source-path is required to generate a raid from a control set from local file or URL") } if viper.GetString("local-templates") != "" { @@ -245,8 +245,8 @@ func readData() (data ControlCatalog, err error) { // Add the test ID to the tactics map for each TLP level for _, tlpLevel := range testReq.TLPLevels { if data.Tactics[tlpLevel] == nil { - data.Tactics[tlpLevel] = []string{} - } + data.Tactics[tlpLevel] = []string{} + } data.Tactics[tlpLevel] = append(data.Tactics[tlpLevel], strings.ReplaceAll(testReq.ID, ".", "_")) } } diff --git a/cmd/list.go b/cmd/list.go index 99c6b6c..11e2788 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -16,29 +16,19 @@ var ( // listCmd represents the list command var listCmd = &cobra.Command{ Use: cmdName, - Short: "Consult the Charts! List all raids that have been requested", + Short: "Consult the Charts! List all raids that have been installed.", Run: func(cmd *cobra.Command, args []string) { - raids := SortAvailableAndRequested() - if len(raids) < 1 { - if viper.GetBool("available") { - fmt.Fprintln(writer, "No raids present in the binaries path:", viper.GetString("binaries-path")) - } else { - fmt.Fprintln(writer, "No raids requested in the current configuration.") + if viper.GetBool("all") { + fmt.Fprintln(writer, "| Raid \t | Available \t| Requested \t|") + for _, raidPkg := range GetRaids() { + fmt.Fprintf(writer, "| %s \t | %t \t| %t \t|\n", raidPkg.Name, raidPkg.Available, raidPkg.Requested) } } else { - if viper.GetBool("available") { - // list only the available raids - fmt.Fprintln(writer, "| Raid \t | Available \t|") - for raidName, raidStatus := range raids { - if raidStatus.Available { - fmt.Fprintf(writer, "| %s \t | %t \t|\n", raidName, raidStatus.Available) - } - } - } else { - // print all raids requested and available - fmt.Fprintln(writer, "| Raid \t | Available \t| Requested \t|") - for raidName, raidStatus := range raids { - fmt.Fprintf(writer, "| %s \t | %t \t| %t \t|\n", raidName, raidStatus.Available, raidStatus.Requested) + // list only the available raids + fmt.Fprintln(writer, "| Raid \t | Requested \t|") + for _, raidPkg := range GetRaids() { + if raidPkg.Available { + fmt.Fprintf(writer, "| %s \t | %t \t|\n", raidPkg.Name, raidPkg.Requested) } } } @@ -49,53 +39,61 @@ var listCmd = &cobra.Command{ func init() { rootCmd.AddCommand(listCmd) - listCmd.PersistentFlags().BoolP("available", "a", false, "Review the fleet! List all raids that have been installed.") - viper.BindPFlag("available", listCmd.PersistentFlags().Lookup("available")) + listCmd.PersistentFlags().BoolP("all", "a", false, "Review the Fleet! List all raids that have been installed or requested in the current config.") + viper.BindPFlag("all", listCmd.PersistentFlags().Lookup("all")) } // GetRequestedRaids returns a list of raid names requested in the config -func GetRequestedRaids() (raids []string) { - if viper.Get("Raids") != nil { - raidsVars := viper.Get("Raids").(map[string]interface{}) - for raidName := range raidsVars { - raids = append(raids, raidName) +func getRequestedRaids() (requestedRaidPackages []*RaidPkg) { + services := viper.GetStringMap("services") + for serviceName := range services { + raidName := viper.GetString("services." + serviceName + ".raid") + if raidName != "" && !Contains(requestedRaidPackages, raidName) { + raidPkg := NewRaidPkg(raidName, serviceName) + raidPkg.Requested = true + requestedRaidPackages = append(requestedRaidPackages, raidPkg) } } - return + return requestedRaidPackages } // GetAvailableRaids returns a list of raids found in the binaries path -func GetAvailableRaids() (raids []string) { +func getAvailableRaids() (availableRaidPackages []*RaidPkg) { raidPaths, _ := hcplugin.Discover("*", viper.GetString("binaries-path")) for _, raidPath := range raidPaths { - raids = append(raids, path.Base(raidPath)) + raidPkg := NewRaidPkg(path.Base(raidPath), "") + raidPkg.Available = true + availableRaidPackages = append(availableRaidPackages, raidPkg) } - return + return availableRaidPackages } -type RaidStatus struct { - Available bool - Requested bool -} +var allRaids []*RaidPkg -func SortAvailableAndRequested() map[string]RaidStatus { - raids := make(map[string]RaidStatus) - requestedRaids := GetRequestedRaids() - availableRaids := GetAvailableRaids() - // loop through available raids, then requested raids to make sure available raids have requested status - for _, availableRaid := range availableRaids { - raids[availableRaid] = RaidStatus{Available: true, Requested: false} - for _, requestedRaid := range requestedRaids { - if requestedRaid == availableRaid { - raids[availableRaid] = RaidStatus{Available: true, Requested: true} - } +func GetRaids() []*RaidPkg { + if allRaids != nil { + return allRaids + } + output := make([]*RaidPkg, 0) + for _, raid := range getRequestedRaids() { + if Contains(getAvailableRaids(), raid.Name) { + raid.Available = true } + output = append(output, raid) } - // loop through requested raids to make sure all requested raids are represented - for _, requestedRaid := range requestedRaids { - if _, ok := raids[requestedRaid]; !ok { - raids[requestedRaid] = RaidStatus{Available: false, Requested: true} + for _, raid := range getAvailableRaids() { + if !Contains(output, raid.Name) { + output = append(output, raid) + } + } + return output +} + +func Contains(slice []*RaidPkg, search string) bool { + for _, raid := range slice { + if raid.Name == search { + return true } } - return raids + return false } diff --git a/cmd/objects.go b/cmd/objects.go new file mode 100644 index 0000000..99d5b05 --- /dev/null +++ b/cmd/objects.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "os/exec" + "path/filepath" + "runtime" + "strings" + + hcplugin "github.com/hashicorp/go-plugin" + "github.com/spf13/viper" +) + +// RaidError retains an error object and the name of the pack that generated it +type RaidError struct { + Raid string + Err error +} + +// RaidErrors holds a list of errors and an Error() method +// so it adheres to the standard Error interface +type RaidErrors struct { + Errors []RaidError +} + +func (e *RaidErrors) Error() string { + return fmt.Sprintf("Service Pack Errors: %v", e.Errors) +} + +type RaidPkg struct { + Name string + Path string + ServiceTarget string + Command *exec.Cmd + Result string + + Available bool + Requested bool + Error error +} + +func (p *RaidPkg) getBinary() (binaryName string, err error) { + p.Name = filepath.Base(strings.ToLower(p.Name)) // in some cases a filepath may arrive here instead of the base name; overwrite if so + if runtime.GOOS == "windows" && !strings.HasSuffix(p.Name, ".exe") { + p.Name = fmt.Sprintf("%s.exe", p.Name) + } + plugins, _ := hcplugin.Discover(p.Name, viper.GetString("binaries-path")) + if len(plugins) != 1 { + err = fmt.Errorf("failed to locate requested plugin '%s' at path '%s'", p.Name, viper.GetString("binaries-path")) + return + } + binaryName = plugins[0] + + return +} + +func (p *RaidPkg) queueCmd() { + cmd := exec.Command(p.Path) + flags := []string{ + fmt.Sprintf("--config=%s", viper.GetString("config")), + fmt.Sprintf("--loglevel=%s", viper.GetString("loglevel")), + fmt.Sprintf("--service=%s", p.ServiceTarget), + } + for _, flag := range flags { + cmd.Args = append(cmd.Args, flag) + p.Command = cmd + } +} + +func NewRaidPkg(raidName string, serviceName string) *RaidPkg { + raid := &RaidPkg{ + Name: raidName, + } + path, err := raid.getBinary() + if err != nil { + raid.Error = err + } + raid.Path = path + raid.ServiceTarget = serviceName + raid.queueCmd() + return raid +} diff --git a/cmd/root.go b/cmd/root.go index 5b81fd2..f09055f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/viper" "github.com/privateerproj/privateer-sdk/command" - "github.com/privateerproj/privateer-sdk/raidengine" + "github.com/privateerproj/privateer-sdk/config" ) var ( @@ -49,8 +49,8 @@ func init() { } func persistentPreRun(cmd *cobra.Command, args []string) { - command.InitializeConfig() - logger = raidengine.GetLogger("overview", false) + cfg := config.NewConfig(nil) + logger = cfg.Logger // writer is used for output in the list & version commands writer = tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) diff --git a/cmd/sally.go b/cmd/sally.go index 2d48a42..dc39e08 100644 --- a/cmd/sally.go +++ b/cmd/sally.go @@ -2,12 +2,10 @@ package cmd import ( "fmt" + "log" "os" "os/exec" "os/signal" - "path/filepath" - "runtime" - "strings" "syscall" hcplugin "github.com/hashicorp/go-plugin" @@ -20,16 +18,14 @@ import ( // runCmd represents the sally command var runCmd = &cobra.Command{ Use: "sally", - Short: "Run raids that have been specified within the command or configuration.", + Short: "Run raids that have been specified in the config.", Long: ` When everything is battoned down, it is time to sally forth.`, Run: func(cmd *cobra.Command, args []string) { logger.Trace("sally called") - if len(args) > 1 { + if len(args) > 0 { logger.Error(fmt.Sprintf( - "Sally only accepts a single argument; all other elements should be flags. Unknown args: %v", args[1:])) - } else if len(args) == 1 { - StartApprovedRaid(args[0]) + "Unknown args: %v", args)) } else { Run() } @@ -45,107 +41,52 @@ func Run() (err error) { // Setup for handling SIGTERM (Ctrl+C) setupCloseHandler() + logger.Trace(fmt.Sprintf( + "Using bin: %s", viper.GetString("binaries-path"))) - cmdSet, errString := getCommands() - if errString != "" { - logger.Error(fmt.Sprintf( - "Error loading plugins from config: %s", err)) + raids := GetRaids() + if len(raids) == 0 { + logger.Error("no requested raids were found in " + viper.GetString("binaries-path")) return } - logger.Trace(fmt.Sprintf("cmdSet: %s", cmdSet)) - // Run all plugins - err = AllPlugins(cmdSet) - if err != nil { - // TODO: Log the config values to a file, with sensitive values redacted - switch e := err.(type) { - case *RaidErrors: - logger.Error(fmt.Sprintf( - "%d out of %d raids failed.", len(e.Errors), len(cmdSet))) - return - default: - logger.Error(err.Error()) - } - } - - logger.Trace(fmt.Sprintf( - "No errors encountered during plugin execution. Output directory: %s", - viper.GetString("WriteDirectory"))) - return -} - -// AllPlugins executes specified plugins in a loop -func AllPlugins(cmdSet []*exec.Cmd) (err error) { - // Capture any plugin errors received during execution - raidErrors := make([]RaidError, 0) - - for _, cmd := range cmdSet { - raidErrors, err = Plugin(cmd, raidErrors) - if err != nil { - return - } - } - - if len(raidErrors) > 0 { - // Return all raid errors to main - err = &RaidErrors{ - Errors: raidErrors, + for serviceName := range viper.GetStringMap("services") { + serviceRaidName := viper.GetString(fmt.Sprintf("services.%s.raid", serviceName)) + for _, raidPkg := range raids { + if raidPkg.Name == serviceRaidName { + client := newClient(raidPkg.Command) + defer client.Kill() + + // Connect via RPC + var rpcClient hcplugin.ClientProtocol + rpcClient, err = client.Client() + if err != nil { + log.Println("4a:", err) + return err + } + // Request the plugin + var rawRaid interface{} + rawRaid, err = rpcClient.Dispense(plugin.RaidPluginName) + if err != nil { + logger.Error(err.Error()) + } + // Execute raid + raid := rawRaid.(plugin.Raid) + + // Execute + response := raid.Start() + if response != nil { + logger.Error(fmt.Sprintf("Error running raid for %s: %v", serviceName, response)) + } else { + logger.Info(fmt.Sprintf("Raid %s completed successfully", raidPkg.Name)) + } + } } } return } -// Plugin executes single plugin based on the provided command -func Plugin(cmd *exec.Cmd, raidErrors []RaidError) ([]RaidError, error) { - // Launch the plugin process - client := newClient(cmd) - defer client.Kill() - - // Connect via RPC - rpcClient, err := client.Client() - if err != nil { - return raidErrors, err - } - - // Request the plugin - rawRaid, err := rpcClient.Dispense(plugin.RaidPluginName) - if err != nil { - return raidErrors, err - } - - // Execute raid, expecting a silent response - raid := rawRaid.(plugin.Raid) - response := raid.Start() - if response != nil { - raidErr := RaidError{ - Raid: cmd.String(), // TODO: retrieve raid name from interface function - Err: response, - } - raidErrors = append(raidErrors, raidErr) - logger.Error(fmt.Sprintf("%v", raidErrors)) - } else { - logger.Info(fmt.Sprintf("Victory! Raid %s completed with successful results.", plugin.RaidPluginName)) - } - return raidErrors, nil -} - -// GetRaidBinary returns the path to the executable for the specified raid -func GetRaidBinary(name string) (binaryName string, err error) { - name = filepath.Base(strings.ToLower(name)) // in some cases a filepath may arrive here instead of the base name - if runtime.GOOS == "windows" && !strings.HasSuffix(name, ".exe") { - name = fmt.Sprintf("%s.exe", name) - } - plugins, _ := hcplugin.Discover(name, viper.GetString("binaries-path")) - if len(plugins) != 1 { - err = fmt.Errorf("failed to locate requested plugin '%s' at path '%s'", name, viper.GetString("binaries-path")) - return - } - binaryName = plugins[0] - - return -} - // setupCloseHandler creates a 'listener' on a new goroutine which will notify the // program if it receives an interrupt from the OS. We then handle this by calling // our clean up procedure and exiting the program. @@ -160,39 +101,6 @@ func setupCloseHandler() { }() } -func getCommands() (cmdSet []*exec.Cmd, errString string) { - // TODO: give any exec errors a familiar format - var err error - raids := GetRequestedRaids() - for _, raidName := range raids { - cmd, err := getCommand(raidName) - if err != nil { - break - } - cmdSet = append(cmdSet, cmd) - } - logger.Debug(fmt.Sprintf( - "Using bin: %s", viper.GetString("binaries-path"))) - if err == nil && len(cmdSet) == 0 { - // If there are no errors but also no commands run, it's probably unexpected - available := GetAvailableRaids() - errString = fmt.Sprintf("No valid raids specified. Requested: %v, Available: %v", raids, available) - } - return -} - -func getCommand(raid string) (cmd *exec.Cmd, err error) { - binaryName, binErr := GetRaidBinary(raid) - if binErr != nil { - err = binErr - return - } - cmd = exec.Command(binaryName) - flags := fmt.Sprintf("--config=%s", viper.GetString("config")) - cmd.Args = append(cmd.Args, flags) - return -} - // newClient client handles the lifecycle of a plugin application // Plugin hosts should use one Client for each plugin executable // (this is different from the client that manages gRPC) diff --git a/cmd/sally_test.go b/cmd/sally_test.go new file mode 100644 index 0000000..ecf8e02 --- /dev/null +++ b/cmd/sally_test.go @@ -0,0 +1,3 @@ +package cmd_test + +// diff --git a/example-config.yml b/example-config.yml index 02b1acd..c322342 100644 --- a/example-config.yml +++ b/example-config.yml @@ -1,6 +1,15 @@ -# Empty and omitted keys will use default values -LogLevel: info -WriteDirectory: -Verbose: -Raids: - Wireframe: \ No newline at end of file +logLevel: Debug +writeDirectory: test_output +services: + my-service-0: + raid: abs + my-service-1: + raid: dev + loglevel: Debug + tactics: + # - tlp_red + # - tlp_amber + - tlp_green + - tlp_clear + vars: + key: value \ No newline at end of file diff --git a/go.mod b/go.mod index 57ad0cc..78a2433 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ require ( github.com/go-git/go-git/v5 v5.12.0 github.com/hashicorp/go-hclog v1.2.0 github.com/hashicorp/go-plugin v1.4.10 - github.com/labstack/gommon v0.4.2 - github.com/privateerproj/privateer-sdk v0.0.7 + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/privateerproj/privateer-sdk v0.0.15 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.15.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 3166376..464f44e 100644 --- a/go.sum +++ b/go.sum @@ -677,8 +677,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -777,8 +775,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/privateerproj/privateer-sdk v0.0.7 h1:amvOH0fFDR/HsarKqBNMCBUNGhv7kUweXsOjsUi/Xhs= -github.com/privateerproj/privateer-sdk v0.0.7/go.mod h1:wLc/yv9UDFXR9kZ0ioXpCOdWhm4hTSK3VqMEziJqMo4= +github.com/privateerproj/privateer-sdk v0.0.15 h1:cjnDkZ48om7GGYSTz70R6aKrduog/NZoXpl1mN51TiA= +github.com/privateerproj/privateer-sdk v0.0.15/go.mod h1:wLc/yv9UDFXR9kZ0ioXpCOdWhm4hTSK3VqMEziJqMo4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -853,10 +851,6 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=