Skip to content

Commit

Permalink
feat(checks): add check for policy default values (#182)
Browse files Browse the repository at this point in the history
* feat(check): add check for defaults

* feat: refactor check defaults to reference the definition rather then the assignment

* chore: rename err message
  • Loading branch information
matt-FFFFFF authored Oct 26, 2024
1 parent f763e81 commit 0d34966
Show file tree
Hide file tree
Showing 20 changed files with 388 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ coverage.html

# compiled binary
/alzlibtool

# goreleaser files
dist/
27 changes: 23 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@ before:
hooks:
- go mod tidy
builds:
- env:
- id: alzlibtool
env:
# goreleaser does not work with CGO, it could also complicate
# usage by users in CI/CD systems like Terraform Cloud where
# they are unable to install libraries.
- CGO_ENABLED=0
no_main_check: true
skip: true
mod_timestamp: "{{ .CommitTimestamp }}"
flags:
- -trimpath
ldflags:
- '-s -w -X "github.com/Azure/alzlib/cmd/alzlibtool/command.version={{.Version}}" -X main.commit={{.Commit}}'
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- amd64
- "386"
- arm
- arm64
ignore:
- goos: darwin
goarch: "386"
binary: "alzlibtool"
dir: cmd/alzlibtool
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}-next"
version_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
use: github
Expand Down
9 changes: 0 additions & 9 deletions alzlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,15 +493,6 @@ func (az *AlzLib) GetDefinitionsFromAzure(ctx context.Context, pds []string) err
return nil
}

// DefaultPolicyAssignmentValues returns the DefaultPolicyAssignmentValuesValue associated with the given defaultName.
// If the defaultName does not exist in the defaultPolicyAssignmentValues map, it returns nil.
func (az *AlzLib) DefaultPolicyAssignmentValues(defaultName string) DefaultPolicyAssignmentValuesValue {
if _, ok := az.defaultPolicyAssignmentValues[defaultName]; !ok {
return nil
}
return az.defaultPolicyAssignmentValues[defaultName].copy()
}

