Skip to content

Commit 9d81ad7

Browse files
authored
chore(e2e): add firewall to e2es (#7350)
1 parent 05149ff commit 9d81ad7

File tree

4 files changed

+286
-7
lines changed

4 files changed

+286
-7
lines changed

e2e/aks_model.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,258 @@ func getBaseClusterModel(clusterName, location, k8sSystemPoolSKU string) *armcon
182182
}
183183
}
184184

185+
func getFirewall(ctx context.Context, location, firewallSubnetID, publicIPID string) *armnetwork.AzureFirewall {
186+
var (
187+
natRuleCollections []*armnetwork.AzureFirewallNatRuleCollection
188+
netRuleCollections []*armnetwork.AzureFirewallNetworkRuleCollection
189+
)
190+
191+
// Application rule for AKS FQDN tags
192+
aksAppRule := armnetwork.AzureFirewallApplicationRule{
193+
Name: to.Ptr("aks-fqdn"),
194+
SourceAddresses: []*string{to.Ptr("*")},
195+
Protocols: []*armnetwork.AzureFirewallApplicationRuleProtocol{
196+
{
197+
ProtocolType: to.Ptr(armnetwork.AzureFirewallApplicationRuleProtocolTypeHTTP),
198+
Port: to.Ptr[int32](80),
199+
},
200+
{
201+
ProtocolType: to.Ptr(armnetwork.AzureFirewallApplicationRuleProtocolTypeHTTPS),
202+
Port: to.Ptr[int32](443),
203+
},
204+
},
205+
FqdnTags: []*string{to.Ptr("AzureKubernetesService")},
206+
}
207+
208+
// needed for scriptless e2e hack
209+
blobStorageFqdn := config.Config.BlobStorageAccount() + ".blob.core.windows.net"
210+
blobStorageAppRule := armnetwork.AzureFirewallApplicationRule{
211+
Name: to.Ptr("blob-storage-fqdn"),
212+
SourceAddresses: []*string{to.Ptr("*")},
213+
Protocols: []*armnetwork.AzureFirewallApplicationRuleProtocol{
214+
{
215+
ProtocolType: to.Ptr(armnetwork.AzureFirewallApplicationRuleProtocolTypeHTTPS),
216+
Port: to.Ptr[int32](443),
217+
},
218+
},
219+
TargetFqdns: []*string{to.Ptr(blobStorageFqdn)},
220+
}
221+
222+
appRuleCollection := armnetwork.AzureFirewallApplicationRuleCollection{
223+
Name: to.Ptr("aksfwar"),
224+
Properties: &armnetwork.AzureFirewallApplicationRuleCollectionPropertiesFormat{
225+
Priority: to.Ptr[int32](100),
226+
Action: &armnetwork.AzureFirewallRCAction{
227+
Type: to.Ptr(armnetwork.AzureFirewallRCActionTypeAllow),
228+
},
229+
Rules: []*armnetwork.AzureFirewallApplicationRule{&aksAppRule, &blobStorageAppRule},
230+
},
231+
}
232+
233+
ipConfigurations := []*armnetwork.AzureFirewallIPConfiguration{
234+
{
235+
Name: to.Ptr("firewall-ip-config"),
236+
Properties: &armnetwork.AzureFirewallIPConfigurationPropertiesFormat{
237+
Subnet: &armnetwork.SubResource{
238+
ID: to.Ptr(firewallSubnetID),
239+
},
240+
PublicIPAddress: &armnetwork.SubResource{
241+
ID: to.Ptr(publicIPID),
242+
},
243+
},
244+
},
245+
}
246+
247+
logf(ctx, "Firewall rules configured successfully")
248+
return &armnetwork.AzureFirewall{
249+
Location: to.Ptr(location),
250+
Properties: &armnetwork.AzureFirewallPropertiesFormat{
251+
ApplicationRuleCollections: []*armnetwork.AzureFirewallApplicationRuleCollection{&appRuleCollection},
252+
NetworkRuleCollections: netRuleCollections,
253+
NatRuleCollections: natRuleCollections,
254+
IPConfigurations: ipConfigurations,
255+
},
256+
}
257+
}
258+
259+
func addFirewallRules(
260+
ctx context.Context, clusterModel *armcontainerservice.ManagedCluster,
261+
location string,
262+
) error {
263+
264+
vnet, err := getClusterVNet(ctx, *clusterModel.Properties.NodeResourceGroup)
265+
if err != nil {
266+
return err
267+
}
268+
269+
// Create AzureFirewallSubnet - this subnet name is required by Azure Firewall
270+
firewallSubnetName := "AzureFirewallSubnet"
271+
firewallSubnetParams := armnetwork.Subnet{
272+
Properties: &armnetwork.SubnetPropertiesFormat{
273+
AddressPrefix: to.Ptr("10.225.0.0/24"), // Use a different CIDR that doesn't overlap with 10.224.0.0/16
274+
},
275+
}
276+
277+
logf(ctx, "Creating subnet %s in VNet %s", firewallSubnetName, vnet.name)
278+
subnetPoller, err := config.Azure.Subnet.BeginCreateOrUpdate(
279+
ctx,
280+
*clusterModel.Properties.NodeResourceGroup,
281+
vnet.name,
282+
firewallSubnetName,
283+
firewallSubnetParams,
284+
nil,
285+
)
286+
if err != nil {
287+
return fmt.Errorf("failed to start creating firewall subnet: %w", err)
288+
}
289+
290+
subnetResp, err := subnetPoller.PollUntilDone(ctx, config.DefaultPollUntilDoneOptions)
291+
if err != nil {
292+
return fmt.Errorf("failed to create firewall subnet: %w", err)
293+
}
294+
295+
firewallSubnetID := *subnetResp.ID
296+
logf(ctx, "Created firewall subnet with ID: %s", firewallSubnetID)
297+
298+
// Create public IP for the firewall
299+
publicIPName := "abe2e-fw-pip"
300+
publicIPParams := armnetwork.PublicIPAddress{
301+
Location: to.Ptr(location),
302+
SKU: &armnetwork.PublicIPAddressSKU{
303+
Name: to.Ptr(armnetwork.PublicIPAddressSKUNameStandard),
304+
},
305+
Properties: &armnetwork.PublicIPAddressPropertiesFormat{
306+
PublicIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
307+
},
308+
}
309+
310+
logf(ctx, "Creating public IP %s", publicIPName)
311+
pipPoller, err := config.Azure.PublicIPAddresses.BeginCreateOrUpdate(
312+
ctx,
313+
*clusterModel.Properties.NodeResourceGroup,
314+
publicIPName,
315+
publicIPParams,
316+
nil,
317+
)
318+
if err != nil {
319+
return fmt.Errorf("failed to start creating public IP: %w", err)
320+
}
321+
322+
pipResp, err := pipPoller.PollUntilDone(ctx, config.DefaultPollUntilDoneOptions)
323+
if err != nil {
324+
return fmt.Errorf("failed to create public IP: %w", err)
325+
}
326+
327+
publicIPID := *pipResp.ID
328+
logf(ctx, "Created public IP with ID: %s", publicIPID)
329+
330+
firewallName := "abe2e-fw"
331+
firewall := getFirewall(ctx, location, firewallSubnetID, publicIPID)
332+
fwPoller, err := config.Azure.AzureFirewall.BeginCreateOrUpdate(ctx, *clusterModel.Properties.NodeResourceGroup, firewallName, *firewall, nil)
333+
if err != nil {
334+
return fmt.Errorf("failed to start Firewall creation: %w", err)
335+
}
336+
fwResp, err := fwPoller.PollUntilDone(ctx, nil)
337+
if err != nil {
338+
return fmt.Errorf("failed to create Firewall: %w", err)
339+
}
340+
341+
// Get the firewall's private IP address
342+
var firewallPrivateIP string
343+
if fwResp.Properties != nil && fwResp.Properties.IPConfigurations != nil && len(fwResp.Properties.IPConfigurations) > 0 {
344+
if fwResp.Properties.IPConfigurations[0].Properties != nil && fwResp.Properties.IPConfigurations[0].Properties.PrivateIPAddress != nil {
345+
firewallPrivateIP = *fwResp.Properties.IPConfigurations[0].Properties.PrivateIPAddress
346+
logf(ctx, "Firewall private IP: %s", firewallPrivateIP)
347+
}
348+
}
349+
350+
if firewallPrivateIP == "" {
351+
return fmt.Errorf("failed to get firewall private IP address")
352+
}
353+
354+
routeTableName := "abe2e-fw-rt"
355+
routeTableParams := armnetwork.RouteTable{
356+
Location: to.Ptr(location),
357+
Properties: &armnetwork.RouteTablePropertiesFormat{
358+
Routes: []*armnetwork.Route{
359+
// Allow internal VNet traffic to bypass the firewall
360+
{
361+
Name: to.Ptr("vnet-local"),
362+
Properties: &armnetwork.RoutePropertiesFormat{
363+
AddressPrefix: to.Ptr("10.224.0.0/16"), // AKS subnet CIDR
364+
NextHopType: to.Ptr(armnetwork.RouteNextHopTypeVnetLocal),
365+
},
366+
},
367+
// Route all other traffic (internet-bound) through the firewall
368+
{
369+
Name: to.Ptr("default-route-to-firewall"),
370+
Properties: &armnetwork.RoutePropertiesFormat{
371+
AddressPrefix: to.Ptr("0.0.0.0/0"),
372+
NextHopType: to.Ptr(armnetwork.RouteNextHopTypeVirtualAppliance),
373+
NextHopIPAddress: to.Ptr(firewallPrivateIP),
374+
},
375+
},
376+
},
377+
DisableBgpRoutePropagation: to.Ptr(true),
378+
},
379+
}
380+
381+
logf(ctx, "Creating route table %s", routeTableName)
382+
rtPoller, err := config.Azure.RouteTables.BeginCreateOrUpdate(
383+
ctx,
384+
*clusterModel.Properties.NodeResourceGroup,
385+
routeTableName,
386+
routeTableParams,
387+
nil,
388+
)
389+
if err != nil {
390+
return fmt.Errorf("failed to start creating route table: %w", err)
391+
}
392+
393+
rtResp, err := rtPoller.PollUntilDone(ctx, config.DefaultPollUntilDoneOptions)
394+
if err != nil {
395+
return fmt.Errorf("failed to create route table: %w", err)
396+
}
397+
398+
logf(ctx, "Created route table with ID: %s", *rtResp.ID)
399+
400+
// Get the AKS subnet and associate it with the route table
401+
aksSubnetResp, err := config.Azure.Subnet.Get(ctx, *clusterModel.Properties.NodeResourceGroup, vnet.name, "aks-subnet", nil)
402+
if err != nil {
403+
return fmt.Errorf("failed to get AKS subnet: %w", err)
404+
}
405+
406+
// Update subnet to associate with route table
407+
aksSubnet := aksSubnetResp.Subnet
408+
if aksSubnet.Properties == nil {
409+
aksSubnet.Properties = &armnetwork.SubnetPropertiesFormat{}
410+
}
411+
aksSubnet.Properties.RouteTable = &armnetwork.RouteTable{
412+
ID: rtResp.ID,
413+
}
414+
415+
logf(ctx, "Associating route table with AKS subnet")
416+
subnetUpdatePoller, err := config.Azure.Subnet.BeginCreateOrUpdate(
417+
ctx,
418+
*clusterModel.Properties.NodeResourceGroup,
419+
vnet.name,
420+
"aks-subnet",
421+
aksSubnet,
422+
nil,
423+
)
424+
if err != nil {
425+
return fmt.Errorf("failed to start updating subnet with route table: %w", err)
426+
}
427+
428+
_, err = subnetUpdatePoller.PollUntilDone(ctx, config.DefaultPollUntilDoneOptions)
429+
if err != nil {
430+
return fmt.Errorf("failed to associate route table with subnet: %w", err)
431+
}
432+
433+
logf(ctx, "Successfully configured firewall and routing for AKS cluster")
434+
return nil
435+
}
436+
185437
func addAirgapNetworkSettings(ctx context.Context, clusterModel *armcontainerservice.ManagedCluster, privateACRName, location string) error {
186438
logf(ctx, "Adding network settings for airgap cluster %s in rg %s", *clusterModel.Name, *clusterModel.Properties.NodeResourceGroup)
187439

e2e/cache.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,49 +157,49 @@ var ClusterKubenet = cachedFunc(clusterKubenet)
157157

158158
// clusterKubenet creates a basic cluster using kubenet networking
159159
func clusterKubenet(ctx context.Context, request ClusterRequest) (*Cluster, error) {
160-
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-v2", request.Location, request.K8sSystemPoolSKU), false, false)
160+
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-v3", request.Location, request.K8sSystemPoolSKU), false, false)
161161
}
162162

163163
var ClusterKubenetAirgap = cachedFunc(clusterKubenetAirgap)
164164

165165
// clusterKubenetAirgap creates an airgapped kubenet cluster (no internet access)
166166
func clusterKubenetAirgap(ctx context.Context, request ClusterRequest) (*Cluster, error) {
167-
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-airgap", request.Location, request.K8sSystemPoolSKU), true, false)
167+
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-airgap-v2", request.Location, request.K8sSystemPoolSKU), true, false)
168168
}
169169

170170
var ClusterKubenetAirgapNonAnon = cachedFunc(clusterKubenetAirgapNonAnon)
171171

172172
// clusterKubenetAirgapNonAnon creates an airgapped kubenet cluster with non-anonymous image pulls
173173
func clusterKubenetAirgapNonAnon(ctx context.Context, request ClusterRequest) (*Cluster, error) {
174-
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-nonanonpull-airgap", request.Location, request.K8sSystemPoolSKU), true, true)
174+
return prepareCluster(ctx, getKubenetClusterModel("abe2e-kubenet-nonanonpull-airgap-v2", request.Location, request.K8sSystemPoolSKU), true, true)
175175
}
176176

177177
var ClusterAzureNetwork = cachedFunc(clusterAzureNetwork)
178178

179179
// clusterAzureNetwork creates a cluster with Azure CNI networking
180180
func clusterAzureNetwork(ctx context.Context, request ClusterRequest) (*Cluster, error) {
181-
return prepareCluster(ctx, getAzureNetworkClusterModel("abe2e-azure-network", request.Location, request.K8sSystemPoolSKU), false, false)
181+
return prepareCluster(ctx, getAzureNetworkClusterModel("abe2e-azure-network-v2", request.Location, request.K8sSystemPoolSKU), false, false)
182182
}
183183

184184
var ClusterAzureOverlayNetwork = cachedFunc(clusterAzureOverlayNetwork)
185185

186186
// clusterAzureOverlayNetwork creates a cluster with Azure CNI Overlay networking
187187
func clusterAzureOverlayNetwork(ctx context.Context, request ClusterRequest) (*Cluster, error) {
188-
return prepareCluster(ctx, getAzureOverlayNetworkClusterModel("abe2e-azure-overlay-network", request.Location, request.K8sSystemPoolSKU), false, false)
188+
return prepareCluster(ctx, getAzureOverlayNetworkClusterModel("abe2e-azure-overlay-network-v2", request.Location, request.K8sSystemPoolSKU), false, false)
189189
}
190190

191191
var ClusterAzureOverlayNetworkDualStack = cachedFunc(clusterAzureOverlayNetworkDualStack)
192192

193193
// clusterAzureOverlayNetworkDualStack creates a dual-stack (IPv4+IPv6) Azure CNI Overlay cluster
194194
func clusterAzureOverlayNetworkDualStack(ctx context.Context, request ClusterRequest) (*Cluster, error) {
195-
return prepareCluster(ctx, getAzureOverlayNetworkDualStackClusterModel("abe2e-azure-overlay-dualstack", request.Location, request.K8sSystemPoolSKU), false, false)
195+
return prepareCluster(ctx, getAzureOverlayNetworkDualStackClusterModel("abe2e-azure-overlay-dualstack-v2", request.Location, request.K8sSystemPoolSKU), false, false)
196196
}
197197

198198
var ClusterCiliumNetwork = cachedFunc(clusterCiliumNetwork)
199199

200200
// clusterCiliumNetwork creates a cluster with Cilium CNI networking
201201
func clusterCiliumNetwork(ctx context.Context, request ClusterRequest) (*Cluster, error) {
202-
return prepareCluster(ctx, getCiliumNetworkClusterModel("abe2e-cilium-network", request.Location, request.K8sSystemPoolSKU), false, false)
202+
return prepareCluster(ctx, getCiliumNetworkClusterModel("abe2e-cilium-network-v2", request.Location, request.K8sSystemPoolSKU), false, false)
203203
}
204204

205205
// isNotFoundErr checks if an error represents a "not found" response from Azure API

e2e/cluster.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ func prepareCluster(ctx context.Context, cluster *armcontainerservice.ManagedClu
100100
}
101101
}
102102

