Files
cloudfox/internal/output.go

212 lines
6.7 KiB
Go

package internal
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"github.com/aquasecurity/table"
"github.com/aws/smithy-go/ptr"
"github.com/spf13/afero"
"golang.org/x/crypto/ssh/terminal"
)
// This struct is here to maintain compatibility with legacy cloudfox code
type OutputData2 struct {
Headers []string
Body [][]string
FilePath string
FullFilename string
CallingModule string
Verbosity int
Directory string
}
// verbosity = 1 (Output and loot printed to file).
// verbosity = 2 (Output and loot printed to file, output printed screen).
// verbosity = 3 (Output and loot printed to file and screen).
// outputType = "table", "csv"
// prefixIdentifier = this string gets printed with control message calling module (e.g. aws profile, azure resource group, gcp project, etc)
func OutputSelector(verbosity int, outputType string, header []string, body [][]string, outputDirectory string, fileName string, callingModule string, wrapTable bool, prefixIdentifier string) {
switch verbosity {
case 2:
PrintTableToScreen(header, body, wrapTable)
case 3:
PrintTableToScreen(header, body, wrapTable)
// Add writeLootToScreen function here
}
switch outputType {
case "wide":
case "table":
fmt.Println("")
outputFileTable := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "table")),
ptr.String(fmt.Sprintf("%s.txt", fileName)),
outputType,
callingModule)
printTableToFile(header, body, wrapTable, outputFileTable)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileTable.Name())
// Add writeLootToFile function here
case "csv":
outputFileCSV := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "csv")),
ptr.String(fmt.Sprintf("%s.csv", fileName)),
outputType,
callingModule)
printCSVtoFile(header, body, outputFileCSV)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileCSV.Name())
// Add writeLootToFile function here
default:
outputFileTable := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "table")),
ptr.String(fmt.Sprintf("%s.txt", fileName)),
outputType,
callingModule)
printTableToFile(header, body, wrapTable, outputFileTable)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileTable.Name())
outputFileCSV := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "csv")),
ptr.String(fmt.Sprintf("%s.csv", fileName)),
outputType,
callingModule)
printCSVtoFile(header, body, outputFileCSV)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileCSV.Name())
// Add writeLootToFile function here
outputFileJSON := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "json")),
ptr.String(fmt.Sprintf("%s.json", fileName)),
outputType,
callingModule)
printJsonToFile(header, body, outputFileJSON)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileJSON.Name())
}
}
func printCSVtoFile(header []string, body [][]string, outputFile afero.File) {
csvWriter := csv.NewWriter(outputFile)
csvWriter.Write(header)
for _, row := range body {
row = removeColorCodesFromSlice(row)
csvWriter.Write(row)
}
csvWriter.Flush()
}
func printJsonToFile(header []string, body [][]string, outputFile afero.File) {
body = removeColorCodesFromNestedSlice(body)
jsonData := make([]map[string]string, len(body))
for i, row := range body {
jsonData[i] = make(map[string]string)
for j, column := range row {
jsonData[i][header[j]] = column
}
}
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
fmt.Println("error marshalling json:", err)
return
}
outputFile.Write(jsonBytes)
}
func printTableToFile(header []string, body [][]string, wrapLines bool, outputFile afero.File) {
body = removeColorCodesFromNestedSlice(body)
standardColumnWidth := 1000
t := table.New(outputFile)
if wrapLines {
terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
fmt.Println("error getting terminal size:", err)
return
}
columnCount := len(header)
// The offset value was defined by trial and error to get the best wrapping
trialAndErrorOffset := 1
standardColumnWidth = terminalWidth / (columnCount + trialAndErrorOffset)
}
t.SetColumnMaxWidth(standardColumnWidth)
t.SetHeaders(header...)
t.AddRows(body...)
t.SetRowLines(false)
t.SetDividers(table.UnicodeRoundedDividers)
t.SetAlignment(table.AlignLeft)
t.Render()
}
func PrintTableToScreen(header []string, body [][]string, wrapLines bool) {
standardColumnWidth := 1000
t := table.New(os.Stdout)
if wrapLines {
terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
fmt.Println("error getting terminal size:", err)
return
}
columnCount := len(header)
// The offset value was defined by trial and error to get the best wrapping
trialAndErrorOffset := 1
standardColumnWidth = terminalWidth / (columnCount + trialAndErrorOffset)
}
t.SetColumnMaxWidth(standardColumnWidth)
t.SetHeaders(header...)
t.AddRows(body...)
t.SetHeaderStyle(table.StyleBold)
t.SetRowLines(false)
t.SetLineStyle(table.StyleCyan)
t.SetDividers(table.UnicodeRoundedDividers)
t.SetAlignment(table.AlignLeft)
t.Render()
}
// The Afero library enables file system mocking:
// fileSystem = afero.NewOsFs() if not unit testing (real file system) OR
// fileSystem = afero.NewMemMapFs() for a mocked file system (when unit testing)
// outputDirectory = nil (creates the file in the current directory ".")
func createOutputFile(outputDirectory *string, fileName *string, outputType string, callingModule string) afero.File {
if outputDirectory == nil {
outputDirectory = ptr.String(".")
}
if _, err := fileSystem.Stat(ptr.ToString(outputDirectory)); os.IsNotExist(err) {
err = fileSystem.MkdirAll(ptr.ToString(outputDirectory), 0700)
if err != nil {
TxtLog.Fatal(err)
}
}
if fileName == nil {
TxtLog.Fatalf("Error creating output file because no file name was specified")
}
outputFile, err := fileSystem.OpenFile(path.Join(ptr.ToString(outputDirectory), ptr.ToString(fileName)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
TxtLog.Fatalf("error creating output file: %s", err)
}
//fmt.Printf("[%s] Creating output file: %s\n", cyan(callingModule), outputFile.Name())
return outputFile
}
func MockFileSystem(switcher bool) afero.Fs {
if switcher {
fmt.Println("Using mocked file system")
fileSystem = afero.NewMemMapFs()
return fileSystem
} else {
fmt.Println("Using OS file system. Make sure to clean up your disk!")
fileSystem = afero.NewOsFs()
return fileSystem
}
}