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 13, 2023
1 parent ce9ad0f commit 81332dc
Show file tree
Hide file tree
Showing 4 changed files with 1,421 additions and 0 deletions.
176 changes: 176 additions & 0 deletions pkg/monitor/azure/nsg/nsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package nsg

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

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

"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/sirupsen/logrus"

"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"
)

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"
DimTenantID = "tenantid"

MetricInvalidDenyRule = "preconfigurednsg.invalid.denyrule"
MetricSubnetAcessResponseCode = "preconfiguredNSG.subnetaccess.responsecode"
MetricSubnetAccessForbidden = "preconfigurednsg.subnetaccess.forbidden"
MetricUnsuccessfulFPCreation = "preconfigurednsg.fpcreation.unsuccessful"
MetricNSGMonitoringTimedOut = "preconfigurednsg.monitoring.timedout"

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
done chan error
}

func (n *NSGMonitor) Done() <-chan error {
return n.done
}

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

subnetCli: network.NewSubnetsClient(environment, subscriptionID, cred),
done: make(chan error),
}
}

type subnetNSGConfig struct {
// subnet CIDR range
prefix netip.Prefix
// The rules from the subnet NSG
nsg *mgmtnetwork.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) {
masterSubnet, err := n.toSubnetConfig(ctx, n.oc.Properties.MasterProfile.SubnetID)
if err != nil {
// FP has no access to the subnet
n.done <- err
return
}

// 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
n.done <- err
return
}
workerSubnets = append(workerSubnets, s)
workerPrefixes = append(workerPrefixes, s.prefix)
}

// to make sure each NSG is processed only once
nsgSet := map[string]*mgmtnetwork.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 == mgmtnetwork.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)
}
}
}
n.done <- nil
}
Loading

0 comments on commit 81332dc

Please sign in to comment.