Skip to content

Commit

Permalink
feat: update universal scanner (#1007)
Browse files Browse the repository at this point in the history
* feat: update the universal scanner to include ARM
* feat: add detection for Azure ARM
* fix: fix required check for azurerm
* fix: make azure arm name clearer
* feat: add deployment placeholder function

Signed-off-by: Owen Rumney <[email protected]>
  • Loading branch information
Owen Rumney authored Oct 12, 2022
1 parent 43b72f0 commit 58cd405
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 60 deletions.
27 changes: 27 additions & 0 deletions pkg/detection/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"path/filepath"
"strings"

"github.com/aquasecurity/defsec/pkg/scanners/azure/arm/parser/armjson"
"github.com/aquasecurity/defsec/pkg/types"
"gopkg.in/yaml.v3"
)

Expand All @@ -23,6 +25,7 @@ const (
FileTypeTOML FileType = "toml"
FileTypeJSON FileType = "json"
FileTypeHelm FileType = "helm"
FileTypeAzureARM FileType = "azure-arm"
)

var matchers = map[FileType]func(name string, r io.ReadSeeker) bool{}
Expand Down Expand Up @@ -127,6 +130,30 @@ func init() {
return sniff.Resources != nil
}

matchers[FileTypeAzureARM] = func(name string, r io.ReadSeeker) bool {

if resetReader(r) == nil {
return false
}

data, err := io.ReadAll(r)
if err != nil {
return false
}
sniff := struct {
ContentType string `json:"contentType"`
Parameters map[string]interface{} `json:"parameters"`
Resources []interface{} `json:"resources"`
}{}
metadata := types.NewUnmanagedMetadata()
if err := armjson.Unmarshal(data, &sniff, &metadata); err != nil {
return false
}

return (sniff.Parameters != nil && len(sniff.Parameters) > 0) ||
(sniff.Resources != nil && len(sniff.Resources) > 0)
}

matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool {
requiredFiles := []string{"Dockerfile", "Containerfile"}
for _, requiredFile := range requiredFiles {
Expand Down
4 changes: 3 additions & 1 deletion pkg/scan/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ func (r *Result) GetCode(opts ...CodeOption) (*Code, error) {
outerRange := innerRange
metadata := r.Metadata()
for {
if parent := metadata.Parent(); parent != nil && parent.Range().GetFilename() == metadata.Range().GetFilename() {
if parent := metadata.Parent(); parent != nil &&
parent.Range().GetFilename() == metadata.Range().GetFilename() &&
parent.Range().GetStartLine() > 0 {
outerRange = parent.Range()
metadata = *parent
continue
Expand Down
129 changes: 79 additions & 50 deletions pkg/scanners/azure/arm/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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 {
Expand All @@ -41,81 +52,90 @@ func New(opts ...options.ScannerOption) *Scanner {
}

func (s *Scanner) Name() string {
// TODO implement me
panic("implement me")
}

func (s *Scanner) ScanFS(ctx context.Context, fs fs.FS, dir string) (scan.Results, error) {
// TODO implement me
p := parser.New(fs, s.parserOptions...)
deployments, err := p.ParseFS(ctx, dir)
if err != nil {
return nil, err
}
return s.scanDeployments(ctx, deployments)
return "Azure ARM"
}

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) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetPolicyDirs(dirs ...string) {
s.policyDirs = dirs
}

func (s *Scanner) SetPerResultTracingEnabled(b bool) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetSkipRequiredCheck(skipRequired bool) {
s.skipRequired = skipRequired
}

func (s *Scanner) SetPolicyDirs(s2 ...string) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetPolicyReaders(readers []io.Reader) {
s.policyReaders = readers
}

func (s *Scanner) SetDataDirs(s2 ...string) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetPolicyFilesystem(_ fs.FS) {
// handled by rego when option is passed on
}

func (s *Scanner) SetPolicyNamespaces(s2 ...string) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetUseEmbeddedPolicies(loadEmbedded bool) {
s.loadEmbedded = loadEmbedded
}

func (s *Scanner) SetSkipRequiredCheck(b bool) {
// TODO implement me
panic("implement me")
func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {
s.frameworks = frameworks
}

func (s *Scanner) SetPolicyReaders(readers []io.Reader) {
// TODO implement me
panic("implement me")
}
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) {
// TODO implement me
panic("implement me")
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) {
// TODO implement me
panic("implement me")
}
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():
Expand All @@ -125,15 +145,24 @@ 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 {
s.debug.Log("Found %d results for %s", len(ruleResults), rule.Rule().AVDID)
results = append(results, ruleResults...)
}
}
}

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 {
Expand Down
88 changes: 84 additions & 4 deletions pkg/scanners/azure/deployment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package azure

import (
"os"

"github.com/aquasecurity/defsec/pkg/types"
)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 58cd405

Please sign in to comment.