Skip to content

Commit

Permalink
Add first cloudstack check for user data (#985)
Browse files Browse the repository at this point in the history
The user_data must not have sensitive data

Co-authored-by: Liam Galvin <liam.galvin@aquasec.com>
  • Loading branch information
Owen Rumney and liamg committed Jul 30, 2021
1 parent be46961 commit d55c6de
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 9 deletions.
10 changes: 2 additions & 8 deletions cmd/tfsec-docs/webpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"text/template"

"github.com/aquasecurity/tfsec/pkg/provider"
"github.com/aquasecurity/tfsec/pkg/rule"
)

Expand Down Expand Up @@ -90,14 +91,7 @@ func formatProviderName(providerName string) string {
if providerName == "digitalocean" {
providerName = "digital ocean"
}
switch providerName {
case "aws":
return strings.ToUpper(providerName)
case "openstack":
return "OpenStack"
default:
return strings.Title(strings.ToLower(providerName))
}
return provider.Provider(providerName).DisplayName()
}

func generateWebPage(webProviderPath string, r rule.Rule) error {
Expand Down
1 change: 1 addition & 0 deletions cmd/tfsec-skeleton/generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var providers = []string{
"Google",
"Kubernetes",
"OpenStack",
"CloudStack",
"Oracle",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func init() {
ShortCode: "no-sensitive-info",
Documentation: rule.RuleDocumentation{
Summary: "Ensure all data stored in the Launch configuration EBS is securely encrypted",
Explanation: `When creating Launch Configurations, user data can be used for the initial configuration of the check. User data must not contain any sensitive data.`,
Explanation: `When creating Launch Configurations, user data can be used for the initial configuration of the instance. User data must not contain any sensitive data.`,
Impact: "Sensitive credentials in user data can be leaked",
Resolution: "Don't use sensitive data in user data",
BadExample: []string{`
Expand Down
108 changes: 108 additions & 0 deletions internal/app/tfsec/rules/cloudstack/compute/no_sensitive_info_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package compute

// ATTENTION!
// This rule was autogenerated!
// Before making changes, consider updating the generator.

import (
"encoding/base64"

"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
"github.com/aquasecurity/tfsec/internal/app/tfsec/debug"
"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
"github.com/aquasecurity/tfsec/pkg/provider"
"github.com/aquasecurity/tfsec/pkg/result"
"github.com/aquasecurity/tfsec/pkg/rule"
"github.com/aquasecurity/tfsec/pkg/severity"
"github.com/owenrumney/squealer/pkg/squealer"
)

func init() {
scanner.RegisterCheckRule(rule.Rule{
Provider: provider.CloudStackProvider,
Service: "compute",
ShortCode: "no-sensitive-info",
Documentation: rule.RuleDocumentation{
Summary: "No sensitive data stored in user_data",
Explanation: `When creating instances, user data can be used during the initial confiugtation. User data must not contain sensitive information`,
Impact: "Sensitive credentials in the user data can be leaked",
Resolution: "Don't use sensitive data in the user data section",
BadExample: []string{`
resource "cloudstack_instance" "web" {
name = "server-1"
service_offering = "small"
network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
template = "CentOS 6.5"
zone = "zone-1"
user_data = <<EOF
export DATABASE_PASSWORD=\"SomeSortOfPassword\"
EOF
}
`, `
resource "cloudstack_instance" "web" {
name = "server-1"
service_offering = "small"
network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
template = "CentOS 6.5"
zone = "zone-1"
user_data = "ZXhwb3J0IERBVEFCQVNFX1BBU1NXT1JEPSJTb21lU29ydE9mUGFzc3dvcmQi"
}
`},
GoodExample: []string{`
resource "cloudstack_instance" "web" {
name = "server-1"
service_offering = "small"
network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
template = "CentOS 6.5"
zone = "zone-1"
user_data = <<EOF
export GREETING="Hello there"
EOF
}
`, `
resource "cloudstack_instance" "web" {
name = "server-1"
service_offering = "small"
network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
template = "CentOS 6.5"
zone = "zone-1"
user_data = "ZXhwb3J0IEVESVRPUj12aW1hY3M="
}
`},
Links: []string{
"https://registry.terraform.io/providers/hashicorp/cloudstack/latest/docs/resources/instance#",
},
},
RequiredTypes: []string{
"resource",
},
RequiredLabels: []string{
"cloudstack_instance",
},
DefaultSeverity: severity.High,
CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {

customDataAttr := resourceBlock.GetAttribute("user_data")

if customDataAttr.IsNotNil() && customDataAttr.IsString() {
encoded, err := base64.StdEncoding.DecodeString(customDataAttr.Value().AsString())
if err != nil {
debug.Log("could not decode the base64 string in the terraform, trying with the string verbatim")
encoded = []byte(customDataAttr.Value().AsString())
}
if checkStringForSensitive(string(encoded)) {
set.AddResult().
WithDescription("Resource '%s' has user_data_base64 with sensitive data.", resourceBlock.FullName()).
WithAttribute(customDataAttr)
}
}

},
})
}

func checkStringForSensitive(stringToCheck string) bool {
scanResult := squealer.NewStringScanner().Scan(stringToCheck)
return scanResult.TransgressionFound
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package compute

import (
"strings"
"testing"

"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
)

func Test_CloudstackNoSensitiveInfo_FailureExamples(t *testing.T) {
expectedCode := "cloudstack-compute-no-sensitive-info"

rule, err := scanner.GetRuleById(expectedCode)
if err != nil {
t.FailNow()
}
for i, badExample := range rule.Documentation.BadExample {
t.Logf("Running bad example for '%s' #%d", expectedCode, i+1)
if strings.TrimSpace(badExample) == "" {
t.Fatalf("bad example code not provided for %s", rule.ID())
}
defer func() {
if err := recover(); err != nil {
t.Fatalf("Scan (bad) failed: %s", err)
}
}()
results := testutil.ScanHCL(badExample, t)
testutil.AssertCheckCode(t, rule.ID(), "", results)
}
}

func Test_CloudstackNoSensitiveInfo_SuccessExamples(t *testing.T) {
expectedCode := "cloudstack-compute-no-sensitive-info"

rule, err := scanner.GetRuleById(expectedCode)
if err != nil {
t.FailNow()
}
for i, example := range rule.Documentation.GoodExample {
t.Logf("Running good example for '%s' #%d", expectedCode, i+1)
if strings.TrimSpace(example) == "" {
t.Fatalf("good example code not provided for %s", rule.ID())
}
defer func() {
if err := recover(); err != nil {
t.Fatalf("Scan (good) failed: %s", err)
}
}()
results := testutil.ScanHCL(example, t)
testutil.AssertCheckCode(t, "", rule.ID(), results)
}
}
1 change: 1 addition & 0 deletions internal/app/tfsec/rules/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/securitycenter"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/storage"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/synapse"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/cloudstack/compute"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/compute"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/droplet"
_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/loadbalancing"
Expand Down
3 changes: 3 additions & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
KubernetesProvider Provider = "kubernetes"
OracleProvider Provider = "oracle"
OpenStackProvider Provider = "openstack"
CloudStackProvider Provider = "cloudstack"
)

func RuleProviderToString(provider Provider) string {
Expand All @@ -31,6 +32,8 @@ func (p Provider) DisplayName() string {
return "Digital Ocean"
case "openstack":
return "OpenStack"
case "cloudstack":
return "Cloudstack"
default:
return strings.Title(strings.ToLower(string(p)))
}
Expand Down

0 comments on commit d55c6de

Please sign in to comment.