Skip to content

Commit

Permalink
feature: add Azure Virtual Network Gateway scanner (#237)
Browse files Browse the repository at this point in the history
add Azure Virtual Network Gateway scanner

---------

Co-authored-by: Seppe Van WInkel <van.winkel.seppe@me.com>
  • Loading branch information
cmendible and vanwinkelseppe committed May 16, 2024
1 parent a3c2156 commit 9076e3d
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Jetbrains IDE
.idea/
# Dependency directories (remove the comment below to include it)
# vendor/

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y
* Azure Database for PostgreSQL Single Server
* Azure Event Grid
* Azure Event Hub
* Azure ExpressRoute Gateway
* Azure Firewall
* Azure Front Door
* Azure Functions
* Azure Key Vault
* Azure Kubernetes Service
* Azure Load Balancer
* Azure Local Gateway
* Azure Logic Apps
* Azure Managed Grafana
* Azure Service Bus
Expand All @@ -107,6 +109,7 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y
* Azure Virtual Machine
* Azure Virtual Network
* Azure Virtual WAN
* Azure VPN Gateway
* Azure Web PubSub

## Usage
Expand Down
28 changes: 28 additions & 0 deletions cmd/azqr/vgw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package azqr

import (
"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azqr/internal/scanners/vgw"
"github.com/spf13/cobra"
)

func init() {
scanCmd.AddCommand(vgwCmd)
}

var vgwCmd = &cobra.Command{
Use: "vgw",
Short: "Scan Virtual Network Gateway",
Long: "Scan Virtual Network Gateway",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
serviceScanners := []scanners.IAzureScanner{
&vgw.VirtualNetworkGatewayScanner{},
}

scan(cmd, serviceScanners)
},
}
3 changes: 3 additions & 0 deletions docs/content/en/docs/Overview/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y
* Azure Database for PostgreSQL Single Server
* Azure Event Grid
* Azure Event Hub
* Azure ExpressRoute Gateway
* Azure Firewall
* Azure Front Door
* Azure Functions
* Azure Key Vault
* Azure Kubernetes Service
* Azure Load Balancer
* Azure Local Gateway
* Azure Logic Apps
* Azure Managed Grafana
* Azure Service Bus
Expand All @@ -103,6 +105,7 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y
* Azure Virtual Machine Scale Set
* Azure Virtual Network
* Azure Virtual WAN
* Azure VPN Gateway
* Azure Web PubSub

## Code of Conduct
Expand Down
2 changes: 2 additions & 0 deletions internal/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (
"github.com/Azure/azqr/internal/scanners/st"
"github.com/Azure/azqr/internal/scanners/synw"
"github.com/Azure/azqr/internal/scanners/traf"
"github.com/Azure/azqr/internal/scanners/vgw"
"github.com/Azure/azqr/internal/scanners/vm"
"github.com/Azure/azqr/internal/scanners/vmss"
"github.com/Azure/azqr/internal/scanners/vnet"
Expand Down Expand Up @@ -483,6 +484,7 @@ func GetScanners() []scanners.IAzureScanner {
&vm.VirtualMachineScanner{},
&vmss.VirtualMachineScaleSetScanner{},
&vnet.VirtualNetworkScanner{},
&vgw.VirtualNetworkGatewayScanner{},
&wps.WebPubSubScanner{},
}
}
91 changes: 91 additions & 0 deletions internal/scanners/vgw/rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package vgw

import (
"strings"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5"
)

// GetRules - Returns the rules for the VirtualNetworkGatewayScanner
func (a *VirtualNetworkGatewayScanner) GetRules() map[string]scanners.AzureRule {
return a.GetVirtualNetworkGatewayRules()
}

