Skip to content

Instantly share code, notes, and snippets.

@mvasilenko
Last active April 2, 2026 12:09
Show Gist options
  • Select an option

  • Save mvasilenko/df8c293ce67f8f9b709cb1b0b5836c5b to your computer and use it in GitHub Desktop.

Select an option

Save mvasilenko/df8c293ce67f8f9b709cb1b0b5836c5b to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# Usage examples:
# ./az-nsg-lockdown.sh on -g my-rg -v my-vm # VM: lock down (interactive)
# ./az-nsg-lockdown.sh off -g my-rg -v my-vm # VM: unlock (interactive)
# ./az-nsg-lockdown.sh on -g my-rg -s my-vmss # VMSS: lock down via NIC NSG only (interactive)
# ./az-nsg-lockdown.sh off -g my-rg -s my-vmss # VMSS: unlock via NIC NSG only (interactive)
# ./az-nsg-lockdown.sh on -g my-rg -v my-vm --yes # lock down (no prompt)
# ./az-nsg-lockdown.sh off -g my-rg -s my-vmss -y # unlock (no prompt)
set -euo pipefail
RULE_NAME_INBOUND="DenyAllInbound"
RULE_NAME_OUTBOUND="DenyAllOutbound"
RULE_PRIORITY=100
usage() {
cat <<EOF
Usage: $(basename "$0") <on|off> [OPTIONS]
Toggle deny-all NSG rules for an Azure VM, VMSS, or NSG.
Commands:
on Apply deny-all inbound/outbound rules
off Remove deny-all rules
Options:
-g, --resource-group <name> Resource group (required)
-n, --nsg-name <name> NSG name (use this OR --vm-name/--vmss-name)
-v, --vm-name <name> VM name — NSG will be auto-detected
-s, --vmss-name <name> VMSS name — NSG will be auto-detected
-p, --priority <num> Rule priority (default: ${RULE_PRIORITY})
--inbound-only Only affect inbound rules
--outbound-only Only affect outbound rules
-y, --yes Skip confirmation prompt
-h, --help Show this help
Examples:
$(basename "$0") on -g my-rg -v my-vm
$(basename "$0") off -g my-rg -v my-vm
$(basename "$0") on -g my-rg -s my-vmss
$(basename "$0") off -g my-rg -s my-vmss
$(basename "$0") on -g my-rg -n my-nsg -p 50 --yes
EOF
}
die() { echo "ERROR: $*" >&2; exit 1; }
# Parse command
[[ $# -lt 1 ]] && { usage; exit 1; }
ACTION="$1"; shift
[[ "$ACTION" == "-h" || "$ACTION" == "--help" ]] && { usage; exit 0; }
[[ "$ACTION" != "on" && "$ACTION" != "off" ]] && die "First argument must be 'on' or 'off', got: '$ACTION'"
RESOURCE_GROUP=""
NSG_NAME=""
VM_NAME=""
VMSS_NAME=""
INBOUND_ONLY=false
OUTBOUND_ONLY=false
YES=false
while [[ $# -gt 0 ]]; do
case "$1" in
-g|--resource-group) RESOURCE_GROUP="$2"; shift 2 ;;
-n|--nsg-name) NSG_NAME="$2"; shift 2 ;;
-v|--vm-name) VM_NAME="$2"; shift 2 ;;
-s|--vmss-name) VMSS_NAME="$2"; shift 2 ;;
-p|--priority) RULE_PRIORITY="$2"; shift 2 ;;
--inbound-only) INBOUND_ONLY=true; shift ;;
--outbound-only) OUTBOUND_ONLY=true; shift ;;
-y|--yes) YES=true; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unknown option: $1" ;;
esac
done
[[ -z "$RESOURCE_GROUP" ]] && die "--resource-group is required"
[[ "$INBOUND_ONLY" == true && "$OUTBOUND_ONLY" == true ]] && die "--inbound-only and --outbound-only are mutually exclusive"
[[ -n "$VM_NAME" && -n "$VMSS_NAME" ]] && die "--vm-name and --vmss-name are mutually exclusive"
# Resolve NSG name from VM or VMSS if needed
if [[ -z "$NSG_NAME" ]]; then
if [[ -n "$VM_NAME" ]]; then
echo "Looking up NSG for VM '$VM_NAME'..."
NIC_ID=$(az vm show -g "$RESOURCE_GROUP" -n "$VM_NAME" \
--query "networkProfile.networkInterfaces[0].id" -o tsv)
[[ -z "$NIC_ID" ]] && die "No NIC found for VM '$VM_NAME'"
NSG_ID=$(az network nic show --ids "$NIC_ID" \
--query "networkSecurityGroup.id" -o tsv 2>/dev/null || true)
[[ -z "$NSG_ID" ]] && die "No NSG attached to NIC of VM '$VM_NAME'"
NSG_NAME=$(basename "$NSG_ID")
elif [[ -n "$VMSS_NAME" ]]; then
echo "Looking up NSG for VMSS '$VMSS_NAME'..."
NSG_ID=$(az vmss show -g "$RESOURCE_GROUP" -n "$VMSS_NAME" \
--query "virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0].networkSecurityGroup.id" \
-o tsv 2>/dev/null || true)
[[ -z "$NSG_ID" ]] && die "No NSG attached to NIC config of VMSS '$VMSS_NAME'"
NSG_NAME=$(basename "$NSG_ID")
else
die "Either --nsg-name, --vm-name, or --vmss-name is required"
fi
echo "Resolved NSG: $NSG_NAME"
fi
apply_rule() {
local direction="$1"
local rule_name="$2"
echo " Creating $direction deny-all rule '$rule_name' (priority $RULE_PRIORITY)..."
az network nsg rule create \
--resource-group "$RESOURCE_GROUP" \
--nsg-name "$NSG_NAME" \
--name "$rule_name" \
--priority "$RULE_PRIORITY" \
--direction "$direction" \
--access Deny \
--protocol '*' \
--source-address-prefixes '*' \
--source-port-ranges '*' \
--destination-address-prefixes '*' \
--destination-port-ranges '*' \
--output none
echo " Done."
}
remove_rule() {
local direction="$1"
local rule_name="$2"
if az network nsg rule show \
--resource-group "$RESOURCE_GROUP" \
--nsg-name "$NSG_NAME" \
--name "$rule_name" &>/dev/null; then
echo " Removing $direction deny-all rule '$rule_name'..."
az network nsg rule delete \
--resource-group "$RESOURCE_GROUP" \
--nsg-name "$NSG_NAME" \
--name "$rule_name" \
--output none
echo " Done."
else
echo " Rule '$rule_name' not found — skipping."
fi
}
echo "NSG: $NSG_NAME | Resource group: $RESOURCE_GROUP | Action: $ACTION"
echo ""
if [[ "$YES" == false ]]; then
read -r -p "Proceed with lockdown $ACTION? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
echo ""
fi
if [[ "$ACTION" == "on" ]]; then
[[ "$OUTBOUND_ONLY" == false ]] && apply_rule Inbound "$RULE_NAME_INBOUND"
[[ "$INBOUND_ONLY" == false ]] && apply_rule Outbound "$RULE_NAME_OUTBOUND"
echo "Lockdown ON — all traffic denied."
else
[[ "$OUTBOUND_ONLY" == false ]] && remove_rule Inbound "$RULE_NAME_INBOUND"
[[ "$INBOUND_ONLY" == false ]] && remove_rule Outbound "$RULE_NAME_OUTBOUND"
echo "Lockdown OFF — deny-all rules removed."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment