diff --git a/.gitignore b/.gitignore index e695e018..d8cc9d05 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ Desktop.ini .vscode/ - # Temp playground/_output @@ -50,4 +49,5 @@ release/ docs/.vitepress/dist/ docs/.vitepress/cache/ -local/ \ No newline at end of file +local/ +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e5cacb..f98a4918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,32 @@ All notable changes to this project will be documented in this file. +

-## Unreleased - feature/loadremote +## EXPERIMENTAL FEATURE - feature/targetid + +**Commit**: [b84f0f9](https://github.com/xfhg/intercept/commit/b84f0f9) + +**Branch** [feature/targetid](https://github.com/xfhg/intercept/tree/feature/targetid) + +**Summary**: Fingerprint hosts for reporting --experimental + +### Breaking +- Properties on Final SARIF report key names corrected to kebab case. + +### Added +- Added Global hostData & hostFingerprint +- Added "host-data" & "host-fingerprint" to Final SARIF Report + +### Changed +- Properties on Final SARIF report key names corrected to kebab case. + +### Removed +- None + +



+ +## FEATURE - feature/loadremote **Commit**: [e95c0ed](https://github.com/xfhg/intercept/commit/e95c0ed) diff --git a/cmd/policy.go b/cmd/policy.go index 5dbe986b..e95c6d28 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -26,6 +26,12 @@ type Config struct { ReportSchedule string `yaml:"report_schedule"` } `yaml:"Flags"` Metadata struct { + HostOS string `yaml:"host_os,omitempty"` + HostMAC string `yaml:"host_mac,omitempty"` + HostARCH string `yaml:"host_arch,omitempty"` + HostNAME string `yaml:"host_name,omitempty"` + HostFingerprint string `yaml:"host_fingerprint,omitempty"` + HostInfo string `yaml:"host_info,omitempty"` MsgExitClean string `yaml:"MsgExitClean"` MsgExitWarning string `yaml:"MsgExitWarning"` MsgExitCritical string `yaml:"MsgExitCritical"` @@ -146,14 +152,15 @@ func LoadPolicyFile(filename string) (*PolicyFile, error) { // LoadRemotePolicy loads a policy file from a remote HTTPS endpoint func LoadRemotePolicy(url string, expectedChecksum string) (*PolicyFile, error) { // Create a temporary directory to store the downloaded file - tempDir, err := os.MkdirTemp(outputDir, "_remote") + remoteDir := filepath.Join(outputDir, "_remote") + err := os.MkdirAll(remoteDir, 0755) if err != nil { return nil, fmt.Errorf("failed to create temporary directory: %w", err) } - defer os.RemoveAll(tempDir) // Clean up the temporary directory when done + defer os.RemoveAll(remoteDir) // Clean up the temporary directory when done // Generate a temporary file name - tempFile := filepath.Join(tempDir, "remote_policy.yaml") + tempFile := filepath.Join(remoteDir, "remote_policy.yaml") // Create a resty client client := resty.New() diff --git a/cmd/root.go b/cmd/root.go index c0164ebf..6aa4141b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,6 +20,9 @@ var ( silentMode bool nologMode bool + hostData string + hostFingerprint string + buildVersion string buildSignature string @@ -94,8 +97,30 @@ func setupLogging() { log = zerolog.New(output).With().Timestamp().Logger() if experimentalMode { + + // ---------------------------------------------- + // ---------------------------------------------- EXPERIMENTAL log caller debug + // ---------------------------------------------- + log = zerolog.New(output).With().Timestamp().Logger().With().Caller().Logger() // log = zerolog.New(output).With().Timestamp().Logger().With().Str("id", intercept_run_id).Logger() + + // ---------------------------------------------- + // ---------------------------------------------- EXPERIMENTAL feature/targetid + // ---------------------------------------------- + + hostInfo, err := GetHostInfo() + if err != nil { + log.Error().Msgf("Error gathering host info: %v\n", err) + } + + hostData, hostFingerprint, err := FingerprintHost(hostInfo) + if err != nil { + log.Error().Msgf("Error generating fingerprint: %v\n", err) + } + log.Info().Msgf("Host Data: %s", hostData) + log.Info().Msgf("Host Fingerprint: %s", hostFingerprint) + } if silentMode { diff --git a/cmd/sarif.go b/cmd/sarif.go index ed1ad4e1..6fef639b 100644 --- a/cmd/sarif.go +++ b/cmd/sarif.go @@ -8,6 +8,8 @@ import ( "path/filepath" "strings" "time" + + "github.com/charlievieth/fastwalk" ) // SARIFLevel represents the severity level in SARIF format @@ -127,9 +129,14 @@ type ArtifactLocation struct { } type Region struct { - StartLine int `json:"startLine"` - StartColumn int `json:"startColumn"` - EndColumn int `json:"endColumn"` + StartLine int `json:"startLine"` + StartColumn int `json:"startColumn"` + EndColumn int `json:"endColumn"` + Snippet Snippet `json:"snippet"` +} + +type Snippet struct { + Text string `json:"text"` } type Invocation struct { @@ -204,7 +211,8 @@ func GenerateSARIFReport(inputFile string, policy Policy) (SARIFReport, error) { }, }, Properties: map[string]string{ - "result-type": "detail", "observe-run-id": policy.RunID, + "result-type": "detail", + "observe-run-id": policy.RunID, "result-timestamp": timestamp, "name": policy.Metadata.Name, "description": policy.Metadata.Description, @@ -234,6 +242,9 @@ func GenerateSARIFReport(inputFile string, policy Policy) (SARIFReport, error) { StartLine: rgOutput.Data.LineNumber, StartColumn: strings.Index(rgOutput.Data.Lines.Text, matchText) + 1, EndColumn: strings.Index(rgOutput.Data.Lines.Text, matchText) + len(matchText) + 1, + Snippet: Snippet{ + Text: matchText, + }, }, }, }, @@ -545,13 +556,15 @@ func MergeSARIFReports(commandLine string, perf Performance, isScheduled bool) ( ExecutionSuccessful: true, CommandLine: commandLine, Properties: map[string]string{ - "run_id": intercept_run_id, - "start_time": perf.StartTime.Format(time.RFC3339), - "end_time": perf.EndTime.Format(time.RFC3339), - "execution_time_ms": fmt.Sprintf("%d", perf.Delta.Milliseconds()), + "run-id": intercept_run_id, + "start-time": perf.StartTime.Format(time.RFC3339), + "end-time": perf.EndTime.Format(time.RFC3339), + "execution-time-ms": fmt.Sprintf("%d", perf.Delta.Milliseconds()), "environment": environment, "debug": fmt.Sprintf("%v", debugOutput), "report-timestamp": timestamp, + "host-data": hostData, + "host-fingerprint": hostFingerprint, }, }, }, @@ -634,7 +647,7 @@ func cleanupSARIFFolder() error { sarifDir = filepath.Join(outputDir, sarifDir) } - err := filepath.WalkDir(sarifDir, func(path string, d fs.DirEntry, err error) error { + err := fastwalk.Walk(nil, sarifDir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err // Error accessing the path } diff --git a/cmd/target.go b/cmd/target.go index d3580bc7..e8e2d860 100644 --- a/cmd/target.go +++ b/cmd/target.go @@ -3,10 +3,13 @@ package cmd import ( "encoding/json" "fmt" + "io/fs" "os" "path/filepath" "regexp" "strings" + + "github.com/charlievieth/fastwalk" ) type FileInfo struct { @@ -21,12 +24,12 @@ func CalculateFileHashes(targetDir string) ([]FileInfo, error) { ignorePaths := policyData.Config.Flags.Ignore - err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { + err := fastwalk.Walk(nil, targetDir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - if !info.IsDir() && !isIgnored(ignorePaths, path) { + if !d.IsDir() && !isIgnored(ignorePaths, path) { hash, err := calculateSHA256(path) if err != nil { diff --git a/cmd/watch.go b/cmd/watch.go index d1390222..7bf24c36 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -1,13 +1,27 @@ package cmd import ( + "crypto/sha256" + "encoding/hex" "fmt" + "net" + "os" + "runtime" + "strings" "time" "github.com/fsnotify/fsnotify" "github.com/segmentio/ksuid" ) +type HostInfo struct { + Hostname string + OS string + Architecture string + IPs []string + MAC string +} + func watchPaths(paths ...string) { if len(paths) < 1 { log.Fatal().Msg("must specify at least one path to watch") @@ -95,3 +109,72 @@ func processEvent(e fsnotify.Event) { log.Error().Msgf("Policy not found in cache, watcher event [%s] didn't trigger policy process for: %s", e.Op.String(), e.Name) } } + +func GetHostInfo() (*HostInfo, error) { + hostInfo := &HostInfo{} + + // Get hostname + hostname, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("failed to get hostname: %v", err) + } + hostInfo.Hostname = hostname + + // Get OS and architecture + hostInfo.OS = runtime.GOOS + hostInfo.Architecture = runtime.GOARCH + + // Get IPs and MAC addresses + interfaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("failed to get network interfaces: %v", err) + } + + for _, iface := range interfaces { + if iface.Flags&net.FlagUp == 0 { + continue // ignore interfaces that are down + } + + addrs, err := iface.Addrs() + if err != nil { + return nil, fmt.Errorf("failed to get addresses for interface %v: %v", iface.Name, err) + } + + for _, addr := range addrs { + ip, _, err := net.ParseCIDR(addr.String()) + if err != nil { + return nil, fmt.Errorf("failed to parse IP address %v: %v", addr.String(), err) + } + + if ip.IsLoopback() { + continue // ignore loopback addresses + } + + hostInfo.IPs = append(hostInfo.IPs, ip.String()) + } + // main MAC + if iface.Flags&net.FlagUp != 0 && iface.HardwareAddr.String() != "" { + hostInfo.MAC = iface.HardwareAddr.String() + } + + } + + return hostInfo, nil +} + +// FingerprintHost generates a fingerprint for the host using its identifiable information +func FingerprintHost(hostInfo *HostInfo) (string, string, error) { + data := strings.Join([]string{ + hostInfo.MAC, + hostInfo.OS, + hostInfo.Architecture, + hostInfo.Hostname, + }, "|") + hash := sha256.New() + _, err := hash.Write([]byte(data)) + if err != nil { + return "", "", fmt.Errorf("failed to generate hash: %v", err) + } + fingerprint := hex.EncodeToString(hash.Sum(nil)) + return data, fingerprint, nil +} diff --git a/go.mod b/go.mod index cdaf56f8..2a5c7130 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( require ( cuelang.org/go v0.10.0 github.com/adhocore/gronx v1.19.0 + github.com/charlievieth/fastwalk v1.0.8 github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/fsnotify/fsnotify v1.7.0 github.com/go-resty/resty/v2 v2.14.0 diff --git a/go.sum b/go.sum index d7611624..b0ab4356 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko= +github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=