Introduction
In this tutorial, we will guide you through the development of the initial Hub VNet for a private AKS environment using Pulumi.
We will demonstrate how to seamlessly integrate a VNet with an Azure Firewall, along with configuring outbound public IP addresses.
Security is paramount. Our focus will be on enhancing security by implementing network policies, firewalls, and encryption to safeguard our environment, all while maintaining cost-effectiveness on Azure.
Table of Contents
Open Table of Contents
The Hub VNet modules
The VNet
Module
This module facilitates the creation of a Virtual Network, allowing for the specification of subnets as parameters. It also enables VNet encryption by default to enhance security.
View code:
import { getName } from '@az-commons';
import * as network from '@pulumi/azure-native/network';
import * as resources from '@pulumi/azure-native/resources';
import * as inputs from '@pulumi/azure-native/types/input';
export default (
name: string,
{
rsGroup,
subnets,
}: {
rsGroup: resources.ResourceGroup;
subnets: inputs.network.SubnetArgs[];
}
) =>
new network.VirtualNetwork(
getName(name, 'vnet'),
{
// Resource group name
resourceGroupName: rsGroup.name,
//Enable VN protection
enableVmProtection: true,
//Enable Vnet encryption
encryption: {
enabled: true,
enforcement:
network.VirtualNetworkEncryptionEnforcement
.AllowUnencrypted,
},
addressSpace: {
addressPrefixes: subnets.map((s) => s.addressPrefix!),
},
subnets,
},
// Ensure the virtual network depends on the resource group
{
dependsOn: rsGroup,
//Ignore this property as the peering will be manage by instance of VirtualNetworkPeering
ignoreChanges: ['virtualNetworkPeerings'],
}
);
The FirewallPolicy.ts
Module
This module is responsible for creating a FirewallPolicy resource, which serves as the root policy for the Azure Firewall. This root policy will be the foundation for linking additional policy groups in subsequent Pulumi projects.
View code:
import * as network from '@pulumi/azure-native/network';
import * as resources from '@pulumi/azure-native/resources';
// Export a function that creates a Firewall Policy
export default (
name: string,
{
rsGroup,
//This tier should be similar to Firewall tier
tier = network.FirewallPolicySkuTier.Basic,
}: {
rsGroup: resources.ResourceGroup;
tier?: network.FirewallPolicySkuTier;
}
) =>
new network.FirewallPolicy(
`${name}-fw-policy`,
{
resourceGroupName: rsGroup.name,
sku: { tier },
snat: { autoLearnPrivateRanges: 'Enabled' },
},
{ dependsOn: rsGroup }
);
The Firewall.ts
Module
This module is designed to set up an Azure Firewall, including essential parts such as IP addresses and diagnostic settings. It ensures the firewall is connected to the designated subnet within the VNet and is associated with the root policy resources.
View code:
import { getName } from '@az-commons';
import * as azure from '@pulumi/azure-native';
import * as pulumi from '@pulumi/pulumi';
export default (
name: string,
{
vnet,
rsGroup,
policy,
//The Policy tier and Firewall tier must be the same
tier = azure.network.AzureFirewallSkuTier.Basic,
logWorkspaceId,
}: {
vnet: azure.network.VirtualNetwork;
rsGroup: azure.resources.ResourceGroup;
policy: azure.network.FirewallPolicy;
tier?: azure.network.AzureFirewallSkuTier;
logWorkspaceId?: pulumi.Output<string>;
}
) => {
const firewallSubnetId = vnet.subnets!.apply(
(s) => s!.find((s) => s!.name === 'AzureFirewallSubnet')!.id!
);
const firewallManageSubnetId = vnet.subnets!.apply(
(s) => s!.find((s) => s!.name === 'AzureFirewallManagementSubnet')!.id!
);
// Create Public IP Address for outbound traffic
const publicIP = new azure.network.PublicIPAddress(
getName(`${name}-outbound`, 'ip'),
{
resourceGroupName: rsGroup.name, // Resource group name
publicIPAllocationMethod: azure.network.IPAllocationMethod.Static, // Static IP allocation
sku: {
name: azure.network.PublicIPAddressSkuName.Standard, // Standard SKU
tier: azure.network.PublicIPAddressSkuTier.Regional, // Regional tier
},
},
{ dependsOn: rsGroup } // Ensure the public IP depends on the resource group
);
// Create Management Public IP Address for Firewall "Basic" tier
const managePublicIP = new azure.network.PublicIPAddress(
getName(`${name}-manage`, 'ip'),
{
resourceGroupName: rsGroup.name, // Resource group name
publicIPAllocationMethod: azure.network.IPAllocationMethod.Static, // Static IP allocation
sku: {
name: azure.network.PublicIPAddressSkuName.Standard, // Standard SKU
tier: azure.network.PublicIPAddressSkuTier.Regional, // Regional tier
},
},
{ dependsOn: rsGroup } // Ensure the management public IP depends on the resource group
);
// Create Azure Firewall
const firewallName = getName(name, 'firewall');
const firewall = new azure.network.AzureFirewall(
firewallName,
{
resourceGroupName: rsGroup.name,
firewallPolicy: { id: policy.id },
ipConfigurations: [
{
name: publicIP.name,
publicIPAddress: { id: publicIP.id },
subnet: { id: firewallSubnetId },
},
],
managementIpConfiguration: {
name: managePublicIP.name,
publicIPAddress: { id: managePublicIP.id },
subnet: { id: firewallManageSubnetId },
},
sku: {
name: azure.network.AzureFirewallSkuName.AZFW_VNet,
tier,
},
},
{
// Ensure the firewall dependents
dependsOn: [publicIP, vnet, managePublicIP, policy],
}
);
//create Diagnostic
if (logWorkspaceId) {
new azure.insights.DiagnosticSetting(
firewallName,
{
resourceUri: firewall.id,
workspaceId: logWorkspaceId,
//Logs
logs: [
'AzureFirewallApplicationRule',
'AzureFirewallNetworkRule',
'AzureFirewallDnsProxy',
].map((c) => ({
category: c,
retentionPolicy: {
enabled: false,
//TODO: For demo purpose the log is only 7 day however, it should be 30 days or longer in PRD
days: 7,
},
enabled: true,
})),
},
{ dependsOn: firewall }
);
}
//Return Firewall info out
return { firewall, publicIP };
};
Developing a Hub VNET
Our goal is to set up the main parts required for the Hub VNet, which include:
- Resource Group: A container for managing related Azure resources.
- Virtual Network (VNet): The main network that hosts our subnets.
- Subnets: Segments within the VNet to isolate and organize resources.
- Public IP Addresses: For outbound internet connectivity and firewall management.
- Firewall Policy: Defines rules to control network traffic.
- Azure Firewall: A managed firewall service to protect our network.
View code:
import { getGroupName, StackReference } from '@az-commons';
import * as network from '@pulumi/azure-native/network';
import * as resources from '@pulumi/azure-native/resources';
import * as config from '../config';
import Firewall from './Firewall';
import FirewallPolicy from './FirewallPolicy';
import VNet from './VNet';
//Reference to the output of `az-01-shared` and link workspace to firewall for log monitoring.
const sharedStack = StackReference<config.SharedStackOutput>('az-01-shared');
// Create Hub Resource Group
const rsGroup = new resources.ResourceGroup(getGroupName(config.azGroups.hub));
// Create Virtual Network with Subnets
const vnet = VNet(config.azGroups.hub, {
rsGroup,
subnets: [
{
// Azure Firewall subnet name must be `AzureFirewallSubnet`
name: 'AzureFirewallSubnet',
addressPrefix: config.subnetSpaces.firewall,
},
{
// Azure Firewall Management subnet name must be `AzureFirewallManagementSubnet`
name: 'AzureFirewallManagementSubnet',
addressPrefix: config.subnetSpaces.firewallManage,
},
{
name: 'general',
addressPrefix: config.subnetSpaces.general,
// Allows Azure Resources Private Link
privateEndpointNetworkPolicies:
network.VirtualNetworkPrivateEndpointNetworkPolicies.Enabled,
},
],
});
//Firewall Policy
const policy = FirewallPolicy(config.azGroups.hub, {
rsGroup,
//The Policy tier and Firewall tier must be the same
tier: network.FirewallPolicySkuTier.Basic,
});
const firewallInfo = Firewall(config.azGroups.hub, {
policy,
rsGroup,
vnet,
//The Policy tier and Firewall tier must be the same
tier: network.AzureFirewallSkuTier.Basic,
logWorkspaceId: sharedStack.logWorkspace.id,
});
// Export the information that will be used in the other projects
export default {
rsGroup: { name: rsGroup.name, id: rsGroup.id },
hubVnet: { name: vnet.name, id: vnet.id },
ipAddress: {
name: firewallInfo.publicIP.name,
address: firewallInfo.publicIP.ipAddress,
id: firewallInfo.publicIP.id,
},
firewallPolicy: { name: policy.name, id: policy.id },
firewall: {
name: firewallInfo.firewall.name,
address: firewallInfo.firewall.ipConfigurations.apply(
(c) => c![0]!.privateIPAddress!
),
id: firewallInfo.firewall.id,
},
};
Note:
- Properly setting the
dependsOn
property ensures that resources are created and destroyed in the correct sequence.- The code above demonstrates how to reuse the log workspace from the
az-01-shared
project for Firewall diagnostics, enabling effective tracing and monitoring of firewall rules.
Deployment and Cleanup
Deploying the Stack
To deploy the stack, execute the pnpm run up
command. This provisions the necessary Azure resources. We can verify the deployment as follows:
- Successfully deployed Azure resources: Overview of successfully deployed Azure resources.
Cleaning Up the Stack
To remove the stack and clean up all associated Azure resources, run the pnpm run destroy
command. This ensures that any resources no longer needed are properly deleted.
Conclusion
In this guide, we have successfully constructed a Hub Virtual Network (VNet) for our private AKS environment using Pulumi. This Hub VNet serves as a crucial element in managing and securing access to all resources within our infrastructure, ensuring robust control and enhanced security measures.
References
Next
Day 05: Implementing a Private AKS Cluster with Pulumi
In the next tutorial, We’ll build a private AKS cluster with advanced networking features. We’ll explore how to integrate the AKS cluster with the Hub VNet and apply the firewall policies we’ve created.
Thank You
Thank you for taking the time to read this guide! I hope it has been helpful, feel free to explore further, and happy coding! 🌟✨
Steven | GitHub