Skip to content

Commit

Permalink
chore: add St Bernard to help with module syncing in the workspace (#58)
Browse files Browse the repository at this point in the history
* feat: add proper variant of St Bernard

* chore: use modsync instead of build for init

* chore: add config files to air reloads

* chore: change foo to a more useful example command

* chore: cleanup

* chore: start up integration testing services as part of init

* chore: add volume clearing and clean build conditionally

* chore: add wiping mechanic to integration testing services

* fix: possible infinite loop if running modsync in a directory with no go.work anywhere above it

* fix: remove bh-testing service start from init
  • Loading branch information
superlinkx authored Sep 19, 2023
1 parent cfc127c commit a8bb5bd
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .air.debug.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = "dlv exec --accept-multiclient --log --headless --listen :2345 --api-version 2 ../tmp/main --"
include_dir = ["cmd/api/src", "packages/go"]
include_dir = ["cmd/api/src", "packages/go", "local-harnesses"]
include_ext = ["go", "json"]
include_file = []
kill_delay = "0s"
Expand Down
2 changes: 1 addition & 1 deletion .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = ["cmd/api/src", "packages/go"]
include_dir = ["cmd/api/src", "packages/go", "local-harnesses"]
include_ext = ["go", "json"]
include_file = []
kill_delay = "0s"
Expand Down
74 changes: 37 additions & 37 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"recommendations": [
"alefragnani.bookmarks",
"nickgo.cuelang",
"hediet.vscode-drawio",
"ms-toolsai.jupyter-keymap",
"pkief.material-icon-theme",
"ms-vscode-remote.vscode-remote-extensionpack",
"ms-vscode.remote-explorer",
"ms-python.black-formatter",
"jakeboone02.cypher-query-language",
"redhat.fabric8-analytics",
"ms-azuretools.vscode-docker",
"tombonnike.vscode-status-bar-format-toggle",
"felipecaputo.git-project-manager",
"eamodio.gitlens",
"golang.go",
"bierner.markdown-checkbox",
"ryu1kn.partial-diff",
"jebbs.plantuml",
"ms-ossdata.vscode-postgresql",
"ms-python.vscode-pylance",
"ms-python.python",
"buenon.scratchpads",
"richie5um2.vscode-sort-json",
"luisfontes19.vscode-swissknife",
"tabnine.tabnine-vscode",
"gruntfuggly.todo-tree",
"tomsaunders.vscode-workspace-explorer",
"tonybaloney.vscode-pets",
"gitlab.gitlab-workflow",
"yoavbls.pretty-ts-errors",
"esbenp.prettier-vscode",
"prisma.prisma",
"skellock.just",
"github.vscode-github-actions"
]
}
"recommendations": [
"alefragnani.bookmarks",
"nickgo.cuelang",
"hediet.vscode-drawio",
"ms-toolsai.jupyter-keymap",
"pkief.material-icon-theme",
"ms-vscode-remote.vscode-remote-extensionpack",
"ms-vscode.remote-explorer",
"ms-python.black-formatter",
"jakeboone02.cypher-query-language",
"redhat.fabric8-analytics",
"ms-azuretools.vscode-docker",
"tombonnike.vscode-status-bar-format-toggle",
"felipecaputo.git-project-manager",
"eamodio.gitlens",
"golang.go",
"bierner.markdown-checkbox",
"ryu1kn.partial-diff",
"jebbs.plantuml",
"ms-ossdata.vscode-postgresql",
"ms-python.vscode-pylance",
"ms-python.python",
"buenon.scratchpads",
"richie5um2.vscode-sort-json",
"luisfontes19.vscode-swissknife",
"tabnine.tabnine-vscode",
"gruntfuggly.todo-tree",
"tomsaunders.vscode-workspace-explorer",
"tonybaloney.vscode-pets",
"gitlab.gitlab-workflow",
"yoavbls.pretty-ts-errors",
"esbenp.prettier-vscode",
"prisma.prisma",
"kokakiwi.vscode-just",
"github.vscode-github-actions"
]
}
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ use (
./packages/go/params
./packages/go/schemagen
./packages/go/slices
./packages/go/stbernard
)
30 changes: 24 additions & 6 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set positional-arguments
# Initialize your dev environment (use "just init clean" to reset your config files)
init wipe="":
#!/usr/bin/env bash
echo "Init BloodHound CE"
echo "Make local copies of configuration files"
if [[ -d "./local-harnesses/build.config.json" ]]; then
rm -rf "./local-harnesses/build.config.json"
Expand All @@ -37,13 +38,26 @@ init wipe="":
echo "Install additional Go tools"
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2

