Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add Azure Virtual Network Gateway scanner #237

Merged
merged 14 commits into from
May 16, 2024
Merged
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
Loading