diff --git a/pkg/scanners/azure/arm/scanner.go b/pkg/scanners/azure/arm/scanner.go index e7554e922..794c5f545 100644 --- a/pkg/scanners/azure/arm/scanner.go +++ b/pkg/scanners/azure/arm/scanner.go @@ -2,13 +2,17 @@ package arm import ( "context" + "fmt" "io" "io/fs" + "sync" "github.com/aquasecurity/defsec/internal/adapters/arm" + "github.com/aquasecurity/defsec/pkg/rego" "github.com/aquasecurity/defsec/pkg/rules" "github.com/aquasecurity/defsec/pkg/scanners/azure" "github.com/aquasecurity/defsec/pkg/state" + "github.com/aquasecurity/defsec/pkg/types" "github.com/aquasecurity/defsec/pkg/debug" @@ -28,6 +32,13 @@ type Scanner struct { parserOptions []options.ParserOption debug debug.Logger frameworks []framework.Framework + skipRequired bool + regoOnly bool + loadEmbedded bool + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + sync.Mutex } func New(opts ...options.ScannerOption) *Scanner { @@ -44,58 +55,87 @@ func (s *Scanner) Name() string { return "Azure ARM" } -func (s *Scanner) ScanFS(ctx context.Context, fs fs.FS, dir string) (scan.Results, error) { - p := parser.New(fs, s.parserOptions...) - deployments, err := p.ParseFS(ctx, dir) - if err != nil { - return nil, err - } - return s.scanDeployments(ctx, deployments) -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "azure", "arm") s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) } -func (s *Scanner) SetTraceWriter(writer io.Writer) { +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs } -func (s *Scanner) SetPerResultTracingEnabled(b bool) { +func (s *Scanner) SetSkipRequiredCheck(skipRequired bool) { + s.skipRequired = skipRequired } - -func (s *Scanner) SetPolicyDirs(s2 ...string) { +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers } -func (s *Scanner) SetDataDirs(s2 ...string) { +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on } -func (s *Scanner) SetPolicyNamespaces(s2 ...string) { +func (s *Scanner) SetUseEmbeddedPolicies(loadEmbedded bool) { + s.loadEmbedded = loadEmbedded } -func (s *Scanner) SetSkipRequiredCheck(b bool) { +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { -} +func (s *Scanner) SetTraceWriter(io.Writer) {} +func (s *Scanner) SetPerResultTracingEnabled(bool) {} +func (s *Scanner) SetDataDirs(...string) {} +func (s *Scanner) SetPolicyNamespaces(...string) {} -func (s *Scanner) SetPolicyFilesystem(fs fs.FS) { +func (s *Scanner) initRegoScanner(srcFS fs.FS) error { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return nil + } + regoScanner := rego.NewScanner(types.SourceCloud, s.scannerOptions...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbedded, srcFS, s.policyDirs, s.policyReaders); err != nil { + return err + } + s.regoScanner = regoScanner + return nil } -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { -} +func (s *Scanner) ScanFS(ctx context.Context, fs fs.FS, dir string) (scan.Results, error) { + p := parser.New(fs, s.parserOptions...) + deployments, err := p.ParseFS(ctx, dir) + if err != nil { + return nil, err + } + if err := s.initRegoScanner(fs); err != nil { + return nil, err + } -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks + return s.scanDeployments(ctx, deployments, fs) } -func (s *Scanner) scanDeployments(ctx context.Context, deployments []azure.Deployment) (scan.Results, error) { +func (s *Scanner) scanDeployments(ctx context.Context, deployments []azure.Deployment, f fs.FS) (scan.Results, error) { var results scan.Results for _, deployment := range deployments { - // TODO: adapt each deployment into a state - cloudState := s.adaptDeployment(ctx, deployment) + + result, err := s.scanDeployment(ctx, deployment, f) + if err != nil { + return nil, err + } + results = append(results, result...) + } + + return results, nil +} + +func (s *Scanner) scanDeployment(ctx context.Context, deployment azure.Deployment, fs fs.FS) (scan.Results, error) { + var results scan.Results + deploymentState := s.adaptDeployment(ctx, deployment) + if !s.regoOnly { for _, rule := range rules.GetRegistered(s.frameworks...) { select { case <-ctx.Done(): @@ -105,7 +145,7 @@ func (s *Scanner) scanDeployments(ctx context.Context, deployments []azure.Deplo if rule.Rule().RegoPackage != "" { continue } - ruleResults := rule.Evaluate(cloudState) + ruleResults := rule.Evaluate(deploymentState) s.debug.Log("Found %d results for %s", len(ruleResults), rule.Rule().AVDID) if len(ruleResults) > 0 { results = append(results, ruleResults...) @@ -113,7 +153,16 @@ func (s *Scanner) scanDeployments(ctx context.Context, deployments []azure.Deplo } } - return results, nil + regoResults, err := s.regoScanner.ScanInput(ctx, rego.Input{ + Path: deployment.Metadata.Range().GetFilename(), + FS: fs, + Contents: deploymentState.ToRego(), + }) + if err != nil { + return nil, fmt.Errorf("rego scan error: %w", err) + } + + return append(results, regoResults...), nil } func (s *Scanner) adaptDeployment(ctx context.Context, deployment azure.Deployment) *state.State { diff --git a/pkg/scanners/azure/deployment.go b/pkg/scanners/azure/deployment.go index e11c8aeab..6df8b48d6 100644 --- a/pkg/scanners/azure/deployment.go +++ b/pkg/scanners/azure/deployment.go @@ -1,6 +1,8 @@ package azure import ( + "os" + "github.com/aquasecurity/defsec/pkg/types" ) @@ -52,10 +54,7 @@ type Decorator struct { type Scope string const ( - ScopeResourceGroup Scope = "resourceGroup" - ScopeSubscription Scope = "subscription" - ScopeTenant Scope = "tenant" - ScopeManagementGroup Scope = "managementGroup" + ScopeResourceGroup Scope = "resourceGroup" ) func (d *Deployment) GetResourcesByType(t string) []Resource { @@ -97,3 +96,84 @@ func (d *Deployment) GetVariable(variableName string) interface{} { } return nil } + +func (d *Deployment) GetEnvVariable(envVariableName string) interface{} { + + if envVariable, exists := os.LookupEnv(envVariableName); exists { + return envVariable + } + return nil +} + +func (d *Deployment) GetOutput(outputName string) interface{} { + + for _, output := range d.Outputs { + if output.Name == outputName { + return output.Value.Raw() + } + } + return nil +} + +func (d *Deployment) GetDeployment() interface{} { + + type template struct { + Schema string `json:"$schema"` + ContentVersion string `json:"contentVersion"` + Parameters map[string]interface{} `json:"parameters"` + Variables map[string]interface{} `json:"variables"` + Resources []interface{} `json:"resources"` + Outputs map[string]interface{} `json:"outputs"` + } + + type templateLink struct { + URI string `json:"uri"` + } + + type properties struct { + TemplateLink templateLink `json:"templateLink"` + Template template `json:"template"` + TemplateHash string `json:"templateHash"` + Parameters map[string]interface{} `json:"parameters"` + Mode string `json:"mode"` + ProvisioningState string `json:"provisioningState"` + } + + deploymentShell := struct { + Name string `json:"name"` + Properties properties `json:"properties"` + }{ + Name: "Placeholder Deployment", + Properties: properties{ + TemplateLink: templateLink{ + URI: "https://placeholder.com", + }, + Template: template{ + Schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + ContentVersion: "", + Parameters: make(map[string]interface{}), + Variables: make(map[string]interface{}), + Resources: make([]interface{}, 0), + Outputs: make(map[string]interface{}), + }, + }, + } + + for _, parameter := range d.Parameters { + deploymentShell.Properties.Template.Parameters[parameter.Name] = parameter.Value.Raw() + } + + for _, variable := range d.Variables { + deploymentShell.Properties.Template.Variables[variable.Name] = variable.Value.Raw() + } + + for _, resource := range d.Resources { + deploymentShell.Properties.Template.Resources = append(deploymentShell.Properties.Template.Resources, resource) + } + + for _, output := range d.Outputs { + deploymentShell.Properties.Template.Outputs[output.Name] = output.Value.Raw() + } + + return deploymentShell +} diff --git a/pkg/scanners/azure/functions/deployment.go b/pkg/scanners/azure/functions/deployment.go index 4a88157d8..afafb2b35 100644 --- a/pkg/scanners/azure/functions/deployment.go +++ b/pkg/scanners/azure/functions/deployment.go @@ -3,18 +3,61 @@ package functions type DeploymentData interface { GetParameter(name string) interface{} GetVariable(variableName string) interface{} + GetEnvVariable(envVariableName string) interface{} } func Deployment(deploymentProvider DeploymentData, args ...interface{}) interface{} { - panic("not implemented") + + /* + + { + "name": "", + "properties": { + "templateLink": { + "uri": "" + }, + "template": { + "$schema": "", + "contentVersion": "", + "parameters": {}, + "variables": {}, + "resources": [], + "outputs": {} + }, + "templateHash": "", + "parameters": {}, + "mode": "", + "provisioningState": "" + } + } + + */ + + return nil } -func Environment(deploymentProvider DeploymentData, args ...interface{}) interface{} { - panic("not implemented") +func Environment(envProvider DeploymentData, args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + envVarName, ok := args[0].(string) + if !ok { + return nil + } + return envProvider.GetEnvVariable(envVarName) } -func Variables(deploymentProvider DeploymentData, args ...interface{}) interface{} { - panic("not implemented") +func Variables(varProvider DeploymentData, args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + varName, ok := args[0].(string) + if !ok { + return nil + } + return varProvider.GetVariable(varName) } func Parameters(paramProvider DeploymentData, args ...interface{}) interface{} {