// GetVirtualNetworkGatewayRules - Returns the rules for the VirtualNetworkGatewayScanner
func (a *VirtualNetworkGatewayScanner) GetVirtualNetworkGatewayRules() map[string]scanners.AzureRule {
return map[string]scanners.AzureRule{
"vgw-001": {
Id: "vgw-001",
Category: scanners.RulesCategoryMonitoringAndAlerting,
Recommendation: "Virtual Network Gateway should have diagnostic settings enabled",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
service := target.(*armnetwork.VirtualNetworkGateway)
_, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)]
return !ok, ""
},
Url: "https://learn.microsoft.com/en-us/azure/vpn-gateway/monitor-vpn-gateway",
},
"vgw-002": {
Id: "vgw-002",
Category: scanners.RulesCategoryGovernance,
Recommendation: "Virtual Network Gateway Name should comply with naming conventions",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armnetwork.VirtualNetworkGateway)
switch *c.Properties.GatewayType {
case armnetwork.VirtualNetworkGatewayTypeVPN:
return !strings.HasPrefix(*c.Name, "vpng"), ""
case armnetwork.VirtualNetworkGatewayTypeExpressRoute:
return !strings.HasPrefix(*c.Name, "ergw"), ""
default:
return !strings.HasPrefix(*c.Name, "lgw"), ""
}
},
Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations",
},
"vgw-003": {
Id: "vgw-003",
Category: scanners.RulesCategoryGovernance,
Recommendation: "Virtual Network Gateway should have tags",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armnetwork.VirtualNetworkGateway)
return len(c.Tags) == 0, ""
},
Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json",
},
"vgw-004": {
Id: "vgw-004",
Category: scanners.RulesCategoryHighAvailability,
Recommendation: "Virtual Network Gateway should have a SLA",
Impact: scanners.ImpactHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
g := target.(*armnetwork.VirtualNetworkGateway)
sku := string(*g.Properties.SKU.Tier)
sla := "99.9%"
if sku != string(armnetwork.VirtualNetworkGatewaySKUTierBasic) {
sla = "99.95%"
}
return false, sla
},
Url: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services",
},
"vgw-005": {
Id: "vgw-005",
Category: scanners.RulesCategoryHighAvailability,
Recommendation: "Storage should have availability zones enabled",
Impact: scanners.ImpactHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
g := target.(*armnetwork.VirtualNetworkGateway)
sku := string(*g.Properties.SKU.Name)
return !strings.HasSuffix(strings.ToLower(sku), "az"), ""
},
Url: "https://learn.microsoft.com/en-us/azure/vpn-gateway/create-zone-redundant-vnet-gateway",
},
}
}
148 changes: 148 additions & 0 deletions internal/scanners/vgw/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package vgw

import (
"reflect"
"testing"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azqr/internal/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5"
)

func TestVirtualNetworkGatewayScanner_Rules(t *testing.T) {
type fields struct {
rule string
target interface{}
scanContext *scanners.ScanContext
}
type want struct {
broken bool
result string
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "VirtualNetworkGatewayScanner DiagnosticSettings",
fields: fields{
rule: "vgw-001",
target: &armnetwork.VirtualNetworkGateway{
ID: to.Ptr("test"),
},
scanContext: &scanners.ScanContext{
DiagnosticsSettings: map[string]bool{
"test": true,
},
},
},
want: want{
broken: false,
result: "",
},
},
{
name: "VirtualNetworkGatewayScanner CAF",
fields: fields{
rule: "vgw-002",
target: &armnetwork.VirtualNetworkGateway{
Name: to.Ptr("vpng-test"),
Properties: &armnetwork.VirtualNetworkGatewayPropertiesFormat{
GatewayType: to.Ptr(armnetwork.VirtualNetworkGatewayTypeVPN),
},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "",
},
},
{
name: "VirtualNetworkGatewayScanner SLA 99.9%",
fields: fields{
rule: "vgw-004",
target: &armnetwork.VirtualNetworkGateway{
Properties: &armnetwork.VirtualNetworkGatewayPropertiesFormat{
SKU: &armnetwork.VirtualNetworkGatewaySKU{
Tier: to.Ptr(armnetwork.VirtualNetworkGatewaySKUTierBasic),
}},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "99.9%",
},
},
{
name: "VirtualNetworkGatewayScanner SLA 99.9%",
fields: fields{
rule: "vgw-004",
target: &armnetwork.VirtualNetworkGateway{
Properties: &armnetwork.VirtualNetworkGatewayPropertiesFormat{
SKU: &armnetwork.VirtualNetworkGatewaySKU{
Tier: to.Ptr(armnetwork.VirtualNetworkGatewaySKUTierErGw1AZ),
}},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "99.95%",
},
},
{
name: "VirtualNetworkGatewayScanner without AZ",
fields: fields{
rule: "vgw-005",
target: &armnetwork.VirtualNetworkGateway{
Properties: &armnetwork.VirtualNetworkGatewayPropertiesFormat{
SKU: &armnetwork.VirtualNetworkGatewaySKU{
Name: to.Ptr(armnetwork.VirtualNetworkGatewaySKUNameBasic),
}},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: true,
result: "",
},
},
{
name: "VirtualNetworkGatewayScanner with AZ",
fields: fields{
rule: "vgw-005",
target: &armnetwork.VirtualNetworkGateway{
Properties: &armnetwork.VirtualNetworkGatewayPropertiesFormat{
SKU: &armnetwork.VirtualNetworkGatewaySKU{
Name: to.Ptr(armnetwork.VirtualNetworkGatewaySKUNameErGw1AZ),
}},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &VirtualNetworkGatewayScanner{}
rules := s.GetVirtualNetworkGatewayRules()
b, w := rules[tt.fields.rule].Eval(tt.fields.target, tt.fields.scanContext)
got := want{
broken: b,
result: w,
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("VirtualNetworkGatewayScanner Rule.Eval() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 9076e3d

Please sign in to comment.