diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..a7e13fcb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## [[Unreleased]] - feature/loadremote +Commit: [55e24a0](https://github.com/xfhg/intercept/commit/55e24a0) + +Summary: Capability to load the main policy file from remote endpoint (and check their SHA256) + +### Added +- Added this CHANGELOG +- Added shorthand for policy (-p) +- Added shorthand for tag filtering "tags_any" (-f) +- Added sha256 checksum on command line for policy (--checksum) +- INTERCEPT can now load a remote policy (ex: https://raw.githubusercontent.com/xfhg/intercept/master/playground/policies/test_scan.yaml) +- INTERCEPT can verify the checksum of remote policies + +### Changed +- Modified go build version to 1.23 + +### Removed + diff --git a/cmd/audit.go b/cmd/audit.go index 276f3f01..b7071fd0 100644 --- a/cmd/audit.go +++ b/cmd/audit.go @@ -18,17 +18,18 @@ type Performance struct { } var ( - targetDir string - tagsAny string - tagsAll string - environment string - envDetection bool - debugOutput bool - rgPath string - gossPath string - policyFile string - outputType string - policyData *PolicyFile + targetDir string + tagsAny string + tagsAll string + environment string + envDetection bool + debugOutput bool + rgPath string + gossPath string + policyFile string + policyFileSHA256 string + outputType string + policyData *PolicyFile ) var runAuditPerfCmd = &cobra.Command{ @@ -41,12 +42,13 @@ var runAuditPerfCmd = &cobra.Command{ func init() { rootCmd.AddCommand(runAuditPerfCmd) runAuditPerfCmd.Flags().StringVarP(&targetDir, "target", "t", "", "Target directory to audit") - runAuditPerfCmd.Flags().StringVar(&tagsAny, "tags_any", "", "Filter policies that match any of the provided tags (comma-separated)") + runAuditPerfCmd.Flags().StringVarP(&tagsAny, "tags_any", "f", "", "Filter policies that match any of the provided tags (comma-separated)") runAuditPerfCmd.Flags().StringVar(&tagsAll, "tags_all", "", "Filter policies that match all of the provided tags (comma-separated)") - runAuditPerfCmd.Flags().StringVar(&environment, "environment", "", "Filter policies that match the specified environment") + runAuditPerfCmd.Flags().StringVarP(&environment, "environment", "e", "", "Filter policies that match the specified environment") runAuditPerfCmd.Flags().BoolVar(&envDetection, "env-detection", false, "Enable environment detection if no environment is specified") runAuditPerfCmd.Flags().BoolVar(&debugOutput, "debug", false, "Enable debug verbose output") - runAuditPerfCmd.Flags().StringVar(&policyFile, "policy", "", "policy file") + runAuditPerfCmd.Flags().StringVarP(&policyFile, "policy", "p", "", "policy FILE or URL") + runAuditPerfCmd.Flags().StringVar(&policyFileSHA256, "checksum", "", "policy file SHA256 checksum") runAuditPerfCmd.Flags().StringVar(&outputType, "output", "sarif", "output type") } @@ -56,7 +58,22 @@ func runAuditPerf(cmd *cobra.Command, args []string) { perf := Performance{StartTime: time.Now()} - policyData, err = LoadPolicyFile(policyFile) + sourceType, processedInput, err := DeterminePolicySource(policyFile) + if err != nil { + log.Fatal().Err(err) + } + + switch sourceType { + case LocalFile: + policyData, err = LoadPolicyFile(processedInput) + case RemoteURL: + policyData, err = LoadRemotePolicy(processedInput, policyFileSHA256) + default: + log.Fatal().Msg("unknown policy source type") + } + + //policyData, err = LoadPolicyFile(policyFile) + if err != nil { log.Fatal().Err(err).Str("file", policyFile).Msg("Error loading policy file") } diff --git a/cmd/aux.go b/cmd/aux.go index c7188824..1b2860e9 100644 --- a/cmd/aux.go +++ b/cmd/aux.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "net/url" "os" "path/filepath" "strconv" @@ -411,3 +412,13 @@ func PathInfo(path string) (exists bool, isDir bool, err error) { func GetDirectory(path string) string { return filepath.Dir(path) + "/" } + +// isURL checks if the input string is a valid URL +func isURL(input string) bool { + // Check for common URL schemes + if strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://") { + _, err := url.ParseRequestURI(input) + return err == nil + } + return false +} diff --git a/cmd/policy.go b/cmd/policy.go index 1e20489e..5dbe986b 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -1,9 +1,13 @@ package cmd import ( + "fmt" + "net/http" "os" + "path/filepath" "sync" + "github.com/go-resty/resty/v2" "gopkg.in/yaml.v3" ) @@ -108,6 +112,13 @@ type PolicyFile struct { Policies []Policy `yaml:"Policies"` } +type PolicySourceType int + +const ( + LocalFile PolicySourceType = iota + RemoteURL +) + func LoadPolicyFile(filename string) (*PolicyFile, error) { data, err := os.ReadFile(filename) if err != nil { @@ -130,6 +141,79 @@ func LoadPolicyFile(filename string) (*PolicyFile, error) { return &policyFile, nil } +// Load Remote + +// 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") + 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 + + // Generate a temporary file name + tempFile := filepath.Join(tempDir, "remote_policy.yaml") + + // Create a resty client + client := resty.New() + + // Download the file + resp, err := client.R().SetOutput(tempFile).Get(url) + if err != nil { + return nil, fmt.Errorf("failed to download policy file: %w", err) + } + + if resp.StatusCode() != http.StatusOK { + return nil, fmt.Errorf("failed to download policy file: HTTP status %d", resp.StatusCode()) + } + + // If a checksum is provided, validate it + if expectedChecksum != "" { + actualChecksum, err := calculateSHA256(tempFile) + if err != nil { + log.Fatal().Err(err).Msg("failed to calculate policy checksum") + } + + if actualChecksum != expectedChecksum { + log.Fatal().Msgf("Policy checksum mismatch: expected %s, got %s", expectedChecksum, actualChecksum) + + } + } + + // Load the policy file + policyFile, err := LoadPolicyFile(tempFile) + if err != nil { + log.Fatal().Err(err).Msg("failed to load policy file") + } + + return policyFile, nil +} + +func DeterminePolicySource(input string) (PolicySourceType, string, error) { + // First, check if it's a valid URL + if isURL(input) { + return RemoteURL, input, nil + } + + // If not a URL, treat it as a file path + absPath, err := filepath.Abs(input) + if err != nil { + return LocalFile, "", err + } + + // Check if the file exists + _, err = os.Stat(absPath) + if err != nil { + if os.IsNotExist(err) { + return LocalFile, "", fmt.Errorf("file does not exist: %s", absPath) + } + return LocalFile, "", err + } + + return LocalFile, absPath, nil +} + // Policy store var ( diff --git a/cmd/version.go b/cmd/version.go index 51880c68..15fd5bd0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -9,8 +9,8 @@ var versionCmd = &cobra.Command{ Short: "Print the build info of intercept", Long: `Print the build version number of intercept along with its signature`, Run: func(cmd *cobra.Command, args []string) { - log.Info().Msgf("Intercept build version %s", buildVersion) - log.Info().Msgf("Intercept signature [%s]", buildSignature) + log.Log().Msgf("Intercept build version %s", buildVersion) + log.Log().Msgf("Intercept signature [%s]", buildSignature) }, }