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 1 commit
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
166 changes: 156 additions & 10 deletions cmd/generate-raid.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,177 @@ package cmd

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

"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)

var genRaidName = "generate-raid"
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("service-name", "n", "", "The name of the service.")
genRaidCmd.PersistentFlags().StringP("output-dir", "o", "", "The name of the service.")

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// versionCmd.PersistentFlags().String("foo", "", "A help for foo")
viper.BindPFlag("source-path", genRaidCmd.PersistentFlags().Lookup("source-path"))
viper.BindPFlag("service-name", genRaidCmd.PersistentFlags().Lookup("service-name"))
viper.BindPFlag("output-dir", genRaidCmd.PersistentFlags().Lookup("output-dir"))

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

// 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"`
}

func generateRaid() {
// TODO: Pull this from a stable repo, or otherwise get it from the binary somehow
TemplatesDir = filepath.Join("cmd", "templates")

SourcePath = viper.GetString("source-path")
OutputDir = viper.GetString("output-dir")

Data = readData()
Data.ServiceName = viper.GetString("service-name")

err := os.MkdirAll(OutputDir, os.ModePerm)
if err != nil {
log.Fatalf("Failed to create output directory: %s", err)
}

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 {
log.Fatal(fmt.Sprintf("Failed while writing in dir '%s': %s", OutputDir, err))
}
}
return nil
})
if err != nil {
log.Fatalf("Error walking through templates directory: %s", err)
}
fmt.Println("Go project directory generated successfully.")
}

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 {
log.Fatalf("Failed to fetch URL: %v", err)
}
defer resp.Body.Close()

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

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

return Data
}

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

var Data ComponentDefinition
err = yaml.Unmarshal(yamlFile, &Data)
if err != nil {
log.Fatalf("Error unmarshalling YAML file: %v", err)
}

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
56 changes: 56 additions & 0 deletions cmd/templates/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
PACKNAME={{ .ServiceName }}
BUILD_FLAGS=-X 'main.GitCommitHash=`git rev-parse --short HEAD`' -X 'main.BuiltAt=`date +%FT%T%z`'
BUILD_WIN=@env GOOS=windows GOARCH=amd64 go build -o $(PACKNAME).exe
BUILD_LINUX=@env GOOS=linux GOARCH=amd64 go build -o $(PACKNAME)
BUILD_MAC=@env GOOS=darwin GOARCH=amd64 go build -o $(PACKNAME)-darwin

release: go-package go-release go-bin
release-candidate: go-package go-release-candidate
binary: go-package go-build

go-release: release-nix release-win release-mac

go-build:
@echo " > Building binary ..."
@go build -o $(PACKNAME) -ldflags="$(BUILD_FLAGS)"

go-package: go-tidy go-test
@echo " > Packaging static files..."

go-test:
@echo " > Validating code ..."
@go vet ./...
@go test ./...

go-tidy:
@echo " > Tidying go.mod ..."
@go mod tidy

go-test-cov:
@echo "Running tests and generating coverage output ..."
@go test ./... -coverprofile coverage.out -covermode count
@sleep 2 # Sleeping to allow for coverage.out file to get generated
@echo "Current test coverage : $(shell go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+') %"

go-release-candidate: go-tidy go-test
@echo " > Building release candidate for Linux..."
$(BUILD_LINUX) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=nix-rc'"
@echo " > Building release candidate for Windows..."
$(BUILD_WIN) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=win-rc'"
@echo " > Building release for Darwin..."
$(BUILD_MAC) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=darwin-rc'"

release-nix:
@echo " > Building release for Linux..."
$(BUILD_LINUX) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=linux'"

release-win:
@echo " > Building release for Windows..."
$(BUILD_WIN) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=windows'"

release-mac:
@echo " > Building release for Darwin..."
$(BUILD_MAC) -ldflags="$(BUILD_FLAGS) -X 'main.VersionPostfix=darwin'"

go-bin:
@mv $(PACKNAME)* ~/privateer/bin
Loading
Loading