Skip to content

Commit

Permalink
Merge pull request #158 from ttacon/ttacon/elasticache
Browse files Browse the repository at this point in the history
feat: add checks for encryption at rest and in transit for Elasticache
  • Loading branch information
liamg committed Sep 22, 2020
2 parents 5b1a405 + 880a6cd commit 1ab384f
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ there are also checks which are provider agnostic.
| AWS032 | aws | Elasticsearch domain uses plaintext traffic for node to node communication.
| AWS033 | aws | Elasticsearch doesn't enforce HTTPS traffic.
| AWS034 | aws | Elasticsearch domain endpoint is using outdated TLS policy.
| AWS035 | aws | Unencrypted Elasticache Replication Group.
| AWS036 | aws | Elasticache Replication Group uses unencrypted traffic.
| AZU001 | azurerm | An inbound network security rule allows traffic from `/0`.
| AZU002 | azurerm | An outbound network security rule allows traffic to `/0`.
| AZU003 | azurerm | Unencrypted managed disk.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tfsec

import (
"testing"

"github.com/liamg/tfsec/internal/app/tfsec/scanner"

"github.com/liamg/tfsec/internal/app/tfsec/checks"
)

func Test_AWSUnencryptedAtRestElasticacheReplicationGroup(t *testing.T) {
var tests = []struct {
name string
source string
mustIncludeResultCode scanner.RuleID
mustExcludeResultCode scanner.RuleID
}{
{
name: "check aws_elasticache_replication_group missing at_rest_encryption_enabled",
source: `
resource "aws_elasticache_replication_group" "my-resource" {
replication_group_id = "foo"
replication_group_description = "my foo cluster"
}`,
mustIncludeResultCode: checks.AWSUnencryptedAtRestElasticacheReplicationGroup,
},
{
name: "check aws_elasticache_replication_group with at_rest_encryption_enabled",
source: `
resource "aws_elasticache_replication_group" "my-resource" {
replication_group_id = "foo"
replication_group_description = "my foo cluster"
at_rest_encryption_enabled = true
}`,
mustExcludeResultCode: checks.AWSUnencryptedAtRestElasticacheReplicationGroup,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
results := scanSource(test.source)
assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
})
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tfsec

import (
"testing"

"github.com/liamg/tfsec/internal/app/tfsec/scanner"

"github.com/liamg/tfsec/internal/app/tfsec/checks"
)

func Test_AWSUnencryptedInTransitElasticacheReplicationGroup(t *testing.T) {
var tests = []struct {
name string
source string
mustIncludeResultCode scanner.RuleID
mustExcludeResultCode scanner.RuleID
}{
{
name: "check aws_elasticache_replication_group missing transit_encryption_enabled",
source: `
resource "aws_elasticache_replication_group" "my-resource" {
replication_group_id = "foo"
replication_group_description = "my foo cluster"
}`,
mustIncludeResultCode: checks.AWSUnencryptedInTransitElasticacheReplicationGroup,
},
{
name: "check aws_elasticache_replication_group with transit_encryption_enabled",
source: `
resource "aws_elasticache_replication_group" "my-resource" {
replication_group_id = "foo"
replication_group_description = "my foo cluster"
transit_encryption_enabled = true
}`,
mustExcludeResultCode: checks.AWSUnencryptedInTransitElasticacheReplicationGroup,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
results := scanSource(test.source)
assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
})
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package checks

import (
"fmt"

"github.com/liamg/tfsec/internal/app/tfsec/scanner"

"github.com/liamg/tfsec/internal/app/tfsec/parser"
)

// AWSUnencryptedAtRestElasticacheReplicationGroup See https://github.com/liamg/tfsec#included-checks for check info
const AWSUnencryptedAtRestElasticacheReplicationGroup scanner.RuleID = "AWS035"

func init() {
scanner.RegisterCheck(scanner.Check{
Code: AWSUnencryptedAtRestElasticacheReplicationGroup,
RequiredTypes: []string{"resource"},
RequiredLabels: []string{"aws_elasticache_replication_group"},
CheckFunc: func(check *scanner.Check, block *parser.Block, context *scanner.Context) []scanner.Result {

encryptionAttr := block.GetAttribute("at_rest_encryption_enabled")
if encryptionAttr == nil {
return []scanner.Result{
check.NewResult(
fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing at_rest_encryption_enabled attribute).", block.Name()),
block.Range(),
scanner.SeverityError,
),
}
} else if !isBooleanOrStringTrue(encryptionAttr) {
return []scanner.Result{
check.NewResultWithValueAnnotation(
fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (at_rest_encryption_enabled set to false).", block.Name()),
encryptionAttr.Range(),
encryptionAttr,
scanner.SeverityError,
),
}
}

return nil
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package checks

import (
"fmt"

"github.com/liamg/tfsec/internal/app/tfsec/scanner"

"github.com/liamg/tfsec/internal/app/tfsec/parser"
)

// AWSUnencryptedInTransitElasticacheReplicationGroup See https://github.com/liamg/tfsec#included-checks for check info
const AWSUnencryptedInTransitElasticacheReplicationGroup scanner.RuleID = "AWS036"

func init() {
scanner.RegisterCheck(scanner.Check{
Code: AWSUnencryptedInTransitElasticacheReplicationGroup,
RequiredTypes: []string{"resource"},
RequiredLabels: []string{"aws_elasticache_replication_group"},
CheckFunc: func(check *scanner.Check, block *parser.Block, context *scanner.Context) []scanner.Result {

encryptionAttr := block.GetAttribute("transit_encryption_enabled")
if encryptionAttr == nil {
return []scanner.Result{
check.NewResult(
fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing transit_encryption_enabled attribute).", block.Name()),
block.Range(),
scanner.SeverityError,
),
}
} else if !isBooleanOrStringTrue(encryptionAttr) {
return []scanner.Result{
check.NewResultWithValueAnnotation(
fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (transit_encryption_enabled set to false).", block.Name()),
encryptionAttr.Range(),
encryptionAttr,
scanner.SeverityError,
),
}

}

return nil
},
})
}
19 changes: 19 additions & 0 deletions internal/app/tfsec/checks/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package checks

import (
"github.com/liamg/tfsec/internal/app/tfsec/parser"
"github.com/zclconf/go-cty/cty"
)

// isBooleanOrStringTrue returns true if the attribute is a boolean and is
// `true` or if the attribute is a string and is `"true"`.
func isBooleanOrStringTrue(val *parser.Attribute) bool {
switch val.Type() {
case cty.Bool:
return val.Value().True()
case cty.String:
return val.Value().Equals(cty.StringVal("true")).True()
default:
return false
}
}
55 changes: 55 additions & 0 deletions internal/app/tfsec/checks/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package checks

import (
"testing"

hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/liamg/tfsec/internal/app/tfsec/parser"
)

func Test_isBooleanOrStringTrue(t *testing.T) {
var tests = []struct {
val *parser.Attribute
rawExpr string
result bool
}{
{
rawExpr: "false",
result: false,
},
{
rawExpr: "true",
result: true,
},
{
rawExpr: `"false"`,
result: false,
},
{
rawExpr: `"true"`,
result: true,
},
{
rawExpr: `"foo"`,
result: false,
},
{
rawExpr: "5",
result: false,
},
}

for _, test := range tests {
expr, _ := hclsyntax.ParseExpression([]byte(test.rawExpr), "", hcl.Pos{0, 0, 0})
attr := parser.NewAttribute(
&hclsyntax.Attribute{
Expr: expr,
},
nil,
)
if result := isBooleanOrStringTrue(attr); result != test.result {
t.Errorf("expected %v got %v\n", test.val, result)
}
}
}

0 comments on commit 1ab384f

Please sign in to comment.