Skip to content

Commit

Permalink
add interface for ExternalRuleInvoker, add test
Browse files Browse the repository at this point in the history
  • Loading branch information
lhitchon committed Mar 20, 2018
1 parent 22bad46 commit 7a81455
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 15 deletions.
4 changes: 4 additions & 0 deletions assertion/engine.go
Expand Up @@ -63,3 +63,7 @@ type Resource struct {
Properties interface{}
Filename string
}

type ExternalRuleInvoker interface {
Invoke(Rule, Resource) (string, []Violation)
}
10 changes: 7 additions & 3 deletions assertion/invoke.go
Expand Up @@ -15,7 +15,11 @@ type InvokeResponse struct {
Violations []InvokeViolation
}

func invoke(rule Rule, resource Resource, log LoggingFunction) (string, []Violation) {
type StandardExternalRuleInvoker struct {
Log LoggingFunction
}

func (e StandardExternalRuleInvoker) Invoke(rule Rule, resource Resource) (string, []Violation) {
status := "OK"
violations := make([]Violation, 0)
payload := resource.Properties
Expand All @@ -27,7 +31,7 @@ func invoke(rule Rule, resource Resource, log LoggingFunction) (string, []Violat
payload = p
}
payloadJSON, err := JSONStringify(payload)
log(fmt.Sprintf("Invoke %s on %s\n", rule.Invoke.Url, payloadJSON))
e.Log(fmt.Sprintf("Invoke %s on %s\n", rule.Invoke.Url, payloadJSON))
httpResponse, err := http.Get(rule.Invoke.Url)
if err != nil {
return rule.Severity, violations // TODO set violation to HTTP call failed
Expand All @@ -37,7 +41,7 @@ func invoke(rule Rule, resource Resource, log LoggingFunction) (string, []Violat
if err != nil {
return rule.Severity, violations // TODO set violation to read body failed
}
log(string(body))
e.Log(string(body))
var invokeResponse InvokeResponse
err = json.Unmarshal(body, &invokeResponse)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions assertion/rules.go
Expand Up @@ -68,15 +68,15 @@ func ResolveRule(rule Rule, valueSource ValueSource, log LoggingFunction) Rule {
return resolvedRule
}

func CheckRule(rule Rule, resource Resource, log LoggingFunction) (string, []Violation) {
func CheckRule(rule Rule, resource Resource, e ExternalRuleInvoker, log LoggingFunction) (string, []Violation) {
returnStatus := "OK"
violations := make([]Violation, 0)
if ExcludeResource(rule, resource) {
fmt.Println("Ignoring resource:", resource.Id)
return returnStatus, violations
}
if rule.Invoke.Url != "" {
return invoke(rule, resource, log)
return e.Invoke(rule, resource)
}
for _, ruleAssertion := range rule.Assertions {
log(fmt.Sprintf("Checking resource %s", resource.Id))
Expand Down
47 changes: 43 additions & 4 deletions assertion/rules_test.go
Expand Up @@ -18,6 +18,20 @@ func testValueSource() ValueSource {
return TestValueSource{}
}

type MockExternalRuleInvoker struct {
InvokeCount int
}

func mockExternalRuleInvoker() *MockExternalRuleInvoker {
return &MockExternalRuleInvoker{InvokeCount: 0}
}

func (e *MockExternalRuleInvoker) Invoke(Rule, Resource) (string, []Violation) {
e.InvokeCount += 1
noViolations := make([]Violation, 0)
return "OK", noViolations
}

var content = `Rules:
- id: TEST1
message: Test message
Expand Down Expand Up @@ -96,7 +110,7 @@ func TestRuleWithMultipleFilter(t *testing.T) {
Properties: map[string]interface{}{"instance_type": "t2.micro", "ami": "ami-000000"},
Filename: "test.tf",
}
status, violations := CheckRule(rules.Rules[0], resource, testLogging)
status, violations := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker(), testLogging)
if status != "OK" {
t.Error("Expecting multiple rule to match")
}
Expand All @@ -113,7 +127,7 @@ func TestMultipleFiltersWithSingleFailure(t *testing.T) {
Properties: map[string]interface{}{"instance_type": "t2.micro", "ami": "ami-111111"},
Filename: "test.tf",
}
status, violations := CheckRule(rules.Rules[0], resource, testLogging)
status, violations := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker(), testLogging)
if status != "FAILURE" {
t.Error("Expecting multiple rule to return FAILURE")
}
Expand All @@ -130,7 +144,7 @@ func TestMultipleFiltersWithMultipleFailures(t *testing.T) {
Properties: map[string]interface{}{"instance_type": "c3.medium", "ami": "ami-111111"},
Filename: "test.tf",
}
status, violations := CheckRule(rules.Rules[0], resource, testLogging)
status, violations := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker(), testLogging)
if status != "FAILURE" {
t.Error("Expecting multiple rule to return FAILURE")
}
Expand Down Expand Up @@ -162,11 +176,36 @@ func TestValueFrom(t *testing.T) {
Filename: "test.tf",
}
resolved := ResolveRules(rules.Rules, testValueSource(), testLogging)
status, violations := CheckRule(resolved[0], resource, testLogging)
status, violations := CheckRule(resolved[0], resource, mockExternalRuleInvoker(), testLogging)
if status != "OK" {
t.Error("Expecting value_from to match")
}
if len(violations) != 0 {
t.Error("Expecting value_from test to have 0 violations")
}
}

var ruleWithInvoke = `Rules:
- id: FROM1
message: Test value_from
severity: FAILURE
resource: aws_instance
invoke:
url: http://localhost
`

func TestInvoke(t *testing.T) {
rules := MustParseRules(ruleWithInvoke)
resource := Resource{
Id: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "m3.medium"},
Filename: "test.tf",
}
resolved := ResolveRules(rules.Rules, testValueSource(), testLogging)
e := mockExternalRuleInvoker()
CheckRule(resolved[0], resource, e, testLogging)
if e.InvokeCount != 1 {
t.Error("Expecting external rule engine to be invoked")
}
}
7 changes: 5 additions & 2 deletions cli/app.go
Expand Up @@ -74,7 +74,10 @@ func makeLinter(linterType string, log assertion.LoggingFunction) Linter {
type arrayFlags []string

func (i *arrayFlags) String() string {
return "my string representation"
if i != nil {
return strings.Join(*i, ",")
}
return ""
}

func (i *arrayFlags) Set(value string) error {
Expand All @@ -85,7 +88,7 @@ func (i *arrayFlags) Set(value string) error {
func main() {
var rulesFilenames arrayFlags
verboseLogging := flag.Bool("verbose", false, "Verbose logging")
flag.Var(&rulesFilenames, "rules", "Rules file")
flag.Var(&rulesFilenames, "rules", "Rules file, can be specified multiple times")
tags := flag.String("tags", "", "Run only tests with tags in this comma separated list")
ids := flag.String("ids", "", "Run only the rules in this comma separated list")
queryExpression := flag.String("query", "", "JMESPath expression to query the results")
Expand Down
3 changes: 2 additions & 1 deletion cli/kubernetes.go
Expand Up @@ -47,6 +47,7 @@ func (l KubernetesLinter) ValidateKubernetesResources(resources []assertion.Reso
valueSource := assertion.StandardValueSource{Log: l.Log}
filteredRules := assertion.FilterRulesByTag(rules, tags)
resolvedRules := assertion.ResolveRules(filteredRules, valueSource, l.Log)
externalRules := assertion.StandardExternalRuleInvoker{Log: l.Log}

allViolations := make([]assertion.Violation, 0)
for _, rule := range resolvedRules {
Expand All @@ -55,7 +56,7 @@ func (l KubernetesLinter) ValidateKubernetesResources(resources []assertion.Reso
if assertion.ExcludeResource(rule, resource) {
l.Log(fmt.Sprintf("Ignoring resource %s", resource.Id))
} else {
_, violations := assertion.CheckRule(rule, resource, l.Log)
_, violations := assertion.CheckRule(rule, resource, externalRules, l.Log)
allViolations = append(allViolations, violations...)
}
}
Expand Down
3 changes: 2 additions & 1 deletion cli/security_group.go
Expand Up @@ -53,6 +53,7 @@ func (l SecurityGroupLinter) ValidateSecurityGroupResources(resources []assertio
valueSource := assertion.StandardValueSource{Log: l.Log}
filteredRules := assertion.FilterRulesByTag(rules, tags)
resolvedRules := assertion.ResolveRules(filteredRules, valueSource, l.Log)
externalRules := assertion.StandardExternalRuleInvoker{Log: l.Log}

allViolations := make([]assertion.Violation, 0)
for _, rule := range resolvedRules {
Expand All @@ -61,7 +62,7 @@ func (l SecurityGroupLinter) ValidateSecurityGroupResources(resources []assertio
if assertion.ExcludeResource(rule, resource) {
l.Log(fmt.Sprintf("Ignoring resource %s", resource.Id))
} else {
_, violations := assertion.CheckRule(rule, resource, l.Log)
_, violations := assertion.CheckRule(rule, resource, externalRules, l.Log)
allViolations = append(allViolations, violations...)
}
}
Expand Down
3 changes: 2 additions & 1 deletion cli/terraform.go
Expand Up @@ -94,6 +94,7 @@ func (l TerraformLinter) ValidateTerraformResources(resources []assertion.Resour
valueSource := assertion.StandardValueSource{Log: l.Log}
filteredRules := assertion.FilterRulesByTag(rules, tags)
resolvedRules := assertion.ResolveRules(filteredRules, valueSource, l.Log)
externalRules := assertion.StandardExternalRuleInvoker{Log: l.Log}

allViolations := make([]assertion.Violation, 0)
for _, rule := range resolvedRules {
Expand All @@ -102,7 +103,7 @@ func (l TerraformLinter) ValidateTerraformResources(resources []assertion.Resour
if assertion.ExcludeResource(rule, resource) {
l.Log(fmt.Sprintf("Ignoring resource %s", resource.Id))
} else {
_, violations := assertion.CheckRule(rule, resource, l.Log)
_, violations := assertion.CheckRule(rule, resource, externalRules, l.Log)
allViolations = append(allViolations, violations...)
}
}
Expand Down
3 changes: 2 additions & 1 deletion lambda/lambda.go
Expand Up @@ -103,14 +103,15 @@ func handler(configEvent events.ConfigEvent) (string, error) {
ruleSet := assertion.MustParseRules(rulesString)
valueSource := assertion.StandardValueSource{Log: log}
resolvedRules := assertion.ResolveRules(ruleSet.Rules, valueSource, log)
externalRules := StandardExternalRuleInvoker{Log: log}
for _, rule := range resolvedRules {
if rule.Resource == configurationItem.ResourceType {
resource := assertion.Resource{
Id: configurationItem.ResourceId,
Type: configurationItem.ResourceType,
Properties: configurationItem.Configuration,
}
_, violations := assertion.CheckRule(rule, resource, log)
_, violations := assertion.CheckRule(rule, resource, externalRules, log)
if len(violations) > 0 {
fmt.Println("Resource in NON_COMPLIANT")
complianceType = "NON_COMPLIANT"
Expand Down

0 comments on commit 7a81455

Please sign in to comment.