diff --git a/pkg/model/azuremodel/network.go b/pkg/model/azuremodel/network.go index 8b981e1a70..2062f17741 100644 --- a/pkg/model/azuremodel/network.go +++ b/pkg/model/azuremodel/network.go @@ -17,8 +17,12 @@ limitations under the License. package azuremodel import ( + "fmt" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-05-01/network" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/azuretasks" + "k8s.io/utils/net" ) // NetworkModelBuilder configures a Virtual Network and subnets. @@ -41,14 +45,134 @@ func (b *NetworkModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { } c.AddTask(networkTask) + nsgTask := &azuretasks.NetworkSecurityGroup{ + Name: fi.PtrTo(b.NameForVirtualNetwork()), + Lifecycle: b.Lifecycle, + ResourceGroup: b.LinkToResourceGroup(), + Tags: map[string]*string{}, + } + var sshAccessIPv4, sshAccessIPv6 []string + for _, cidr := range b.Cluster.Spec.SSHAccess { + switch net.IPFamilyOfCIDRString(cidr) { + case net.IPv4: + sshAccessIPv4 = append(sshAccessIPv4, cidr) + case net.IPv6: + sshAccessIPv6 = append(sshAccessIPv6, cidr) + default: + return fmt.Errorf("unknown IP family for CIDR: %q", cidr) + } + } + if len(sshAccessIPv4) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowSSH"), + Priority: fi.PtrTo[int32](100), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &sshAccessIPv4, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("22"), + }) + } + if len(sshAccessIPv6) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowSSH_v6"), + Priority: fi.PtrTo[int32](101), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &sshAccessIPv6, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("22"), + }) + } + var k8sAccessIPv4, k8sAccessIPv6 []string + for _, cidr := range b.Cluster.Spec.API.Access { + switch net.IPFamilyOfCIDRString(cidr) { + case net.IPv4: + k8sAccessIPv4 = append(k8sAccessIPv4, cidr) + case net.IPv6: + k8sAccessIPv6 = append(k8sAccessIPv6, cidr) + default: + return fmt.Errorf("unknown IP family for CIDR: %q", cidr) + } + } + if len(k8sAccessIPv4) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowKubernetesAPI"), + Priority: fi.PtrTo[int32](200), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &k8sAccessIPv4, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("443"), + }) + } + if len(k8sAccessIPv6) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowKubernetesAPI_v6"), + Priority: fi.PtrTo[int32](201), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &k8sAccessIPv6, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("443"), + }) + } + var nodePortAccessIPv4, nodePortAccessIPv6 []string + for _, cidr := range b.Cluster.Spec.NodePortAccess { + switch net.IPFamilyOfCIDRString(cidr) { + case net.IPv4: + nodePortAccessIPv4 = append(nodePortAccessIPv4, cidr) + case net.IPv6: + nodePortAccessIPv6 = append(nodePortAccessIPv6, cidr) + default: + return fmt.Errorf("unknown IP family for CIDR: %q", cidr) + } + } + if len(nodePortAccessIPv4) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowNodePort"), + Priority: fi.PtrTo[int32](300), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &nodePortAccessIPv4, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("443"), + }) + } + if len(nodePortAccessIPv6) > 0 { + nsgTask.SecurityRules = append(nsgTask.SecurityRules, &azuretasks.NetworkSecurityRule{ + Name: fi.PtrTo("AllowNodePort_v6"), + Priority: fi.PtrTo[int32](301), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + Protocol: network.SecurityRuleProtocolTCP, + SourceAddressPrefixes: &nodePortAccessIPv6, + SourcePortRange: fi.PtrTo("*"), + DestinationAddressPrefix: fi.PtrTo("*"), + DestinationPortRange: fi.PtrTo("443"), + }) + } + c.AddTask(nsgTask) + for _, subnetSpec := range b.Cluster.Spec.Networking.Subnets { subnetTask := &azuretasks.Subnet{ - Name: fi.PtrTo(subnetSpec.Name), - Lifecycle: b.Lifecycle, - ResourceGroup: b.LinkToResourceGroup(), - VirtualNetwork: b.LinkToVirtualNetwork(), - CIDR: fi.PtrTo(subnetSpec.CIDR), - Shared: fi.PtrTo(b.Cluster.SharedVPC()), + Name: fi.PtrTo(subnetSpec.Name), + Lifecycle: b.Lifecycle, + ResourceGroup: b.LinkToResourceGroup(), + VirtualNetwork: b.LinkToVirtualNetwork(), + NetworkSecurityGroup: nsgTask, + CIDR: fi.PtrTo(subnetSpec.CIDR), + Shared: fi.PtrTo(b.Cluster.SharedVPC()), } c.AddTask(subnetTask) } diff --git a/pkg/resources/azure/azure.go b/pkg/resources/azure/azure.go index beca71934a..f6012f9084 100644 --- a/pkg/resources/azure/azure.go +++ b/pkg/resources/azure/azure.go @@ -31,15 +31,16 @@ import ( ) const ( - typeResourceGroup = "ResourceGroup" - typeVirtualNetwork = "VirtualNetwork" - typeSubnet = "Subnet" - typeRouteTable = "RouteTable" - typeVMScaleSet = "VMScaleSet" - typeDisk = "Disk" - typeRoleAssignment = "RoleAssignment" - typeLoadBalancer = "LoadBalancer" - typePublicIPAddress = "PublicIPAddress" + typeResourceGroup = "ResourceGroup" + typeVirtualNetwork = "VirtualNetwork" + typeNetworkSecurityGroup = "NetworkSecurityGroup" + typeSubnet = "Subnet" + typeRouteTable = "RouteTable" + typeVMScaleSet = "VMScaleSet" + typeDisk = "Disk" + typeRoleAssignment = "RoleAssignment" + typeLoadBalancer = "LoadBalancer" + typePublicIPAddress = "PublicIPAddress" ) // ListResourcesAzure lists all resources for the cluster by quering Azure. @@ -85,6 +86,7 @@ func (g *resourceGetter) listAll() ([]*resources.Resource, error) { fns := []func(ctx context.Context) ([]*resources.Resource, error){ g.listResourceGroups, g.listVirtualNetworksAndSubnets, + g.listNetworkSecurityGroups, g.listRouteTables, g.listVMScaleSetsAndRoleAssignments, g.listDisks, @@ -209,6 +211,39 @@ func (g *resourceGetter) deleteSubnet(vnetName string, r *resources.Resource) er return g.cloud.Subnet().Delete(context.TODO(), g.resourceGroupName(), vnetName, r.Name) } +func (g *resourceGetter) listNetworkSecurityGroups(ctx context.Context) ([]*resources.Resource, error) { + NetworkSecurityGroups, err := g.cloud.NetworkSecurityGroup().List(ctx, g.resourceGroupName()) + if err != nil { + return nil, err + } + + var rs []*resources.Resource + for i := range NetworkSecurityGroups { + rs = append(rs, g.toNetworkSecurityGroupResource(&NetworkSecurityGroups[i])) + } + return rs, nil +} + +func (g *resourceGetter) toNetworkSecurityGroupResource(NetworkSecurityGroup *network.SecurityGroup) *resources.Resource { + return &resources.Resource{ + Obj: NetworkSecurityGroup, + Type: typeNetworkSecurityGroup, + ID: *NetworkSecurityGroup.Name, + Name: *NetworkSecurityGroup.Name, + Deleter: func(_ fi.Cloud, r *resources.Resource) error { + return g.deleteNetworkSecurityGroup(r) + }, + Blocks: []string{ + toKey(typeResourceGroup, g.resourceGroupName()), + }, + Shared: g.clusterInfo.AzureNetworkShared, + } +} + +func (g *resourceGetter) deleteNetworkSecurityGroup(r *resources.Resource) error { + return g.cloud.NetworkSecurityGroup().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 { diff --git a/upup/pkg/fi/cloudup/azure/azure_cloud.go b/upup/pkg/fi/cloudup/azure/azure_cloud.go index e77cef1ff8..549ad611c0 100644 --- a/upup/pkg/fi/cloudup/azure/azure_cloud.go +++ b/upup/pkg/fi/cloudup/azure/azure_cloud.go @@ -49,6 +49,7 @@ type AzureCloud interface { VirtualNetwork() VirtualNetworksClient Subnet() SubnetsClient RouteTable() RouteTablesClient + NetworkSecurityGroup() NetworkSecurityGroupsClient VMScaleSet() VMScaleSetsClient VMScaleSetVM() VMScaleSetVMsClient Disk() DisksClient @@ -59,20 +60,21 @@ type AzureCloud interface { } type azureCloudImplementation struct { - subscriptionID string - location string - tags map[string]string - resourceGroupsClient ResourceGroupsClient - 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 + vnetsClient VirtualNetworksClient + subnetsClient SubnetsClient + routeTablesClient RouteTablesClient + vmscaleSetsClient VMScaleSetsClient + vmscaleSetVMsClient VMScaleSetVMsClient + disksClient DisksClient + roleAssignmentsClient RoleAssignmentsClient + networkInterfacesClient NetworkInterfacesClient + loadBalancersClient LoadBalancersClient + publicIPAddressesClient PublicIPAddressesClient } var _ fi.Cloud = &azureCloudImplementation{} @@ -85,20 +87,21 @@ 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), - 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), + 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 } @@ -286,6 +289,10 @@ func (c *azureCloudImplementation) RouteTable() RouteTablesClient { return c.routeTablesClient } +func (c *azureCloudImplementation) NetworkSecurityGroup() NetworkSecurityGroupsClient { + return c.networkSecurityGroupsClient +} + func (c *azureCloudImplementation) VMScaleSet() VMScaleSetsClient { return c.vmscaleSetsClient } diff --git a/upup/pkg/fi/cloudup/azure/networksecuritygroup.go b/upup/pkg/fi/cloudup/azure/networksecuritygroup.go new file mode 100644 index 0000000000..53f40f24d6 --- /dev/null +++ b/upup/pkg/fi/cloudup/azure/networksecuritygroup.go @@ -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" +) + +// NetworkSecurityGroupsClient is a client for managing Network Security Groups. +type NetworkSecurityGroupsClient interface { + CreateOrUpdate(ctx context.Context, resourceGroupName, NetworkSecurityGroupName string, parameters network.SecurityGroup) (*network.SecurityGroup, error) + List(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, error) + Delete(ctx context.Context, resourceGroupName, NetworkSecurityGroupName string) error +} + +type NetworkSecurityGroupsClientImpl struct { + c *network.SecurityGroupsClient +} + +var _ NetworkSecurityGroupsClient = &NetworkSecurityGroupsClientImpl{} + +func (c *NetworkSecurityGroupsClientImpl) CreateOrUpdate(ctx context.Context, resourceGroupName, NetworkSecurityGroupName string, parameters network.SecurityGroup) (*network.SecurityGroup, error) { + future, err := c.c.CreateOrUpdate(ctx, resourceGroupName, NetworkSecurityGroupName, parameters) + if err != nil { + return nil, fmt.Errorf("error creating/updating Network Security Group: %w", err) + } + if err := future.WaitForCompletionRef(ctx, c.c.Client); err != nil { + return nil, fmt.Errorf("error waiting for Network Security Group create/update completion: %w", err) + } + asg, err := future.Result(*c.c) + if err != nil { + return nil, fmt.Errorf("error obtaining result for Network Security Group create/update: %w", err) + } + return &asg, err +} + +func (c *NetworkSecurityGroupsClientImpl) List(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, error) { + var l []network.SecurityGroup + 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 *NetworkSecurityGroupsClientImpl) Delete(ctx context.Context, resourceGroupName, NetworkSecurityGroupName string) error { + future, err := c.c.Delete(ctx, resourceGroupName, NetworkSecurityGroupName) + if err != nil { + return fmt.Errorf("error deleting Network Security Group: %w", err) + } + if err := future.WaitForCompletionRef(ctx, c.c.Client); err != nil { + return fmt.Errorf("error waiting for Network Security Group deletion completion: %w", err) + } + return nil +} + +func newNetworkSecurityGroupsClientImpl(subscriptionID string, authorizer autorest.Authorizer) *NetworkSecurityGroupsClientImpl { + c := network.NewSecurityGroupsClient(subscriptionID) + c.Authorizer = authorizer + return &NetworkSecurityGroupsClientImpl{ + c: &c, + } +} diff --git a/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup.go b/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup.go new file mode 100644 index 0000000000..fdb5abdacb --- /dev/null +++ b/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup.go @@ -0,0 +1,197 @@ +/* +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 azuretasks + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-05-01/network" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/klog/v2" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/azure" +) + +// NetworkSecurityGroup is an Azure Cloud Network Security Group +// +kops:fitask +type NetworkSecurityGroup struct { + Name *string + ID *string + Lifecycle fi.Lifecycle + ResourceGroup *ResourceGroup + + SecurityRules []*NetworkSecurityRule + + Tags map[string]*string +} + +var ( + _ fi.CloudupTask = &NetworkSecurityGroup{} + _ fi.CompareWithID = &NetworkSecurityGroup{} + _ fi.CloudupTaskNormalize = &NetworkSecurityGroup{} +) + +// CompareWithID returns the Name of the Network Security Group +func (nsg *NetworkSecurityGroup) CompareWithID() *string { + return nsg.ID +} + +// Find discovers the Network Security Group in the cloud provider +func (nsg *NetworkSecurityGroup) Find(c *fi.CloudupContext) (*NetworkSecurityGroup, error) { + cloud := c.T.Cloud.(azure.AzureCloud) + l, err := cloud.NetworkSecurityGroup().List(context.TODO(), *nsg.ResourceGroup.Name) + if err != nil { + return nil, err + } + var found *network.SecurityGroup + for _, v := range l { + if *v.Name == *nsg.Name { + found = &v + break + } + } + if found == nil { + return nil, nil + } + + actual := &NetworkSecurityGroup{ + Name: nsg.Name, + Lifecycle: nsg.Lifecycle, + ResourceGroup: &ResourceGroup{ + Name: nsg.ResourceGroup.Name, + }, + ID: found.ID, + Tags: found.Tags, + } + for _, rule := range *found.SecurityRules { + nsr := &NetworkSecurityRule{ + Name: rule.Name, + Priority: rule.Priority, + Access: rule.Access, + Direction: rule.Direction, + Protocol: rule.Protocol, + SourceAddressPrefix: rule.SourceAddressPrefix, + SourceAddressPrefixes: rule.SourceAddressPrefixes, + SourcePortRange: rule.SourcePortRange, + DestinationAddressPrefix: rule.DestinationAddressPrefix, + DestinationAddressPrefixes: rule.DestinationAddressPrefixes, + DestinationPortRange: rule.DestinationPortRange, + } + actual.SecurityRules = append(actual.SecurityRules, nsr) + } + + nsg.ID = found.ID + + return actual, nil +} + +func (nsg *NetworkSecurityGroup) Normalize(c *fi.CloudupContext) error { + c.T.Cloud.(azure.AzureCloud).AddClusterTags(nsg.Tags) + return nil +} + +// Run implements fi.Task.Run. +func (nsg *NetworkSecurityGroup) Run(c *fi.CloudupContext) error { + return fi.CloudupDefaultDeltaRunMethod(nsg, c) +} + +// CheckChanges returns an error if a change is not allowed. +func (*NetworkSecurityGroup) CheckChanges(a, e, changes *NetworkSecurityGroup) error { + if a == nil { + // Check if required fields are set when a new resource is created. + if e.Name == nil { + return fi.RequiredField("Name") + } + return nil + } + + // Check if unchangeable fields won't be changed. + if changes.Name != nil { + return fi.CannotChangeField("Name") + } + return nil +} + +// RenderAzure creates or updates a Network Security Group. +func (*NetworkSecurityGroup) RenderAzure(t *azure.AzureAPITarget, a, e, changes *NetworkSecurityGroup) error { + if a == nil { + klog.Infof("Creating a new Network Security Group with name: %s", fi.ValueOf(e.Name)) + } else { + klog.Infof("Updating a Network Security Group with name: %s", fi.ValueOf(e.Name)) + } + + p := network.SecurityGroup{ + SecurityGroupPropertiesFormat: &network.SecurityGroupPropertiesFormat{ + SecurityRules: &[]network.SecurityRule{}, + }, + Location: to.StringPtr(t.Cloud.Region()), + Name: to.StringPtr(*e.Name), + Tags: e.Tags, + } + for _, nsr := range e.SecurityRules { + securityRule := network.SecurityRule{ + Name: nsr.Name, + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Priority: nsr.Priority, + Access: nsr.Access, + Direction: nsr.Direction, + Protocol: nsr.Protocol, + SourceAddressPrefix: nsr.SourceAddressPrefix, + SourceAddressPrefixes: nsr.SourceAddressPrefixes, + SourcePortRange: nsr.SourcePortRange, + DestinationAddressPrefix: nsr.DestinationAddressPrefix, + DestinationAddressPrefixes: nsr.DestinationAddressPrefixes, + DestinationPortRange: nsr.DestinationPortRange, + }, + } + *p.SecurityRules = append(*p.SecurityRules, securityRule) + } + + nsg, err := t.Cloud.NetworkSecurityGroup().CreateOrUpdate( + context.TODO(), + *e.ResourceGroup.Name, + *e.Name, + p) + if err != nil { + return err + } + + e.ID = nsg.ID + + return nil +} + +// NetworkSecurityRule represents a NetworkSecurityGroup rule. +type NetworkSecurityRule struct { + Name *string + Priority *int32 + Access network.SecurityRuleAccess + Direction network.SecurityRuleDirection + Protocol network.SecurityRuleProtocol + SourceAddressPrefix *string + SourceAddressPrefixes *[]string + SourcePortRange *string + DestinationAddressPrefixes *[]string + DestinationAddressPrefix *string + DestinationPortRange *string +} + +var _ fi.CloudupHasDependencies = &NetworkSecurityRule{} + +func (e *NetworkSecurityRule) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask { + return nil +} diff --git a/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup_fitask.go b/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup_fitask.go new file mode 100644 index 0000000000..9436a47a1d --- /dev/null +++ b/upup/pkg/fi/cloudup/azuretasks/networksecuritygroup_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package azuretasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// NetworkSecurityGroup + +var _ fi.HasLifecycle = &NetworkSecurityGroup{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *NetworkSecurityGroup) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *NetworkSecurityGroup) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &NetworkSecurityGroup{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *NetworkSecurityGroup) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *NetworkSecurityGroup) String() string { + return fi.CloudupTaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/azuretasks/subnet.go b/upup/pkg/fi/cloudup/azuretasks/subnet.go index 70fe13cbe9..9446f69a07 100644 --- a/upup/pkg/fi/cloudup/azuretasks/subnet.go +++ b/upup/pkg/fi/cloudup/azuretasks/subnet.go @@ -31,10 +31,12 @@ type Subnet struct { Name *string Lifecycle fi.Lifecycle - ResourceGroup *ResourceGroup - VirtualNetwork *VirtualNetwork - CIDR *string - Shared *bool + ResourceGroup *ResourceGroup + VirtualNetwork *VirtualNetwork + NetworkSecurityGroup *NetworkSecurityGroup + + CIDR *string + Shared *bool } var ( @@ -65,7 +67,7 @@ func (s *Subnet) Find(c *fi.CloudupContext) (*Subnet, error) { return nil, nil } - return &Subnet{ + fs := &Subnet{ Name: s.Name, Lifecycle: s.Lifecycle, Shared: s.Shared, @@ -76,7 +78,14 @@ func (s *Subnet) Find(c *fi.CloudupContext) (*Subnet, error) { Name: s.VirtualNetwork.Name, }, CIDR: found.AddressPrefix, - }, nil + } + if found.NetworkSecurityGroup != nil { + fs.NetworkSecurityGroup = &NetworkSecurityGroup{ + ID: found.NetworkSecurityGroup.ID, + } + } + + return fs, nil } // Run implements fi.Task.Run. @@ -109,12 +118,17 @@ func (*Subnet) RenderAzure(t *azure.AzureAPITarget, a, e, changes *Subnet) error klog.Infof("Updating a Subnet with name: %s", fi.ValueOf(e.Name)) } - // TODO(kenji): Be able to specify security groups. subnet := network.Subnet{ SubnetPropertiesFormat: &network.SubnetPropertiesFormat{ AddressPrefix: e.CIDR, }, } + if e.NetworkSecurityGroup != nil { + subnet.NetworkSecurityGroup = &network.SecurityGroup{ + ID: e.NetworkSecurityGroup.ID, + } + } + return t.Cloud.Subnet().CreateOrUpdate( context.TODO(), *e.ResourceGroup.Name, diff --git a/upup/pkg/fi/cloudup/azuretasks/testing.go b/upup/pkg/fi/cloudup/azuretasks/testing.go index c80318685d..dd2ce904a2 100644 --- a/upup/pkg/fi/cloudup/azuretasks/testing.go +++ b/upup/pkg/fi/cloudup/azuretasks/testing.go @@ -46,18 +46,19 @@ const ( // MockAzureCloud is a mock implementation of AzureCloud. type MockAzureCloud struct { - Location string - ResourceGroupsClient *MockResourceGroupsClient - VirtualNetworksClient *MockVirtualNetworksClient - SubnetsClient *MockSubnetsClient - RouteTablesClient *MockRouteTablesClient - VMScaleSetsClient *MockVMScaleSetsClient - VMScaleSetVMsClient *MockVMScaleSetVMsClient - DisksClient *MockDisksClient - RoleAssignmentsClient *MockRoleAssignmentsClient - NetworkInterfacesClient *MockNetworkInterfacesClient - LoadBalancersClient *MockLoadBalancersClient - PublicIPAddressesClient *MockPublicIPAddressesClient + Location string + ResourceGroupsClient *MockResourceGroupsClient + VirtualNetworksClient *MockVirtualNetworksClient + SubnetsClient *MockSubnetsClient + RouteTablesClient *MockRouteTablesClient + NetworkSecurityGroupsClient *MockNetworkSecurityGroupsClient + VMScaleSetsClient *MockVMScaleSetsClient + VMScaleSetVMsClient *MockVMScaleSetVMsClient + DisksClient *MockDisksClient + RoleAssignmentsClient *MockRoleAssignmentsClient + NetworkInterfacesClient *MockNetworkInterfacesClient + LoadBalancersClient *MockLoadBalancersClient + PublicIPAddressesClient *MockPublicIPAddressesClient } var _ azure.AzureCloud = &MockAzureCloud{} @@ -78,6 +79,9 @@ func NewMockAzureCloud(location string) *MockAzureCloud { RouteTablesClient: &MockRouteTablesClient{ RTs: map[string]network.RouteTable{}, }, + NetworkSecurityGroupsClient: &MockNetworkSecurityGroupsClient{ + NSGs: map[string]network.SecurityGroup{}, + }, VMScaleSetsClient: &MockVMScaleSetsClient{ VMSSes: map[string]compute.VirtualMachineScaleSet{}, }, @@ -195,6 +199,11 @@ func (c *MockAzureCloud) RouteTable() azure.RouteTablesClient { return c.RouteTablesClient } +// NetworkSecurityGroup returns the VM Scale Set client. +func (c *MockAzureCloud) NetworkSecurityGroup() azure.NetworkSecurityGroupsClient { + return c.NetworkSecurityGroupsClient +} + // VMScaleSet returns the VM Scale Set client. func (c *MockAzureCloud) VMScaleSet() azure.VMScaleSetsClient { return c.VMScaleSetsClient @@ -612,3 +621,49 @@ func (c *MockPublicIPAddressesClient) Delete(ctx context.Context, scope, publicI delete(c.PubIPs, publicIPAddressName) return nil } + +// MockNetworkSecurityGroupsClient is a mock implementation of Network Security Group client. +type MockNetworkSecurityGroupsClient struct { + NSGs map[string]network.SecurityGroup +} + +var _ azure.NetworkSecurityGroupsClient = &MockNetworkSecurityGroupsClient{} + +// CreateOrUpdate creates or updates a Network Security Group. +func (c *MockNetworkSecurityGroupsClient) CreateOrUpdate(ctx context.Context, resourceGroupName, asgName string, parameters network.SecurityGroup) (*network.SecurityGroup, error) { + // Ignore resourceGroupName for simplicity. + if _, ok := c.NSGs[asgName]; ok { + return nil, fmt.Errorf("update not supported") + } + parameters.Name = &asgName + c.NSGs[asgName] = parameters + return ¶meters, nil +} + +// List returns a slice of Network Security Groups. +func (c *MockNetworkSecurityGroupsClient) List(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, error) { + var l []network.SecurityGroup + for _, nsg := range c.NSGs { + l = append(l, nsg) + } + return l, nil +} + +// Get Returns a specified Network Security Group. +func (c *MockNetworkSecurityGroupsClient) Get(ctx context.Context, resourceGroupName string, nsgName string) (*network.SecurityGroup, error) { + asg, ok := c.NSGs[nsgName] + if !ok { + return nil, nil + } + return &asg, nil +} + +// Delete deletes a specified Network Security Group. +func (c *MockNetworkSecurityGroupsClient) Delete(ctx context.Context, resourceGroupName, nsgName string) error { + // Ignore resourceGroupName for simplicity. + if _, ok := c.NSGs[nsgName]; !ok { + return fmt.Errorf("%s does not exist", nsgName) + } + delete(c.NSGs, nsgName) + return nil +}