Skip to content

Commit

Permalink
azure: Add support for application security groups
Browse files Browse the repository at this point in the history
  • Loading branch information
hakman committed Jul 19, 2023
1 parent d9efa71 commit b2387e4
Show file tree
Hide file tree
Showing 10 changed files with 519 additions and 72 deletions.
10 changes: 10 additions & 0 deletions pkg/model/azuremodel/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ func (c *AzureModelContext) NameForLoadBalancer() string {
return "api-" + c.ClusterName()
}

// LinkToApplicationSecurityGroup returns the Application Security Group object for the InstanceGroup role.
func (c *AzureModelContext) LinkToApplicationSecurityGroup(role kops.InstanceGroupRole) *azuretasks.ApplicationSecurityGroup {
return &azuretasks.ApplicationSecurityGroup{Name: fi.PtrTo(c.NameForApplicationSecurityGroup(role))}
}

// NameForApplicationSecurityGroup returns the name of the Application Security Group object for the InstanceGroup role.
func (c *AzureModelContext) NameForApplicationSecurityGroup(role kops.InstanceGroupRole) string {
return role.ToLowerString() + "." + c.ClusterName()
}

// CloudTagsForInstanceGroup computes the tags to apply to instances in the specified InstanceGroup
// Mostly copied from pkg/model/context.go, but "/" in tag keys are replaced with "_" as Azure
// doesn't allow "/" in tag keys.
Expand Down
30 changes: 22 additions & 8 deletions pkg/model/azuremodel/vmscaleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ var _ fi.CloudupModelBuilder = &VMScaleSetModelBuilder{}