echo "Run a build to ensure go.work.sum is valid"
just build -vf
echo "Run modsync to ensure workspace is up to date"
just modsync

echo "Ensure containers have been rebuilt"
just bh-dev build
if [[ "{{wipe}}" != "clean" ]]; then
just bh-dev build
else
echo "Clear volumes and rebuild without cache"
just bh-clear-volumes
just bh-clean-docker-build
fi

echo "Init Complete"
echo "Start integration testing services"
if [[ "{{wipe}}" == "clean" ]]; then
echo "Clear volumes and restart testing services without cache"
just bh-testing-clear-volumes
just bh-testing build --no-cache
fi

echo "BloodHound CE Init Complete"

# Show available targets for this context.
show *FLAGS:
Expand All @@ -63,6 +77,10 @@ test *FLAGS:
set -euo pipefail
python3 packages/python/beagle/main.py test {{FLAGS}}
# sync modules in workspace
modsync:
@go run github.com/specterops/bloodhound/packages/go/stbernard modsync

# updates favicon.ico, logo192.png and logo512.png from logo.svg
update-favicon:
@just imagemagick convert -background none ./cmd/ui/public/logo-light.svg -define icon:auto-resize ./cmd/ui/public/favicon-light.ico
Expand Down Expand Up @@ -142,8 +160,8 @@ bh-testing-clear-volumes *ARGS='':
@docker compose --project-name bh-testing -f docker-compose.testing.yml down -v {{ARGS}}

# clear BH docker compose volumes (pass --remove-orphans if troubleshooting)
bh-clear-volumes *ARGS='':
@docker compose -f docker-compose.dev.yml down -v {{ARGS}}
bh-clear-volumes target='dev' *ARGS='':
@docker compose --profile {{target}} -f docker-compose.dev.yml down -v {{ARGS}}

# build BH target cleanly (default profile dev with --no-cache flag)
bh-clean-docker-build target='dev' *ARGS='':
Expand Down
106 changes: 106 additions & 0 deletions packages/go/stbernard/command/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package command

import (
"errors"
"flag"
"fmt"
"os"
"strings"

"github.com/specterops/bloodhound/packages/go/stbernard/command/envdump"
"github.com/specterops/bloodhound/packages/go/stbernard/command/modsync"
)

// Commander is an interface for commands, allowing commands to implement the minimum
// set of requirements to observe and run the command from above. It is used as a return
// type to allow passing a usable command to the caller after parsing and creating
// the command implementation
type Commander interface {
Name() string
Usage() string
Run() error
}

var NoCmdErr = errors.New("no command specified")
var InvalidCmdErr = errors.New("invalid command specified")
var FailedCreateCmdErr = errors.New("failed to create command")

// ParseCLI parses for a subcommand as the first argument to the calling binary,
// and initializes the command (if it exists). It also provides the default usage
// statement.
//
// It does not support flags of its own, each subcommand is responsible for parsing
// their flags.
func ParseCLI() (Commander, error) {
// Generate a nice usage message
flag.Usage = usage

// Default usage if no arguments provided
if len(os.Args) < 2 {
flag.Usage()
return nil, NoCmdErr
}

switch os.Args[1] {
case ModSync.String():
config := modsync.Config{Environment: environment()}
if cmd, err := modsync.Create(config); err != nil {
return nil, fmt.Errorf("%w: %w", FailedCreateCmdErr, err)
} else {
return cmd, nil
}

case EnvDump.String():
config := envdump.Config{Environment: environment()}
if cmd, err := envdump.Create(config); err != nil {
return nil, fmt.Errorf("%w: %w", FailedCreateCmdErr, err)
} else {
return cmd, nil
}

default:
flag.Parse()
flag.Usage()
return nil, InvalidCmdErr
}
}

