Skip to content

Commit

Permalink
Add NSG monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
nwnt committed Sep 11, 2023
1 parent ce9ad0f commit aceb852
Show file tree
Hide file tree
Showing 5 changed files with 1,420 additions and 0 deletions.
166 changes: 166 additions & 0 deletions pkg/monitor/azure/nsg/nsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package nsg

Check failure on line 1 in pkg/monitor/azure/nsg/nsg.go

View workflow job for this annotation

GitHub Actions / validate-go

group 3: duplicate group or invalid group ordering

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"
"net/http"
"net/netip"
"strings"

"github.com/sirupsen/logrus"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
sdknetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network"

Check failure on line 15 in pkg/monitor/azure/nsg/nsg.go

View workflow job for this annotation

GitHub Actions / golangci-lint

import "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" imported as "sdknetwork" but must be "mgmtnetwork" according to config (importas)

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/metrics"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
"github.com/Azure/ARO-RP/pkg/util/refreshable"
)

const (
dimSubnet = "subnet"
dimVnet = "vNet"
dimResourceGroup = "resourceGroup"
dimSubscriptionID = "subscriptionID"
dimSecurityGroup = "networkSecurityGroup"
dimNSGRuleName = "ruleName"
dimNSGRuleSources = "sources"
dimNSGRuleDestinations = "destinations"
dimNSGRuleDirection = "direction"
dimNSGRulePriority = "priority"
dimClusterResourceID = "clusterresourceid"
dimLocation = "location"

metricInvalidDenyRule = "preconfigurednsg.invalid.denyrule"
metricSubnetAcessResponseCode = "preconfiguredNSG.subnetaccess.responsecode"
metricSubnetAccessForbidden = "preconfigurednsg.subnetaccess.forbidden"

expandNSG = "NetworkSecurityGroup"
)

// NSGMonitor is responsible for performing NSG rule validations when preconfiguredNSG is enabled
type NSGMonitor struct {
log *logrus.Entry
emitter metrics.Emitter
oc *api.OpenShiftCluster

subnetCli network.SubnetsClient
}

func NewNSGMonitor(log *logrus.Entry, oc *api.OpenShiftCluster, subscriptionID string, environment *azureclient.AROEnvironment, cred refreshable.Authorizer, emitter metrics.Emitter) *NSGMonitor {
return &NSGMonitor{
log: log,
emitter: emitter,
oc: oc,

subnetCli: network.NewSubnetsClient(environment, subscriptionID, cred),
}
}

type subnetNSGConfig struct {
// subnet CIDR range
prefix netip.Prefix
// The rules from the subnet NSG
nsg *sdknetwork.SecurityGroup
}

func (n *NSGMonitor) toSubnetConfig(ctx context.Context, subnetID string) (subnetNSGConfig, error) {
r, err := arm.ParseResourceID(subnetID)
if err != nil {
return subnetNSGConfig{}, err
}

dims := map[string]string{
dimClusterResourceID: n.oc.ID,
dimLocation: n.oc.Location,
dimSubnet: r.Name,
dimVnet: r.Parent.Name,
dimResourceGroup: r.ResourceGroupName,
dimSubscriptionID: r.SubscriptionID,
}

subnet, err := n.subnetCli.Get(ctx, r.ResourceGroupName, r.Parent.Name, r.Name, expandNSG)
// response code is always returned as part of autorest
n.emitter.EmitGauge(metricSubnetAcessResponseCode, int64(subnet.StatusCode), dims)
if err != nil {
if subnet.StatusCode == http.StatusForbidden {
n.emitter.EmitGauge(metricSubnetAccessForbidden, int64(1), dims)
}
n.log.Errorf("Error while getting subnet %s. %s", subnetID, err)
return subnetNSGConfig{}, err
}

cidr, err := toPrefix(*subnet.AddressPrefix)
if err != nil {
return subnetNSGConfig{}, err
}
return subnetNSGConfig{cidr, subnet.NetworkSecurityGroup}, nil
}

func (n *NSGMonitor) Monitor(ctx context.Context) error {
masterSubnet, err := n.toSubnetConfig(ctx, n.oc.Properties.MasterProfile.SubnetID)
if err != nil {
// FP has no access to the subnet
return err
}

// need this to get the right workerProfiles
workerProfiles, _ := api.GetEnrichedWorkerProfiles(n.oc.Properties)
workerSubnets := make([]subnetNSGConfig, 0, len(workerProfiles))
workerPrefixes := make([]netip.Prefix, 0, len(workerProfiles))
for _, wp := range workerProfiles {
s, err := n.toSubnetConfig(ctx, wp.SubnetID)
if err != nil {
// FP has no access to the subnet
return err
}
workerSubnets = append(workerSubnets, s)
workerPrefixes = append(workerPrefixes, s.prefix)
}

// to make sure each NSG is processed only once
nsgSet := map[string]*sdknetwork.SecurityGroup{
*masterSubnet.nsg.ID: masterSubnet.nsg,
}
for _, w := range workerSubnets {
nsgSet[*w.nsg.ID] = w.nsg
}

for nsgID, nsg := range nsgSet {
for _, rule := range *nsg.SecurityRules {
if rule.Access == sdknetwork.SecurityRuleAccessAllow {
// Allow rule - skip.
continue
}
// Deny rule
nsgResource, err := arm.ParseResourceID(nsgID)
if err != nil {
n.log.Errorf("Unable to parse NSG resource ID: %s. %s", nsgID, err)
continue
}

r := newRuleChecker(n.log, masterSubnet.prefix, workerPrefixes, &rule)

if r.isInvalidDenyRule() {
dims := map[string]string{
dimClusterResourceID: n.oc.ID,
dimLocation: n.oc.Location,
dimSubscriptionID: nsgResource.SubscriptionID,
dimResourceGroup: nsgResource.ResourceGroupName,
dimSecurityGroup: nsgResource.Name,
dimNSGRuleName: *rule.Name,
dimNSGRuleSources: strings.Join(r.sourceStrings, ","),
dimNSGRuleDestinations: strings.Join(r.destinationStrings, ","),
dimNSGRuleDirection: string(rule.Direction),
dimNSGRulePriority: string(*rule.Priority),
}
n.emitter.EmitGauge(metricInvalidDenyRule, int64(1), dims)
}
}
}
return nil
}
Loading

0 comments on commit aceb852

Please sign in to comment.