Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added raid generator #20

Merged
merged 2 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Ad-hoc TODO tracking should be removed before v0.2.

This roadmap should be moved into a GitHub Project before v0.3.

## v0.1 - August 2023
## v0.1

### Feature Additions

- [x] Write plugin logs to independent files
- [x] Write end-to-end summary to independent file
- [ ] Create Quickstart guide:
- [x] Create Quickstart guide:
- can be in readme or elsewhere as appropriate
- Just fix the readme in general!
- [ ] Install trusted packages if they are not found:
Expand All @@ -21,7 +21,7 @@ This roadmap should be moved into a GitHub Project before v0.3.
### Feature Improvements

- [x] remove redundancy of plugin name statements
- [ ] Create a sample close handler on the raid wireframe
- [x] Create a sample close handler on the raid wireframe

### Bugfixes

Expand All @@ -30,13 +30,12 @@ This roadmap should be moved into a GitHub Project before v0.3.
- [x] Corrected -v usage and removed any instance of `log` in favor of `logger`. Basic `log` should not be used due to persistent unexpected behavior.
- [x] default loglevel is now error

## v0.2 - September 2023
## v0.2

### Feature Additions

- Improve version handling
- a la ArgoCD

- Secret handling
- plugins should not be able to read configs from other plugins!

Expand All @@ -56,7 +55,7 @@ This roadmap should be moved into a GitHub Project before v0.3.
- [ ] Possible Inconsistent RPC error(s)
- [ERROR] wireframe: plugin: plugin server: accept unix /var/folders/mv/x9vm780x6l755g028989fy500000gn/T/plugin3010505469: use of closed network connection:

## v0.3 - October 2023
## v0.3

- Remote keystore support (etcd, consul, etc)
- Create website: privateerproj.com
Expand Down
202 changes: 192 additions & 10 deletions cmd/generate-raid.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,213 @@ package cmd

import (
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/go-git/go-git/v5"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)

var genRaidName = "generate-raid"
// ComponentDefinition represents the structure of the input YAML file.
type ComponentDefinition struct {
ServiceName string
CategoryIDFriendly string
CategoryID string `yaml:"category-id"`
Title string `yaml:"title"`
Controls []Control `yaml:"controls"`
}

// Control represents the structure of each control within the YAML file.
type Control struct {
ServiceName string
IDFriendly string
ID string `yaml:"id"`
FeatureID string `yaml:"feature-id"`
Title string `yaml:"title"`
Objective string `yaml:"objective"`
NISTCSF string `yaml:"nist-csf"`
MITREAttack string `yaml:"mitre-attack"`
ControlMappings map[string][]string `yaml:"control-mappings"`
TestRequirements map[string]string `yaml:"test-requirements"`
}

var Data ComponentDefinition
var TemplatesDir string
var SourcePath string
var OutputDir string

// versionCmd represents the version command
var genRaidCmd = &cobra.Command{
Use: genRaidName,
Use: "generate-raid",
Short: "Generate a new raid",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s called", genRaidName)
generateRaid()
},
}

func init() {
rootCmd.AddCommand(genRaidCmd)

// Here you will define your flags and configuration settings.
genRaidCmd.PersistentFlags().StringP("source-path", "p", "", "The source file to generate the raid from.")
genRaidCmd.PersistentFlags().StringP("local-templates", "", "", "Path to a directory to use instead of downloading the latest templates.")
genRaidCmd.PersistentFlags().StringP("service-name", "n", "", "The name of the service (e.g. 'ECS, AKS, GCS').")
genRaidCmd.PersistentFlags().StringP("output-dir", "o", "generated-raid/", "Pathname for the generated raid.")

viper.BindPFlag("source-path", genRaidCmd.PersistentFlags().Lookup("source-path"))
viper.BindPFlag("local-templates", genRaidCmd.PersistentFlags().Lookup("local-templates"))
viper.BindPFlag("service-name", genRaidCmd.PersistentFlags().Lookup("service-name"))
viper.BindPFlag("output-dir", genRaidCmd.PersistentFlags().Lookup("output-dir"))
}

func generateRaid() {
err := setupTemplatingEnvironment()
if err != nil {
logger.Error(err.Error())
return
}

err = filepath.Walk(TemplatesDir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
err = generateFileFromTemplate(path, OutputDir)
if err != nil {
logger.Error(fmt.Sprintf("Failed while writing in dir '%s': %s", OutputDir, err))
}
} else if info.Name() == ".git" {
return filepath.SkipDir
}
return nil
},
)
if err != nil {
logger.Error("Error walking through templates directory: %s", err)
}
}

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.")
}

if viper.GetString("local-templates") != "" {
TemplatesDir = viper.GetString("local-templates")
} else {
TemplatesDir = filepath.Join(os.TempDir(), "privateer-templates")
setupTemplatesDir()
}

OutputDir = viper.GetString("output-dir")
logger.Trace("Generated raid will be stored in this directory: %s", OutputDir)

if viper.GetString("service-name") == "" {
return fmt.Errorf("--service-name is required to generate a raid.")
}
Data = readData()
Data.ServiceName = viper.GetString("service-name")

return os.MkdirAll(OutputDir, os.ModePerm)
}