// usage creates a pretty usage message for our main command
func usage() {
var longestCmdLen int

w := flag.CommandLine.Output()
fmt.Fprint(w, "A BloodHound Swiss Army Knife\n\nUsage: stbernard COMMAND\n\nCommands:\n")

for _, cmd := range Commands() {
if len(cmd.String()) > longestCmdLen {
longestCmdLen = len(cmd.String())
}
}

for cmd, usage := range CommandsUsage() {
cmdStr := Command(cmd).String()
padding := strings.Repeat(" ", longestCmdLen-len(cmdStr))
fmt.Fprintf(w, " %s%s %s\n", cmdStr, padding, usage)
}
}

// environment is used to add default env vars as needed to the existing environment variables
func environment() []string {
var envMap = make(map[string]string)

for _, env := range os.Environ() {
envTuple := strings.SplitN(env, "=", 2)
envMap[envTuple[0]] = envTuple[1]
}

// Make any changes here
envMap["FOO"] = "foo" // For illustrative purposes only

var envSlice = make([]string, 0, len(envMap))
for key, val := range envMap {
envSlice = append(envSlice, strings.Join([]string{key, val}, "="))
}

return envSlice
}
58 changes: 58 additions & 0 deletions packages/go/stbernard/command/envdump/envdump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package envdump

import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
)

const (
Name = "envdump"
Usage = "Dump your environment variables"
)

type Config struct {
Environment []string
}

type command struct {
config Config
}

func (s command) Name() string {
return Name
}

func (s command) Usage() string {
return Usage
}

func (s command) Run() error {
log.Printf("Environment:\n\n")
for _, env := range s.config.Environment {
envTuple := strings.SplitN(env, "=", 2)
log.Printf("%s: %s\n", envTuple[0], envTuple[1])
}
log.Printf("\n")

return nil
}

func Create(config Config) (command, error) {
envdumpCmd := flag.NewFlagSet(Name, flag.ExitOnError)

envdumpCmd.Usage = func() {
w := flag.CommandLine.Output()
fmt.Fprintf(w, "%s\n\nUsage: %s %s [OPTIONS]\n", Usage, filepath.Base(os.Args[0]), Name)
}

if err := envdumpCmd.Parse(os.Args[2:]); err != nil {
envdumpCmd.Usage()
return command{}, fmt.Errorf("failed to parse %s command: %w", Name, err)
} else {
return command{config: config}, nil
}
}
68 changes: 68 additions & 0 deletions packages/go/stbernard/command/modsync/modsync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package modsync

import (
"flag"
"fmt"
"os"
"path/filepath"

"github.com/specterops/bloodhound/packages/go/stbernard/workspace"
)

const (
Name = "modsync"
Usage = "Sync all modules in current workspace"
)

type flags struct {
verbose bool
}

type Config struct {
flags flags
Environment []string
}

type command struct {
config Config
}

func (s command) Usage() string {
return Usage
}

func (s command) Name() string {
return Name
}

func (s command) Run() error {
if cwd, err := workspace.FindRoot(); err != nil {
return fmt.Errorf("could not find workspace root: %w", err)
} else if modPaths, err := workspace.ParseModulesAbsPaths(cwd); err != nil {
return fmt.Errorf("could not parse module absolute paths: %w", err)
} else if err := workspace.DownloadModules(modPaths, s.config.Environment); err != nil {
return fmt.Errorf("could not download modules: %w", err)
} else if err := workspace.SyncWorkspace(cwd, s.config.Environment); err != nil {
return fmt.Errorf("could not sync workspace: %w", err)
} else {
return nil
}
}

func Create(config Config) (command, error) {
modsyncCmd := flag.NewFlagSet(Name, flag.ExitOnError)
modsyncCmd.BoolVar(&config.flags.verbose, "v", false, "Print verbose logs")

modsyncCmd.Usage = func() {
w := flag.CommandLine.Output()
fmt.Fprintf(w, "%s\n\nUsage: %s %s [OPTIONS]\n\nOptions:\n", Usage, filepath.Base(os.Args[0]), Name)
modsyncCmd.PrintDefaults()
}

if err := modsyncCmd.Parse(os.Args[2:]); err != nil {
modsyncCmd.Usage()
return command{}, fmt.Errorf("failed to parse modsync command: %w", err)
} else {
return command{config: config}, nil
}
}
Loading

0 comments on commit a8bb5bd

Please sign in to comment.