├── .bashrc ├── DemoAppConfig.sh ├── Deploy-NVAs.json ├── Hub-VNet.json ├── MultiVM-LoadBalanced.json ├── Single-VM.json ├── Spoke-VNet.json ├── VDC-Networking-Master.json ├── VNet-Peering-Hub.json ├── VNet-Peering-Spoke.json ├── images ├── AzMon1.jpg ├── AzMonAlert.jpg ├── AzMonCPU.jpg ├── BackendPools.JPG ├── DomainName.jpg ├── EffectiveRoutes1.JPG ├── EffectiveRoutes2.jpg ├── FlowLogs1.jpg ├── Hub-RBAC.jpg ├── Identity.jpg ├── NSG1.jpg ├── NSG2.jpg ├── NSGLogs.jpg ├── NetWatcher1.jpg ├── NetWatcherTopo.jpg ├── NextHop.jpg ├── PolicyCompliance.jpg ├── SSHtoNVA.jpg ├── SecCenter.jpg ├── SecRecommendations.jpg ├── SubLimits.jpg ├── UDR.jpg ├── UDR2.jpg ├── UseRemoteGW.JPG ├── VDC-Networking-Main.jpg ├── VDC-Spoke1-RG.jpg └── armpolicies1.jpg ├── naming-policy.json ├── readme.md ├── vdc-vnets.json └── vpn-gw.json /.bashrc: -------------------------------------------------------------------------------- 1 | # ~/.bashrc: executed by bash(1) for non-login shells. 2 | # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) 3 | # for examples 4 | 5 | # If not running interactively, don't do anything 6 | case $- in 7 | *i*) ;; 8 | *) return;; 9 | esac 10 | 11 | # don't put duplicate lines or lines starting with space in the history. 12 | # See bash(1) for more options 13 | HISTCONTROL=ignoreboth 14 | 15 | # append to the history file, don't overwrite it 16 | shopt -s histappend 17 | 18 | # for setting history length see HISTSIZE and HISTFILESIZE in bash(1) 19 | HISTSIZE=1000 20 | HISTFILESIZE=2000 21 | 22 | # check the window size after each command and, if necessary, 23 | # update the values of LINES and COLUMNS. 24 | shopt -s checkwinsize 25 | 26 | # If set, the pattern "**" used in a pathname expansion context will 27 | # match all files and zero or more directories and subdirectories. 28 | #shopt -s globstar 29 | 30 | # make less more friendly for non-text input files, see lesspipe(1) 31 | [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" 32 | 33 | # set variable identifying the chroot you work in (used in the prompt below) 34 | if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then 35 | debian_chroot=$(cat /etc/debian_chroot) 36 | fi 37 | 38 | # set a fancy prompt (non-color, unless we know we "want" color) 39 | case "$TERM" in 40 | xterm-color|*-256color) color_prompt=yes;; 41 | esac 42 | 43 | # uncomment for a colored prompt, if the terminal has the capability; turned 44 | # off by default to not distract the user: the focus in a terminal window 45 | # should be on the output of commands, not on the prompt 46 | #force_color_prompt=yes 47 | 48 | if [ -n "$force_color_prompt" ]; then 49 | if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then 50 | # We have color support; assume it's compliant with Ecma-48 51 | # (ISO/IEC-6429). (Lack of such support is extremely rare, and such 52 | # a case would tend to support setf rather than setaf.) 53 | color_prompt=yes 54 | else 55 | color_prompt= 56 | fi 57 | fi 58 | 59 | if [ "$color_prompt" = yes ]; then 60 | case ${HOSTNAME%%-*} in 61 | OnPrem) PS1='${debian_chroot:+($debian_chroot)}\[\033[07;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ';; # Inverse 62 | Spoke1) PS1='${debian_chroot:+($debian_chroot)}\[\033[01;33m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ';; # Yellow 63 | Spoke2) PS1='${debian_chroot:+($debian_chroot)}\[\033[01;34m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ';; # Cyan 64 | *) PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ';; # Green 65 | esac 66 | else 67 | PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 68 | fi 69 | unset color_prompt force_color_prompt 70 | 71 | # If this is an xterm set the title to user@host:dir 72 | case "$TERM" in 73 | xterm*|rxvt*) 74 | PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" 75 | ;; 76 | *) 77 | ;; 78 | esac 79 | 80 | # enable color support of ls and also add handy aliases 81 | if [ -x /usr/bin/dircolors ]; then 82 | test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" 83 | alias ls='ls --color=auto' 84 | #alias dir='dir --color=auto' 85 | #alias vdir='vdir --color=auto' 86 | 87 | alias grep='grep --color=auto' 88 | alias fgrep='fgrep --color=auto' 89 | alias egrep='egrep --color=auto' 90 | fi 91 | 92 | # colored GCC warnings and errors 93 | #export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' 94 | 95 | # some more ls aliases 96 | alias ll='ls -alF' 97 | alias la='ls -A' 98 | alias l='ls -CF' 99 | 100 | # Add an "alert" alias for long running commands. Use like so: 101 | # sleep 10; alert 102 | alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' 103 | 104 | # Alias definitions. 105 | # You may want to put all your additions into a separate file like 106 | # ~/.bash_aliases, instead of adding them here directly. 107 | # See /usr/share/doc/bash-doc/examples in the bash-doc package. 108 | 109 | if [ -f ~/.bash_aliases ]; then 110 | . ~/.bash_aliases 111 | fi 112 | 113 | # enable programmable completion features (you don't need to enable 114 | # this, if it's already enabled in /etc/bash.bashrc and /etc/profile 115 | # sources /etc/bash.bashrc). 116 | if ! shopt -oq posix; then 117 | if [ -f /usr/share/bash-completion/bash_completion ]; then 118 | . /usr/share/bash-completion/bash_completion 119 | elif [ -f /etc/bash_completion ]; then 120 | . /etc/bash_completion 121 | fi 122 | fi -------------------------------------------------------------------------------- /DemoAppConfig.sh: -------------------------------------------------------------------------------- 1 | # Update system 2 | sudo apt-get update 3 | 4 | # Install node.js & NPM 5 | sudo apt-get install npm -y 6 | curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -- 7 | sudo apt-get install -y nodejs 8 | 9 | # Clone Git repo for demo app 10 | sudo apt-get install git -y 11 | git clone https://github.com/araffe/nodejs-demoapp.git 12 | cd nodejs-demoapp/ 13 | sudo npm install forever -g 14 | sudo npm install 15 | forever start ./bin/www -------------------------------------------------------------------------------- /Deploy-NVAs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Username for the Virtual Machine." 9 | } 10 | }, 11 | "adminPassword": { 12 | "type": "securestring", 13 | "metadata": { 14 | "description": "Password for the Virtual Machine." 15 | } 16 | }, 17 | "vnetName": { 18 | "defaultValue": "VNet", 19 | "type": "string" 20 | }, 21 | "VDCResourceGroup": { 22 | "defaultValue": "VDC-Lab", 23 | "type": "string" 24 | } 25 | }, 26 | "variables": { 27 | "imagePublisher": "cisco", 28 | "imageOffer": "cisco-csr-1000v", 29 | "imageSKU": "16_6", 30 | "storageAccountName": "[concat('vhds', uniqueString(resourceGroup().id))]", 31 | "publicIPAddressName": "csr1-PIP", 32 | "dnsPrefix": "[concat('csr-', uniqueString(resourceGroup().id))]", 33 | "vmName": "vdc-csr-1", 34 | "vmSize": "Standard_D2_V2", 35 | "subnet1Name": "Hub_VNet-Subnet1", 36 | "subnet2Name": "Hub_VNet-Subnet2", 37 | "subnet1Ref": "[concat(variables('vnetID'),'/subnets/', variables('subnet1Name'))]", 38 | "subnet2Ref": "[concat(variables('vnetID'),'/subnets/', variables('subnet2Name'))]", 39 | "addressPrefix": "10.1.0.0/16", 40 | "subnet1Prefix": "10.101.1.0/24", 41 | "subnet2Prefix": "10.101.2.0/24", 42 | "subnet1StartAddress": "10.101.1.4", 43 | "subnet2StartAddress": "10.101.2.4", 44 | "nsgname": "[concat(variables('vmName'),'-SSH-SecurityGroup')]", 45 | "vnetID": "[resourceId(parameters('VDCResourceGroup'), 'Microsoft.Network/virtualNetworks', parameters('vNetName'))]" 46 | }, 47 | "resources": [ 48 | { 49 | "type": "Microsoft.Storage/storageAccounts", 50 | "name": "[variables('storageAccountName')]", 51 | "apiVersion": "2016-01-01", 52 | "location": "[resourceGroup().location]", 53 | "sku": { 54 | "name": "Standard_LRS" 55 | }, 56 | "kind": "Storage", 57 | "properties": {} 58 | }, 59 | { 60 | "apiVersion": "2016-03-30", 61 | "type": "Microsoft.Network/publicIPAddresses", 62 | "name": "[variables('publicIPAddressName')]", 63 | "location": "[resourceGroup().location]", 64 | "properties": { 65 | "publicIPAllocationMethod": "Dynamic", 66 | "dnsSettings": { 67 | "domainNameLabel": "[variables('dnsPrefix')]" 68 | } 69 | } 70 | }, 71 | { 72 | "apiVersion": "2015-06-15", 73 | "type": "Microsoft.Network/networkSecurityGroups", 74 | "name": "[variables('nsgName')]", 75 | "location": "[resourceGroup().location]", 76 | "properties": { 77 | "securityRules": [ 78 | { 79 | "name": "SSH-Rule", 80 | "properties": { 81 | "description": "Allow SSH", 82 | "protocol": "Tcp", 83 | "sourcePortRange": "*", 84 | "destinationPortRange": "22", 85 | "sourceAddressPrefix": "Internet", 86 | "destinationAddressPrefix": "*", 87 | "access": "Allow", 88 | "priority": 100, 89 | "direction": "Inbound" 90 | } 91 | }, 92 | { 93 | "name": "UDP-Rule1", 94 | "properties": { 95 | "description": "Allow UDP", 96 | "protocol": "Udp", 97 | "sourcePortRange": "*", 98 | "destinationPortRange": "500", 99 | "sourceAddressPrefix": "Internet", 100 | "destinationAddressPrefix": "*", 101 | "access": "Allow", 102 | "priority": 101, 103 | "direction": "Inbound" 104 | } 105 | }, 106 | { 107 | "name": "UDP-Rule2", 108 | "properties": { 109 | "description": "Allow UDP", 110 | "protocol": "Udp", 111 | "sourcePortRange": "*", 112 | "destinationPortRange": "4500", 113 | "sourceAddressPrefix": "Internet", 114 | "destinationAddressPrefix": "*", 115 | "access": "Allow", 116 | "priority": 102, 117 | "direction": "Inbound" 118 | } 119 | } 120 | ] 121 | } 122 | }, 123 | { 124 | "apiVersion": "2015-06-15", 125 | "type": "Microsoft.Network/networkInterfaces", 126 | "name": "[concat(variables('vmName'),'-Nic0')]", 127 | "location": "[resourceGroup().location]", 128 | "dependsOn": [ 129 | "[resourceId('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" 130 | ], 131 | "properties": { 132 | "ipConfigurations": [ 133 | { 134 | "name": "ipconfig1", 135 | "properties": { 136 | "privateIPAllocationMethod": "Static", 137 | "privateIPAddress": "[variables('subnet1StartAddress')]", 138 | "publicIPAddress": { 139 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" 140 | }, 141 | "subnet": { 142 | "id": "[variables('subnet1Ref')]" 143 | }, 144 | "networkSecurityGroup": { 145 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgname'))]" 146 | } 147 | } 148 | } 149 | ], 150 | "enableIPForwarding": true 151 | } 152 | }, 153 | { 154 | "apiVersion": "2015-06-15", 155 | "type": "Microsoft.Network/networkInterfaces", 156 | "name": "[concat(variables('vmName'),'-Nic1')]", 157 | "location": "[resourceGroup().location]", 158 | "dependsOn": [], 159 | "properties": { 160 | "ipConfigurations": [ 161 | { 162 | "name": "ipconfig1", 163 | "properties": { 164 | "privateIPAllocationMethod": "Static", 165 | "privateIPAddress": "[variables('subnet2StartAddress')]", 166 | "subnet": { 167 | "id": "[variables('subnet2Ref')]" 168 | } 169 | } 170 | } 171 | ], 172 | "enableIPForwarding": true 173 | } 174 | }, 175 | { 176 | "apiVersion": "2015-06-15", 177 | "type": "Microsoft.Compute/virtualMachines", 178 | "name": "[variables('vmName')]", 179 | "location": "[resourceGroup().location]", 180 | "plan": { 181 | "name": "csr-azure-byol", 182 | "publisher": "cisco", 183 | "product": "cisco-csr-1000v" 184 | }, 185 | "dependsOn": [ 186 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 187 | "[resourceId('Microsoft.Network/networkInterfaces/', concat(variables('vmName'), '-Nic0'))]", 188 | "[resourceId('Microsoft.Network/networkInterfaces/', concat(variables('vmName'), '-Nic1'))]" 189 | ], 190 | "properties": { 191 | "hardwareProfile": { 192 | "vmSize": "[variables('vmSize')]" 193 | }, 194 | "osProfile": { 195 | "computerName": "[variables('vmName')]", 196 | "adminUsername": "[parameters('adminUsername')]", 197 | "adminPassword": "[parameters('adminPassword')]" 198 | }, 199 | "storageProfile": { 200 | "imageReference": { 201 | "publisher": "[variables('imagePublisher')]", 202 | "offer": "[variables('imageOffer')]", 203 | "sku": "[variables('imageSKU')]", 204 | "version": "latest" 205 | }, 206 | "osDisk": { 207 | "name": "osdisk", 208 | "vhd": { 209 | "uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob, 'vhds/osdisk.vhd')]" 210 | }, 211 | "caching": "ReadWrite", 212 | "createOption": "FromImage" 213 | } 214 | }, 215 | "networkProfile": { 216 | "networkInterfaces": [ 217 | { 218 | "properties": { 219 | "primary": true 220 | }, 221 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('vmName'),'-Nic0'))]" 222 | }, 223 | { 224 | "properties": { 225 | "primary": false 226 | }, 227 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('vmName'),'-Nic1'))]" 228 | } 229 | ] 230 | }, 231 | "diagnosticsProfile": { 232 | "bootDiagnostics": { 233 | "enabled": "true", 234 | "storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob]" 235 | } 236 | } 237 | } 238 | } 239 | ], 240 | "outputs": { 241 | "hostname": { 242 | "type": "string", 243 | "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]" 244 | } 245 | } 246 | } -------------------------------------------------------------------------------- /Hub-VNet.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "defaultValue": "Hub_VNet", 7 | "type": "string", 8 | "metadata": { 9 | "description": "VNet name prefix." 10 | } 11 | }, 12 | "vnetOctets": { 13 | "defaultValue": "10.101", 14 | "type": "string", 15 | "metadata": { 16 | "description": "First two octets of the VNet IP space." 17 | } 18 | } 19 | }, 20 | "resources": [ 21 | { 22 | "comments": "VNet with 2 subnets and gateway subnet", 23 | "name": "[concat(parameters('vnetName'))]", 24 | "type": "Microsoft.Network/virtualNetworks", 25 | "apiVersion": "2017-06-01", 26 | "location": "[resourceGroup().location]", 27 | "properties": { 28 | "addressSpace": { 29 | "addressPrefixes": [ 30 | "[concat(parameters('vnetOctets'), '.0.0/16')]" 31 | ] 32 | }, 33 | "subnets": [ 34 | { 35 | "name": "[concat(parameters('vnetName'), '-Subnet1')]", 36 | "properties": { 37 | "addressPrefix": "[concat(parameters('vnetOctets'), '.1.0/24')]" 38 | } 39 | }, 40 | { 41 | "name": "[concat(parameters('vnetName'), '-Subnet2')]", 42 | "properties": { 43 | "addressPrefix": "[concat(parameters('vnetOctets'), '.2.0/24')]" 44 | } 45 | }, 46 | { 47 | "name": "GatewaySubnet", 48 | "properties": { 49 | "addressPrefix": "[concat(parameters('vnetOctets'), '.3.0/24')]" 50 | } 51 | } 52 | ] 53 | } 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /MultiVM-LoadBalanced.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "defaultValue": "VNet", 7 | "type": "string" 8 | }, 9 | "subnetName": { 10 | "defaultValue": "Subnet", 11 | "type": "string" 12 | }, 13 | "storageAccountName": { 14 | "defaultValue": "[concat('storage',uniqueString(resourceGroup().id))]", 15 | "type": "string" 16 | }, 17 | "adminUsername": { 18 | "defaultValue": null, 19 | "type": "string" 20 | }, 21 | "adminPassword": { 22 | "defaultValue": null, 23 | "type": "securestring" 24 | }, 25 | "vmName": { 26 | "defaultValue": "vm", 27 | "type": "string" 28 | }, 29 | "availabilitySetName": { 30 | "defaultValue": "AvSet", 31 | "type": "string" 32 | }, 33 | "spokeNicCount": { 34 | "defaultValue": 2, 35 | "type": "int" 36 | }, 37 | "vmCount": { 38 | "defaultValue": 2, 39 | "type": "int" 40 | }, 41 | "vmType": { 42 | "defaultValue": "ubuntu", 43 | "type": "string", 44 | "allowedValues": [ 45 | "ubuntu" 46 | ] 47 | }, 48 | "lbPIPName": { 49 | "defaultValue": "lbPIP", 50 | "type": "string" 51 | }, 52 | "lbName": { 53 | "defaultValue": "lb", 54 | "type": "string" 55 | }, 56 | "scriptURL": { 57 | "defaultValue": "https://raw.githubusercontent.com/araffe/vdc-networking-lab/master/DemoAppConfig.sh", 58 | "type": "string" 59 | }, 60 | "scriptCommand": { 61 | "defaultValue": "sh DemoAppConfig.sh", 62 | "type": "string" 63 | } 64 | }, 65 | "variables": { 66 | "nicName": "[concat(parameters('vmName'),'-nic')]", 67 | "diskName": "[concat(parameters('vmName'),'-disk')]", 68 | "apiVersion": "2016-03-30" 69 | }, 70 | "resources": [ 71 | { 72 | "apiVersion": "2017-04-01", 73 | "type": "Microsoft.Network/networkInterfaces", 74 | "name": "[concat(parameters('vmName'), copyIndex(1), '-nic')]", 75 | "location": "[resourceGroup().location]", 76 | "dependsOn": [ 77 | "[concat('Microsoft.Network/loadBalancers/', parameters('lbName'))]" 78 | ], 79 | "properties": { 80 | "ipConfigurations": [ 81 | { 82 | "name": "ipconfig1", 83 | "properties": { 84 | "privateIPAllocationMethod": "Dynamic", 85 | "subnet": { 86 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '/subnets/', parameters('subnetName'), '1')]" 87 | }, 88 | "loadBalancerBackendAddressPools": [ 89 | { 90 | "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbName')), '/backendAddressPools/BackendPool')]" 91 | } 92 | ] 93 | } 94 | } 95 | ] 96 | }, 97 | "copy": { 98 | "name": "nicLoop", 99 | "count": "[parameters('spokeNicCount')]" 100 | } 101 | }, 102 | { 103 | "comments": "Create public IP address for load balancer", 104 | "apiVersion": "2017-06-01", 105 | "type": "Microsoft.Network/publicIPAddresses", 106 | "name": "[parameters('lbPIPName')]", 107 | "location": "[resourceGroup().location]", 108 | "properties": { 109 | "publicIPAllocationMethod": "Static" 110 | } 111 | }, 112 | { 113 | "comments": "Create load balancer for spokes", 114 | "name": "[parameters('lbName')]", 115 | "apiVersion": "2017-03-01", 116 | "type": "Microsoft.Network/loadBalancers", 117 | "location": "[resourceGroup().location]", 118 | "dependsOn": [ 119 | "[concat('Microsoft.Network/publicIPAddresses/', parameters('lbPIPName'))]" 120 | ], 121 | "properties": { 122 | "frontendIPConfigurations": [ 123 | { 124 | "name": "LoadBalancerFrontEnd", 125 | "properties": { 126 | "privateIPAllocationMethod": "Dynamic", 127 | "subnet": { 128 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '/subnets/', parameters('subnetName'), '1')]" 129 | } 130 | } 131 | } 132 | ], 133 | "backendAddressPools": [ 134 | { 135 | "name": "BackendPool" 136 | } 137 | ], 138 | "loadBalancingRules": [ 139 | { 140 | "name": "DemoAppRule", 141 | "properties": { 142 | "frontendIPConfiguration": { 143 | "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbName')), '/frontendIpConfigurations/loadBalancerFrontEnd')]" 144 | }, 145 | "backendAddressPool": { 146 | "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbName')), '/backendAddressPools/BackendPool')]" 147 | }, 148 | "probe": { 149 | "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbName')), '/probes/DemoAppProbe')]" 150 | }, 151 | "protocol": "Tcp", 152 | "frontendPort": 80, 153 | "backendPort": 3000, 154 | "idleTimeoutInMinutes": 15 155 | } 156 | } 157 | ], 158 | "probes": [ 159 | { 160 | "properties": { 161 | "protocol": "Http", 162 | "port": 3000, 163 | "intervalInSeconds": 15, 164 | "numberOfProbes": 2, 165 | "requestPath": "/" 166 | }, 167 | "name": "DemoAppProbe" 168 | } 169 | ] 170 | } 171 | }, 172 | { 173 | "type": "Microsoft.Compute/availabilitySets", 174 | "name": "[parameters('availabilitySetName')]", 175 | "apiVersion": "2016-04-30-preview", 176 | "location": "[resourceGroup().location]", 177 | "properties": { 178 | "platformFaultDomainCount": "2", 179 | "platformUpdateDomainCount": "2", 180 | "managed": "true" 181 | } 182 | }, 183 | { 184 | "apiVersion": "2017-03-30", 185 | "type": "Microsoft.Compute/virtualMachines", 186 | "name": "[concat(parameters('vmName'), copyIndex(1))]", 187 | "location": "[resourceGroup().location]", 188 | "dependsOn": [ 189 | "[concat('Microsoft.Network/networkInterfaces/', parameters('vmName'), copyIndex(1) ,'-nic')]", 190 | "[resourceId('Microsoft.Compute/availabilitySets/', parameters('availabilitySetName'))]" 191 | ], 192 | "properties": { 193 | "hardwareProfile": { 194 | "vmSize": "Standard_A0" 195 | }, 196 | "osProfile": { 197 | "computerName": "[concat(parameters('vmName'), copyIndex(1))]", 198 | "adminUsername": "[parameters('adminUsername')]", 199 | "adminPassword": "[parameters('adminPassword')]" 200 | }, 201 | "storageProfile": { 202 | "imageReference": { 203 | "publisher": "canonical", 204 | "offer": "UbuntuServer", 205 | "sku": "17.10", 206 | "version": "latest" 207 | }, 208 | "osDisk": { 209 | "createOption": "FromImage" 210 | } 211 | }, 212 | "networkProfile": { 213 | "networkInterfaces": [ 214 | { 215 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('vmName'), copyIndex(1), '-nic'))]" 216 | } 217 | ] 218 | }, 219 | "diagnosticsProfile": { 220 | "bootDiagnostics": { 221 | "enabled": "false" 222 | } 223 | }, 224 | "availabilitySet": { 225 | "id": "[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]" 226 | } 227 | }, 228 | "copy": { 229 | "name": "vmLoop", 230 | "count": "[parameters('vmCount')]" 231 | } 232 | }, 233 | { 234 | "type": "Microsoft.Compute/virtualMachines/extensions", 235 | "name": "[concat(parameters('vmName'), copyIndex(1), '/script')]", 236 | "apiVersion": "2015-05-01-preview", 237 | "location": "[resourceGroup().location]", 238 | "properties": { 239 | "publisher": "Microsoft.Azure.Extensions", 240 | "type": "CustomScript", 241 | "typeHandlerVersion": "2.0", 242 | "autoUpgradeMinorVersion": true, 243 | "settings": { 244 | "fileUris": [ 245 | "[parameters('scriptURL')]" 246 | ], 247 | "commandToExecute": "[parameters('scriptCommand')]" 248 | } 249 | }, 250 | "dependsOn": [ 251 | "[concat(parameters('vmName'), copyIndex(1))]" 252 | ], 253 | "copy": { 254 | "name": "[concat(parameters('vmName'), '-script')]", 255 | "count": "[parameters('vmCount')]" 256 | } 257 | } 258 | ] 259 | } -------------------------------------------------------------------------------- /Single-VM.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "defaultValue": "VNet", 7 | "type": "string" 8 | }, 9 | "subnetName": { 10 | "defaultValue": "Subnet", 11 | "type": "string" 12 | }, 13 | "storageAccountName": { 14 | "defaultValue": "[concat('storage',uniqueString(resourceGroup().id))]", 15 | "type": "string" 16 | }, 17 | "adminUsername": { 18 | "defaultValue": null, 19 | "type": "string" 20 | }, 21 | "adminPassword": { 22 | "defaultValue": null, 23 | "type": "securestring" 24 | }, 25 | "vmName": { 26 | "defaultValue": "vm", 27 | "type": "string" 28 | }, 29 | "vmPIPName": { 30 | "defaultValue": "vmPIP", 31 | "type": "string" 32 | }, 33 | "vmType": { 34 | "defaultValue": "ubuntu", 35 | "type": "string", 36 | "allowedValues": [ 37 | "ubuntu" 38 | ] 39 | } 40 | }, 41 | "variables": { 42 | "nicName": "[concat(parameters('vmName'),'-nic')]", 43 | "diskName": "[concat(parameters('vmName'),'-disk')]", 44 | "apiVersion": "2016-03-30" 45 | }, 46 | "resources": [ 47 | { 48 | "apiVersion": "2017-04-01", 49 | "type": "Microsoft.Network/networkInterfaces", 50 | "name": "[concat(parameters('vmName'), '-nic')]", 51 | "location": "[resourceGroup().location]", 52 | "dependsOn": [ 53 | "[concat('Microsoft.Network/publicIPAddresses/', parameters('vmPIPName'))]" 54 | ], 55 | "properties": { 56 | "ipConfigurations": [ 57 | { 58 | "name": "ipconfig1", 59 | "properties": { 60 | "privateIPAllocationMethod": "Dynamic", 61 | "subnet": { 62 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '/subnets/', parameters('subnetName'))]" 63 | }, 64 | "publicIPAddress": { 65 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('vmPIPName'))]" 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | }, 72 | { 73 | "comments": "Create public IP address for VM", 74 | "apiVersion": "2015-05-01-preview", 75 | "type": "Microsoft.Network/publicIPAddresses", 76 | "name": "[parameters('vmPIPName')]", 77 | "location": "[resourceGroup().location]", 78 | "properties": { 79 | "publicIPAllocationMethod": "Dynamic" 80 | } 81 | }, 82 | { 83 | "apiVersion": "2017-03-30", 84 | "type": "Microsoft.Compute/virtualMachines", 85 | "name": "[concat(parameters('vmName'))]", 86 | "location": "[resourceGroup().location]", 87 | "dependsOn": [ 88 | "[concat('Microsoft.Network/networkInterfaces/', parameters('vmName'), '-nic')]" 89 | ], 90 | "properties": { 91 | "hardwareProfile": { 92 | "vmSize": "Standard_A0" 93 | }, 94 | "osProfile": { 95 | "computerName": "[parameters('vmName')]", 96 | "adminUsername": "[parameters('adminUsername')]", 97 | "adminPassword": "[parameters('adminPassword')]" 98 | }, 99 | "storageProfile": { 100 | "imageReference": { 101 | "publisher": "canonical", 102 | "offer": "UbuntuServer", 103 | "sku": "16.04.0-LTS", 104 | "version": "latest" 105 | }, 106 | "osDisk": { 107 | "createOption": "FromImage" 108 | } 109 | }, 110 | "networkProfile": { 111 | "networkInterfaces": [ 112 | { 113 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('vmName'), '-nic'))]" 114 | } 115 | ] 116 | }, 117 | "diagnosticsProfile": { 118 | "bootDiagnostics": { 119 | "enabled": "false" 120 | } 121 | } 122 | } 123 | } 124 | ] 125 | } -------------------------------------------------------------------------------- /Spoke-VNet.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "defaultValue": "Spoke_VNet", 7 | "type": "string", 8 | "metadata": { 9 | "description": "Spoke VNet name prefix." 10 | } 11 | }, 12 | "vnetOctets": { 13 | "defaultValue": "10.1", 14 | "type": "string", 15 | "metadata": { 16 | "description": "First two octets of the spoke vnet IP space." 17 | } 18 | } 19 | }, 20 | "resources": [ 21 | { 22 | "comments": "Spoke VNet with 2 subnets", 23 | "name": "[concat(parameters('vnetName'))]", 24 | "type": "Microsoft.Network/virtualNetworks", 25 | "apiVersion": "2017-06-01", 26 | "location": "[resourceGroup().location]", 27 | "properties": { 28 | "addressSpace": { 29 | "addressPrefixes": [ 30 | "[concat(parameters('vnetOctets'), '.0.0/16')]" 31 | ] 32 | }, 33 | "subnets": [ 34 | { 35 | "name": "[concat(parameters('vnetName'), '-Subnet1')]", 36 | "properties": { 37 | "addressPrefix": "[concat(parameters('vnetOctets'), '.1.0/24')]" 38 | } 39 | }, 40 | { 41 | "name": "[concat(parameters('vnetName'), '-Subnet2')]", 42 | "properties": { 43 | "addressPrefix": "[concat(parameters('vnetOctets'), '.2.0/24')]" 44 | } 45 | } 46 | ] 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /VDC-Networking-Master.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "defaultValue": "labuser", 7 | "type": "string", 8 | "metadata": { 9 | "description": "Username for VMs" 10 | } 11 | }, 12 | "adminPassword": { 13 | "defaultValue": "M1crosoft123", 14 | "type": "securestring", 15 | "metadata": { 16 | "description": "Password for VMs" 17 | } 18 | }, 19 | "hubRG": { 20 | "defaultValue": "VDC-Hub", 21 | "type": "string", 22 | "metadata": { 23 | "description": "Resource group name for Hub" 24 | } 25 | }, 26 | "spoke1RG": { 27 | "defaultValue": "VDC-Spoke1", 28 | "type": "string", 29 | "metadata": { 30 | "description": "Resource group name for Spoke1" 31 | } 32 | }, 33 | "spoke2RG": { 34 | "defaultValue": "VDC-Spoke2", 35 | "type": "string", 36 | "metadata": { 37 | "description": "Resource group name for Spoke2" 38 | } 39 | }, 40 | "onPremRG": { 41 | "defaultValue": "VDC-OnPrem", 42 | "type": "string", 43 | "metadata": { 44 | "description": "Resource group name for On-Premises" 45 | } 46 | }, 47 | "nvaRG": { 48 | "defaultValue": "VDC-NVA", 49 | "type": "string", 50 | "metadata": { 51 | "description": "Resource group name for NVA deployment" 52 | } 53 | }, 54 | "createVPNGateway": { 55 | "defaultValue": "yes", 56 | "type": "string", 57 | "allowedValues": [ 58 | "yes", 59 | "no" 60 | ], 61 | "metadata": { 62 | "description": "Controls whether VPN gateways will be deployed" 63 | } 64 | } 65 | }, 66 | "variables": { 67 | "baseURL": "https://raw.githubusercontent.com/Araffe/vdc-networking-lab/master/", 68 | "hubVnetTemplateURL": "[concat(variables('baseURL'), 'Hub-VNet.json')]", 69 | "spokeVnetTemplateURL": "[concat(variables('baseURL'), 'Spoke-VNet.json')]", 70 | "vnetPeeringHubTemplateURL": "[concat(variables('baseURL'), 'VNet-Peering-Hub.json')]", 71 | "vnetPeeringSpokeTemplateURL": "[concat(variables('baseURL'), 'VNet-Peering-Spoke.json')]", 72 | "vpnGwTemplateURL": "[concat(variables('baseURL'), 'vpn-gw.json')]", 73 | "singleVMTemplateURL": "[concat(variables('baseURL'), 'Single-VM.json')]", 74 | "multiVMTemplateURL": "[concat(variables('baseURL'), 'MultiVM-LoadBalanced.json')]", 75 | "nvaTemplateURL": "[concat(variables('baseURL'), 'Deploy-NVAs.json')]", 76 | "hubVnetTemplate": { 77 | "vnetName": { 78 | "value": "Hub_VNet" 79 | }, 80 | "vnetOctets": { 81 | "value": "10.101" 82 | } 83 | }, 84 | "onPremVnetTemplate": { 85 | "vnetName": { 86 | "value": "OnPrem_VNet" 87 | }, 88 | "vnetOctets": { 89 | "value": "10.102" 90 | } 91 | }, 92 | "spoke1VnetTemplate": { 93 | "vnetName": { 94 | "value": "Spoke1_VNet" 95 | }, 96 | "vnetOctets": { 97 | "value": "10.1" 98 | } 99 | }, 100 | "spoke2VnetTemplate": { 101 | "vnetName": { 102 | "value": "Spoke2_VNet" 103 | }, 104 | "vnetOctets": { 105 | "value": "10.2" 106 | } 107 | }, 108 | "spoke1PeeringTemplate": { 109 | "spokeVnetName": { 110 | "value": "Spoke1_VNet" 111 | } 112 | }, 113 | "spoke2PeeringTemplate": { 114 | "spokeVnetName": { 115 | "value": "Spoke2_VNet" 116 | } 117 | }, 118 | "vpnGwTemplateHub": { 119 | "vnetName": { 120 | "value": "Hub_VNet" 121 | }, 122 | "gwName": { 123 | "value": "Hub_GW1" 124 | }, 125 | "PIPName": { 126 | "value": "Hub_VNet-gwPip" 127 | }, 128 | "asNumber": { 129 | "value": "65514" 130 | } 131 | }, 132 | "vpnGwTemplateOnPrem": { 133 | "vnetName": { 134 | "value": "OnPrem_VNet" 135 | }, 136 | "gwName": { 137 | "value": "OnPrem_GW1" 138 | }, 139 | "PIPName": { 140 | "value": "OnPrem_VNet-gwPip" 141 | }, 142 | "RGName": { 143 | "value": "VDC-OnPrem" 144 | }, 145 | "asNumber": { 146 | "value": "65515" 147 | } 148 | }, 149 | "vmTemplateSpoke1": { 150 | "vmName": { 151 | "value": "Spoke1-VM" 152 | }, 153 | "lbPIPName": { 154 | "value": "Spoke1-LB-PIP" 155 | }, 156 | "lbName": { 157 | "value": "Spoke1-LB" 158 | }, 159 | "vnetName": { 160 | "value": "Spoke1_VNet" 161 | }, 162 | "subnetName": { 163 | "value": "Spoke1_VNet-Subnet" 164 | }, 165 | "vmType": { 166 | "value": "ubuntu" 167 | }, 168 | "adminUsername": { 169 | "value": "[parameters('adminUsername')]" 170 | }, 171 | "adminPassword": { 172 | "value": "[parameters('adminPassword')]" 173 | }, 174 | "availabilitySetName": { 175 | "value": "Spoke1-AvailabilitySet" 176 | } 177 | }, 178 | "vmTemplateSpoke2": { 179 | "vmName": { 180 | "value": "Spoke2-VM" 181 | }, 182 | "lbPIPName": { 183 | "value": "Spoke2-LB-PIP" 184 | }, 185 | "lbName": { 186 | "value": "Spoke2-LB" 187 | }, 188 | "vnetName": { 189 | "value": "Spoke2_VNet" 190 | }, 191 | "subnetName": { 192 | "value": "Spoke2_VNet-Subnet" 193 | }, 194 | "vmType": { 195 | "value": "ubuntu" 196 | }, 197 | "adminUsername": { 198 | "value": "[parameters('adminUsername')]" 199 | }, 200 | "adminPassword": { 201 | "value": "[parameters('adminPassword')]" 202 | }, 203 | "availabilitySetName": { 204 | "value": "Spoke2-AvailabilitySet" 205 | } 206 | }, 207 | "vmTemplateOnPrem": { 208 | "vmName": { 209 | "value": "OnPrem-VM1" 210 | }, 211 | "vnetName": { 212 | "value": "OnPrem_VNet" 213 | }, 214 | "subnetName": { 215 | "value": "OnPrem_VNet-Subnet1" 216 | }, 217 | "vmType": { 218 | "value": "ubuntu" 219 | }, 220 | "adminUsername": { 221 | "value": "[parameters('adminUsername')]" 222 | }, 223 | "adminPassword": { 224 | "value": "[parameters('adminPassword')]" 225 | }, 226 | "vmPIPName": { 227 | "value": "OnPrem-VM1-PIP" 228 | } 229 | }, 230 | "nvaTemplate": { 231 | "vnetName": { 232 | "value": "Hub_VNet" 233 | }, 234 | "adminUsername": { 235 | "value": "[parameters('adminUsername')]" 236 | }, 237 | "adminPassword": { 238 | "value": "[parameters('adminPassword')]" 239 | }, 240 | "VDCResourceGroup": { 241 | "value": "[resourceGroup().name]" 242 | } 243 | } 244 | }, 245 | "resources": [ 246 | { 247 | "comments": "Create Hub VNet", 248 | "name": "hubVnet", 249 | "type": "Microsoft.Resources/deployments", 250 | "apiVersion": "2017-05-10", 251 | "properties": { 252 | "mode": "Incremental", 253 | "templateLink": { 254 | "uri": "[variables('hubVnetTemplateURL')]", 255 | "contentVersion": "1.0.0.0" 256 | }, 257 | "parameters": "[variables('hubVnetTemplate')]" 258 | } 259 | }, 260 | { 261 | "comments": "Create OnPremises VNet", 262 | "name": "onPremVnet", 263 | "type": "Microsoft.Resources/deployments", 264 | "resourceGroup": "[parameters('onPremRG')]", 265 | "apiVersion": "2017-05-10", 266 | "properties": { 267 | "mode": "Incremental", 268 | "templateLink": { 269 | "uri": "[variables('hubVnetTemplateURL')]", 270 | "contentVersion": "1.0.0.0" 271 | }, 272 | "parameters": "[variables('onPremVnetTemplate')]" 273 | } 274 | }, 275 | { 276 | "comments": "Create Spoke1 VNet", 277 | "name": "spoke1Vnet", 278 | "type": "Microsoft.Resources/deployments", 279 | "resourceGroup": "[parameters('spoke1RG')]", 280 | "apiVersion": "2017-05-10", 281 | "properties": { 282 | "mode": "Incremental", 283 | "templateLink": { 284 | "uri": "[variables('spokeVnetTemplateURL')]", 285 | "contentVersion": "1.0.0.0" 286 | }, 287 | "parameters": "[variables('spoke1VnetTemplate')]" 288 | } 289 | }, 290 | { 291 | "comments": "create VNet peering - Hub_VNet to Spoke VNets", 292 | "name": "Hub-Spoke-Peering", 293 | "type": "Microsoft.Resources/deployments", 294 | "apiVersion": "2017-05-10", 295 | "properties": { 296 | "mode": "Incremental", 297 | "templateLink": { 298 | "uri": "[variables('vnetPeeringHubTemplateURL')]", 299 | "contentVersion": "1.0.0.0" 300 | } 301 | }, 302 | "dependsOn": [ 303 | "spoke1Vnet", 304 | "hubVnet" 305 | ] 306 | }, 307 | { 308 | "comments": "create VNet peering - Spoke1 VNet to Hub VNet", 309 | "name": "Spoke1-Hub-Peering", 310 | "type": "Microsoft.Resources/deployments", 311 | "resourceGroup": "[parameters('spoke1RG')]", 312 | "apiVersion": "2017-05-10", 313 | "properties": { 314 | "mode": "Incremental", 315 | "templateLink": { 316 | "uri": "[variables('vnetPeeringSpokeTemplateURL')]", 317 | "contentVersion": "1.0.0.0" 318 | }, 319 | "parameters": "[variables('spoke1PeeringTemplate')]" 320 | }, 321 | "dependsOn": [ 322 | "spoke1Vnet", 323 | "hubVnet" 324 | ] 325 | }, 326 | { 327 | "comments": "create VNet peering - Spoke2 VNet to Hub VNet", 328 | "name": "Spoke2-Hub-Peering", 329 | "type": "Microsoft.Resources/deployments", 330 | "resourceGroup": "[parameters('spoke2RG')]", 331 | "apiVersion": "2017-05-10", 332 | "properties": { 333 | "mode": "Incremental", 334 | "templateLink": { 335 | "uri": "[variables('vnetPeeringSpokeTemplateURL')]", 336 | "contentVersion": "1.0.0.0" 337 | }, 338 | "parameters": "[variables('spoke2PeeringTemplate')]" 339 | }, 340 | "dependsOn": [ 341 | "spoke2Vnet", 342 | "hubVnet" 343 | ] 344 | }, 345 | { 346 | "comments": "Create Spoke2 VNet", 347 | "name": "spoke2Vnet", 348 | "type": "Microsoft.Resources/deployments", 349 | "resourceGroup": "[parameters('spoke2RG')]", 350 | "apiVersion": "2017-05-10", 351 | "properties": { 352 | "mode": "Incremental", 353 | "templateLink": { 354 | "uri": "[variables('spokeVnetTemplateURL')]", 355 | "contentVersion": "1.0.0.0" 356 | }, 357 | "parameters": "[variables('spoke2VnetTemplate')]" 358 | } 359 | }, 360 | { 361 | "comments": "Create VPN gateway in Hub_VNet1.", 362 | "name": "Hub_GW1", 363 | "condition": "[equals(parameters('createVPNGateway'), 'yes')]", 364 | "type": "Microsoft.Resources/deployments", 365 | "apiVersion": "2017-05-10", 366 | "properties": { 367 | "mode": "Incremental", 368 | "templateLink": { 369 | "uri": "[variables('vpnGwTemplateURL')]", 370 | "contentVersion": "1.0.0.0" 371 | }, 372 | "parameters": "[variables('vpnGwTemplateHub')]" 373 | }, 374 | "dependsOn": [ 375 | "hubVnet" 376 | ] 377 | }, 378 | { 379 | "comments": "Create VPN gateway in OnPrem_VNet1.", 380 | "name": "OnPrem_GW1", 381 | "resourceGroup": "[parameters('onPremRG')]", 382 | "condition": "[equals(parameters('createVPNGateway'), 'yes')]", 383 | "type": "Microsoft.Resources/deployments", 384 | "apiVersion": "2017-05-10", 385 | "properties": { 386 | "mode": "Incremental", 387 | "templateLink": { 388 | "uri": "[variables('vpnGwTemplateURL')]", 389 | "contentVersion": "1.0.0.0" 390 | }, 391 | "parameters": "[variables('vpnGwTemplateOnPrem')]" 392 | }, 393 | "dependsOn": [ 394 | "onPremVnet" 395 | ] 396 | }, 397 | { 398 | "comments": "Create two VMs in an availability set with load balancing in Spoke 1 VNet", 399 | "name": "createSpoke1VMs", 400 | "type": "Microsoft.Resources/deployments", 401 | "resourceGroup": "[parameters('spoke1RG')]", 402 | "apiVersion": "2017-05-10", 403 | "properties": { 404 | "mode": "Incremental", 405 | "templateLink": { 406 | "uri": "[variables('multiVMTemplateURL')]", 407 | "contentVersion": "1.0.0.0" 408 | }, 409 | "parameters": "[variables('vmTemplateSpoke1')]" 410 | }, 411 | "dependsOn": [ 412 | "spoke1Vnet" 413 | ] 414 | }, 415 | { 416 | "comments": "Create two VMs in an availability set with load balancing in Spoke 2 VNet", 417 | "name": "createSpoke2VMs", 418 | "type": "Microsoft.Resources/deployments", 419 | "resourceGroup": "[parameters('spoke2RG')]", 420 | "apiVersion": "2017-05-10", 421 | "properties": { 422 | "mode": "Incremental", 423 | "templateLink": { 424 | "uri": "[variables('multiVMTemplateURL')]", 425 | "contentVersion": "1.0.0.0" 426 | }, 427 | "parameters": "[variables('vmTemplateSpoke2')]" 428 | }, 429 | "dependsOn": [ 430 | "spoke2Vnet" 431 | ] 432 | }, 433 | { 434 | "comments": "Create single VM in on-premises VNet", 435 | "name": "createOnPremVM", 436 | "type": "Microsoft.Resources/deployments", 437 | "resourceGroup": "[parameters('onPremRG')]", 438 | "apiVersion": "2017-05-10", 439 | "properties": { 440 | "mode": "Incremental", 441 | "templateLink": { 442 | "uri": "[variables('singleVMTemplateURL')]", 443 | "contentVersion": "1.0.0.0" 444 | }, 445 | "parameters": "[variables('vmTemplateOnPrem')]" 446 | }, 447 | "dependsOn": [ 448 | "onPremVnet" 449 | ] 450 | } 451 | ] 452 | } -------------------------------------------------------------------------------- /VNet-Peering-Hub.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "spoke1VnetName": { 6 | "defaultValue": "Spoke1_Vnet", 7 | "type": "string", 8 | "metadata": { 9 | "description": "Name of the spoke 1 Vnet" 10 | } 11 | }, 12 | "spoke2VnetName": { 13 | "defaultValue": "Spoke2_Vnet", 14 | "type": "string", 15 | "metadata": { 16 | "description": "Name of the spoke 2 Vnet" 17 | } 18 | }, 19 | "hubVnetName": { 20 | "defaultValue": "Hub_VNet", 21 | "type": "string", 22 | "metadata": { 23 | "description": "Name of the hub Vnet" 24 | } 25 | }, 26 | "spoke1RGName": { 27 | "defaultValue": "VDC-Spoke1", 28 | "type": "string", 29 | "metadata": { 30 | "description": "Name of the spoke 1 resource group" 31 | } 32 | }, 33 | "spoke2RGName": { 34 | "defaultValue": "VDC-Spoke2", 35 | "type": "string", 36 | "metadata": { 37 | "description": "Name of the spoke 2 resource group" 38 | } 39 | } 40 | }, 41 | "variables": {}, 42 | "resources": [ 43 | { 44 | "apiVersion": "2016-06-01", 45 | "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", 46 | "name": "[concat(parameters('hubVnetName'), '/to-', parameters('spoke1VnetName'))]", 47 | "location": "[resourceGroup().location]", 48 | "properties": { 49 | "allowVirtualNetworkAccess": true, 50 | "allowForwardedTraffic": false, 51 | "allowGatewayTransit": true, 52 | "useRemoteGateways": false, 53 | "remoteVirtualNetwork": { 54 | "id": "[resourceId(parameters('spoke1RGName'), 'Microsoft.Network/virtualNetworks', parameters('spoke1VnetName'))]" 55 | } 56 | } 57 | }, 58 | { 59 | "apiVersion": "2016-06-01", 60 | "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", 61 | "name": "[concat(parameters('hubVnetName'), '/to-', parameters('spoke2VnetName'))]", 62 | "location": "[resourceGroup().location]", 63 | "properties": { 64 | "allowVirtualNetworkAccess": true, 65 | "allowForwardedTraffic": false, 66 | "allowGatewayTransit": true, 67 | "useRemoteGateways": false, 68 | "remoteVirtualNetwork": { 69 | "id": "[resourceId(parameters('spoke2RGName'), 'Microsoft.Network/virtualNetworks', parameters('spoke2VnetName'))]" 70 | } 71 | } 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /VNet-Peering-Spoke.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "spokeVnetName": { 6 | "defaultValue": "Spoke1_VNet", 7 | "type": "string", 8 | "metadata": { 9 | "description": "Name of the spoke VNet" 10 | } 11 | }, 12 | "hubVnetName": { 13 | "defaultValue": "Hub_VNet", 14 | "type": "string", 15 | "metadata": { 16 | "description": "Name of the hub VNet" 17 | } 18 | }, 19 | "hubRGName": { 20 | "defaultValue": "VDC-Hub", 21 | "type": "string", 22 | "metadata": { 23 | "description": "Name of the Hub resource group" 24 | } 25 | } 26 | }, 27 | "variables": {}, 28 | "resources": [ 29 | { 30 | "apiVersion": "2016-06-01", 31 | "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", 32 | "name": "[concat(parameters('spokeVnetName'), '/to-', parameters('hubVnetName'))]", 33 | "location": "[resourceGroup().location]", 34 | "properties": { 35 | "allowVirtualNetworkAccess": true, 36 | "allowForwardedTraffic": false, 37 | "allowGatewayTransit": false, 38 | "useRemoteGateways": false, 39 | "remoteVirtualNetwork": { 40 | "id": "[resourceId(parameters('hubRGName'), 'Microsoft.Network/virtualNetworks', parameters('hubVnetName'))]" 41 | } 42 | } 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /images/AzMon1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/AzMon1.jpg -------------------------------------------------------------------------------- /images/AzMonAlert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/AzMonAlert.jpg -------------------------------------------------------------------------------- /images/AzMonCPU.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/AzMonCPU.jpg -------------------------------------------------------------------------------- /images/BackendPools.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/BackendPools.JPG -------------------------------------------------------------------------------- /images/DomainName.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/DomainName.jpg -------------------------------------------------------------------------------- /images/EffectiveRoutes1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/EffectiveRoutes1.JPG -------------------------------------------------------------------------------- /images/EffectiveRoutes2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/EffectiveRoutes2.jpg -------------------------------------------------------------------------------- /images/FlowLogs1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/FlowLogs1.jpg -------------------------------------------------------------------------------- /images/Hub-RBAC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/Hub-RBAC.jpg -------------------------------------------------------------------------------- /images/Identity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/Identity.jpg -------------------------------------------------------------------------------- /images/NSG1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NSG1.jpg -------------------------------------------------------------------------------- /images/NSG2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NSG2.jpg -------------------------------------------------------------------------------- /images/NSGLogs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NSGLogs.jpg -------------------------------------------------------------------------------- /images/NetWatcher1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NetWatcher1.jpg -------------------------------------------------------------------------------- /images/NetWatcherTopo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NetWatcherTopo.jpg -------------------------------------------------------------------------------- /images/NextHop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/NextHop.jpg -------------------------------------------------------------------------------- /images/PolicyCompliance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/PolicyCompliance.jpg -------------------------------------------------------------------------------- /images/SSHtoNVA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/SSHtoNVA.jpg -------------------------------------------------------------------------------- /images/SecCenter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/SecCenter.jpg -------------------------------------------------------------------------------- /images/SecRecommendations.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/SecRecommendations.jpg -------------------------------------------------------------------------------- /images/SubLimits.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/SubLimits.jpg -------------------------------------------------------------------------------- /images/UDR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/UDR.jpg -------------------------------------------------------------------------------- /images/UDR2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/UDR2.jpg -------------------------------------------------------------------------------- /images/UseRemoteGW.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/UseRemoteGW.JPG -------------------------------------------------------------------------------- /images/VDC-Networking-Main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/VDC-Networking-Main.jpg -------------------------------------------------------------------------------- /images/VDC-Spoke1-RG.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/VDC-Spoke1-RG.jpg -------------------------------------------------------------------------------- /images/armpolicies1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Araffe/vdc-networking-lab/29f48f2302f1c08cb2078b1ea1fdd141079042db/images/armpolicies1.jpg -------------------------------------------------------------------------------- /naming-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "if": { 3 | "not": { 4 | "field": "name", 5 | "like": "VDC-*" 6 | } 7 | }, 8 | "then": { 9 | "effect": "deny" 10 | } 11 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Azure Virtual Data Centre Lab 2 | 3 | # Contents 4 | 5 | **[VDC Lab Introduction](#intro)** 6 | 7 | **[Initial Lab Setup](#setup)** 8 | 9 | **[Lab 1: Explore the VDC Environment](#explore)** 10 | 11 | **[Lab 2: Configure the VDC Infrastructure](#configure)** 12 | 13 | - [2.1: Configure VNet to VNet Connection](#vpn) 14 | 15 | - [2.2: Configure Cisco CSR1000V](#cisco) 16 | 17 | - [2.3: Configure User Defined Routes](#udr) 18 | 19 | - [2.4: Test Connectivity Between On-Premises and Spoke VNets](#testconn) 20 | 21 | **[Lab 3: Secure the VDC Environment](#secure)** 22 | 23 | - [3.1: Network Security Groups](#nsgsec) 24 | 25 | - [3.2: Using Azure Security Center](#seccenter) 26 | 27 | - [3.3: Implementing Azure Resource Policies](#armpolicies) 28 | 29 | - [3.4: Monitor Compliance Using Azure Policy](#policycompliance) 30 | 31 | **[Lab 4: Monitor the VDC Environment](#monitor)** 32 | 33 | - [4.1: Enable Network Watcher](#netwatcher) 34 | 35 | - [4.2: NSG Flow Logs](#nsgflowlogs) 36 | 37 | - [4.3: Tracing Next Hop Information](#nexthop) 38 | 39 | - [4.4: Metrics and Alerts with Azure Monitor](#azmonalert) 40 | 41 | - [4.5: Diagnostics with Azure Monitor](#azmondiag) 42 | 43 | **[Lab 5: Identity in the VDC Environment](#identity)** 44 | 45 | - [5.1: Configure Users and Groups](#usersgroups) 46 | 47 | - [5.2: Assign Users and Roles to Resource Groups](#roles) 48 | 49 | - [5.3: Test User and Group Access](#useraccess) 50 | 51 | **[Decommission the lab](#decommission)** 52 | 53 | **[Conclusion](#conclusion)** 54 | 55 | **[Useful References](#ref)** 56 | 57 | # VDC Lab Introduction 58 | 59 | This lab guide allows the user to deploy and test a complete Microsoft Azure Virtual Data Centre (VDC) environment. A VDC is not a specific Azure product; instead, it is a combination of features and capabilities that are brought together to meet the requirements of a modern application environment in the cloud. 60 | 61 | More information on VDCs can be found at the following link: 62 | 63 | [https://docs.microsoft.com/en-us/azure/networking/networking-virtual-datacenter] 64 | 65 | Before proceeding with this lab, please make sure you have fulfilled all of the following prerequisites: 66 | 67 | - A valid subscription to Azure. If you don't currently have a subscription, consider setting up a free trial (https://azure.microsoft.com/en-gb/free/). Please note however that some free trial accounts have been found to have limits on the number of compute cores available - if this is the case, it may not be possible to create the virtual machines required for this lab (6 VMs). 68 | - Access to the Azure CLI 2.0. You can achieve this in one of two ways: either by installing the CLI on the Windows 10 Bash shell (https://docs.microsoft.com/en-us/cli/azure/install-azure-cli), or by using the built-in Cloud Shell in the Azure portal - you can access this by clicking on the ">_" symbol in the top right corner of the portal. 69 | 70 | **Some subscription types (e.g. Azure Passes) do not have the necessary resource provider enabled to use NSG Flow Logs. Before beginning the lab, enable the resource provider by entering the following Azure CLI command - this will save time later.** 71 | 72 |
73 | az provider register --namespace Microsoft.Insights
74 |
75 |
76 | # Initial Lab Setup
77 |
78 | **Important: The initial lab setup using ARM templates takes around 45 minutes - please initiate this process as soon as possible to avoid a delay in starting the lab.**
79 |
80 | *All usernames and passwords for virtual machines are set to labuser / M1crosoft123*
81 |
82 | Perform the following steps to initialise the lab environment:
83 |
84 | **1)** Open an Azure CLI session, either using a local machine (e.g. Windows 10 Bash shell), or using the Azure portal cloud shell. If you are using a local CLI session, you must log in to Azure using the *az login* command as follows:
85 |
86 |
87 | az login
88 | To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code XXXXXXXXX to authenticate.
89 |
90 |
91 | The above command will provide a code as output. Open a browser and navigate to aka.ms/devicelogin to complete the login process.
92 |
93 | **2)** Use the Azure CLI to create five resource groups: *VDC-Hub*, *VDC-Spoke1*, *VDC-Spoke2*, *VDC-OnPrem* and *VDC-NVA* . Note that the resource groups *must* be named exactly as shown here to ensure that the ARM templates deploy correctly. Use the following CLI command to achieve this:
94 |
95 |
96 | for rg in Hub Spoke1 Spoke2 OnPrem NVA; do az group create -l westeurope -n VDC-$rg; done
97 |
98 |
99 | **3)** Once the resource groups have been deployed, you can deploy the main lab environment into these using a set of pre-defined ARM templates. The templates are available at https://github.com/Araffe/vdc-networking-lab if you wish to learn more about how the lab is defined. Essentially, a single master template (*VDC-Networking-Master.json*) is used to call a number of other templates, which in turn complete the deployment of virtual networks, virtual machines, load balancers, availability sets and VPN gateways. The templates also deploy a simple Node.js application on the spoke virtual machines. Use the following CLI command to deploy the template:
100 |
101 |
102 | az group deployment create --name VDC-Create -g VDC-Hub --template-uri https://raw.githubusercontent.com/Araffe/vdc-networking-lab/master/VDC-Networking-Master.json
103 |
104 |
105 | The template deployment process will take approximately 45 minutes. You can monitor the progress of the deployment from the portal (navigate to the *VDC-Hub* resource group and click on *Deployments* at the top of the Overview blade). Alternatively, the CLI can be used to monitor the template deployment progress as follows:
106 |
107 |
108 | az group deployment list -g VDC-Hub -o table
109 | Name Timestamp State
110 | ----------------- -------------------------------- ---------
111 | hubVnet 2017-08-07T08:02:09.623941+00:00 Succeeded
112 | Hub-Spoke-Peering 2017-08-07T08:02:23.784459+00:00 Succeeded
113 | Hub_GW1 2017-08-07T08:27:42.052311+00:00 Succeeded
114 | VDC-Create 2017-08-07T08:30:02.786296+00:00 Succeeded
115 |
116 |
117 | Once the template deployment has succeeded, you can proceed to the deployment of the Cisco CSR1000V, as follows:
118 |
119 | **1)** Using the Azure portal, navigate to the 'VDC-NVA' resource group. Click on the 'Add' button at the top of the screen and enter 'Cisco CSR' in the search box. Press enter.
120 |
121 | **2)** Select the option entitled 'Cisco CSR 1000v - XE 16.6 Deployment with 2 NICs' and then select create.
122 |
123 | **3)** Name the virtual machine 'vdc-csr-1' and use the username and password *labuser / M1crosoft123*. Make sure the 'VDC-NVA' resource group is selected and West Europe is the location.
124 |
125 | **4)** In the next step, select 'storage account' and create a storage account with a unique name (you will receive an error if the name is not unique). Leave the storage as 'locally redundant'.
126 |
127 | **5)** Select 'Public IP address' and call the IP address 'csr-pip'. Use the default options (Basic SKU and Dynamic Assignment).
128 |
129 | **6)** Assign a unique DNS label (you will receive an error if the name is not unique).
130 |
131 | **7)** Click on 'Virtual Network' and then select 'Hub_VNet'
132 |
133 | **8)** Select 'Subnets' and choose 'Hub_VNet-Subnet1' as the first subnet, with 'Hub\_Vnet-Subnet2' as the second subnet.
134 |
135 | **9)** Select 'OK' and check the 'purchase' button until the virtual appliance starts to deploy. Wait for the deployment to finish.
136 |
137 | **10)** Once the deployment has completed, navigate again to the VDC-NVA resource group and click on the Network Security Group named 'vdc-csr-1-SSH-SecurityGroup'.
138 |
139 | **11)** Click on 'Network Interfaces' and then click on the three dots on the right side of the interface. Select 'Dissociate'
140 |
141 | You are now ready to proceed to the next sections of the lab.
142 |
143 | # Lab 1: Explore VDC Environment
144 |
145 | In this section of the lab, we will explore the environment that has been deployed to Azure by the ARM templates. The lab environment has the following topology:
146 |
147 | 
148 |
149 | **Figure 1:** VDC Lab Environment
150 |
151 | Note that each of the virtual networks resides in its own Azure resource group. While this environment could be deployed in one resource group only, splitting it up in this manner makes it easier to apply Role Based Access Control to the various areas individually.
152 |
153 | **1)** Use the Azure portal to explore the resources that have been created for you. Navigate to the various resource groups in turn to get an overall view of the resources deployed.
154 |
155 | 
156 |
157 | **Figure 2:** VDC-Spoke1 Resource Group View
158 |
159 | **Tip**: Select 'group by type' on the top right of the resource group view to group the resources together.
160 |
161 | **2)** Under the resource groups *VDC-Hub* and *VDC-OnPrem*, look at each of the virtual networks and the subnets created within each one. You will notice that *Hub_Vnet* and *OnPrem_VNet* have an additional subnet called *GatewaySubnet* - this is a special subnet used for the VPN gateway.
162 |
163 | **3)** Navigate to the *Spoke1-LB* load balancer in the VDC-Spoke1 resource group. From here, navigate to 'Backend Pools' - you will see that both virtual machines are configured as part of the backend pool for the load balancer, as shown in figure 3.
164 |
165 | 
166 |
167 | **Figure 3:** Load Balancer Backend Pools View
168 |
169 | **4)** Under the load balancer, navigate to 'Load Balancing Rules'. Here, you will see that we have a single rule configured (*DemoAppRule*) that maps incoming HTTP requests to port 3000 on the backend (our simple Node.js application listens on port 3000).
170 |
171 | **Note:** Two types of load balancer are available in Azure - either external or internal. In our case, we have an internal load balancer deployed; that is, the load balancer has only a private IP address - in other words, it is not accessible from the Internet.
172 |
173 | Now that you are familiar with the overall architecture, let's move on to the next lab where you will start to add some additional configuration.
174 |
175 | # Lab 2: Configure the VDC Infrastructure
176 |
177 | ## 2.1: Configure VNet to Vnet Connection
178 |
179 | In our VDC environment, we have a hub virtual network (used as a central point for control and inspection of ingress / egress traffic between different zones) and a virtual network used to simulate an on-premises environment. In order to provide connectivity between the hub and on-premises, we will configure a site-to-site VPN. The VPN gateways required to achieve this have already been deployed, however they must be configured before traffic will flow. Follow the steps below to configure the site-to-site VPN connection.
180 |
181 | **1)** Using the Azure portal, click on 'More Services' on the bottom left of the screen and then search for and select 'Virtual Network Gateways'. Click on the virtual network gateway named 'Hub_GW1'. Select 'Connections'.
182 |
183 | **2)** Add a connection and name it 'Hub2OnPrem'. Ensure the connection type is 'VNet-to-VNet'.
184 |
185 | **3)** Choose 'OnPrem_GW1' as the second gateway.
186 |
187 | **4)** Use 'M1crosoft123' as the shared key. Select 'OK' to complete the connection.
188 |
189 | **5)** Repeat the process for the other VPN gateway (OnPrem_GW1), but reverse the first and second gateways when creating the connection (name the connection 'OnPrem2Hub').
190 |
191 | **6)** Under the resource group *VDC-OnPrem* navigate back to the *OnPrem_GW1* virtual network gateway resource and then click 'Connections'. You should see a successful VPN connection between the OnPrem and Hub VPN gateways.
192 |
193 | **Note:** It may take a few minutes before a successful connection is shown between the gateways.
194 |
195 | At this point, we can start to verify the connectivity we have set up. One of the ways we can do this is by inspecting the *effective routes* associated with a virtual machine. Let's take a look at the effective routes associated with the *OnPrem_VM1* virtual machine that resides in the OnPrem VNet.
196 |
197 | **6)** Using the Azure portal, navigate to the *OnPrem_VM1-nic* object under the VDC-OnPrem resource group. This object is the network interface associated with the OnPrem_VM virtual machine.
198 |
199 | **7)** Under 'Support + Troubleshooting', select 'Effective Routes'. You should see an entry for 'virtual network gateway', specifying an address range of 10.101.0.0/16, as shown in figure 4.
200 |
201 | 
202 |
203 | **Figure 4:** OnPrem_VM Effective Routes
204 |
205 | Figure 5 shows a diagram explaining what we see when we view the effective routes of OnPrem_VM.
206 |
207 | 
208 |
209 | **Figure 5:** Routing from OnPrem_VM1
210 |
211 | Next, let's move on to configuring our Cisco Network Virtual Appliances.
212 |
213 | ## 2.2: Configure Cisco CSR1000V
214 |
215 | One of the requirements of many enterprise organisations is to provide a secure perimeter or DMZ environment using third party routers or firewall devices. Azure allows for this requirement to be met through the use of third party Network Virtual Appliances (NVAs). An NVA is essentially a virtual machine that runs specialised software, typically from a network equipment manufacturer, and that provides routing or firewall functionality within the Azure environment.
216 |
217 | In our VDC environment, we are using Cisco CSR1000V routers in the Hub virtual network - CSR stands for *Cloud Services Router* and is a virtualised Cisco router running IOS-XE software. The CSR1000V is a fully featured Cisco router that supports most routing functionality, such as OSPF and BGP routing, IPSec VPNs and Zone Based Firewalls.
218 |
219 | In the initial lab setup, you provisioned the CSR1000V router in the Hub virtual network, however it must now be configured in order to route traffic. Follow the steps in this section to configure the CSR1000V.
220 |
221 | **1)** To log on to the CSR1000V, you'll need to obtain the public IP address assigned to it. You can obtain this using the Azure portal (navigate to the *VDC-NVA* resource group and inspect the object named 'csr-pip'). Alternatively, you can use the Azure CLI to obtain the public IP address, as follows:
222 |
223 |
224 | az network public-ip list -g VDC-NVA --query [*].[name,ipAddress]
225 | [
226 | [
227 | "csr-pip",
228 | "52.142.215.217"
229 | ]
230 | ]
231 |
232 |
233 | **2)** Now that you have the public IP address, SSH to the CSR1000V VM from within the Cloud Shell using 'ssh labuser@_public-ip_'. The username and password for the CSR are *labuser / M1crosoft123*.
234 |
235 | **3)** Enter configuration mode on the CSR:
236 |
237 |
238 | conf t
239 |
240 |
241 | **4)** The CSR1000V has two interfaces - one connected to Hub_VNet-Subnet1 and the other connected to Hub_VNet-Subnet2. We want to ensure that these interfaces are configured using DHCP, so use the following CLI config to ensure that this is the case and that the interfaces are both in the 'up' state:
242 |
243 |
244 | vdc-csr-1(config)#interface gig1
245 | vdc-csr-1(config-if)#ip address dhcp
246 | vdc-csr-1(config-if)#no shut
247 | vdc-csr-1(config)#interface gig2
248 | vdc-csr-1(config-if)#ip address dhcp
249 | vdc-csr-1(config-if)#no shut
250 | vdc-csr-1(config-if)#exit
251 | vdc-csr-1(config)#exit
252 |
253 |
254 | **5)** Verify that the interfaces are up and configured with an IP address as follows:
255 |
256 |
257 | vdc-csr-1#show ip interface brief
258 | Interface IP-Address OK? Method Status Protocol
259 | GigabitEthernet1 10.101.1.4 YES DHCP up up
260 | GigabitEthernet2 10.101.2.4 YES DHCP up up
261 |
262 |
263 | **6)** Exit the SSH session to the Cisco CSR and then find the public IP address of the virtual machine named *OnPrem_VM1* using the following command:
264 |
265 |
266 | az network public-ip list -g VDC-OnPrem --query [*].[name,ipAddress]
267 |
268 |
269 | **7)** SSH to the public IP of OnPrem_VM1. From within the VM, attempt to connect to the private IP address of one of the CSR1000V interfaces (10.101.1.4):
270 |
271 |
272 | ssh labuser@10.101.1.4
273 |
274 |
275 | This step should succeed, which proves connectivity between the On Premises and Hub VNets using the VPN connection. Figure 6 shows the connection we have just made.
276 |
277 | 
278 |
279 | **Figure 6:** SSH from OnPrem_VM1 to vdc-csr-1
280 |
281 | **8)** Exit the SSH session to the CSR1000V and then exit the SSH session to the OnPrem virtual machine. Find the internal IP address of a virtual machine's NIC in VDC-Spoke1, either by using the portal or any of the following CLI 2.0 commands:
282 |
283 |
284 | az network nic ip-config list --resource-group VDC-Spoke1 --nic-name Spoke1-VM1-nic -o table
285 | az network nic list --query "[].{group: resourceGroup, NIC:name, IPaddress: ipConfigurations[0].privateIpAddress}" -o table
286 |
287 |
288 | The second command is more complicated, using a JMESPATH query to customise the output.
289 |
290 | The VM's IP address is expected to be 10.1.1.5 or 10.1.1.6 depending on the order in which the VM builds completed.
291 |
292 | **9)** Log back in to OnPrem_VM1, and then attempt to connect to the private IP address of the virtual machine within the Spoke 1 Vnet:
293 |
294 |
295 | ssh labuser@10.1.1.5
296 |
297 |
298 | This attempt will fail - the reason for this is that we do not yet have the correct routing in place to allow connectivity between the On Premises VNet and the Spoke VNets via the hub / NVA. In the next section, we will configure the routing required to achieve this.
299 |
300 | ## 2.3: Configure User Defined Routes
301 |
302 | In this section, we will configure a number of *User Defined Routes*. A UDR in Azure is a routing table that you as the user define, potentially overriding the default routing that Azure sets up for you. UDRs are generally required any time a Network Virtual Appliance (NVA) is deployed, such as the Cisco CSR router we are using in our lab. The goal of this exercise is to allow traffic to flow from VMs residing in the Spoke VNets, to the VM in the On Premises VNet. This traffic will flow through the Cisco CSR router in the Hub VNet. The diagram in figure 7 shows what we are trying to achieve in this section.
303 |
304 | 
305 |
306 | **Figure 7:** User Defined Routes
307 |
308 | We'll create our first User Defined Route using the Azure portal, with subsequent UDRs configured using the Azure CLI.
309 |
310 | **1)** In the Azure portal, navigate to the *VDC-OnPrem* resource group. Click 'Add' and then search for 'Route Table'. Select this and then create a new route table named *OnPrem-UDR*. Once complete, navigate to the newly created UDR in the VDC-OnPrem resource group and select it.
311 |
312 | **2)** Click on 'Routes' and then 'Add'. Create a new route with the following parameters:
313 |
314 | - Route Name: *Spoke1-Route*
315 | - Address Prefix: *10.1.0.0/16*
316 | - Next Hop Type: *Virtual Network Gateway*
317 |
318 | Click 'Submit' to create the route. Repeat the process for Spoke 2 as follows:
319 |
320 | - Route Name: *Spoke2-Route*
321 | - Address Prefix: *10.2.0.0/16*
322 | - Next Hop Type: *Virtual Network Gateway*
323 |
324 | Figure 8 shows the route creation screen.
325 |
326 | 
327 |
328 | **Figure 8:** Defining UDRs
329 |
330 | **3)** We now need to associate the UDR with a specific subnet. Click on 'Subnets' and then 'Associate'. Select the VNet 'OnPrem\_Vnet' and then the subnet 'OnPrem\_Vnet-Subnet1'. Click OK to associate the UDR to the subnet.
331 |
332 | We'll now switch to the Azure CLI to define the rest of the UDRs that we need.
333 |
334 | **4)** Create the UDR for the Hub Vnet (GatewaySubnet):
335 |
336 |
337 | az network route-table create --name Hub_UDR -g VDC-Hub
338 |
339 |
340 | **5)** Create the routes to point to Spoke1 and Spoke2, via the Cisco CSR router:
341 |
342 |
343 | az network route-table route create --name Spoke1-Route --address-prefix 10.1.0.0/16 --next-hop-type VirtualAppliance --next-hop-ip-address 10.101.1.4 --route-table-name Hub_UDR -g VDC-Hub
344 | az network route-table route create --name Spoke2-Route --address-prefix 10.2.0.0/16 --next-hop-type VirtualAppliance --next-hop-ip-address 10.101.1.4 --route-table-name Hub_UDR -g VDC-Hub
345 |
346 |
347 | **6)** Associate the UDR with the GatewaySubnet inside the Hub Vnet:
348 |
349 |
350 | az network vnet subnet update --name GatewaySubnet --vnet-name Hub_VNet --route-table Hub_UDR -g VDC-Hub
351 |
352 |
353 | **7)** Configure the UDRs for the Spoke VNets, with relevant routes and associate to the subnets:
354 |
355 |
356 | az network route-table create --name Spoke1_UDR -g VDC-Spoke1
357 | az network route-table create --name Spoke2_UDR -g VDC-Spoke2
358 |
359 | az network route-table route create --name OnPrem-Route --address-prefix 10.102.0.0/16 --next-hop-type VirtualAppliance --next-hop-ip-address 10.101.2.4 --route-table-name Spoke1_UDR -g VDC-Spoke1
360 | az network route-table route create --name OnPrem-Route --address-prefix 10.102.0.0/16 --next-hop-type VirtualAppliance --next-hop-ip-address 10.101.2.4 --route-table-name Spoke2_UDR -g VDC-Spoke2
361 |
362 | az network vnet subnet update --name Spoke1_VNet-Subnet1 --vnet-name Spoke1_Vnet --route-table Spoke1_UDR -g VDC-Spoke1
363 | az network vnet subnet update --name Spoke2_VNet-Subnet1 --vnet-name Spoke2_Vnet --route-table Spoke2_UDR -g VDC-Spoke2
364 |
365 |
366 | Great, everything is in place - we are now ready to test connectivity between our on-premises environment and the Spoke VNets.
367 |
368 | ## 2.4: Test Connectivity Between On-Premises and Spoke VNets
369 |
370 | In this section, we'll perform some simple tests to validate connectivity between our "on-premises" environment and the Spoke VNets - this communication should occur through the Cisco CSR router that resides in the Hub VNet.
371 |
372 | **1)** SSH into the virtual machine named *OnPrem-VM1* as you did earlier.
373 |
374 | **2)** From within this VM, attempt to SSH to the first virtual machine inside the Spoke 1 virtual network (e.g. with an IP address of 10.1.1.5) - this will fail:
375 |
376 |
377 | ssh labuser@10.1.1.5
378 |
379 |
380 | Although we have all the routing we need configured, this connectivity is still failing. Why?
381 |
382 | It turns out that there is an additional setting we must configure on the VNet peerings to allow this type of hub and spoke connectivity to happen. Follow these steps to make the required changes:
383 |
384 | **3)** In the Azure portal, navigate to *Spoke1_VNet* in the 'VDC-Spoke1' resource group. Select 'peerings' and then select the 'to-Hub_Vnet' peering. You'll see that the option entitled *Use Remote Gateways* is unchecked. Checking this option allows the VNet to use a gateway in a *remote* virtual network - as we need our Spoke VNets to use a gateway residing in the Hub VNet, this is exactly what we need, so check the box as shown in figure 9.
385 |
386 | 
387 |
388 | **Figure 9:** Use Remote Gateway Option
389 |
390 | **4)** From within the OnPrem_VM1 virtual machine, try to SSH to the Spoke VM once more. The connection attempt should now succeed.
391 |
392 | **5)** Configure the Spoke 2 VNet peering with 'Use Remote Network Gateway' and then attempt to connect to one of the virtual machines in Spoke 2 (e.g. 10.2.1.5). This connection should also now succeed.
393 |
394 | **6)** Still from the OnPrem_VM1 machine, use the curl command to make an HTTP request to the load balancer private IP address in Spoke1. Note that the IP address *should* be 10.1.1.4, however you may need to verify this in the portal or CLI:
395 |
396 |
397 | curl http://10.1.1.4
398 |
399 |
400 | This command should return an HTML page showing some information, such as the page title, the hostname, system info and whether the application is running inside a container or not.
401 |
402 | If you try the same request a number of times, you may notice that the response contains either *Spoke1-VM1* or *Spoke1-VM2* as the hostname, as the load balancer has both of these machines in the backend pool.
403 |
404 | In the next section, we will lock down the environment to ensure that our On Premises user can only reach the required services.
405 |
406 | # Lab 3: Secure the VDC Environment
407 |
408 | In this section of the lab, we will use Azure features to further secure the virtual data centre environment. We will use the *Network Security Group* (NSG) feature to secure traffic from our On Premises virtual network to the applications running on our spoke VNets. In addition, we will explore the *Azure Security Center* to analyse potential security issues in our environment and take action to resolve them.
409 |
410 | ## 3.1: Network Security Groups
411 |
412 | At the moment, our user in the On Premises VNet is potentially able to access the Spoke 1 & 2 virtual machines on any TCP port - for example, SSH. We want to use Azure Network Security Groups (NSGs) to prevent traffic on any port other than HTTP and port 3000 (the port the application runs on) being allowed into our Spoke VNets.
413 |
414 | An NSG is a list of user-defined security rules that allows or denies traffic on specific ports, or to / from specific IP address ranges. An NSG can be applied at two levels: at the virtual machine NIC level, or at a subnet level.
415 |
416 | Our NSG will define two inbound rules - one for HTTP and another for TCP port 3000. We'll create this NSG within our Hub VNet in order to enforce traffic at a central location. This NSG will be applied at the first CSR1000V interface (i.e. the interface where traffic would come in from the OnPrem VNet).
417 |
418 | **1)** In the Azure portal under the resource group VDC-Hub, click 'Add' and search for 'Network Security Group'. Create a new NSG named *Hub-NSG*.
419 |
420 | **2)** Navigate to the newly created NSG and select it. Select 'Inbound Security Rules'. Click 'Add' to add a new rule. Use the following parameters:
421 |
422 | - Name: *Allow-http*
423 | - Priority: *100*
424 | - Source port range: *Any*
425 | - Destination port range: *80*
426 | - Action: *Allow*
427 |
428 | 
429 |
430 | **Figure 10:** Network Security Group - HTTP Rule
431 |
432 | **3)** Add another rule with the following parameters:
433 |
434 | - Name: *Allow-3000*
435 | - Priority: *110*
436 | - Source port range: *Any*
437 | - Destination port range: *3000*
438 | - Action: *Allow*
439 |
440 | **4)** Add one more rule with the following parameters:
441 |
442 | - Name: *Deny-All*
443 | - Priority: *120*
444 | - Source port range: *Any*
445 | - Destination port range: *Any*
446 | - Action: *Deny*
447 |
448 | **5)** Select 'Network Interfaces'. Click the 'Associate' button and choose 'vdc-csr-1-Nic0'.
449 |
450 | 
451 |
452 | **Figure 11:** Network Security Group - Associating with a Subnet
453 |
454 | **6)** SSH back into the OnPrem-VM1 virtual machine from your terminal emulator (this will refresh the NSG rules for the associated NIC). From this VM, attempt to SSH to the first Spoke1 VM:
455 |
456 |
457 | ssh labuser@10.1.1.5
458 |
459 |
460 | This connection attempt will fail due to the NSG now associated with the Spoke1 subnet.
461 |
462 | **7)** From OnPrem_VM1, make sure you can still access the demo app:
463 |
464 |
465 | curl http://10.1.1.4
466 |
467 |
468 | You might wonder why the third rule denying all traffic is required in this example. The reason for this is that a default rule exists in the NSG that allows all traffic from every virtual network. Therefore, without the specific 'Deny-All' rule in place, all traffic will succeed (in other words, the NSG will have no effect). You can see the default rules by clicking on 'Default Rules' under the security rules view.
469 |
470 | ## 3.2: Using Azure Security Center
471 |
472 | Azure Security Center is a feature built in to Azure which allows administrators to gain visibility into the security of their environment and to detect and respond to issues and threats. In this part of the lab, we'll explore Azure Security Center and what it has to offer.
473 |
474 | **1)** In the Azure portal, expand the left hand menu and select 'More Services'. Search for and then select 'Security Center'.
475 |
476 | **2)** The overview section of the Security Center shows an 'at-a-glance' view of any security recommendations, alerts and prevention items relating to compute, storage, networking and applications.
477 |
478 | 
479 |
480 | **Figure 12:** Azure Security Center - Overview Page
481 |
482 | **3)** Click on 'Recommendations' in the Security Center menu. You will see a list of recommendations relating to various areas of the environment - for example, the need to add Network Security Groups on subnets and VMs, or the recommendation to apply disk encryption to VMs.
483 |
484 | 
485 |
486 | **Figure 13:** Azure Security Center - Recommendations
487 |
488 | **4)** Explore other areas of the Security Center - click through the Compute, Networking and Storage sections to see recommendations specific to these areas.
489 |
490 | ## 3.3: Implementing Azure Resource Policies
491 |
492 | Azure resource policies are used to place restrictions on what actions can be taken at a subscription or resource group level. For example, a resource policy could specify that only certain VM sizes are allowed, or that encryption is required for storage accounts. In this section of the lab, we'll apply both built-in and custom resource policies to one of our resource groups to restrict what can and can't be done in our environment.
493 |
494 | **1)** In the Azure portal, navigate to the VDC-Hub resource group and then click on *Policies* in the menu.
495 |
496 | **2)** Select *Definitions* and then *Policy Definitions* in the right hand pane.
497 |
498 | **3)** Scroll down to the policy entitled 'Allowed Resource Types', click the '...', select 'View Definition' and then click on 'JSON'. This shows you the JSON policy document - this simple example takes a list of resource types and prevents the ability to create them.
499 |
500 | 
501 |
502 | **Figure 14:** Example Resource Policy - Allowed Resource Types
503 |
504 | **4)** Click on 'Assignments' in the menu and then click 'Assign Policy'.
505 |
506 | **5)** Use the following details to create the policy:
507 |
508 | - Policy Definition: *Allowed Resource Types*
509 | - Allowed Resource Types: *Select all 'Microsoft.Network' resources*
510 | - Display Name: *Allow Network*
511 | - ID: *Allow-Network*
512 |
513 | **6)** Use the Azure Cloud Shell to attempt to create a virtual machine using the following commands:
514 |
515 |
516 | az vm create -n policy-test-VM -g VDC-Hub --image UbuntuLTS --generate-ssh-keys
517 |
518 |
519 | **7)** The validation should fail with a message stating "The template deployment failed because of policy violation. Please see details for more information."
520 |
521 | **8)** Return to the 'Policies' page and remove the 'Allow-Network' resource policy assignment.
522 |
523 | If the built-in policies do not meet your requirements, it is also possible to create custom policies. The following steps walk you through custom policy creation.
524 |
525 | In this example, we'll create a policy that enforces a specific naming convention - if the user attempts to create a resource that does not meet the convention, the request will be denied. We first need to define the resource policy template - these are written in JSON format. The JSON for our custom policy is as follows:
526 |
527 |
528 | {
529 | "if": {
530 | "not": {
531 | "field": "name",
532 | "like": "VDC-*"
533 | }
534 | },
535 | "then": {
536 | "effect": "deny"
537 | }
538 | }
539 |
540 |
541 | This policy states that we must name our resources with the 'VDC-' prefix.
542 |
543 | In this exercise, a file has been created on Github containing the above policy - that file will then be referenced from an AZ CLI command in order to create the policy in Azure.
544 |
545 | **1)** Using the AZ CLI, enter the following command:
546 |
547 |
548 | az policy definition create --name EnforceNaming --display-name EnforceNamingConvention --rules https://raw.githubusercontent.com/Araffe/vdc-networking-lab/master/naming-policy.json
549 |
550 |
551 | **2)** Assign the policy to the VDC-Hub resource group using the following AZ CLI command:
552 |
553 |
554 | az policy assignment create --policy EnforceNaming -g VDC-Hub --name EnforceNaming
555 |
556 |
557 | **3)** In the VDC-Hub resource group, create a new virtual network named "test-net" using default parameters. You should receive a validation error as the name does not meet the required convention, as specified in the resource policy.
558 |
559 | **4)** Attempt to create the virtual network again, but this time name it "VDC-testnet". This attempt should succeed as the name matches the convention.
560 |
561 | **5)** Remove the VDC-Test virtual network from the resource group.
562 |
563 | **6)** Unassign and remove the naming convention policy using the following commands:
564 |
565 |
566 | az policy assignment delete -g VDC-Hub --name EnforceNaming
567 | az policy definition delete --name EnforceNaming
568 |
569 |
570 | ## 3.4: Monitor Compliance Using Azure Policy
571 |
572 | In addition to the ability to define and assign resource policies, Azure Policy allows you to identify existing resources within a subscription or resource that are not compliant against the configured policies. In this section, we will use Azure Policy to view non-compliant resources within the VDC-Hub resource group.
573 |
574 | **1)** In the 'Policies' section under VDC-Hub, click on 'Definitions' and then '+ Policy Definition'. Name the policy 'compliance-test' and select your subscription under 'Definition Location'.
575 |
576 | **2)** Select 'Use existing' under the category section and select 'Compute'.
577 |
578 | **3)** Cut and paste the following JSON into the 'Policy Rule and Parameters' box and then click 'save':
579 |
580 |
581 | {
582 | "policyRule": {
583 | "if": {
584 | "not": {
585 | "field": "name",
586 | "like": "test-*"
587 | }
588 | },
589 | "then": {
590 | "effect": "audit"
591 | }
592 | }
593 | }
594 |
595 |
596 | **4)** Click on 'Assignments' and then 'Assign Policy'. Under 'Policy Definition', select the policy created in the last step (compliance-test). Name the assignment 'compliance-test'.
597 |
598 | **5)** Under 'Scope', ensure that the 'VDC-Hub' resource group is selected. Finally, click on 'Assign'.
599 |
600 | **6)** Click on the 'Compliance' menu option - after some time, this screen should show the resources within the VDC-Hub resource group that are not compliant with the policy (all of them, as none match the name 'test-*').
601 |
602 | **Note: it can take around 30 - 60 minutes for the compliance information to be updated, so you may wish to return to this section.**
603 |
604 | 
605 |
606 | **Figure 15:** Azure Policy - Compliance View
607 |
608 | **8)** Under the 'Assignments' page, click on the '...' on the right hand side of the assignment and select 'Delete Assignment'.
609 |
610 | **9)** Under the 'Definitions' page, click on 'Policy Definitions' and delete the 'compliance-test' definition we created earlier.
611 |
612 | # Lab 4: Monitor the VDC Environment
613 |
614 | In this section, we will explore some of the monitoring options we have in Azure and how those can be used to troubleshoot and diagnose issues in a VDC environment. The first tool we will look at is *Network Watcher*. Network Watcher is a collection of tools available to monitor and troubleshoot issues with network connectivity in Azure, including packet capture, NSG flow logs and IP flow verify.
615 |
616 | ## 4.1: Enabling Network Watcher
617 |
618 | Before we can use the tools in this section, we must first enable Network Watcher. To do this, follow these steps:
619 |
620 | **1)** In the Azure portal, expand the left hand menu and then click *More Services*. In the filter bar, type 'Network Watcher' and then click on the Network Watcher service.
621 |
622 | **2)** You should see your Azure subscription listed in the right hand pane - find your region and then click on the'...' on the right hand side. Click 'Enable Network Watcher':
623 |
624 | 
625 |
626 | **Figure 16:** Enabling Network Watcher
627 |
628 | **3)** On the left hand side of screen under 'Monitoring', click on 'Topology'. Select your subscription and then the resource group 'VDC-Hub' and 'Hub_Vnet'. You will see a graphical representation of the topology on the screen:
629 |
630 | 
631 |
632 | **Figure 17:** Network Topology View in Network Watcher
633 |
634 | **4)** A useful feature of Network Watcher is the ability to view network related subscription limits and track your resource utilisation against these. In the left hand menu, select 'Network Subscription Limit'. You will see a list of resources, including virtual networks, public IP addresses and more:
635 |
636 | 
637 |
638 | **Figure 18:** Network Related Subscription Limits
639 |
640 | ## 4.2: NSG Flow Logs
641 |
642 | Network Security Group (NSG) Flow Logs are a feature of Network Watcher that allows you to view information about traffic flowing through a NSG. The logs are written in JSON format and are stored in an Azure storage account that you must designate. In this section, we will enable flow logging for the NSG we configured in the earlier lab and inspect the results.
643 |
644 |
645 | **1)** To begin with, we need to create a storage account to store the NSG flow logs. Use the following CLI to do this, substituting the storage account name for a unique name of your choice:
646 |
647 |
648 | az storage account create --name storage-account-name -g VDC-Hub --sku Standard_LRS
649 |
650 |
651 | **2)** Use the Azure portal to navigate to the Network Watcher section (expand left menu, select 'More Services' and search for 'Network Watcher'). Select 'NSG Flow Logs' from the Network Watcher menu. Filter using your subscription and Resource Group at the top of the page and you should see the NSG we created in the earlier lab.
652 |
653 | **3)** Click on the NSG and then in the settings screen, change the status to 'On'. Select the storage account you created in step 1 and change the retention to 5 days. Click 'Save'.
654 |
655 | 
656 | **Figure 19:** NSG Flow Log Settings
657 |
658 | **4)** In order to view data from the NSG logs, we must initiate some traffic that will flow through the NSG. SSH to the OnPrem_VM1 virtual machine as described earlier in the lab. From here, use the curl command to view the demo app on Spoke1\_VM1 (through the load balancer) and attempt to SSH to the same VM (this will fail):
659 |
660 |
661 | curl http://10.1.1.4
662 | ssh labuser@10.1.1.5
663 |
664 |
665 | **5)** NSG Flow Logs are stored in the storage account you configured earlier in this section - in order to view the logs, you must download the JSON file from Blob storage. You can do this either using the Azure portal, or using the *Microsoft Azure Storage Explorer* program available as a free download from http://storageexplorer.com/. If using the Azure portal, navigate to the storage account you created earlier and select 'Blobs'. You will see a container named 'insights-logs-networksecuritygroupflowevent'. Navigate through the directory structure (structured as subscription / resource group / day / month / year / time) until you reach a file named 'PT1H.json'. Download this file to your local machine.
666 |
667 | 
668 |
669 | **Figure 20:** NSG Flow Log Download
670 |
671 | **6)** Open the PT1H.json file in an editor on your local machine (Visual Studio Code is a good choice - available as a free download from https://code.visualstudio.com/). The file should show a number of flow entries which can be inspected. Let's start by looking for an entry for TCP port 80 from our OnPrem_VM1 machine to the Spoke1 load balancer IP address. You can search for the IP address '10.102.1.4' to see entries associated with OnPrem\_VM1.
672 |
673 | Here is an example of a relevant JSON entry:
674 |
675 |
676 | "rule":"UserRule_Allow-HTTP","flows":[{"mac":"000D3A25DC84","flowTuples":["1501685102,10.102.1.4,10.1.1.5,56934,80,T,I,A"
677 |
678 |
679 | The above entry shows that a flow has hit the user rule named 'Allow-HTTP' (a rule that we configured earlier) and that the flow has a source address of 10.102.1.4 and a destination address of 10.1.1.5 (one of our Spoke1 VMs), using TCP port 80. The letters T, I and A signify the following:
680 |
681 | - **T:** A TCP flow (a 'U' would indicate UDP)
682 | - **I:** An ingress flow (an 'E' would indicate an egress flow)
683 | - **A**: An allowed flow (a 'D' would indicate a denied flow)
684 |
685 | **7)** Search the JSON file for a flow using port 22 (SSH).
686 |
687 |
688 | "rule":"UserRule_Deny-All","flows":[{"mac":"000D3A25DC84","flowTuples":["1501684054,10.102.1.4,10.1.1.5,60084,22,T,I,D"
689 |
690 |
691 | The above example shows a flow that has hit our user defined rule name 'Deny-All'. The source and destination addresses are the same as in the previous example, however the TCP port is 22 (SSH), which is not allowed through the NSG (note the 'D' flag).
692 |
693 | ## 4.3: Tracing Next Hop Information
694 |
695 | Another useful feature of Network Watcher is the ability to trace the next hop for a given network destination. As an example, this is useful for diagnosing issues with User Defined Routes.
696 |
697 | **1)** Navigate to Network Watcher as described in earlier sections.
698 |
699 | **2)** In the left hand menu, select 'Next Hop'. Use the following parameters as input:
700 |
701 | - Resource Group: *VDC-Spoke1*
702 | - Virtual Machine: *Spoke1-VM1*
703 | - Network Interface: *Spoke1-VM1-nic*
704 | - Source IP address: *10.1.1.5*
705 | - Destination IP address: *10.102.1.4*
706 |
707 | **3)** The resulting output should display *10.101.2.4* as the next hop. This is the IP address of our Network Virtual Appliance (Cisco CSR) and corresponds to the User Defined Route we configured earlier.
708 |
709 | 
710 |
711 | **Figure 21:** Next Hop Tracking
712 |
713 | **4)** Try other combinations of IP address / virtual machine. For example, reverse the IP addresses used in the previous step.
714 |
715 | ## 4.4: Metrics and Alerts with Azure Monitor
716 |
717 | Azure Monitor is a tool that provides central monitoring of most Azure services, designed to give you infrastructure level diagnostics about a service and the surrounding environment. In this section of the lab, we will use Azure Monitor to look at metrics on a resource and create an alert to receive an email when a CPU threshold is crossed.
718 |
719 | **1)** Start by using the Azure portal to navigate to the Azure Monitor view by expanding the left hand main menu and selecting 'Monitor'. If it is not shown, select 'More Services' and search for it. Select 'Activity Log' from the left hand menu. This shows a filterable view of all activity in your subscription - you can filter based on timespan, event severity, resource type and operation. Modify some of the filter fields in this screen to narrow down the search criteria.
720 |
721 | 
722 |
723 | **Figure 22:** Azure Monitor Activity Log
724 |
725 | **2)** In the Azure Monitor menu on the left, select 'Metrics (Preview)'. In the 'resource' box at the top of the screen, search for 'onprem' and then select the 'OnPrem1_VM' virtual machine in the drop-down menu. Select 'host' as the sub-service and then 'Percentage CPU' as the metric.
726 |
727 | 
728 |
729 | **Figure 23:** Azure Monitor CPU Metrics
730 |
731 | **3)** For all types of metric displayed, it is possible to configure alerts when a specific threshold is reached. Select 'Alerts' from the left hand menu and then click on 'New Alert Rule' at the top of the screen.
732 |
733 | **4)** Click on 'Select Target' and then choose your subscription. Select 'Virtual Machines' as the resource type and then choose 'OnPrem-VM1'.
734 |
735 | **5)** Click on 'Add Criteria' and then select 'Percentage CPU' as the signal type.
736 |
737 | **6)** Under 'Alert Logic', ensure the condition is 'Greater than' and set the threshold to 40%. Change the period to 'Over the last 1 minute'.
738 |
739 | **7)** Expand the section named 'Define Alert Details'. Name the alert rule 'alert-cpu' and give it a suitable description. Click OK.
740 |
741 | **8)** Expand the section named 'Define Action Group'. Name the action group 'alert-email' (use this for the short name as well). Under 'Actions', use the same name and select 'Email/SMS/Push/Voice'. In the resulting dialog box, configure your own email address. Select OK.
742 |
743 | **9)** On the main page, click 'Select Action Group' and select the group you have just configured.
744 |
745 | **10)** SSH to your OnPrem_VM1 virtual machine and install the 'Stress' tool:
746 |
747 |
748 | sudo apt-get install stress
749 |
750 |
751 | **11)** Use the Stress program to hog the CPU:
752 |
753 |
754 | stress -c 50
755 | stress: info: [61727] dispatching hogs: 50 cpu, 0 io, 0 vm, 0 hdd
756 |
757 |
758 | **12)** After approximately 5 minutes, you should receive an email alerting you to the high CPU on your VM:
759 |
760 | 
761 |
762 | **Figure 24:** Azure Monitor CPU Alert
763 |
764 | **13)** Stop the Stress program. After another few minutes you should receive another mail informing you that the CPU percentage has reduced.
765 |
766 | # Lab 5: Identity in the VDC Environment
767 |
768 | A critical part of any data centre - whether on-premises or in the cloud - is managing identity. In this section of the lab, we will look at two of the primary mechanisms for managing identity in the virtual data centre: Azure Active Directory (AAD) and Role Based Access Control (RBAC). We will use Azure AD to create users and groups and then use RBAC to assign roles and access to resources for these groups.
769 |
770 | In this lab, we will create three groups of users, as shown in figure 23:
771 |
772 | 
773 |
774 | **Figure 25:** VDC Lab Users and Groups
775 |
776 | The groups will have the following rights:
777 |
778 | - The **Central IT** group has overall responsibility for network and security components, therefore should have full control of the hub resources, with network contributor access on the spokes.
779 |
780 | - The **AppDev** group has responsibility for compute resources in the spoke resource groups, therefore should have the contributor role for virtual machines. Users in the AppDev group would also like to view (but not configure) resources in the Hub.
781 |
782 | - The **Ops** group are responsible for managing workloads in production, therefore will need full contributor rights in the spoke resource groups.
783 |
784 | We'll start by configuring a number of users and groups.
785 |
786 | ## 5.1: Configure Users and Groups
787 |
788 | **1)** To begin, we'll verify our domain name in the Azure portal. On the left hand side of the portal screen, click 'More Services' and then search for 'Azure Active Directory'. Click on 'Domain Name' and you will see the domain assigned to your Azure AD directory.
789 |
790 | 
791 |
792 | **Figure 26:** Azure AD Domain Name
793 |
794 | **2)** Create three users (Fred, Bob and Dave) using the Azure CLI. Note that you will need to substitute your own domain in the user principal name.
795 |
796 |
797 | az ad user create --display-name Fred --user-principal-name Fred@*domain*.onmicrosoft.com --password M1crosoft123
798 | az ad user create --display-name Bob --user-principal-name Bob@*domain*.onmicrosoft.com --password M1crosoft123
799 | az ad user create --display-name Dave --user-principal-name Dave@*domain*.onmicrosoft.com --password M1crosoft123
800 |
801 |
802 | **3)** Create three groups (CentralIT, AppDev and Ops) using the Azure CLI:
803 |
804 |
805 | az ad group create --display-name CentralIT --mail-nickname CentralIT
806 | az ad group create --display-name AppDev --mail-nickname AppDev
807 | az ad group create --display-name Ops --mail-nickname Ops
808 |
809 |
810 | **4)** In order to add users to groups using the CLI, you will need the object ID of each user. To get these IDs, use the following command - make a note of the object IDs associated with each user:
811 |
812 |
813 | az ad user list
814 |
815 |
816 | The following is an example output from the previous command (do not use these object IDs - use your own!!):
817 |
818 | [
819 | {
820 | "displayName": "Bob",
821 | "mail": null,
822 | "mailNickname": "Bob",
823 | "objectId": "d0463199-07c8-4768-abb0-3012b1ef856f",
824 | "objectType": "User",
825 | "signInName": null,
826 | "userPrincipalName": "Bob@*domain*.onmicrosoft.com"
827 | },
828 | {
829 | "displayName": "Dave",
830 | "mail": null,
831 | "mailNickname": "Dave",
832 | "objectId": "69f6f292-2c2f-4451-8054-ff6addb300a4",
833 | "objectType": "User",
834 | "signInName": null,
835 | "userPrincipalName": "Dave@*domain*.onmicrosoft.com"
836 | },
837 |
838 | {
839 | "displayName": "Fred",
840 | "mail": null,
841 | "mailNickname": "Fred",
842 | "objectId": "4c53a57e-2a2d-4a1d-8af6-e811850a869e",
843 | "objectType": "User",
844 | "signInName": null,
845 | "userPrincipalName": "Fred@*domain*.onmicrosoft.com"
846 | }
847 | ]
848 |
849 |
850 | **5)** Use the object IDs to add the users to each group as follows:
851 |
852 | - **Fred**: CentralIT
853 | - **Bob**: AppDev
854 | - **Dave**: Ops
855 |
856 | The Azure CLI can be used to do this, as follows:
857 |
858 |
859 | az ad group member add --member-id *Fred's OID* --group CentralIT
860 | az ad group member add --member-id *Bob's OID* --group AppDev
861 | az ad group member add --member-id *Dave's OID* --group Ops
862 |
863 |
864 | ## 5.2: Assign Users and Roles to Resource Groups
865 |
866 | Now that we have our users and groups in place, it's time to make use of them by assigning the groups to resource groups. We will also assign roles to determine what access a group has on a given resource group.
867 |
868 | **1)** In the Azure portal, navigate to the 'VDC-Hub' resource group and then the 'IAM' section.
869 |
870 | **2)** You will see the user you are currently logged on as (i.e. the admin). Click 'Add' at the top of the screen and then select the 'Contributor' role from the drop down box. Select the 'CentralIT' user from the list of users and groups. Click 'save'.
871 |
872 | **3)** Click 'Add' again, but this time select the 'Reader' role and then choose the 'AppDev' group.
873 |
874 | 
875 |
876 | **Figure 27:** Hub Role Based Access Control
877 |
878 | **4)** Navigate to the 'VDC-Spoke1' resource group and select 'IAM'. Click 'Add' and then select the 'Virtual Machine Contributor' role. Add the AppDev group. Repeat this step for the 'VDC-Spoke2' resource group.
879 |
880 | **5)** For Spokes 1 and 2, add CentralIT with the 'Network Contributor' role.
881 |
882 | **6)** For Spokes 1 and 2, add the 'Ops' group with the 'Contributor' role.
883 |
884 | ## 5.3: Test User and Group Access
885 |
886 | Now that we have Azure AD groups assigned to resource groups with the appropriate roles, we can test the access that each user has.
887 |
888 | **1)** Open a private browsing window / incognito window (depending on browser) and browse to the Azure portal (portal.azure.com).
889 |
890 | **2)** Log on to the portal as Dave (dave@*domain*.onmicrosoft.com) using the password M1crosoft123.
891 |
892 | **3)** Navigate to the resource groups view. As Dave is part of the Ops group, you will see that he has full visibility of the Spoke 1 and 2 resource groups, however Dave has no visibility of any other resource group, including the Hub.
893 |
894 | **4)** Log off from the portal and then log on again, this time as Bob (bob@*domain*.onmicrosoft.com).
895 |
896 | **5)** Navigate to the resource groups view. As Bob is part of the AppDev group, he has full visibility of the two Spoke resource groups, but only has read access to the VDC-Hub group. Select the VDC-Hub group and then 'Hub\_VNet'. Notice that Bob cannot make any changes to the Hub\_VNet resource, or any resource within the group.
897 |
898 | **6)** Log off from the portal and then log on again, this time as Fred (fred@*domain*.onmicrosoft.com).
899 |
900 | **7)** Navigate to the 'VDC-Spoke1' resource group. Select 'Hub\_VNet'. Note that Fred is able to make changes / adds, etc to the Hub\_VNet network resource (remember that Fred is part of the CentralIT group, which has the network contributor role on Spoke 1 and 2 resource groups). However, Fred is not able to see any of the virtual machine resources as the CentralIT group does not have the virtual machine contributor role on this resource group.
901 |
902 |
903 | # Decommission the Lab
904 |
905 | To decommission the VDC lab, simply remove the resource groups using the following commands:
906 |
907 |
908 | az group delete --name VDC-Hub --no-wait -y
909 | az group delete --name VDC-NVA --no-wait -y
910 | az group delete --name VDC-Spoke1 --no-wait -y
911 | az group delete --name VDC-Spoke2 --no-wait -y
912 | az group delete --name VDC-OnPrem --no-wait -y
913 |
914 |
915 | # Conclusion
916 |
917 | Well done, you made it to the end of the lab! We've covered a lot of ground in this lab, including networking, security, monitoring and identity - I hope you enjoyed running through the lab and that you learnt a few useful things from it. Don't forget to delete your resources after you have finished!
918 |
919 | # Useful References
920 |
921 | - **Azure Virtual Data Center White Paper:** https://azure.microsoft.com/mediahandler/files/resourcefiles/1ad643b8-73f7-43f6-b05a-8e160168f9df/Azure_Virtual_Datacenter.pdf
922 |
923 | - **Secure Network Designs:** https://docs.microsoft.com/en-us/azure/best-practices-network-security?toc=%2fazure%2fnetworking%2ftoc.json
924 |
925 | - **Hub and Spoke Network Topologies:** https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/hub-spoke
926 |
927 | - **Azure Role Based Access Control**: https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-what-is
928 |
929 | - **Azure Network Watcher:** https://docs.microsoft.com/en-us/azure/network-watcher/network-watcher-monitoring-overview
930 |
931 | - **Azure Monitor:** https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-overview-azure-monitor
932 |
--------------------------------------------------------------------------------
/vdc-vnets.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "spokeVnetNamePrefix": {
6 | "defaultValue": "Spoke_VNet",
7 | "type": "string",
8 | "metadata": {
9 | "description": "Spoke VNet name prefix."
10 | }
11 | },
12 | "spokeVnetOctet1": {
13 | "defaultValue": "10.",
14 | "type": "string",
15 | "metadata": {
16 | "description": "First octet of the spoke vnet IP space. Second octet depends on the VNet instance (1, 2, 3, etc)."
17 | }
18 | },
19 | "spokeVnetCount": {
20 | "defaultValue": 3,
21 | "type": "int",
22 | "metadata": {
23 | "description": "Number of spoke VNets that will be created"
24 | }
25 | }
26 | },
27 | "resources": [
28 | {
29 | "comments": "Spoke VNet with 2 subnets",
30 | "name": "[concat(parameters('spokeVnetNamePrefix'), copyIndex(1))]",
31 | "type": "Microsoft.Network/virtualNetworks",
32 | "apiVersion": "2016-03-30",
33 | "location": "[resourceGroup().location]",
34 | "properties": {
35 | "addressSpace": {
36 | "addressPrefixes": [
37 | "[concat(parameters('spokeVnetOctet1'), copyIndex(1), '.0.0/16')]"
38 | ]
39 | },
40 | "subnets": [
41 | {
42 | "name": "[concat(parameters('spokeVnetNamePrefix'), copyIndex(1), '-Subnet1')]",
43 | "properties": {
44 | "addressPrefix": "[concat(parameters('spokeVnetOctet1'), copyIndex(1), '.1.0/24')]"
45 | }
46 | },
47 | {
48 | "name": "[concat(parameters('spokeVnetNamePrefix'), copyIndex(1), '-Subnet2')]",
49 | "properties": {
50 | "addressPrefix": "[concat(parameters('spokeVnetOctet1'), copyIndex(1), '.2.0/24')]"
51 | }
52 | }
53 | ]
54 | },
55 | "copy": {
56 | "name": "vnetCopy",
57 | "count": "[parameters('spokeVnetCount')]"
58 | }
59 | },
60 | {
61 | "comments": "Hub VNet with 2 subnets",
62 | "name": "Hub_VNet",
63 | "type": "Microsoft.Network/virtualNetworks",
64 | "apiVersion": "2016-03-30",
65 | "location": "[resourceGroup().location]",
66 | "properties": {
67 | "addressSpace": {
68 | "addressPrefixes": [
69 | "10.101.0.0/16"
70 | ]
71 | },
72 | "subnets": [
73 | {
74 | "name": "Hub_VNet-Subnet1",
75 | "properties": {
76 | "addressPrefix": "10.101.1.0/24"
77 | }
78 | },
79 | {
80 | "name": "Hub_VNet-Subnet2",
81 | "properties": {
82 | "addressPrefix": "10.101.2.0/24"
83 | }
84 | },
85 | {
86 | "name": "GatewaySubnet",
87 | "properties": {
88 | "addressPrefix": "10.101.3.0/24"
89 | }
90 | }
91 | ]
92 | }
93 | },
94 | {
95 | "comments": "On Premises VNet with 2 subnets",
96 | "name": "OnPrem_VNet",
97 | "type": "Microsoft.Network/virtualNetworks",
98 | "apiVersion": "2016-03-30",
99 | "location": "[resourceGroup().location]",
100 | "properties": {
101 | "addressSpace": {
102 | "addressPrefixes": [
103 | "10.102.0.0/16"
104 | ]
105 | },
106 | "subnets": [
107 | {
108 | "name": "OnPrem_VNet-Subnet1",
109 | "properties": {
110 | "addressPrefix": "10.102.1.0/24"
111 | }
112 | },
113 | {
114 | "name": "OnPrem_VNet-Subnet2",
115 | "properties": {
116 | "addressPrefix": "10.102.2.0/24"
117 | }
118 | },
119 | {
120 | "name": "GatewaySubnet",
121 | "properties": {
122 | "addressPrefix": "10.102.3.0/24"
123 | }
124 | }
125 | ]
126 | }
127 | }
128 | ]
129 | }
--------------------------------------------------------------------------------
/vpn-gw.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vnetName": {
6 | "defaultValue": "myVnet",
7 | "type": "string"
8 | },
9 | "gwName": {
10 | "defaultValue": "gw",
11 | "type": "string",
12 | "metadata": {
13 | "description": "Name for the VPN gateway"
14 | }
15 | },
16 | "PIPName": {
17 | "defaultValue": "gwPIP",
18 | "type": "string",
19 | "metadata": {
20 | "description": "Public IP address for the VPN gateway"
21 | }
22 | },
23 | "asNumber": {
24 | "defaultValue": "65515",
25 | "type": "string",
26 | "metadata": {
27 | "description": "BGP AS number for VPN gateways"
28 | }
29 | },
30 | "RGName": {
31 | "defaultValue": "VDC-Hub",
32 | "type": "string",
33 | "metadata": {
34 | "description": "Name of the resource group"
35 | }
36 | }
37 | },
38 | "resources": [
39 | {
40 | "comments": "Create public IP address for VNet GW",
41 | "apiVersion": "2017-06-01",
42 | "type": "Microsoft.Network/publicIPAddresses",
43 | "name": "[parameters('PIPName')]",
44 | "location": "[resourceGroup().location]",
45 | "properties": {
46 | "publicIPAllocationMethod": "Dynamic"
47 | }
48 | },
49 | {
50 | "comments": "Create a VPN gateway",
51 | "apiVersion": "2016-09-01",
52 | "type": "Microsoft.Network/virtualNetworkGateways",
53 | "name": "[parameters('gwName')]",
54 | "location": "[resourceGroup().location]",
55 | "properties": {
56 | "ipConfigurations": [
57 | {
58 | "properties": {
59 | "privateIPAllocationMethod": "Dynamic",
60 | "subnet": {
61 | "id": "[concat(resourceID('Microsoft.Network/virtualNetworks', parameters('vnetName')), '/subnets/', 'GatewaySubnet')]"
62 | },
63 | "publicIPAddress": {
64 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('PIPName'))]"
65 | }
66 | },
67 | "name": "[concat(parameters('vnetName'), 'vnetGwConfig')]"
68 | }
69 | ],
70 | "gatewayType": "Vpn",
71 | "sku": {
72 | "name": "VpnGw1",
73 | "tier": "VpnGw1",
74 | "capacity": 2
75 | },
76 | "gatewaySize": "Default",
77 | "vpnType": "RouteBased",
78 | "enableBgp": "true",
79 | "bgpSettings": {
80 | "asn": "[int(parameters('asNumber'))]"
81 | }
82 | },
83 | "dependsOn": [
84 | "[parameters('PIPName')]"
85 | ]
86 | }
87 | ]
88 | }
--------------------------------------------------------------------------------