3. Creating a Virtual Network with Enhanced Security using Azure CLI
Overview
This exercise will guide you through provisioning a complete solution on Azure using only Azure CLI. The solution comprises three servers: a web server, a reverse proxy, and a bastion host. These components are connected via a virtual network and secured with a Network Security Group (NSG) and Application Security Groups (ASGs).
By the end of this exercise, you will have a working environment with secure, isolated network communication between components.
Objectives
- Create a resource group and a virtual network to connect the servers.
- Set up a Network Security Group (NSG) for subnet-level security.
- Configure Application Security Groups (ASGs) for more granular control.
- Provision three virtual machines for the web server, reverse proxy, and bastion host.
- Test the configuration by accessing the reverse proxy through the bastion host.
Prerequisites
- Azure CLI installed on your machine. If not, install it from here.
- An active Azure subscription.
Step 1: Create a Resource Group
A resource group is a container that holds all the related resources.
Run the following command to set up the resource group:
az group create --name DemoRG --location northeurope
Explanation:
az group create
: Creates a new resource group.--name DemoRG
: Names the resource groupDemoRG
.--location northeurope
: Sets the location to North Europe.
Step 2: Create a Virtual Network
The virtual network (VNet) connects the components.
Run the following command to set up the VNet:
az network vnet create \
--resource-group DemoRG \
--name DemoVNet \
--address-prefix 10.0.0.0/16 \
--subnet-name default \
--subnet-prefix 10.0.0.0/24
Explanation:
az network vnet create
: Creates a virtual network.--resource-group DemoRG
: Specifies the resource group.--name DemoVNet
: Names the VNetDemoVNet
.--address-prefix 10.0.0.0/16
: Sets the address space for the VNet.--subnet-name default
: Names the subnetdefault
.--subnet-prefix 10.0.0.0/24
: Sets the address range for the subnet.
Step 3: Configure Network Security
Set Up Application Security Groups (ASGs)
ASGs allow grouping of virtual machines for applying NSG rules more effectively.
az network asg create \
--resource-group DemoRG \
--name ReverseProxyASG
az network asg create \
--resource-group DemoRG \
--name BastionHostASG
Explanation:
az network asg create
: Creates an Application Security Group.--name ReverseProxyASG
: Names the ASGReverseProxyASG
.
Create the Network Security Group (NSG)
The NSG controls inbound and outbound traffic to the subnet.
az network nsg create \
--resource-group DemoRG \
--name DemoNSG
Explanation:
az network nsg create
: Creates a Network Security Group.--name DemoNSG
: Names the NSGDemoNSG
.--location northeurope
: Ensures the NSG is in the same region as the VNet.
Apply NSG Rules
a. Allow SSH Access to Bastion Host ASG
Command:
az network nsg rule create \
--resource-group DemoRG \
--nsg-name DemoNSG \
--name AllowSSH \
--priority 1000 \
--access Allow \
--protocol Tcp \
--direction Inbound \
--source-address-prefixes Internet \
--source-port-ranges "*" \
--destination-asg BastionHostASG \
--destination-port-ranges 22
Explanation:
az network nsg rule create
: Adds a rule to the NSG.--nsg-name DemoNSG
: Targets the NSGDemoNSG
.--name AllowSSH
: Names the ruleAllowSSH
.--priority 1000
: Sets the priority (lower number means higher priority).--access Allow
: Allows traffic.--protocol Tcp
: Applies to TCP protocol.--direction Inbound
: Applies to incoming traffic.--source-address-prefixes Internet
: Source is any Internet address.--destination-asg BastionHostASG
: Targets theBastionHostASG
.--destination-port-ranges 22
: Applies to port 22 (SSH).
b. Allow HTTP Access to Reverse Proxy ASG
Command:
az network nsg rule create \
--resource-group DemoRG \
--nsg-name DemoNSG \
--name AllowHTTP \
--priority 2000 \
--access Allow \
--protocol Tcp \
--direction Inbound \
--source-address-prefixes Internet \
--source-port-ranges "*" \
--destination-asg ReverseProxyASG \
--destination-port-ranges 80
Explanation:
--name AllowHTTP
: Names the ruleAllowHTTP
.--priority 2000
: Sets a lower priority than SSH rule.--destination-asg ReverseProxyASG
: Targets theReverseProxyASG
.--destination-port-ranges 80
: Applies to port 80 (HTTP).
Associate the NSG with the Subnet
Attach the NSG to the VNet’s subnet.
az network vnet subnet update \
--resource-group DemoRG \
--vnet-name DemoVNet \
--name default \
--network-security-group DemoNSG
Step 4: Provision the Virtual Machines
Deploy three VMs: the web server, reverse proxy, and bastion host. Use the configuration files for the web server and reverse proxy.
Provision the Web Server
Create Configuration File
Create the Web Server Configuration File: Save the following content as
web_server_config.yaml
:#cloud-config packages: - nginx write_files: - path: /var/www/html/index.html content: | <!DOCTYPE html> <html> <head> <title>Hello World!</title> </head> <body> <h1>Hello World!</h1> </body> </html> - path: /etc/nginx/sites-available/default content: | server { listen 8080 default_server; server_name _; root /var/www/html; index index.html; } runcmd: - systemctl restart nginx
Explanation:
- Installs Nginx.
- Creates a custom
index.html
. - Configures Nginx to listen on port 8080.
- Restarts Nginx service.
Ensure the file is accessible to the CLI during VM creation.
Provision the Web Server
Now, use the configuration file to deploy the web server:
az vm create \
--resource-group DemoRG \
--name WebServer \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name DemoVNet \
--subnet default \
--nsg "" \
--public-ip-address "" \
--generate-ssh-keys \
--custom-data @web_server_config.yaml
Explanation:
az vm create
: Creates a virtual machine.--name WebServer
: Names the VMWebServer
.--image Ubuntu2204
: Uses Ubuntu Server 22.04 LTS image.--size Standard_B1s
: Sets the VM size.--admin-username azureuser
: Sets the admin username.--vnet-name DemoVNet
: Places VM inDemoVNet
.--subnet default
: Uses thedefault
subnet.--nsg ""
: No NSG at NIC level.--public-ip-address ""
: No public IP assigned.--generate-ssh-keys
: Generates SSH keys if not present.--custom-data web_server_config.yaml
: Uses cloud-init file to configure the VM.
Provision the Reverse Proxy
Create Configuration File
Create the Reverse Proxy Configuration File: Save the following content as
reverse_proxy_config.yaml
:#cloud-config packages: - nginx write_files: - path: /etc/nginx/sites-available/default content: | server { listen 80; location / { proxy_pass http://webserver.internal.cloudapp.net:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } runcmd: - systemctl restart nginx
Explanation:
- Installs Nginx.
- Configures Nginx as a reverse proxy to the Web Server.
- Restarts Nginx service.
Ensure the file is accessible to the CLI during VM creation.
Provision the Reverse Proxy
Now, use the configuration file to deploy the Reverse Proxy :
az vm create \
--resource-group DemoRG \
--name ReverseProxy \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name DemoVNet \
--subnet default \
--nsg "" \
--generate-ssh-keys \
--custom-data @reverse_proxy_config.yaml
Explanation:
--name ReverseProxy
: Names the VMReverseProxy
.--generate-ssh-keys
: Reuses existing SSH keys.--custom-data reverse_proxy_config.yaml
: Uses cloud-init file.
Provision the Bastion Host
Provision the Bastion Host
Now, use the configuration file to deploy the Bastion Host :
az vm create \
--resource-group DemoRG \
--name BastionHost \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name DemoVNet \
--subnet default \
--nsg "" \
--generate-ssh-keys
Explanation:
--name BastionHost
: Names the VMBastionHost
.- No
--custom-data
: Standard VM without cloud-init. - Public IP Address: Assigned by default for SSH access.
Step 5: Attach ASGs to VM NICs
To ensure proper traffic management and rule enforcement, Application Security Groups (ASGs) must be attached to the Network Interface Cards (NICs) of the virtual machines. This step will be divided into two parts: attaching the ASG to the Reverse Proxy and then to the Bastion Host.
Attach ASG to the Reverse Proxy NIC
Retrieve the NIC ID for the Reverse Proxy virtual machine:
REVERSE_PROXY_NIC_ID=$(az vm show --resource-group DemoRG --name ReverseProxy --query 'networkProfile.networkInterfaces[0].id' --output tsv)
Extract the NIC Name from the NIC ID:
REVERSE_PROXY_NIC_NAME=$(basename $REVERSE_PROXY_NIC_ID)
Get the IP Configuration Name for the Reverse Proxy NIC:
REVERSE_PROXY_NIC_IP_CONFIG=$(az network nic show --resource-group DemoRG --name $REVERSE_PROXY_NIC_NAME --query 'ipConfigurations[0].name' --output tsv)
Attach the Reverse Proxy ASG to the NIC:
az network nic ip-config update \ --resource-group DemoRG \ --nic-name $REVERSE_PROXY_NIC_NAME \ --name $REVERSE_PROXY_NIC_IP_CONFIG \ --application-security-groups ReverseProxyASG
Attach ASG to the Bastion Host NIC
Retrieve the NIC ID for the Bastion Host virtual machine:
BASTION_HOST_NIC_ID=$(az vm show --resource-group DemoRG --name BastionHost --query 'networkProfile.networkInterfaces[0].id' --output tsv)
Extract the NIC Name from the NIC ID:
BASTION_HOST_NIC_NAME=$(basename $BASTION_HOST_NIC_ID)
Get the IP Configuration Name for the Bastion Host NIC:
BASTION_HOST_NIC_IP_CONFIG=$(az network nic show --resource-group DemoRG --name $BASTION_HOST_NIC_NAME --query 'ipConfigurations[0].name' --output tsv)
Attach the Bastion Host ASG to the NIC:
az network nic ip-config update \ --resource-group DemoRG \ --nic-name $BASTION_HOST_NIC_NAME \ --name $BASTION_HOST_NIC_IP_CONFIG \ --application-security-groups BastionHostASG
Test and Verify
Verify NSG Rules
Ensure that:
- SSH is allowed only to the Bastion Host.
- HTTP is allowed only to the Reverse Proxy.
Verify ASG Assignments
Check that:
ReverseProxyASG
is attached to the Reverse Proxy NIC.BastionHostASG
is attached to the Bastion Host NIC.
Test HTTP Access
- Access the Reverse Proxy’s public IP on port 80.
- You should see the “Hello World!” page served by the Web Server.
Test Internal Communication
- From the Bastion Host, SSH into the Reverse Proxy using its private IP.
- From the Reverse Proxy, ensure it can reach the Web Server via its internal DNS name.
Summary
In this exercise, you:
- Created a resource group and a virtual network with a subnet.
- Configured Application Security Groups and a Network Security Group with specific rules.
- Provisioned three VMs with roles of Web Server, Reverse Proxy, and Bastion Host.
- Automated server configurations using cloud-init.
- Verified that the network security settings work as intended.
Cleanup Resources
Once you’ve validated the setup, delete the resources to avoid incurring costs:
az group delete --name DemoRG --yes --no-wait
TL;DR
web_server_config.yaml
#cloud-config
packages:
- nginx
write_files:
- path: /var/www/html/index.html
content: |
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
- path: /etc/nginx/sites-available/default
content: |
server {
listen 8080 default_server;
server_name _;
root /var/www/html;
index index.html;
}
runcmd:
- systemctl restart nginx
reverse_proxy_config.yaml
#cloud-config
packages:
- nginx
write_files:
- path: /etc/nginx/sites-available/default
content: |
server {
listen 80;
location / {
proxy_pass http://webserver.internal.cloudapp.net:8080/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
runcmd:
- systemctl restart nginx
provision_solution.sh
#!/bin/bash
# Variables
RESOURCE_GROUP="DemoRG"
LOCATION="northeurope"
VNET_NAME="DemoVNet"
SUBNET_NAME="default"
NSG_NAME="DemoNSG"
ASG_REVERSE_PROXY="ReverseProxyASG"
ASG_BASTION_HOST="BastionHostASG"
WEB_SERVER="WebServer"
REVERSE_PROXY="ReverseProxy"
BASTION_HOST="BastionHost"
# Create Resource Group
echo "Creating Resource Group..."
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create Virtual Network and Subnet
echo "Creating Virtual Network and Subnet..."
az network vnet create \
--resource-group $RESOURCE_GROUP \
--name $VNET_NAME \
--address-prefix 10.0.0.0/16 \
--subnet-name $SUBNET_NAME \
--subnet-prefix 10.0.0.0/24
# Create Application Security Groups
echo "Creating Application Security Groups..."
az network asg create \
--resource-group $RESOURCE_GROUP \
--name $ASG_REVERSE_PROXY
az network asg create \
--resource-group $RESOURCE_GROUP \
--name $ASG_BASTION_HOST
# Create Network Security Group
echo "Creating Network Security Group..."
az network nsg create \
--resource-group $RESOURCE_GROUP \
--name $NSG_NAME
# Create NSG Rules
echo "Adding SSH and HTTP rules to NSG..."
az network nsg rule create \
--resource-group $RESOURCE_GROUP \
--nsg-name $NSG_NAME \
--name AllowSSH \
--priority 1000 \
--access Allow \
--protocol Tcp \
--direction Inbound \
--source-address-prefixes Internet \
--source-port-ranges "*" \
--destination-asg $ASG_BASTION_HOST \
--destination-port-ranges 22
az network nsg rule create \
--resource-group $RESOURCE_GROUP \
--nsg-name $NSG_NAME \
--name AllowHTTP \
--priority 2000 \
--access Allow \
--protocol Tcp \
--direction Inbound \
--source-address-prefixes Internet \
--source-port-ranges "*" \
--destination-asg $ASG_REVERSE_PROXY \
--destination-port-ranges 80
# Associate NSG with Subnet
echo "Associating NSG with Subnet..."
az network vnet subnet update \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--name $SUBNET_NAME \
--network-security-group $NSG_NAME
# Create Web Server VM
echo "Creating Web Server VM..."
az vm create \
--resource-group $RESOURCE_GROUP \
--name $WEB_SERVER \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg "" \
--public-ip-address "" \
--generate-ssh-keys \
--custom-data @web_server_config.yaml
# Create Reverse Proxy VM
echo "Creating Reverse Proxy VM..."
az vm create \
--resource-group $RESOURCE_GROUP \
--name $REVERSE_PROXY \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg "" \
--generate-ssh-keys \
--custom-data @reverse_proxy_config.yaml
# Create Bastion Host VM
echo "Creating Bastion Host VM..."
az vm create \
--resource-group $RESOURCE_GROUP \
--name $BASTION_HOST \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg "" \
--generate-ssh-keys
# Attach ASGs to NIC IP Configurations
echo "Attaching ASGs to NICs..."
# Get NIC IDs
REVERSE_PROXY_NIC_ID=$(az vm show --resource-group $RESOURCE_GROUP --name $REVERSE_PROXY --query 'networkProfile.networkInterfaces[0].id' --output tsv)
BASTION_HOST_NIC_ID=$(az vm show --resource-group $RESOURCE_GROUP --name $BASTION_HOST --query 'networkProfile.networkInterfaces[0].id' --output tsv)
# Extract NIC Names
REVERSE_PROXY_NIC_NAME=$(basename $REVERSE_PROXY_NIC_ID)
BASTION_HOST_NIC_NAME=$(basename $BASTION_HOST_NIC_ID)
# Get the NIC IP Configurations
REVERSE_PROXY_NIC_IP_CONFIG=$(az network nic show --resource-group $RESOURCE_GROUP --name $REVERSE_PROXY_NIC_NAME --query 'ipConfigurations[0].name' --output tsv)
BASTION_HOST_NIC_IP_CONFIG=$(az network nic show --resource-group $RESOURCE_GROUP --name $BASTION_HOST_NIC_NAME --query 'ipConfigurations[0].name' --output tsv)
# Attach ASG to Reverse Proxy NIC
az network nic ip-config update \
--resource-group $RESOURCE_GROUP \
--nic-name $REVERSE_PROXY_NIC_NAME \
--name $REVERSE_PROXY_NIC_IP_CONFIG \
--application-security-groups $ASG_REVERSE_PROXY
# Attach ASG to Bastion Host NIC
az network nic ip-config update \
--resource-group $RESOURCE_GROUP \
--nic-name $BASTION_HOST_NIC_NAME \
--name $BASTION_HOST_NIC_IP_CONFIG \
--application-security-groups $ASG_BASTION_HOST
echo "Provisioning Complete!"