// AssignmentReferencedDefinitionHasParameter checks if the referenced definition of an assignment has a specific parameter.
// It takes a resource ID and a parameter name as input and returns a boolean indicating whether the parameter exists or not.
func (az *AlzLib) AssignmentReferencedDefinitionHasParameter(res *arm.ResourceID, param string) bool {
Expand Down
1 change: 1 addition & 0 deletions cmd/alzlibtool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alzlibtool
1 change: 1 addition & 0 deletions cmd/alzlibtool/command/check/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var libraryCmd = cobra.Command{
checks.CheckAllDefinitionsAreReferenced,
checks.CheckAllArchitectures,
checks.CheckLibraryMemberPath,
checks.CheckDefaults,
)
err = chk.Validate(az)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion deployment/hierarchy.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (h *Hierarchy) PolicyRoleAssignments(ctx context.Context) (mapset.Set[Polic

// AddDefaultPolicyAssignmentValue adds a default policy assignment value to the hierarchy.
func (h *Hierarchy) AddDefaultPolicyAssignmentValue(ctx context.Context, defaultName string, defaultValue *armpolicy.ParameterValuesValue) error {
defs := h.alzlib.DefaultPolicyAssignmentValues(defaultName)
defs := h.alzlib.PolicyDefaultValue(defaultName)
if defs == nil {
return fmt.Errorf("Hierarchy.AddDefaultPolicyAssignmentValue: A default with name `%s` does not exist", defaultName)
}
Expand Down
49 changes: 49 additions & 0 deletions internal/tools/checks/checkDefaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package checks

import (
"fmt"

"github.com/Azure/alzlib"
"github.com/Azure/alzlib/internal/tools/checker"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
)

var CheckDefaults = checker.NewValidatorCheck("All defaults are valid", checkDefaults)

func checkDefaults(azany any) error {
az, ok := azany.(*alzlib.AlzLib)
if !ok {
return fmt.Errorf("checkDefaults: expected *alzlib.AlzLib, got %T", azany)
}
defs := az.PolicyDefaultValues()
for _, def := range defs {
pdv := az.PolicyDefaultValue(def)
for _, assignment := range pdv.Assignments() {
a := az.PolicyAssignment(assignment)
if a == nil {
return fmt.Errorf("checkDefaults: policy assignment `%s`, referenced by default `%s` is not found in the library", assignment, def)
}
// We need to check that the referenced definition has the parameter as it may not be present in the assignment (e.g. if it has a default value)
// First let's get the referenced policy definition id and parse it into a resource id type.
var pdIdStr string
if a.Properties.PolicyDefinitionID == nil {
return fmt.Errorf("checkDefaults: policy assignment `%s`, referenced by default `%s` does not have a policy definition ID", assignment, def)
}
pdIdStr = *a.Properties.PolicyDefinitionID
pdResId, err := arm.ParseResourceID(pdIdStr)
if err != nil {
return fmt.Errorf("checkDefaults: policy assignment `%s`, referenced by default `%s` has an invalid policy definition ID", assignment, def)
}
// Now we can check that the parameters are present in the referenced definition
for _, param := range pdv.AssignmentParameters(assignment) {
if !az.AssignmentReferencedDefinitionHasParameter(pdResId, param) {
return fmt.Errorf("checkDefaults: policy assignment `%s`, referenced by default `%s` has a parameter `%s` that is not present in the referenced definition", assignment, def, param)
}
}
}
}
return nil
}
40 changes: 40 additions & 0 deletions internal/tools/checks/checkDefaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package checks

import (
"context"
"testing"

"github.com/Azure/alzlib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCheckDefaultsGood(t *testing.T) {
az := alzlib.NewAlzLib(nil)
ctx := context.Background()
lib := alzlib.NewCustomLibraryReference("testdata/defaultsgood")
_, err := lib.Fetch(ctx, "0")
require.NoError(t, err)
require.NoError(t, az.Init(ctx, lib))
assert.NoError(t, checkDefaults(az))
}

func TestCheckDefaultsAssignmentNotPresent(t *testing.T) {
az := alzlib.NewAlzLib(nil)
ctx := context.Background()
lib := alzlib.NewCustomLibraryReference("testdata/defaultsassignmentnotpresent")
_, err := lib.Fetch(ctx, "0")
require.NoError(t, err)
require.NoError(t, az.Init(ctx, lib))
assert.ErrorContains(t, checkDefaults(az), "policy assignment `not_present`, referenced by default `test` is not found in the library")
}

func TestCheckDefaultsParameterNotPresent(t *testing.T) {
az := alzlib.NewAlzLib(nil)
ctx := context.Background()
lib := alzlib.NewCustomLibraryReference("testdata/defaultsparameternotpresent")
_, err := lib.Fetch(ctx, "0")
require.NoError(t, err)
require.NoError(t, az.Init(ctx, lib))
assert.ErrorContains(t, checkDefaults(az), "policy assignment `test-policy-assignment`, referenced by default `test` has a parameter `not_present` that is not present in the referenced definition")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
defaults:
- default_name: test
policy_assignments:
- policy_assignment_name: not_present
parameter_names:
- effect
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: test
policy_assignments:
- test-policy-assignment
policy_definitions:
- test-policy-definition
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"apiVersion": "2022-06-01",
"dependsOn": [],
"identity": {
"type": "None"
},
"location": "${default_location}",
"name": "test-policy-assignment",
"properties": {
"description": "This policy denies the network interfaces which enabled IP forwarding. The setting of IP forwarding disables Azure's check of the source and destination for a network interface. This should be reviewed by the network security team.",
"displayName": "Network interfaces should disable IP forwarding",
"enforcementMode": null,
"nonComplianceMessages": [
{
"message": "Network interfaces {enforcementMode} disable IP forwarding."
}
],
"notScopes": [],
"parameters": {},
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/test-policy-definition",
"scope": "/providers/Microsoft.Management/managementGroups/PLACEHOLDER"
},
"type": "Microsoft.Authorization/policyAssignments"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "test-policy-definition",
"type": "Microsoft.Authorization/policyDefinitions",
"apiVersion": "2021-06-01",
"scope": null,
"properties": {
"policyType": "Custom",
"mode": "Indexed",
"displayName": "Application Gateway should be deployed with WAF enabled",
"description": "This policy enables you to restrict that Application Gateways is always deployed with WAF enabled",
"metadata": {
"version": "1.0.0",
"category": "Network",
"source": "https://github.com/Azure/Enterprise-Scale/",
"alzCloudEnvironments": [
"AzureCloud",
"AzureChinaCloud",
"AzureUSGovernment"
]
},
"parameters": {
"effect": {
"type": "String",
"allowedValues": [
"Audit",
"Deny",
"Disabled"
],
"defaultValue": "Deny",
"metadata": {
"displayName": "Effect",
"description": "Enable or disable the execution of the policy"
}
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Network/applicationGateways"
},
{
"field": "Microsoft.Network/applicationGateways/sku.name",
"notequals": "WAF_v2"
}
]
},
"then": {
"effect": "[parameters('effect')]"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
defaults:
- default_name: test
policy_assignments:
- policy_assignment_name: test-policy-assignment
parameter_names:
- effect
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: test
policy_assignments:
- test-policy-assignment
policy_definitions:
- test-policy-definition
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"apiVersion": "2022-06-01",
"dependsOn": [],
"identity": {
"type": "None"
},
"location": "${default_location}",
"name": "test-policy-assignment",
"properties": {
"description": "This policy denies the network interfaces which enabled IP forwarding. The setting of IP forwarding disables Azure's check of the source and destination for a network interface. This should be reviewed by the network security team.",
"displayName": "Network interfaces should disable IP forwarding",
"enforcementMode": null,
"nonComplianceMessages": [
{
"message": "Network interfaces {enforcementMode} disable IP forwarding."
}
],
"notScopes": [],
"parameters": {},
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/test-policy-definition",
"scope": "/providers/Microsoft.Management/managementGroups/PLACEHOLDER"
},
"type": "Microsoft.Authorization/policyAssignments"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "test-policy-definition",
"type": "Microsoft.Authorization/policyDefinitions",
"apiVersion": "2021-06-01",
"scope": null,
"properties": {
"policyType": "Custom",
"mode": "Indexed",
"displayName": "Application Gateway should be deployed with WAF enabled",
"description": "This policy enables you to restrict that Application Gateways is always deployed with WAF enabled",
"metadata": {
"version": "1.0.0",
"category": "Network",
"source": "https://github.com/Azure/Enterprise-Scale/",
"alzCloudEnvironments": [
"AzureCloud",
"AzureChinaCloud",
"AzureUSGovernment"
]
},
"parameters": {
"effect": {
"type": "String",
"allowedValues": [
"Audit",
"Deny",
"Disabled"
],
"defaultValue": "Deny",
"metadata": {
"displayName": "Effect",
"description": "Enable or disable the execution of the policy"
}
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Network/applicationGateways"
},
{
"field": "Microsoft.Network/applicationGateways/sku.name",
"notequals": "WAF_v2"
}
]
},
"then": {
"effect": "[parameters('effect')]"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
defaults:
- default_name: test
policy_assignments:
- policy_assignment_name: test-policy-assignment
parameter_names:
- not_present
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: test
policy_assignments:
- test-policy-assignment
policy_definitions:
- test-policy-definition
Loading

0 comments on commit 0d34966

Please sign in to comment.