@@ -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+
185437func 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
0 commit comments