103+
if err := addFirewallRules(ctx, cluster, *cluster.Location); err != nil {
104+
return nil, fmt.Errorf("add firewall rules: %w", err)
105+
}
106+
103107
kubeletIdentity, err := getClusterKubeletIdentity(cluster)
104108
if err != nil {
105109
return nil, fmt.Errorf("getting cluster kubelet identity: %w", err)

e2e/config/azure.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939

4040
type AzureClient struct {
4141
AKS *armcontainerservice.ManagedClustersClient
42+
AzureFirewall *armnetwork.AzureFirewallsClient
4243
Blob *azblob.Client
4344
StorageContainers *armstorage.BlobContainersClient
4445
CacheRulesClient *armcontainerregistry.CacheRulesClient
@@ -57,6 +58,8 @@ type AzureClient struct {
5758
SecurityGroup *armnetwork.SecurityGroupsClient
5859
StorageAccounts *armstorage.AccountsClient
5960
Subnet *armnetwork.SubnetsClient
61+
PublicIPAddresses *armnetwork.PublicIPAddressesClient
62+
RouteTables *armnetwork.RouteTablesClient
6063
UserAssignedIdentities *armmsi.UserAssignedIdentitiesClient
6164
VMSS *armcompute.VirtualMachineScaleSetsClient
6265
VMSSVM *armcompute.VirtualMachineScaleSetVMsClient
@@ -148,6 +151,11 @@ func NewAzureClient() (*AzureClient, error) {
148151
return nil, fmt.Errorf("create core client: %w", err)
149152
}
150153

154+
cloud.PublicIPAddresses, err = armnetwork.NewPublicIPAddressesClient(Config.SubscriptionID, credential, opts)
155+
if err != nil {
156+
return nil, fmt.Errorf("create public ip addresses client: %w", err)
157+
}
158+
151159
cloud.RegistriesClient, err = armcontainerregistry.NewRegistriesClient(Config.SubscriptionID, credential, opts)
152160
if err != nil {
153161
return nil, fmt.Errorf("failed to create registry client: %w", err)
@@ -193,6 +201,11 @@ func NewAzureClient() (*AzureClient, error) {
193201
return nil, fmt.Errorf("create subnet client: %w", err)
194202
}
195203

204+
cloud.RouteTables, err = armnetwork.NewRouteTablesClient(Config.SubscriptionID, credential, opts)
205+
if err != nil {
206+
return nil, fmt.Errorf("create route tables client: %w", err)
207+
}
208+
196209
cloud.AKS, err = armcontainerservice.NewManagedClustersClient(Config.SubscriptionID, credential, opts)
197210
if err != nil {
198211
return nil, fmt.Errorf("create aks client: %w", err)
@@ -258,6 +271,16 @@ func NewAzureClient() (*AzureClient, error) {
258271
return nil, fmt.Errorf("create vnet client: %w", err)
259272
}
260273

274+
cloud.AzureFirewall, err = armnetwork.NewAzureFirewallsClient(Config.SubscriptionID, credential, opts)
275+
if err != nil {
276+
return nil, fmt.Errorf("create firewall client: %w", err)
277+
}
278+
279+
cloud.PublicIPAddresses, err = armnetwork.NewPublicIPAddressesClient(Config.SubscriptionID, credential, opts)
280+
if err != nil {
281+
return nil, fmt.Errorf("create public ip addresses client: %w", err)
282+
}
283+
261284
cloud.Blob, err = azblob.NewClient(Config.BlobStorageAccountURL(), credential, nil)
262285
if err != nil {
263286
return nil, fmt.Errorf("create blob container client: %w", err)

0 commit comments

Comments
 (0)