Skip to content

Commit e5100f6

Browse files
authored
refactor: use cobra for cmd flags (#18)
* refactor: use cobra for cmd flags * fix: lint * fix: README
1 parent 6aeb3ea commit e5100f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+467
-411
lines changed

.github/workflows/golangci.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
3636
# v5.0.0
3737
with:
38-
go-version-file: "go.mod"
38+
go-version: "oldstable"
3939
- name: golangci-lint
4040
uses: golangci/golangci-lint-action@82d40c283aeb1f2b6595839195e95c2d6a49081b
4141
# v5.0.0
@@ -77,6 +77,8 @@ jobs:
7777
# v5.0.0
7878
with:
7979
go-version-file: "go.mod"
80+
cache-dependency-path: |
81+
go.sum
8082
- name: Install dependencies
8183
run: go get .
8284
- name: Build

.vscode/extensions.json

-3
This file was deleted.

.vscode/launch.json

-15
This file was deleted.

Makefile

+33-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ include .env
33
BUILD_FLAGS=-ldflags="-s -w"
44
OUT_DIR=dist
55
OUT_PREFIX=nuvola
6+
NEO4J_PASS ?= neo4j
67

78
define ANNOUNCE_BODY
89
CALL apoc.export.cypher.all("/var/lib/neo4j/import/all.cypher", {
@@ -13,12 +14,23 @@ YIELD file, batches, source, format, nodes, relationships, properties, time, row
1314
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
1415
endef
1516

17+
.PHONY: backup
18+
export ANNOUNCE_BODY
19+
backup:
20+
@echo "$$ANNOUNCE_BODY" | docker-compose exec -T neo4j cypher-shell -u neo4j -p ${PASSWORD} -d nuvoladb --non-interactive
21+
docker-compose exec -T neo4j cp /var/lib/neo4j/import/all.cypher /backup
1622

17-
start:
18-
@if [ ! -f ./.env ]; then\
19-
cp .env_example .env;\
23+
.PHONY: check-dependencies
24+
check-dependencies:
25+
@command -v docker compose >/dev/null 2>&1 || { echo >&2 "docker compose not installed"; exit 1; }
26+
@command -v go >/dev/null 2>&1 || { echo >&2 "Go not installed"; exit 1; }
27+
28+
.PHONY: check-neo4j-password
29+
check-neo4j-password:
30+
@if [ -z "${NEO4J_PASS}" ]; then \
31+
echo "NEO4J_PASS is not set. Set it in the .env file"; \
32+
exit 1; \
2033
fi
21-
docker-compose up -d
2234

2335
build:
2436
go build ${BUILD_FLAGS} -o ${OUT_PREFIX}
@@ -30,23 +42,28 @@ compile: build
3042
GOOS=darwin GOARCH=amd64 go build ${BUILD_FLAGS} -o ${OUT_DIR}/${OUT_PREFIX}-darwin-amd64
3143
GOOS=darwin GOARCH=arm64 go build ${BUILD_FLAGS} -o ${OUT_DIR}/${OUT_PREFIX}-darwin-arm64
3244

33-
clean:
45+
.PHONY: clean
46+
clean: stop-containers
3447
@rm -rf ./${OUT_DIR}
3548
@rm -f ./${OUT_PREFIX}
36-
@docker-compose stop
37-
@docker-compose down -v
38-
@docker-compose rm -fv
39-
40-
.PHONY: backup
41-
export ANNOUNCE_BODY
42-
backup:
43-
@echo "$$ANNOUNCE_BODY" | docker-compose exec -T neo4j cypher-shell -u neo4j -p ${PASSWORD} -d nuvoladb --non-interactive
44-
docker-compose exec -T neo4j cp /var/lib/neo4j/import/all.cypher /backup
4549

4650
.PHONY: restore
4751
restore:
4852
@cat ./backup/all.cypher | docker-compose exec -T neo4j cypher-shell -u neo4j -p ${PASSWORD} -d nuvoladb --non-interactive
4953

54+
.PHONY: start-containers
55+
start-containers: check-dependencies
56+
@if [ ! -f ./.env ]; then\
57+
cp .env_example .env;\
58+
fi
59+
@docker compose up -d
60+
61+
.PHONY: stop-containers
62+
stop-containers:
63+
@docker compose stop
64+
@docker compose down -v
65+
@docker compose rm -fv
66+
5067
.PHONY: tests
51-
tests: start build
52-
$(MAKE) -C tests all
68+
tests: start-containers build
69+
cd ./assets/ && $(MAKE) -C tests all

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ make build
6161
./nuvola assess -import ~/DumpDumpFolder/nuvola-default_RO_20220901.zip
6262
```
6363

64-
3. To only perform static assessments on the data loaded into the Neo4j database using the [predefined ruleset](https://github.com/primait/nuvola/tree/master/assess/rules):
64+
3. To only perform static assessments on the data loaded into the Neo4j database using the [predefined ruleset](https://github.com/primait/nuvola/tree/master/assets/rules):
6565

6666
```bash
6767
./nuvola assess
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

tests/Pipfile assets/tests/Pipfile

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

tests/main.tf assets/tests/main.tf

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

controller/assess/assess.go cmd/assess.go

+42-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package assess
1+
package cmd
22

33
import (
44
"archive/zip"
@@ -8,16 +8,38 @@ import (
88
"log"
99
"strings"
1010

11-
connector "github.com/primait/nuvola/connector"
12-
13-
clioutput "github.com/primait/nuvola/tools/cli/output"
14-
nuvolaerror "github.com/primait/nuvola/tools/error"
11+
"github.com/primait/nuvola/connector"
12+
"github.com/primait/nuvola/pkg/io/logging"
1513
"github.com/primait/nuvola/tools/filesystem/files"
1614
unzip "github.com/primait/nuvola/tools/filesystem/zip"
1715
"github.com/primait/nuvola/tools/yamler"
16+
"github.com/spf13/cobra"
1817
)
1918

20-
func ImportZipFile(connector *connector.StorageConnector, zipfile string) {
19+
var assessCmd = &cobra.Command{
20+
Use: "assess",
21+
Short: "Execute assessment queries against data loaded in Neo4J",
22+
Run: func(cmd *cobra.Command, args []string) {
23+
if cmd.Flags().Changed(flagVerbose) {
24+
logger.SetVerboseLevel()
25+
}
26+
if cmd.Flags().Changed(flagDebug) {
27+
logger.SetDebugLevel()
28+
}
29+
30+
connector.SetActions()
31+
storageConnector := connector.NewStorageConnector()
32+
if importFile != "" && !noImport {
33+
logger.Info("Flushing database")
34+
logger.Info(fmt.Sprintf("Importing %s", importFile))
35+
importZipFile(storageConnector, importFile)
36+
}
37+
38+
assess(storageConnector, "./assets/rules/")
39+
},
40+
}
41+
42+
func importZipFile(connector *connector.StorageConnector, zipfile string) {
2143
connector.FlushAll()
2244
var ordering = []string{
2345
"Groups",
@@ -47,7 +69,7 @@ func ImportZipFile(connector *connector.StorageConnector, zipfile string) {
4769
for _, f := range orderedFiles {
4870
rc, err := f.Open()
4971
if err != nil {
50-
nuvolaerror.HandleError(err, "Assess", "Opening content of ZIP")
72+
logging.HandleError(err, "Assess", "Opening content of ZIP")
5173
}
5274
defer func() {
5375
if err := rc.Close(); err != nil {
@@ -58,27 +80,27 @@ func ImportZipFile(connector *connector.StorageConnector, zipfile string) {
5880
buf := new(bytes.Buffer)
5981
_, err = io.Copy(buf, rc) // #nosecG110
6082
if err != nil {
61-
nuvolaerror.HandleError(err, "Assess", "Copying buffer from ZIP")
83+
logging.HandleError(err, "Assess", "Copying buffer from ZIP")
6284
}
6385
connector.ImportResults(f.Name, buf.Bytes())
6486
}
6587
}
6688

67-
func Assess(connector *connector.StorageConnector, rulesPath string) {
89+
func assess(connector *connector.StorageConnector, rulesPath string) {
6890
// perform checks based on pre-defined static rules
6991
for _, rule := range files.GetFiles(rulesPath, ".ya?ml") {
7092
var c = yamler.GetConf(rule)
7193
if c.Enabled {
7294
query, args := yamler.PrepareQuery(c)
7395
results := connector.Query(query, args)
7496

75-
clioutput.PrintRed("Running rule: " + rule)
76-
clioutput.PrintGreen("Name: " + c.Name)
77-
clioutput.PrintGreen("Arguments:")
78-
clioutput.PrintDarkGreen(yamler.ArgsToQueryNeo4jBrowser(args))
79-
clioutput.PrintGreen("Query:")
80-
clioutput.PrintDarkGreen(query)
81-
clioutput.PrintGreen("Description: " + c.Description)
97+
logging.PrintRed("Running rule: " + rule)
98+
logging.PrintGreen("Name: " + c.Name)
99+
logging.PrintGreen("Arguments:")
100+
logging.PrintDarkGreen(yamler.ArgsToQueryNeo4jBrowser(args))
101+
logging.PrintGreen("Query:")
102+
logging.PrintDarkGreen(query)
103+
logging.PrintGreen("Description: " + c.Description)
82104

83105
for _, resultMap := range results {
84106
for key, value := range resultMap {
@@ -98,3 +120,7 @@ func Assess(connector *connector.StorageConnector, rulesPath string) {
98120
}
99121
}
100122
}
123+
124+
func init() {
125+
rootCmd.AddCommand(assessCmd)
126+
}

controller/dump/dump.go cmd/dump.go

+43-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dump
1+
package cmd
22

33
import (
44
"encoding/json"
@@ -7,11 +7,10 @@ import (
77
"time"
88

99
"github.com/primait/nuvola/connector"
10-
11-
clioutput "github.com/primait/nuvola/tools/cli/output"
12-
nuvolaerror "github.com/primait/nuvola/tools/error"
10+
"github.com/primait/nuvola/pkg/io/logging"
1311
"github.com/primait/nuvola/tools/filesystem/files"
1412
"github.com/primait/nuvola/tools/filesystem/zip"
13+
"github.com/spf13/cobra"
1514
)
1615

1716
var AWSResults = map[string]interface{}{
@@ -29,7 +28,39 @@ var AWSResults = map[string]interface{}{
2928
"RedshiftDBs": nil,
3029
}
3130

32-
func DumpData(storageConnector *connector.StorageConnector, cloudConnector *connector.CloudConnector) map[string]interface{} {
31+
var dumpCmd = &cobra.Command{
32+
Use: "dump",
33+
Short: "Dump AWS resources and policies information and store them in Neo4j",
34+
Run: func(cmd *cobra.Command, args []string) {
35+
startTime := time.Now()
36+
markAsRequired("aws-profile")
37+
if err := rootCmd.ValidateRequiredFlags(); err != nil {
38+
logger.Error("Required flags not provided", "err", err)
39+
}
40+
if cmd.Flags().Changed(flagVerbose) {
41+
logger.SetVerboseLevel()
42+
}
43+
if cmd.Flags().Changed(flagDebug) {
44+
logger.SetDebugLevel()
45+
}
46+
47+
cloudConnector, err := connector.NewCloudConnector(awsProfile, awsEndpointUrl)
48+
if err != nil {
49+
logger.Error(err.Error())
50+
}
51+
52+
if dumpOnly {
53+
dumpData(nil, cloudConnector)
54+
} else {
55+
storageConnector := connector.NewStorageConnector().FlushAll()
56+
dumpData(storageConnector, cloudConnector)
57+
}
58+
saveResults(awsProfile, outputDirectory, outputFormat)
59+
logger.Info("Execution Time", "seconds", time.Since(startTime))
60+
},
61+
}
62+
63+
func dumpData(storageConnector *connector.StorageConnector, cloudConnector *connector.CloudConnector) map[string]interface{} {
3364
dataChan := make(chan map[string]interface{})
3465
go cloudConnector.DumpAll("aws", dataChan)
3566
for {
@@ -41,15 +72,15 @@ func DumpData(storageConnector *connector.StorageConnector, cloudConnector *conn
4172
mapKey := v.MapKeys()[0].Interface().(string)
4273
obj, err := json.Marshal(a[mapKey])
4374
if err != nil {
44-
nuvolaerror.HandleError(err, "DumpData", "Marshalling output")
75+
logger.Error("DumpData: error marshalling output", err)
4576
}
4677
storageConnector.ImportResults(mapKey, obj)
4778
AWSResults[mapKey] = a[mapKey]
4879
}
4980
return AWSResults
5081
}
5182

52-
func SaveResults(awsProfile string, outputDir string, outputFormat string) {
83+
func saveResults(awsProfile string, outputDir string, outputFormat string) {
5384
if awsProfile == "" {
5485
awsProfile = "default"
5586
}
@@ -59,11 +90,14 @@ func SaveResults(awsProfile string, outputDir string, outputFormat string) {
5990

6091
today := time.Now().Format("20060102")
6192
for key, value := range AWSResults {
62-
clioutput.PrintGreen(key + ":")
63-
fmt.Printf("%s\n", clioutput.PrettyJSON(value))
93+
logger.Info(key, logging.PrettyJSON(value))
6494

6595
if outputFormat == "json" {
6696
files.PrettyJSONToFile(outputDir, fmt.Sprintf("%s_%s.json", key, today), value)
6797
}
6898
}
6999
}
100+
101+
func init() {
102+
rootCmd.AddCommand(dumpCmd)
103+
}

cmd/root.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cmd
2+
3+
import (
4+
"github.com/primait/nuvola/pkg/io/logging"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
const (
9+
flagVerbose = "verbose"
10+
flagDebug = "debug"
11+
flagAWSProfile = "aws-profile"
12+
flagAWSEndpointUrl = "aws-endpoint-url"
13+
flagOutputDirectory = "output-dir"
14+
flagOutputFormat = "output-format"
15+
flagDumpOnly = "dump-only"
16+
flagImportFile = "import"
17+
flagNoImport = "no-import"
18+
)
19+
20+
var (
21+
logger logging.LogManager
22+
awsProfile string
23+
awsEndpointUrl string
24+
outputDirectory string
25+
outputFormat string
26+
dumpOnly bool
27+
importFile string
28+
noImport bool
29+
rootCmd = &cobra.Command{
30+
Use: "nuvola",
31+
Short: "A tool to dump and perform automatic and manual security analysis on AWS",
32+
}
33+
)
34+
35+
func init() {
36+
logger = logging.GetLogManager()
37+
rootCmd.PersistentFlags().BoolP(flagVerbose, "v", false, "Verbose output")
38+
rootCmd.PersistentFlags().BoolP(flagDebug, "d", false, "Debug output")
39+
dumpCmd.PersistentFlags().StringVarP(&awsProfile, flagAWSProfile, "p", "default", "AWS Profile to use")
40+
dumpCmd.PersistentFlags().StringVarP(&outputDirectory, flagOutputDirectory, "o", "", "Output folder where the files will be saved (default: \".\")")
41+
dumpCmd.PersistentFlags().StringVarP(&outputFormat, flagOutputFormat, "f", "zip", "Output format: ZIP or json files")
42+
dumpCmd.PersistentFlags().BoolVarP(&dumpOnly, flagDumpOnly, "", false, "Flag to prevent loading data into Neo4j (default: \"false\")")
43+
44+
assessCmd.PersistentFlags().StringVarP(&importFile, flagImportFile, "i", "", "Input ZIP file to load")
45+
assessCmd.PersistentFlags().BoolVarP(&noImport, flagNoImport, "", false, "Use stored data from Neo4j without import (default)")
46+
}
47+
48+
func Execute() {
49+
if err := rootCmd.Execute(); err != nil {
50+
logger.Error("Error executing command", "err", err)
51+
}
52+
}
53+
54+
func markAsRequired(flag string) {
55+
if err := rootCmd.MarkFlagRequired(flag); err != nil {
56+
logger.Error("Required flags not provided", "err", err, "flag", flag)
57+
}
58+
}

0 commit comments

Comments
 (0)