Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
[RM-6342] ARM template support (#278)
Browse files Browse the repository at this point in the history
* Initial ARM support

* [RM-6342] Fix build error

* [RM-6342] Cleanup branch

* [RM-6342] Add rule, test, and input skeletons (#257)

* [RM-6342] Add rule, test, and input skeletons

* [RM-6342] Add CIS-Azure mappings

* [RM-6342] Fix reference in test

* [RM-6366] ARM resource view update & rules for database firewalls

This adds a refinement of the resource view, and three rules:

- FG_R00221 SQL Server firewall rules should not permit start and end IP addresses to be 0.0.0.0
- FG_R00222 MySQL Database server firewall rules should not permit start and end IP addresses to be 0.0.0.0
- FG_R00223 PostgreSQL Database server firewall rules should not permit start and end IP addresses to be 0.0.0.0

* [RM-6366] Update tests

* [RM-6342] Add ARM authorization rule (#262)

* [RM-6366] Add database enforce SSL rules

* [RM-6342] Add ARM app_service rules (#260)

* [RM-6342] Add ARM app_service rules

* [RM-6342] Use number[] comparison for min TLS version

* [RM-6342] Add ARM key_vault rule (#263)

* [RM-6366] Add ARM no inbound 22/3389 rules

* [RM-6342] Add ARM kubernetes rule (#264)

* [RM-6342] Add ARM storage rules (#266)

* [RM-6366] Add gateway_waf_enabled and flow_log_retention ARM network rules

* Add arm/network/app_gateway_waf_enabled rule
* Add arm/network/flow_log_retention rule

* [RM-6366] Add PostgreSQL configuration rules

* [RM-6366] Add ARM SQL auditing rules

* [RM-6387] Add ARM monitor rules (#270)

* [RM-6387] Add ARM monitor rules

* [RM-6387] Use tokenize to find key vault name

* [RM-6366] Add ARM VM disk encryption rules

* [RM-6387] Add ARM app_service "Register with Azure Active Directory" rule (#272)

* [RM-6366] Add arm/key_vault/secret_expiry rule

* [RM-6387] Add ARM securty contact notifications rule (#274)

* [RM-6342] Add arm to inline docs

* [RM-6366] Add arm/storage/account_queue_logging rule (#277)

* [RM-6414] Update some Azure rule descriptions (#276)

* [RM-6414] Update some Azure rule descriptions

* [RM-6414] Fix quotes

* [RM-6342] Add changelog entry

* [RM-6342] Fix indentation in help text

Co-authored-by: Richard Park <[email protected]>
Co-authored-by: Jasper Van der Jeugt <[email protected]>
  • Loading branch information
3 people authored Dec 9, 2021
1 parent 961732a commit 1b90453
Show file tree
Hide file tree
Showing 152 changed files with 9,990 additions and 2 deletions.
3 changes: 3 additions & 0 deletions changes/unreleased/Added-20211209-162551.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Azure Resource Manager (ARM) template support with 38 rules. This feature is
currently in preview.
1 change: 1 addition & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Input types:
cfn CloudFormation template in YAML or JSON format
tf Terraform directory or file
k8s Kubernetes manifest in YAML format
arm Azure Resource Manager (ARM) JSON templates (feature in preview)
`
const formatDescriptions = `
Output formats:
Expand Down
110 changes: 110 additions & 0 deletions pkg/loader/arm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2021 Fugue, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package loader

import (
"encoding/json"
"fmt"
)

var validArmExts map[string]bool = map[string]bool{
".json": true,
}

type ArmDetector struct{}

func (c *ArmDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) {
if !opts.IgnoreExt && !validArmExts[i.Ext()] {
return nil, fmt.Errorf("File does not have .json extension: %v", i.Path())
}
contents, err := i.Contents()
if err != nil {
return nil, err
}

template := &armTemplate{}
if err := json.Unmarshal(contents, &template.Contents); err != nil {
return nil, fmt.Errorf("Failed to parse file as JSON %v: %v", i.Path(), err)
}
_, hasSchema := template.Contents["$schema"]
_, hasResources := template.Contents["resources"]

if !hasSchema && !hasResources {
return nil, fmt.Errorf("Input file is not an ARM template: %v", i.Path())
}
path := i.Path()

return &armConfiguration{
path: path,
template: *template,
}, nil
}

func (c *ArmDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) {
return nil, nil
}

type armConfiguration struct {
path string
template armTemplate
source *SourceInfoNode
}

func (l *armConfiguration) RegulaInput() RegulaInput {
return RegulaInput{
"filepath": l.path,
"content": l.template.Contents,
}
}

func (l *armConfiguration) Location(path []string) (LocationStack, error) {
if l.source == nil || len(path) < 1 {
return nil, nil
}

resourcePath := []string{"Resources"}
resourcePath = append(resourcePath, path[0])
resource, err := l.source.GetPath(resourcePath)
if err != nil {
return nil, nil
}
resourceLine, resourceColumn := resource.Location()
resourceLocation := Location{
Path: l.path,
Line: resourceLine,
Col: resourceColumn,
}

properties, err := resource.GetKey("Properties")
if err != nil {
return []Location{resourceLocation}, nil
}

attribute, err := properties.GetPath(path[1:])
if attribute != nil {
return []Location{resourceLocation}, nil
}

line, column := attribute.Location()
return []Location{{Path: l.path, Line: line, Col: column}}, nil
}

func (l *armConfiguration) LoadedFiles() []string {
return []string{l.path}
}

type armTemplate struct {
Contents map[string]interface{}
}
3 changes: 3 additions & 0 deletions pkg/loader/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const (
Tf
// Kubernetes manifests will be loaded
K8s
// Azure Resource Manager JSON
Arm
)

// InputTypeIDs maps the InputType enums to string values that can be specified in
Expand All @@ -57,6 +59,7 @@ var InputTypeIDs = map[InputType][]string{
Cfn: {"cfn"},
Tf: {"tf"},
K8s: {"k8s", "kubernetes"},
Arm: {"arm"},
}

var DefaultInputTypes = InputTypeIDs[Auto]
Expand Down
3 changes: 3 additions & 0 deletions pkg/loader/loadpaths.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ func detectorByInputType(inputType InputType) (ConfigurationDetector, error) {
&TfPlanDetector{},
&TfDetector{},
&KubernetesDetector{},
&ArmDetector{},
), nil
case Cfn:
return &CfnDetector{}, nil
Expand All @@ -265,6 +266,8 @@ func detectorByInputType(inputType InputType) (ConfigurationDetector, error) {
return &TfDetector{}, nil
case K8s:
return &KubernetesDetector{}, nil
case Arm:
return &ArmDetector{}, nil
default:
return nil, fmt.Errorf("Unsupported input type: %v", inputType)
}
Expand Down
49 changes: 49 additions & 0 deletions rego/lib/arm/disk_encryption_library.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2020 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This helper rego code works for Azure "no ingress" rules.
# It is built on top of the terraform code that does the same,
# through a simple conversion (`rule_to_tf`).
package fugue.arm.disk_encryption_library

import data.fugue

disks := fugue.resources("Microsoft.Compute/disks")

virtual_machines := fugue.resources("Microsoft.Compute/virtualMachines")

data_disk_ids := {id |
disk := virtual_machines[_].properties.storageProfile.dataDisks[_]
id := disk.managedDisk.id
}

os_disk_ids := {id |
id := virtual_machines[_].properties.storageProfile.osDisk.managedDisk.id
}

unattached_disk_ids := {id |
_ := disks[id]
not data_disk_ids[id]
not os_disk_ids[id]
}

disk_encrypted(disk) {
disk.properties.encryptionSettingsCollection.enabled == true
}

disk_encrypted(disk) {
des_id := disk.properties.encryption.diskEncryptionSetId
is_string(des_id)
des_id != ""
}
69 changes: 69 additions & 0 deletions rego/lib/arm/network_security_group_library.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright 2020 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This helper rego code works for Azure "no ingress" rules.
# It is built on top of the terraform code that does the same,
# through a simple conversion (`rule_to_tf`).
package fugue.arm.network_security_group_library

import data.fugue
import data.fugue.azure.network_security_group as tf

rule_to_tf(arm_rule) = ret {
# We only pass in the attributes we actually use.
props = arm_rule.properties
ret := {
"access": props.access,
"direction": props.direction,
"destination_port_range": object.get(props, "destinationPortRange", null),
"destination_port_ranges": object.get(props, "destinationPortRanges", []),
"source_address_prefix": object.get(props, "sourceAddressPrefix", null),
"source_address_prefixes": object.get(props, "sourceAddressPrefixes", null),
}
}

rule_allows_anywhere_to_port(rule, bad_port) {
tf.rule_allows_anywhere_to_port(rule_to_tf(rule), bad_port)
}

group_allows_anywhere_to_port(group, bad_port) {
rule = group.properties.securityRules[_]
tf.rule_allows_anywhere_to_port(rule_to_tf(rule), bad_port)
}

no_inbound_anywhere_to_port_policy(port) = ret {
security_groups := fugue.resources("Microsoft.Network/networkSecurityGroups")
security_groups_policy = {p |
security_group := security_groups[_]
group_allows_anywhere_to_port(security_group, port)
p := fugue.deny_resource(security_group)
} | {p |
security_group := security_groups[_]
not group_allows_anywhere_to_port(security_group, port)
p := fugue.allow_resource(security_group)
}

security_rules := fugue.resources("Microsoft.Network/networkSecurityGroups/securityRules")
security_rules_policy = {p |
security_rule := security_rules[_]
rule_allows_anywhere_to_port(security_rule, port)
p := fugue.deny_resource(security_rule)
} | {p |
security_rule := security_rules[_]
not rule_allows_anywhere_to_port(security_rule, port)
p := fugue.allow_resource(security_rule)
}

ret := security_groups_policy | security_rules_policy
}
47 changes: 47 additions & 0 deletions rego/lib/arm/postgresql_configuration_library.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

package fugue.arm.postgresql_configuration_library

import data.fugue

servers = fugue.resources("Microsoft.DBforPostgreSQL/servers")

configurations = fugue.resources("Microsoft.DBforPostgreSQL/servers/configurations")

configuration_defaults := {
"connection_throttling": "on",
"log_checkpoints": "on",
"log_connections": "on",
"log_disconnections": "off",
"log_duration": "off",
"log_retention_days": "3",
}

configuration_by_server_id := {server_id: configuration |
server := servers[server_id]
configuration := {name: value |
option := configurations[_]
option._parent_id == server_id
id_parts := split(option.id, "/")
name := id_parts[count(id_parts) - 1]
value := option.properties.value
}
}

configuration_value(server, name) = ret {
ret := configuration_by_server_id[server.id][name]
} else = ret {
ret := configuration_defaults[name]
}
8 changes: 8 additions & 0 deletions rego/lib/fugue/input_type.rego
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package fugue.input_type_internal
# - "tf_runtime"
# - "cfn"
# - "k8s"
# - "arm"
#
# To check the current resource type, use `input_type`.
# To check if a rule applies for this input type, use `compatibility`.
Expand All @@ -40,6 +41,8 @@ input_type = "tf" {
_ = input.AWSTemplateFormatVersion
} else = "k8s" {
_ = input.k8s_resource_view_version
} else = "arm" {
_ = input.contentVersion
} else = "unknown" {
true
}
Expand All @@ -58,6 +61,10 @@ kubernetes_input_type {
input_type == "k8s"
}

arm_input_type {
input_type == "arm"
}

rule_input_type(pkg) = ret {
# This is a workaround for an issue in fregot, where the next line will fail
# the typechecker when there isn't a single `input_type` defined, which is
Expand All @@ -77,4 +84,5 @@ compatibility := {
"cfn": {"cfn"},
"cloudformation": {"cfn"}, # Backwards-compatibility
"k8s": {"k8s"},
"arm": {"arm"},
}
7 changes: 7 additions & 0 deletions rego/lib/fugue/resource_view.rego
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import data.fugue.input_type_internal
import data.fugue.resource_view.cloudformation
import data.fugue.resource_view.terraform
import data.fugue.resource_view.kubernetes
import data.fugue.resource_view.arm

resource_view = ret {
# If we are already given a resource view, just pass it through.
Expand All @@ -35,6 +36,9 @@ resource_view = ret {
} else = ret {
input_type_internal.kubernetes_input_type
ret = kubernetes.resource_view
} else = ret {
input_type_internal.arm_input_type
ret = arm.resource_view
}

resource_view_input = ret {
Expand All @@ -49,4 +53,7 @@ resource_view_input = ret {
} else = ret {
input_type_internal.kubernetes_input_type
ret = {"resources": resource_view, "_template": input}
} else = ret {
input_type_internal.arm_input_type
ret = {"resources": resource_view, "_template": input}
}
Loading

0 comments on commit 1b90453

Please sign in to comment.