func setupTemplatesDir() error {
// Pull latest templates from git
err := os.RemoveAll(TemplatesDir)
if err != nil {
logger.Error("Failed to remove templates directory: %s", err)
}

logger.Trace("Cloning templates repo to: ", TemplatesDir)
_, err = git.PlainClone(TemplatesDir, false, &git.CloneOptions{
URL: "https://github.com/privateerproj/raid-generator-templates.git",
Progress: os.Stdout,
})
return err
}

func generateFileFromTemplate(templatePath, OutputDir string) error {
tmpl, err := template.ParseFiles(templatePath)
if err != nil {
return fmt.Errorf("error parsing template file %s: %w", templatePath, err)
}

relativePath, err := filepath.Rel(TemplatesDir, templatePath)
if err != nil {
return err
}

outputPath := filepath.Join(OutputDir, strings.TrimSuffix(relativePath, ".txt"))
err = os.MkdirAll(filepath.Dir(outputPath), os.ModePerm)
if err != nil {
return fmt.Errorf("error creating directories for %s: %w", outputPath, err)
}
outputFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("error creating output file %s: %w", outputPath, err)
}
defer outputFile.Close()

err = tmpl.Execute(outputFile, Data)
if err != nil {
return fmt.Errorf("error executing template for file %s: %w", outputPath, err)
}

return nil
}

func readData() ComponentDefinition {
var Data ComponentDefinition
if strings.HasPrefix(SourcePath, "http") {
Data = readYAMLURL()
} else {
Data = readYAMLFile()
}
Data.CategoryIDFriendly = strings.ReplaceAll(Data.CategoryID, ".", "_")
for i := range Data.Controls {
Data.Controls[i].IDFriendly = strings.ReplaceAll(Data.Controls[i].ID, ".", "_")
}
return Data
}

func readYAMLURL() ComponentDefinition {
resp, err := http.Get(SourcePath)
if err != nil {
logger.Error("Failed to fetch URL: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
logger.Error("Failed to fetch URL: %v", resp.Status)
}

var Data ComponentDefinition
decoder := yaml.NewDecoder(resp.Body)
err = decoder.Decode(&Data)
if err != nil {
logger.Error("Failed to decode YAML from URL: %v", err)
}

return Data
}

func readYAMLFile() ComponentDefinition {
yamlFile, err := os.ReadFile(SourcePath)
if err != nil {
logger.Error("Error reading local source file: %s (%v)", SourcePath, err)
}

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// versionCmd.PersistentFlags().String("foo", "", "A help for foo")
var Data ComponentDefinition
err = yaml.Unmarshal(yamlFile, &Data)
if err != nil {
logger.Error("Error unmarshalling YAML file: %v", err)
}

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
return Data
}
32 changes: 0 additions & 32 deletions cmd/generate-strike.go

This file was deleted.

17 changes: 9 additions & 8 deletions cmd/sally.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/spf13/viper"

"github.com/privateerproj/privateer-sdk/plugin"
"github.com/privateerproj/privateer-sdk/utils"
)

// runCmd represents the sally command
Expand Down Expand Up @@ -47,12 +46,13 @@ func Run() (err error) {
// Setup for handling SIGTERM (Ctrl+C)
setupCloseHandler()

cmdSet, err := getCommands()
if err != nil {
cmdSet, errString := getCommands()
if errString != "" {
logger.Error(fmt.Sprintf(
"Error loading plugins from config: %s", err))
return
}

logger.Trace(fmt.Sprintf("cmdSet: %s", cmdSet))

// Run all plugins
Expand All @@ -68,7 +68,8 @@ func Run() (err error) {
logger.Error(err.Error())
}
}
logger.Info(fmt.Sprintf(

logger.Trace(fmt.Sprintf(
"No errors encountered during plugin execution. Output directory: %s",
viper.GetString("WriteDirectory")))
return
Expand Down Expand Up @@ -124,7 +125,7 @@ func Plugin(cmd *exec.Cmd, raidErrors []RaidError) ([]RaidError, error) {
raidErrors = append(raidErrors, raidErr)
logger.Error(fmt.Sprintf("%v", raidErrors))
} else {
logger.Info("Victory! Raids all completed with successful results.")
logger.Info(fmt.Sprintf("Victory! Raid %s completed with successful results.", plugin.RaidPluginName))
}
return raidErrors, nil
}
Expand Down Expand Up @@ -159,8 +160,9 @@ func setupCloseHandler() {
}()
}

func getCommands() (cmdSet []*exec.Cmd, err error) {
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)
Expand All @@ -174,8 +176,7 @@ func getCommands() (cmdSet []*exec.Cmd, err error) {
if err == nil && len(cmdSet) == 0 {
// If there are no errors but also no commands run, it's probably unexpected
available := GetAvailableRaids()
err = utils.ReformatError(
"No valid raids specified. Requested: %v, Available: %v", raids, available)
errString = fmt.Sprintf("No valid raids specified. Requested: %v, Available: %v", raids, available)
}
return
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module github.com/privateerproj/privateer
go 1.14

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/privateerproj/privateer-sdk v0.0.7
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
golang.org/x/net v0.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0
)

// For SDK Development Only
Expand Down
Loading
Loading