// Build is responsible for constructing the VM ScaleSet from the kops spec.
func (b *VMScaleSetModelBuilder) Build(c *fi.CloudupModelBuilderContext) error {
c.AddTask(&azuretasks.ApplicationSecurityGroup{
Name: fi.PtrTo(b.NameForApplicationSecurityGroup(kops.InstanceGroupRoleControlPlane)),
Lifecycle: b.Lifecycle,
ResourceGroup: b.LinkToResourceGroup(),
Tags: map[string]*string{},
})
c.AddTask(&azuretasks.ApplicationSecurityGroup{
Name: fi.PtrTo(b.NameForApplicationSecurityGroup(kops.InstanceGroupRoleNode)),
Lifecycle: b.Lifecycle,
ResourceGroup: b.LinkToResourceGroup(),
Tags: map[string]*string{},
})

for _, ig := range b.InstanceGroups {
name := b.AutoscalingGroupName(ig)
vmss, err := b.buildVMScaleSetTask(c, name, ig)
Expand Down Expand Up @@ -82,14 +95,15 @@ func (b *VMScaleSetModelBuilder) buildVMScaleSetTask(
azNumbers = append(azNumbers, az)
}
t := &azuretasks.VMScaleSet{
Name: fi.PtrTo(name),
Lifecycle: b.Lifecycle,
ResourceGroup: b.LinkToResourceGroup(),
VirtualNetwork: b.LinkToVirtualNetwork(),
SKUName: fi.PtrTo(ig.Spec.MachineType),
ComputerNamePrefix: fi.PtrTo(ig.Name),
AdminUser: fi.PtrTo(b.Cluster.Spec.CloudProvider.Azure.AdminUser),
Zones: azNumbers,
Name: fi.PtrTo(name),
Lifecycle: b.Lifecycle,
ResourceGroup: b.LinkToResourceGroup(),
VirtualNetwork: b.LinkToVirtualNetwork(),
ApplicationSecurityGroups: []*azuretasks.ApplicationSecurityGroup{b.LinkToApplicationSecurityGroup(ig.Spec.Role)},
SKUName: fi.PtrTo(ig.Spec.MachineType),
ComputerNamePrefix: fi.PtrTo(ig.Name),
AdminUser: fi.PtrTo(b.Cluster.Spec.CloudProvider.Azure.AdminUser),
Zones: azNumbers,
}

var err error
Expand Down
68 changes: 58 additions & 10 deletions pkg/resources/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ import (
)

const (
typeResourceGroup = "ResourceGroup"
typeVirtualNetwork = "VirtualNetwork"
typeNetworkSecurityGroup = "NetworkSecurityGroup"
typeSubnet = "Subnet"
typeRouteTable = "RouteTable"
typeVMScaleSet = "VMScaleSet"
typeDisk = "Disk"
typeRoleAssignment = "RoleAssignment"
typeLoadBalancer = "LoadBalancer"
typePublicIPAddress = "PublicIPAddress"
typeResourceGroup = "ResourceGroup"
typeVirtualNetwork = "VirtualNetwork"
typeNetworkSecurityGroup = "NetworkSecurityGroup"
typeApplicationSecurityGroup = "ApplicationSecurityGroup"
typeSubnet = "Subnet"
typeRouteTable = "RouteTable"
typeVMScaleSet = "VMScaleSet"
typeDisk = "Disk"
typeRoleAssignment = "RoleAssignment"
typeLoadBalancer = "LoadBalancer"
typePublicIPAddress = "PublicIPAddress"
)

// ListResourcesAzure lists all resources for the cluster by quering Azure.
Expand Down Expand Up @@ -87,6 +88,7 @@ func (g *resourceGetter) listAll() ([]*resources.Resource, error) {
g.listResourceGroups,
g.listVirtualNetworksAndSubnets,
g.listNetworkSecurityGroups,
g.listApplicationSecurityGroups,
g.listRouteTables,
g.listVMScaleSetsAndRoleAssignments,
g.listDisks,
Expand Down Expand Up @@ -267,6 +269,39 @@ func (g *resourceGetter) deleteNetworkSecurityGroup(r *resources.Resource) error
return g.cloud.NetworkSecurityGroup().Delete(context.TODO(), g.resourceGroupName(), r.Name)
}

func (g *resourceGetter) listApplicationSecurityGroups(ctx context.Context) ([]*resources.Resource, error) {
ApplicationSecurityGroups, err := g.cloud.ApplicationSecurityGroup().List(ctx, g.resourceGroupName())
if err != nil {
return nil, err
}

var rs []*resources.Resource
for i := range ApplicationSecurityGroups {
rs = append(rs, g.toApplicationSecurityGroupResource(&ApplicationSecurityGroups[i]))
}
return rs, nil
}

func (g *resourceGetter) toApplicationSecurityGroupResource(ApplicationSecurityGroup *network.ApplicationSecurityGroup) *resources.Resource {
return &resources.Resource{
Obj: ApplicationSecurityGroup,
Type: typeApplicationSecurityGroup,
ID: *ApplicationSecurityGroup.Name,
Name: *ApplicationSecurityGroup.Name,
Deleter: func(_ fi.Cloud, r *resources.Resource) error {
return g.deleteApplicationSecurityGroup(r)
},
Blocks: []string{
toKey(typeResourceGroup, g.resourceGroupName()),
},
Shared: g.clusterInfo.AzureNetworkShared,
}
}

func (g *resourceGetter) deleteApplicationSecurityGroup(r *resources.Resource) error {
return g.cloud.ApplicationSecurityGroup().Delete(context.TODO(), g.resourceGroupName(), r.Name)
}

func (g *resourceGetter) listRouteTables(ctx context.Context) ([]*resources.Resource, error) {
rts, err := g.cloud.RouteTable().List(ctx, g.resourceGroupName())
if err != nil {
Expand Down Expand Up @@ -344,6 +379,7 @@ func (g *resourceGetter) toVMScaleSetResource(vmss *compute.VirtualMachineScaleS

vnets := set.New[string]()
subnets := set.New[string]()
asgs := set.New[string]()
lbs := set.New[string]()
for _, iface := range *vmss.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations {
for _, ip := range *iface.IPConfigurations {
Expand All @@ -353,6 +389,15 @@ func (g *resourceGetter) toVMScaleSetResource(vmss *compute.VirtualMachineScaleS
}
vnets.Insert(subnetID.VirtualNetworkName)
subnets.Insert(subnetID.SubnetName)
if ip.ApplicationSecurityGroups != nil {
for _, asg := range *ip.ApplicationSecurityGroups {
asgID, err := azure.ParseApplicationSecurityGroupID(*asg.ID)
if err != nil {
return nil, fmt.Errorf("parsing application security group ID: %s", err)
}
asgs.Insert(asgID.ApplicationSecurityGroupName)
}
}
if ip.LoadBalancerBackendAddressPools != nil {
for _, lb := range *ip.LoadBalancerBackendAddressPools {
lbID, err := azure.ParseLoadBalancerID(*lb.ID)
Expand All @@ -370,6 +415,9 @@ func (g *resourceGetter) toVMScaleSetResource(vmss *compute.VirtualMachineScaleS
for subnet := range subnets {
blocks = append(blocks, toKey(typeSubnet, subnet))
}
for asg := range asgs {
blocks = append(blocks, toKey(typeApplicationSecurityGroup, asg))
}
for lb := range lbs {
blocks = append(blocks, toKey(typeLoadBalancer, lb))
}
Expand Down
83 changes: 83 additions & 0 deletions upup/pkg/fi/cloudup/azure/applicationsecuritygroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package azure

import (
"context"
"fmt"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-05-01/network"
"github.com/Azure/go-autorest/autorest"
)

// ApplicationSecurityGroupsClient is a client for managing Application Security Groups.
type ApplicationSecurityGroupsClient interface {
CreateOrUpdate(ctx context.Context, resourceGroupName, applicationSecurityGroupName string, parameters network.ApplicationSecurityGroup) (*network.ApplicationSecurityGroup, error)
List(ctx context.Context, resourceGroupName string) ([]network.ApplicationSecurityGroup, error)
Delete(ctx context.Context, resourceGroupName, applicationSecurityGroupName string) error
}

type ApplicationSecurityGroupsClientImpl struct {
c *network.ApplicationSecurityGroupsClient
}

var _ ApplicationSecurityGroupsClient = &ApplicationSecurityGroupsClientImpl{}

func (c *ApplicationSecurityGroupsClientImpl) CreateOrUpdate(ctx context.Context, resourceGroupName, applicationSecurityGroupName string, parameters network.ApplicationSecurityGroup) (*network.ApplicationSecurityGroup, error) {
future, err := c.c.CreateOrUpdate(ctx, resourceGroupName, applicationSecurityGroupName, parameters)
if err != nil {
return nil, fmt.Errorf("error creating/updating Application Security Group: %w", err)
}
if err := future.WaitForCompletionRef(ctx, c.c.Client); err != nil {
return nil, fmt.Errorf("error waiting for Application Security Group create/update completion: %w", err)
}
asg, err := future.Result(*c.c)
if err != nil {
return nil, fmt.Errorf("error obtaining result for Application Security Group create/update: %w", err)
}
return &asg, err
}

func (c *ApplicationSecurityGroupsClientImpl) List(ctx context.Context, resourceGroupName string) ([]network.ApplicationSecurityGroup, error) {
var l []network.ApplicationSecurityGroup
for iter, err := c.c.ListComplete(ctx, resourceGroupName); iter.NotDone(); err = iter.NextWithContext(ctx) {
if err != nil {
return nil, err
}
l = append(l, iter.Value())
}
return l, nil
}

func (c *ApplicationSecurityGroupsClientImpl) Delete(ctx context.Context, resourceGroupName, applicationSecurityGroupName string) error {
future, err := c.c.Delete(ctx, resourceGroupName, applicationSecurityGroupName)
if err != nil {
return fmt.Errorf("error deleting virtual network: %w", err)
}
if err := future.WaitForCompletionRef(ctx, c.c.Client); err != nil {
return fmt.Errorf("error waiting for virtual network deletion completion: %w", err)
}
return nil
}

func newApplicationSecurityGroupsClientImpl(subscriptionID string, authorizer autorest.Authorizer) *ApplicationSecurityGroupsClientImpl {
c := network.NewApplicationSecurityGroupsClient(subscriptionID)
c.Authorizer = authorizer
return &ApplicationSecurityGroupsClientImpl{
c: &c,
}
}
67 changes: 37 additions & 30 deletions upup/pkg/fi/cloudup/azure/azure_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type AzureCloud interface {
Subnet() SubnetsClient
RouteTable() RouteTablesClient
NetworkSecurityGroup() NetworkSecurityGroupsClient
ApplicationSecurityGroup() ApplicationSecurityGroupsClient
VMScaleSet() VMScaleSetsClient
VMScaleSetVM() VMScaleSetVMsClient
Disk() DisksClient
Expand All @@ -60,21 +61,22 @@ type AzureCloud interface {
}

type azureCloudImplementation struct {
subscriptionID string
location string
tags map[string]string
resourceGroupsClient ResourceGroupsClient
networkSecurityGroupsClient NetworkSecurityGroupsClient
vnetsClient VirtualNetworksClient
subnetsClient SubnetsClient
routeTablesClient RouteTablesClient
vmscaleSetsClient VMScaleSetsClient
vmscaleSetVMsClient VMScaleSetVMsClient
disksClient DisksClient
roleAssignmentsClient RoleAssignmentsClient
networkInterfacesClient NetworkInterfacesClient
loadBalancersClient LoadBalancersClient
publicIPAddressesClient PublicIPAddressesClient
subscriptionID string
location string
tags map[string]string
resourceGroupsClient ResourceGroupsClient
networkSecurityGroupsClient NetworkSecurityGroupsClient
applicationSecurityGroupsClient ApplicationSecurityGroupsClient
vnetsClient VirtualNetworksClient
subnetsClient SubnetsClient
routeTablesClient RouteTablesClient
vmscaleSetsClient VMScaleSetsClient
vmscaleSetVMsClient VMScaleSetVMsClient
disksClient DisksClient
roleAssignmentsClient RoleAssignmentsClient
networkInterfacesClient NetworkInterfacesClient
loadBalancersClient LoadBalancersClient
publicIPAddressesClient PublicIPAddressesClient
}

var _ fi.Cloud = &azureCloudImplementation{}
Expand All @@ -87,21 +89,22 @@ func NewAzureCloud(subscriptionID, location string, tags map[string]string) (Azu
}

return &azureCloudImplementation{
subscriptionID: subscriptionID,
location: location,
tags: tags,
resourceGroupsClient: newResourceGroupsClientImpl(subscriptionID, authorizer),
vnetsClient: newVirtualNetworksClientImpl(subscriptionID, authorizer),
subnetsClient: newSubnetsClientImpl(subscriptionID, authorizer),
routeTablesClient: newRouteTablesClientImpl(subscriptionID, authorizer),
networkSecurityGroupsClient: newNetworkSecurityGroupsClientImpl(subscriptionID, authorizer),
vmscaleSetsClient: newVMScaleSetsClientImpl(subscriptionID, authorizer),
vmscaleSetVMsClient: newVMScaleSetVMsClientImpl(subscriptionID, authorizer),
disksClient: newDisksClientImpl(subscriptionID, authorizer),
roleAssignmentsClient: newRoleAssignmentsClientImpl(subscriptionID, authorizer),
networkInterfacesClient: newNetworkInterfacesClientImpl(subscriptionID, authorizer),
loadBalancersClient: newLoadBalancersClientImpl(subscriptionID, authorizer),
publicIPAddressesClient: newPublicIPAddressesClientImpl(subscriptionID, authorizer),
subscriptionID: subscriptionID,
location: location,
tags: tags,
resourceGroupsClient: newResourceGroupsClientImpl(subscriptionID, authorizer),
vnetsClient: newVirtualNetworksClientImpl(subscriptionID, authorizer),
subnetsClient: newSubnetsClientImpl(subscriptionID, authorizer),
routeTablesClient: newRouteTablesClientImpl(subscriptionID, authorizer),
networkSecurityGroupsClient: newNetworkSecurityGroupsClientImpl(subscriptionID, authorizer),
applicationSecurityGroupsClient: newApplicationSecurityGroupsClientImpl(subscriptionID, authorizer),
vmscaleSetsClient: newVMScaleSetsClientImpl(subscriptionID, authorizer),
vmscaleSetVMsClient: newVMScaleSetVMsClientImpl(subscriptionID, authorizer),
disksClient: newDisksClientImpl(subscriptionID, authorizer),
roleAssignmentsClient: newRoleAssignmentsClientImpl(subscriptionID, authorizer),
networkInterfacesClient: newNetworkInterfacesClientImpl(subscriptionID, authorizer),
loadBalancersClient: newLoadBalancersClientImpl(subscriptionID, authorizer),
publicIPAddressesClient: newPublicIPAddressesClientImpl(subscriptionID, authorizer),
}, nil
}

Expand Down Expand Up @@ -293,6 +296,10 @@ func (c *azureCloudImplementation) NetworkSecurityGroup() NetworkSecurityGroupsC
return c.networkSecurityGroupsClient
}

func (c *azureCloudImplementation) ApplicationSecurityGroup() ApplicationSecurityGroupsClient {
return c.applicationSecurityGroupsClient
}

func (c *azureCloudImplementation) VMScaleSet() VMScaleSetsClient {
return c.vmscaleSetsClient
}
Expand Down
28 changes: 28 additions & 0 deletions upup/pkg/fi/cloudup/azure/azure_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ func ParseNetworkSecurityGroupID(s string) (*NetworkSecurityGroupID, error) {
}, nil
}

// ApplicationSecurityGroupID contains the resource ID/names required to construct a ApplicationSecurityGroup ID.
type ApplicationSecurityGroupID struct {
SubscriptionID string
ResourceGroupName string
ApplicationSecurityGroupName string
}

// String returns the ApplicationSecurityGroup ID in the path format.
func (s *ApplicationSecurityGroupID) String() string {
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/applicationSecurityGroups/%s",
s.SubscriptionID,
s.ResourceGroupName,
s.ApplicationSecurityGroupName)
}

// ParseApplicationSecurityGroupID parses a given ApplicationSecurityGroup ID string and returns a ApplicationSecurityGroup ID.
func ParseApplicationSecurityGroupID(s string) (*ApplicationSecurityGroupID, error) {
l := strings.Split(s, "/")
if len(l) != 9 {
return nil, fmt.Errorf("malformed format of ApplicationSecurityGroup ID: %s, %d", s, len(l))
}
return &ApplicationSecurityGroupID{
SubscriptionID: l[2],
ResourceGroupName: l[4],
ApplicationSecurityGroupName: l[8],
}, nil
}

// LoadBalancerID contains the resource ID/names required to construct a load balancer ID.
type LoadBalancerID struct {
SubscriptionID string
Expand Down
Loading

0 comments on commit b2387e4

Please sign in to comment.