├── README.md ├── aad_sp.azcli ├── aca.azcli ├── aci-certbot.azcli ├── aci_oauth2.azcli ├── aci_plink.azcli ├── adf.azcli ├── afd-atm.azcli ├── afd.azcli ├── agicv2.azcli ├── aib.azcli ├── aks.azcli ├── aks_appgw.azcli ├── aks_baseline.azcli ├── akv.azcli ├── akv_vm.azcli ├── allsubnet_private.sh ├── aml-private.azcli ├── ampls.azcli ├── apim.azcli ├── appgw.azcli ├── appgw_v1.ps1 ├── arc_data.sh ├── arc_k8s.azcli ├── arm_api.azcli ├── aro.azcli ├── avnm.azcli ├── avs.azcli ├── avs_2vnets.azcli ├── azdevops.azcli ├── azfnc2cosmosdb-rules.sh ├── azfw-afd-vwan.azcli ├── azfw-afd.azcli ├── azfw-appgw-vwan.azcli ├── azfw-appgw.azcli ├── azfw.azcli ├── azfw_stresstest.azcli ├── azsearch.azcli ├── bgp.sh ├── cdn2afd.azcli ├── cloudinit-apache2.txt ├── cloudinit-httpd.txt ├── cloudinit-iptables.txt ├── cloudinit-whoami.txt ├── connection_monitor.azcli ├── cosmosdb_rest.azcli ├── dns_fwding.azcli ├── domain.azcli ├── elk.azcli ├── er_2regions.azcli ├── expressroute.azcli ├── fleet_lb.azcli ├── flowlogs-ADXdashboard.json ├── flowlogs-grafana_dashboard.json ├── flowlogs.azcli ├── flowlogs.kql ├── flowlogs_dashboard.ndjson ├── flowlogs_index.ndjson ├── fortigate.azcli ├── get_secret.py ├── graph.azcli ├── gwlb.azcli ├── hubandspoke_complex.azcli ├── hubandspoke_simple.azcli ├── hubandspoke_simple.json ├── hubandspoke_simple_2region.azcli ├── ipranges.sh ├── ipv6.azcli ├── k8s_fleet.azcli ├── linuxnva.azcli ├── linuxnva_autoconfig.sh ├── megaport.sh ├── mrt2azmon.py ├── myip.py ├── natgw.azcli ├── netwizard.sh ├── nsp.azcli ├── nsp_rest.azcli ├── nva.azcli ├── nva_plink.azcli ├── packer.sh ├── plink.azcli ├── plink_udr.azcli ├── routeserver-vmss-selfcontained-config.sh ├── routeserver-vmss-selfcontained-healthcheck.py ├── routeserver-vmss-selfcontained-routes.txt ├── routeserver-vmss-selfcontained.azcli ├── routeserver-vmss-simplified.azcli ├── routeserver-vmss.azcli ├── routeserver-vpngw.azcli ├── routeserver.azcli ├── routeserver_2hubs.azcli ├── sample_azure_config_msi.yaml ├── sas.azcli ├── service-tags.azcli ├── sqlmi.azcli ├── ssh_rule.sh ├── storage_logs.azcli ├── tag_batch.sh ├── translations └── en_US │ └── LC_MESSAGES │ ├── acsengine.mo │ └── acsengine.po ├── velocloud.azcli ├── vmss-iperf.azcli ├── vmss-nva.azcli ├── vmss_iperf_targetip.sh ├── vnet.azcli ├── vpn.azcli ├── vwan-bowtie.azcli ├── vwan-functions.sh ├── vwan-lab-create.sh ├── vwan.azcli ├── vwan2vwan.azcli ├── vwan_2xshub.azcli ├── vwan_2xshub.sh ├── vwan_infra.azcli ├── vwan_nva.azcli └── webapp.azcli /README.md: -------------------------------------------------------------------------------- 1 | # Azure CLI script samples 2 | 3 | This repo contains some samples of Azure CLI scripts to create certain Azure scenarios. 4 | 5 | The scripts have been tested using zsh, so they will not work in Windows. Regarding Linux, they will probably work with bash too. 6 | 7 | The testing that has gone in each scenario is limited, so please do not expect them to work flawlessly. 8 | 9 | It is recommended to open the files in Visual Studio Code with the Azure CLI extension installed (so that you get IntelliSense support for Azure CLI commands), and open a terminal (a WSL one if you are working on a Windows machine) to test the different commands step by step. 10 | 11 | To download the scripts you can either clone the whole repo with `git clone https://github.com/erjosito/azcli` or download specific files with `wget "https://raw.githubusercontent.com/erjosito/azcli/master/routeserver_2hubs.azcli" -O .sh`. 12 | -------------------------------------------------------------------------------- /aad_sp.azcli: -------------------------------------------------------------------------------- 1 | # Create/retrieve SP ID and secret for a given purpose from AKV 2 | # If it doesnt exist, it will create it 3 | 4 | # Variables 5 | keyvault_name=erjositoKeyvault 6 | keyvault_rg=keyvaults # Only required if new AKV is to be created 7 | keyvault_loc=westeurope # Only required if new AKV is to be created 8 | purpose=aro 9 | 10 | # Day zero: create Azure Key Vault if required 11 | keyvault_rg_found=$(az keyvault list -o tsv --query "[?name=='$keyvault_name'].resourceGroup") 12 | if [[ -n ${keyvault_rg_found} ]] 13 | then 14 | echo "AKV ${keyvault_name} found in resource group $keyvault_rg_found" 15 | keyvault_rg="$keyvault_rg_found" 16 | else 17 | echo "Creating AKV ${keyvault_name} in RG ${keyvault_rg}..." 18 | az group create -n $keyvault_rg -l $keyvault_loc -o none 19 | az keyvault create -n $keyvault_name -g $keyvault_rg -l $keyvault_loc -o none 20 | user_name=$(az account show --query 'user.name' -o tsv) 21 | echo "Setting policies for user ${user_name}..." 22 | az keyvault set-policy -n $keyvault_name -g $keyvault_rg --upn $user_name -o none \ 23 | --certificate-permissions backup create delete deleteissuers get getissuers import list listissuers managecontacts manageissuers purge recover restore setissuers update \ 24 | --key-permissions backup create decrypt delete encrypt get import list purge recover restore sign unwrapKey update verify wrapKey \ 25 | --secret-permissions backup delete get list purge recover restore set \ 26 | --storage-permissions backup delete deletesas get getsas list listsas purge recover regeneratekey restore set setsas update 27 | fi 28 | 29 | # Get SP details from AKV 30 | keyvault_name=erjositoKeyvault 31 | keyvault_appid_secret_name=$purpose-sp-appid 32 | keyvault_password_secret_name=$purpose-sp-secret 33 | sp_app_id=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_appid_secret_name --query 'value' -o tsv) 34 | sp_app_secret=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_password_secret_name --query 'value' -o tsv) 35 | 36 | # If a secret was found, verify whether SP has expired, and if so, renew it 37 | if [[ -n "$sp_app_id" ]] || [[ -n "$sp_app_secret" ]] 38 | then 39 | sp_end_date=$(az ad app show --id $sp_app_id --query 'passwordCredentials[0].endDate' -o tsv) 40 | sp_end_date=$(date --date="$sp_end_date" +%s) 41 | now=$(date +%s) 42 | if [[ $sp_end_date < $now ]] 43 | then 44 | echo "SP expired, extending one year" 45 | new_password=$(az ad app credential reset --id $sp_app_id --years 1 --query password -o tsv) 46 | az keyvault secret set --vault-name $keyvault_name --name $keyvault_password_secret_name --value $new_password -o none 47 | sp_app_secret=$new_password 48 | else 49 | echo "SP not expired" 50 | fi 51 | fi 52 | 53 | # If either is blank, create new SP with the required name 54 | if [[ -z "$sp_app_id" ]] || [[ -z "$sp_app_secret" ]] 55 | then 56 | # Create new SP 57 | sp_name=$purpose 58 | sp_output=$(az ad sp create-for-rbac --name $sp_name --skip-assignment 2>/dev/null) 59 | sp_app_id=$(echo $sp_output | jq -r '.appId') 60 | sp_app_secret=$(echo $sp_output | jq -r '.password') 61 | az keyvault secret set --vault-name $keyvault_name --name $keyvault_appid_secret_name --value $sp_app_id -o none 62 | az keyvault secret set --vault-name $keyvault_name --name $keyvault_password_secret_name --value $sp_app_secret -o none 63 | # Optionally, assign Azure RBAC roles (example a RG) or AKV policy (example certificate/secret get if the SP should be able to retrieve certs) 64 | # rg_name=my-rg 65 | # rg_id=$(az group show -n $rg_name --query id -o tsv) 66 | # az role assignment create --scope $rg_id --assignee $sp_app_id --role Contributor 67 | # az keyvault set-policy -n $keyvault_name --object-id $sp_app_id --certificate-permissions get --secret-permissions get 68 | fi 69 | 70 | 71 | 72 | # Try to login as service principal 73 | tenant=$(az account show --query tenantId -o tsv) 74 | az login --service-principal -u $sp_app_id -p $sp_app_secret --tenant $tenant -------------------------------------------------------------------------------- /aci-certbot.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # December 2020 4 | # 5 | # Using ACI+certbot to generate certs and put them on AKV 6 | ############################################################################ 7 | 8 | # Variables 9 | akv_name=erjositoKeyvault 10 | aci_name=certbot 11 | image=erjosito/certbot-azcli:1.0 12 | rg=acicertbot 13 | location=westeurope 14 | dns_zone=cloudtrooper.net 15 | dns_hostname=certbot 16 | domain="${dns_hostname}.${dns_zone}" 17 | email_address=jomore@microsoft.com 18 | id_name=certbotid 19 | 20 | # Create RG and user identity 21 | az group create -n $rg -l $location 22 | id_resid=$(az identity show -n $id_name -g $rg --query id -o tsv) 23 | if [[ -z "$id_resid" ]] 24 | then 25 | echo "Creating user identity ${id_name}..." 26 | az identity create -n $id_name -g $rg 27 | id_spid=$(az identity show -n $id_name -g $rg --query principalId -o tsv) 28 | id_resid=$(az identity show -n $id_name -g $rg --query id -o tsv) 29 | az keyvault set-policy -n $akv_name --object-id $id_spid \ 30 | --secret-permissions get list set \ 31 | --certificate-permissions create import list setissuers update \ 32 | --key-permissions create get import sign verify 33 | dns_zone_id=$(az network dns zone list --query "[?name=='$dns_zone'].id" -o tsv) 34 | if [[ -n "$dns_zone_id" ]] 35 | then 36 | echo "DNS zone $dns_zone found, resource ID $dns_zone_id, creating role assignment..." 37 | az role assignment create --scope $dns_zone_id --assignee $id_spid --role "DNS Zone Contributor" 38 | else 39 | echo "DNS zone $dns_zone not found" 40 | fi 41 | else 42 | echo "User identity ${id_name} found, ID is $id_resid" 43 | fi 44 | 45 | # Run container 46 | # az container create -n $aci_name -g $rg -l $location --image $image --assign-identity $id_resid \ 47 | # -e "DOMAIN=$domain" "EMAIL=$email_address" "AKV=$akv_name" "DEBUG=yes" "STAGING=yes" 48 | az container create -n $aci_name -g $rg -l $location --image $image --assign-identity $id_resid \ 49 | -e "DOMAIN=$domain" "EMAIL=$email_address" "AKV=$akv_name" 50 | 51 | # Events/Logs 52 | az container logs -n $aci_name -g $rg 53 | az container show -n $aci_name -g $rg --query 'instanceView.events' 54 | az container show -n $aci_name -g $rg --query 'containers[0].instanceView.events' 55 | 56 | # Cert 57 | az keyvault certificate show -n $(echo $domain | tr -d '.') --vault-name $akv_name 58 | 59 | # Cleanup container 60 | az container delete -n $aci_name -g $rg -y 61 | # Cleanup cert 62 | az keyvault certificate delete -n $(echo $domain | tr -d '.') --vault-name $akv_name 63 | sleep 5 64 | az keyvault certificate purge -n $(echo $domain | tr -d '.') --vault-name $akv_name 65 | -------------------------------------------------------------------------------- /adf.azcli: -------------------------------------------------------------------------------- 1 | ################################### 2 | # Sample code to work with ADF 3 | # networking config 4 | # 5 | # Feb 2023 6 | ################################### 7 | 8 | 9 | # Variables 10 | rg=adf 11 | location=eastus 12 | suffix=$RANDOM 13 | adf_name="adf$suffix" 14 | storage_account1_name="storage1$suffix" 15 | storage_account1_cxfile="/tmp/storage1.json" 16 | storage_account1_link_name=$storage_account1_name 17 | storage_account2_name="storage1$suffix" 18 | storage_account2_link_name=$storage_account2_name 19 | storage_container_name=mycontainer 20 | file1_name=/tmp/file1.txt 21 | blob_name=helloworld.txt 22 | input_dataset_file=/tmp/inputdataset.json 23 | output_dataset_file=/tmp/outputdataset.json 24 | pipeline_file=/tmp/pipeline.json 25 | pipeline_name=BlobCopy 26 | output_file_name=/tmp/outputfile.txt 27 | managed_ir_file=/tmp/managedir.json 28 | managed_ir_name=managedir 29 | 30 | # Create RG 31 | az group create -n $rg -l $location -o none 32 | 33 | # Create storage account 34 | az storage account create -n $storage_account1_name -g $rg -l $location -o none 35 | az storage container create -n $storage_container_name --account-name $storage_account1_name --auth-mode key -o none 36 | storage_account1_key=$(az storage account keys list -n $storage_account1_name --query '[0].value' -o tsv) 37 | echo 'HelloWorld' > $file1_name 38 | az storage blob upload --account-name $storage_account1_name --name input/$blob_name --container-name $storage_container_name --file $file1_name --auth-mode key --account-key $storage_account1_key --overwrite -o none 39 | 40 | # Create ADF 41 | az datafactory create --factory-name $adf_name -g $rg -o none 42 | storage_account1_cx=$(az storage account show-connection-string -g $rg -n $storage_account1_name --key key1 -o tsv) 43 | cat < $storage_account1_cxfile 44 | { 45 | "type": "AzureBlobStorage", 46 | "typeProperties": { 47 | "connectionString": "$storage_account1_cx" 48 | } 49 | } 50 | EOF 51 | az datafactory linked-service create -g $rg --factory-name $adf_name --linked-service-name $storage_account1_link_name --properties @$storage_account1_cxfile -o none 52 | 53 | # Input dataset 54 | cat < $input_dataset_file 55 | { 56 | "linkedServiceName": { 57 | "referenceName": "$storage_account1_link_name", 58 | "type": "LinkedServiceReference" 59 | }, 60 | "annotations": [], 61 | "type": "Binary", 62 | "typeProperties": { 63 | "location": { 64 | "type": "AzureBlobStorageLocation", 65 | "fileName": "$blob_name", 66 | "folderPath": "input", 67 | "container": "$storage_container_name" 68 | } 69 | } 70 | } 71 | EOF 72 | az datafactory dataset create -g $rg --dataset-name InputDataset --factory-name $adf_name --properties @$input_dataset_file -o none 73 | 74 | # Output dataset 75 | cat < $output_dataset_file 76 | { 77 | "linkedServiceName": { 78 | "referenceName": "$storage_account1_link_name", 79 | "type": "LinkedServiceReference" 80 | }, 81 | "annotations": [], 82 | "type": "Binary", 83 | "typeProperties": { 84 | "location": { 85 | "type": "AzureBlobStorageLocation", 86 | "folderPath": "output", 87 | "container": "$storage_container_name" 88 | } 89 | } 90 | } 91 | EOF 92 | az datafactory dataset create -g $rg --dataset-name OutputDataset --factory-name $adf_name --properties @$output_dataset_file -o none 93 | 94 | # Pipeline 95 | cat < $pipeline_file 96 | { 97 | "name": "Adfv2QuickStartPipeline", 98 | "properties": { 99 | "activities": [ 100 | { 101 | "name": "CopyFromBlobToBlob", 102 | "type": "Copy", 103 | "dependsOn": [], 104 | "policy": { 105 | "timeout": "7.00:00:00", 106 | "retry": 0, 107 | "retryIntervalInSeconds": 30, 108 | "secureOutput": false, 109 | "secureInput": false 110 | }, 111 | "userProperties": [], 112 | "typeProperties": { 113 | "source": { 114 | "type": "BinarySource", 115 | "storeSettings": { 116 | "type": "AzureBlobStorageReadSettings", 117 | "recursive": true 118 | } 119 | }, 120 | "sink": { 121 | "type": "BinarySink", 122 | "storeSettings": { 123 | "type": "AzureBlobStorageWriteSettings" 124 | } 125 | }, 126 | "enableStaging": false 127 | }, 128 | "inputs": [ 129 | { 130 | "referenceName": "InputDataset", 131 | "type": "DatasetReference" 132 | } 133 | ], 134 | "outputs": [ 135 | { 136 | "referenceName": "OutputDataset", 137 | "type": "DatasetReference" 138 | } 139 | ] 140 | } 141 | ], 142 | "annotations": [] 143 | } 144 | } 145 | EOF 146 | az datafactory pipeline create -g $rg --factory-name $adf_name --name $pipeline_name --pipeline @$pipeline_file -o none 147 | 148 | # Integration runtime 149 | cat < $managed_ir_file 150 | { 151 | "type": "Managed", 152 | "typeProperties": { 153 | "computeProperties": { 154 | "location": "$location", 155 | "dataFlowProperties": { 156 | "computeType": "General", 157 | "coreCount": 8, 158 | "timeToLive": 10 159 | } 160 | } 161 | }, 162 | "managedVirtualNetwork": { 163 | "type": "ManagedVirtualNetworkReference", 164 | "referenceName": "default" 165 | } 166 | } 167 | EOF 168 | 169 | az datafactory integration-runtime managed create -g $rg --factory-name $adf_name --name $managed_ir_name --compute-properties @$managed_ir_file -o none 170 | 171 | # Run pipeline 172 | run_id=$(az datafactory pipeline create-run -g $rg --factory-name $adf_name --name $pipeline_name --query runId -o tsv) 173 | az datafactory pipeline-run show -g $rg --factory-name $adf_name --run-id $run_id 174 | # az storage blob show --account-name $storage_account1_name --account-key $storage_account1_key --container-name $storage_container_name --name output/$blob_name 175 | az storage blob download --account-name $storage_account1_name --account-key $storage_account1_key --container-name $storage_container_name --name output/$blob_name 176 | 177 | ############### 178 | # Diagnostics # 179 | ############### 180 | 181 | az datafactory list -g $rg -o table 182 | az datafactory dataset list -g $rg --factory-name $adf_name -o table 183 | az datafactory pipeline list -g $rg --factory-name $adf_name -o table 184 | az datafactory integration-runtime list -g $rg --factory-name $adf_name -o table 185 | az datafactory integration-runtime show -n $managed_ir_name -g $rg --factory-name $adf_name 186 | az datafactory managed-virtual-network list -g $rg --factory-name $adf_name -o table 187 | managed_vnet_name=$(az datafactory managed-virtual-network list -g $rg --factory-name $adf_name --query '[0].name' -o tsv) 188 | az datafactory managed-private-endpoint list -g $rg --factory-name $adf_name --managed-virtual-network-name $managed_vnet_name -o table 189 | az datafactory managed-private-endpoint show -g $rg --factory-name $adf_name --managed-virtual-network-name $managed_vnet_name -n AzureFunction544 190 | az storage blog list --account-name $storage_account1_name --account-key $storage_account1_key --container-name $storage_container_name -o table -------------------------------------------------------------------------------- /afd.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # August 2024 4 | # 5 | # AFD script 6 | # * The AFD commands use a frontdoor CLI extension 7 | # * It uses ACI to simulate an app in 2 locations 8 | # * Probe interval verification via ACI logs 9 | # * The lab uses AFD SSL offload with default certs 10 | # 11 | ############################################################################ 12 | 13 | # Variables 14 | rg=multiregiontest 15 | location1=westcentralus # blue 16 | location2=germanywestcentral # purple 17 | unique_id=$RANDOM 18 | app_name=app${unique_id} 19 | logws_name=log${unique_id} 20 | 21 | # Resource group 22 | az group create -n $rg -l $location1 23 | 24 | # Create Log analytics workspace 25 | az monitor log-analytics workspace create -n $logws_name -g $rg 26 | logws_id=$(az resource list -g $rg -n $logws_name --query '[].id' -o tsv) 27 | logws_customerid=$(az monitor log-analytics workspace show -n $logws_name -g $rg --query customerId -o tsv) 28 | 29 | # Apps will be simulated with ACI 30 | az container create -n ${app_name}-${location1} --image gcr.io/kuar-demo/kuard-amd64:blue -l $location1 -g $rg \ 31 | --ip-address public --dns-name-label ${app_name}-${location1} --port 8080 \ 32 | --cpu 0.5 --memory 0.5 33 | az container create -n ${app_name}-${location2} --image gcr.io/kuar-demo/kuard-amd64:purple -l $location2 -g $rg \ 34 | --ip-address public --dns-name-label ${app_name}-${location2} --port 8080 \ 35 | --cpu 0.5 --memory 0.5 36 | app1_fqdn=$(az container show -n ${app_name}-${location1} -g $rg --query 'ipAddress.fqdn' -o tsv) 37 | app2_fqdn=$(az container show -n ${app_name}-${location2} -g $rg --query 'ipAddress.fqdn' -o tsv) 38 | echo "App1 available in region $location1 under http://${app1_fqdn}:8080" 39 | echo "App2 available in region $location2 under http://${app2_fqdn}:8080" 40 | 41 | # Traffic Manager - Function to delete all endpoints 42 | delete_atm_endpoints() { 43 | az network traffic-manager endpoint delete -n ${location1} --profile-name $app_name -g $rg --type externalEndpoints 44 | az network traffic-manager endpoint delete -n ${location2} --profile-name $app_name -g $rg --type externalEndpoints 45 | } 46 | 47 | 48 | # Verification with whatsmydns.com 49 | echo "You can check in https://www.whatsmydns.net/#A/${atm_fqdn}" 50 | app1_ip=$(az container show -n ${app_name}-${location1} -g $rg --query 'ipAddress.ip' -o tsv) 51 | app2_ip=$(az container show -n ${app_name}-${location2} -g $rg --query 'ipAddress.ip' -o tsv) 52 | echo "$location1 IP: $app1_ip - $location2 IP: $app2_ip" 53 | 54 | # AFD 55 | az network front-door create -n $app_name -g $rg --backend-address $app1_fqdn 56 | # Backend 57 | backend_pool_name=$(az network front-door backend-pool list -f $app_name -g $rg --query '[0].name' -o tsv) 58 | az network front-door backend-pool backend add --pool-name $backend_pool_name -f $app_name -g $rg --address $app2_fqdn --http-port 8080 \ 59 | --priority 1 --weight 50 60 | az network front-door backend-pool backend remove --pool-name $backend_pool_name -f $app_name -g $rg --index 1 # Remove backend so that we can re-add with the right HttpPort 61 | az network front-door backend-pool backend add --pool-name $backend_pool_name -f $app_name -g $rg --address $app1_fqdn --http-port 8080 \ 62 | --priority 1 --weight 50 63 | az network front-door backend-pool backend list --pool-name $backend_pool_name -f $app_name -g $rg -o table 64 | # Probe 65 | probe_name=$(az network front-door probe list -f $app_name -g $rg --query '[0].name' -o tsv) 66 | az network front-door probe update -n $probe_name -f $app_name -g $rg --protocol Http --path /ready --interval 30 # Change from Https to Http 67 | # Routing rule 68 | routing_rule_name=$(az network front-door routing-rule list -f $app_name -g $rg --query '[0].name' -o tsv) 69 | az network front-door routing-rule update -n $routing_rule_name -f $app_name -g $rg --accepted-protocols Http Https --forwarding-protocol HttpOnly # Change from Https to Http 70 | # Frontend FQDN 71 | afd_frontend_name=$(az network front-door frontend-endpoint list -f $app_name -g $rg --query '[].name' -o tsv) 72 | afd_fqdn=$(az network front-door frontend-endpoint show -n $afd_frontend_name -f $app_name -g $rg --query hostName -o tsv) 73 | echo "Point your browser to http://${afd_fqdn} or https://${afd_fqdn}" 74 | 75 | # Configure AFD logging to Azure Monitor 76 | afd_id=$(az network front-door show -n $app_name -g $rg --query id -o tsv) 77 | az monitor diagnostic-settings create -n mydiag --resource $afd_id --workspace $logws_id \ 78 | --metrics '[{"category": "AllMetrics", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false }, "timeGrain": null}]' \ 79 | --logs '[{"category": "FrontdoorAccessLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}, 80 | {"category": "FrontdoorWebApplicationFirewallLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}]' 81 | 82 | # Custom domain (Assumes you have a public DNS zone in Azure) 83 | dns_zone_name=cloudtrooper.net 84 | dns_name=kuard-afd 85 | dns_zone_rg=$(az network dns zone list -o tsv --query "[?name=='$dns_zone_name'].resourceGroup") 86 | dns_fqdn=${dns_name}.${dns_zone_name} 87 | # az network dns record-set cname set-record -z $dns_zone_name -g $dns_zone_rg -n afdverify.${dns_name} -c afdverify.${afd_fqdn} (recommended for prod, not required for tests) 88 | az network dns record-set cname set-record -z $dns_zone_name -g $dns_zone_rg -n ${dns_name} -c ${afd_fqdn} 89 | az network front-door frontend-endpoint create -n $afd_frontend_name -f $app_name -g $rg --host-name $dns_fqdn # No update command, using "create" to replace 90 | afd_fqdn=$(az network front-door frontend-endpoint show -n $afd_frontend_name -f $app_name -g $rg --query hostName -o tsv) 91 | fqdn_validation_result=$(az network front-door check-custom-domain -n $app_name -g $rg --host-name $dns_fqdn --query customDomainValidated -o tsv) 92 | if [[ "$fqdn_validation_result" == "true" ]] 93 | then 94 | echo "FQDN validated OK" 95 | else 96 | echo "FQDN validation failed" 97 | fi 98 | echo "Point your browser to http://${afd_fqdn} or https://${afd_fqdn}" 99 | 100 | # Get existing certificate from AKV: create SP and store the app id and secret as AKV secrets. You need to do this only **once** 101 | # You need to have AAD Global Admin privilege for this operation to succeed 102 | afd_app_id=ad0e1c7e-6d38-4ba4-9efd-0bc77ba9f037 103 | # Check if SP for the app already exists 104 | afd_sp_oid=$(az ad sp show --id $afd_app_id --query objectId -o tsv 2>/dev/null) 105 | if [[ -z ${afd_sp_oid} ]] 106 | then 107 | echo "Creating SP..." 108 | afd_sp_output=$(az ad sp create --id $afd_app_id) 109 | if [[ -z "$afd_sp_output" ]] 110 | then 111 | echo "It looks like you dont have global admin permission in your AAD tenant" 112 | else 113 | afd_sp_oid=$(echo $afd_sp_output | jq -r '.objectId') 114 | fi 115 | else 116 | echo "SP already exists (${afd_sp_oid})" 117 | fi 118 | 119 | # Set permissions for SP on AKV 120 | keyvault_name=cloudtrooper 121 | az keyvault set-policy -n $keyvault_name --object-id $afd_sp_oid --certificate-permissions get --secret-permissions get 122 | 123 | # Get certificate from AKV (assuming SP id and secret are in the keyvault): insert certificate in AFD 124 | keyvault_cert_name=cloudtroopernet 125 | keyvault_id=$(az keyvault list -o tsv --query "[?name=='$keyvault_name'].id") 126 | cert_sid=$(az keyvault certificate show --vault-name $keyvault_name -n $keyvault_cert_name --query sid -o tsv) 127 | cert_version=$(echo $cert_sid | cut -d/ -f 6) 128 | az network front-door frontend-endpoint enable-https -n $afd_frontend_name -f $app_name -g $rg \ 129 | --certificate-source AzureKeyVault --secret-name $keyvault_cert_name --secret-version $cert_version --vault-id $keyvault_id \ 130 | --minimum-tls-version 1.0 131 | echo "Certificate provisioning started, this could take up to 20 minutes" 132 | while [[ $(az network front-door frontend-endpoint show -n $afd_frontend_name -f $app_name -g $rg --query customHttpsProvisioningSubstate -o tsv) != "CertificateDeployed" ]] 133 | do 134 | echo "Waiting for certificate to be deployed..." 135 | sleep 15 136 | done 137 | 138 | # Check logs 139 | categories_query='AzureDiagnostics | summarize count() by Category' 140 | test_query='search "'$app_name'" | take 5' 141 | atm_query='AzureDiagnostics 142 | | where TimeGenerated >= ago(3m) 143 | | where Category == "ProbeHealthStatusEvents" 144 | | project TimeGenerated, EndpointName_s, Status_s' 145 | afd_query='AzureDiagnostics 146 | | where TimeGenerated >= ago(10m) 147 | | where Category == "FrontdoorAccessLog" 148 | | project TimeGenerated, clientIp_s, httpMethod_s, requestUri_s' 149 | az monitor log-analytics query -w $logws_customerid --analytics-query $categories_query -o tsv 150 | az monitor log-analytics query -w $logws_customerid --analytics-query $atm_query -o tsv 151 | az monitor log-analytics query -w $logws_customerid --analytics-query $afd_query -o tsv 152 | 153 | 154 | ############### 155 | # Diagnostics # 156 | ############### 157 | 158 | # ACI 159 | az container list -g $rg -o table 160 | # Note: this lab configures AFD probes to /ready, and ATM probes to /healthy 161 | az container logs -n ${app_name}-${location1} -g $rg -o table | grep healthy | tail -n 10 # location1, ATM 162 | az container logs -n ${app_name}-${location1} -g $rg -o table | grep ready | tail -n 10 # location1, AFD 163 | az container logs -n ${app_name}-${location2} -g $rg -o table | grep healthy | tail -n 10 # location2, ATM 164 | az container logs -n ${app_name}-${location2} -g $rg -o table | grep ready | tail -n 10 # location2, AFD 165 | 166 | # ATM 167 | az network traffic-manager profile list -g $rg -o table 168 | az network traffic-manager profile show -n $app_name -g $rg --query monitorConfig 169 | az network traffic-manager endpoint list --profile-name $app_name -g $rg -o table 170 | 171 | # AFD 172 | az network front-door list -g $rg -o table 173 | az network front-door frontend-endpoint list -f $app_name -g $rg -o table 174 | az network front-door backend-pool list -f $app_name -g $rg -o table 175 | az network front-door backend-pool backend list --pool-name $backend_pool_name -f $app_name -g $rg -o table 176 | az network front-door load-balancing list -f $app_name -g $rg -o table 177 | az network front-door probe list -f $app_name -g $rg -o table 178 | az network front-door routing-rule list -f $app_name -g $rg -o table 179 | az network front-door waf-policy list -f $app_name -g $rg -o table 180 | az network front-door rules-engine list -f $app_name -g $rg -o table 181 | 182 | 183 | ############### 184 | # Cleanup # 185 | ############### 186 | 187 | # Delete selective components 188 | az container delete -n ${app_name}-${location1} -g $rg -y 189 | az container delete -n ${app_name}-${location2} -g $rg -y 190 | az network traffic-manager profile delete -n $app_name -g $rg 191 | az network front-door delete -n $app_name -g $rg 192 | 193 | # Delete everything 194 | az group delete -n $rg -y --no-wait -------------------------------------------------------------------------------- /aib.azcli: -------------------------------------------------------------------------------- 1 | 2 | # Registration 3 | 4 | # Register feature 5 | az feature register --namespace Microsoft.VirtualMachineImages --name VirtualMachineTemplatePreview 6 | # Wait until the feature is registered 7 | az feature show --namespace Microsoft.VirtualMachineImages --name VirtualMachineTemplatePreview --query properties.state -o tsv 8 | # check you are registered for the providers 9 | az provider show -n Microsoft.VirtualMachineImages | grep registrationState 10 | az provider show -n Microsoft.Storage | grep registrationState 11 | az provider show -n Microsoft.Compute | grep registrationState 12 | az provider show -n Microsoft.KeyVault | grep registrationState 13 | 14 | # Initialization 15 | rg=aibrg 16 | location=westeurope # Check https://raw.githubusercontent.com/danielsollondon/azvmimagebuilder/master/solutions/12_Creating_AIB_Security_Roles/aibRoleImageCreation.json 17 | image_name=aib-custom-w2k 18 | run_output_name=aibCustWinManImg01ro # image distribution metadata reference name 19 | user=jose 20 | keyvault=erjositoKeyvault 21 | password=$(az keyvault secret show --vault-name $keyvault -n defaultPassword --query value -o tsv) 22 | subscription_id=$(az account show --query id -o tsv) 23 | 24 | # RG 25 | az group create -n $rg -l $location 26 | rg_id=$(az group show -n $rg --query id -o tsv) 27 | 28 | # Custom role: 29 | filename=/tmp/aibRoleImageCreation.json 30 | curl https://raw.githubusercontent.com/danielsollondon/azvmimagebuilder/master/solutions/12_Creating_AIB_Security_Roles/aibRoleImageCreation.json -o $filename 31 | sed -i -e "s//$subscription_id/g" $filename 32 | sed -i -e "s//$rg/g" $filename 33 | az role definition create --role-definition $filename 34 | az role definition list -n "Azure Image Builder Service Image Creation Role" 35 | 36 | ##################################### 37 | # Deploy image template as resource # 38 | ##################################### 39 | 40 | aib_app_id=cf32a0cc-373c-47c9-9156-0db11f6a6dfc 41 | # role_name="Azure Image Builder Service Image Creation Role" 42 | role_name=Contributor 43 | # Note that the feature needs to be registered for this to work 44 | az role assignment create --assignee $aib_app_id --role $role_name --scope $rg_id 45 | az role assignment list --scope $rg_id 46 | 47 | # Role assignment (pwsh) 48 | $aib_app_id="cf32a0cc-373c-47c9-9156-0db11f6a6dfc" 49 | $role_name="Contributor" 50 | $rg="aib" 51 | $location="westeurope" 52 | # Note that the feature needs to be registered for this to work 53 | az group create -n $rg -l $location 54 | $rg_id=$(az group show -n $rg --query id -o tsv) 55 | #az role assignment create --assignee $aib_app_id --role $role_name --scope $rg_id 56 | New-AzRoleAssignment -ObjectId ef511139-6170-438e-a6e1-763dc31bdf74 -Scope "/subscriptions/$subscription_id/resourceGroups/$rg" -RoleDefinitionName $role_name 57 | 58 | # Download hello world example 59 | template_url=https://raw.githubusercontent.com/danielsollondon/azvmimagebuilder/master/quickquickstarts/0_Creating_a_Custom_Windows_Managed_Image/helloImageTemplateWin.json 60 | template_file=/tmp/helloImageTemplateWin.json 61 | more $template_file 62 | curl -s $template_url -o $template_file 63 | sed -i -e "s//$subscription_id/g" $template_file 64 | sed -i -e "s//$rg/g" $template_file 65 | sed -i -e "s//$location/g" $template_file 66 | sed -i -e "s//$image_name/g" $template_file 67 | sed -i -e "s//$run_output_name/g" $template_file 68 | 69 | # Create image template 70 | image_template_name=helloImageTemplateWin02 71 | az resource create \ 72 | --resource-group $rg \ 73 | --properties @$template_file \ 74 | --is-full-object \ 75 | --resource-type Microsoft.VirtualMachineImages/imageTemplates \ 76 | --name $image_template_name 77 | # wait approx 1-3mins, depending on external links 78 | az resource list -g $rg -o table 79 | 80 | # Start the image build out of the template 81 | az resource invoke-action \ 82 | --resource-group $rg \ 83 | --resource-type Microsoft.VirtualMachineImages/imageTemplates \ 84 | --name $image_template_name \ 85 | --action Run 86 | 87 | ################################## 88 | # Deploy image template from ARM # 89 | ################################## 90 | 91 | version=$(az vm image list -l $location -p MicrosoftWindowsDesktop -f windows-10 -s 19h1-evd --all --query '[0].version' -o tsv) 92 | arm_template_url=https://raw.githubusercontent.com/TomHickling/WVD-Images/master/1.AzureImageBuilder/DeployAnImage.json 93 | # az deployment group create -n newimagetemplate -g $rg --template-uri $arm_template_url --parameters "{\"version\": {\"value\": \"$version\"}}" 94 | az deployment group create -n newimagetemplate -g $rg --template-uri $arm_template_url --parameters version=$version 95 | 96 | #################### 97 | # az image builder # 98 | #################### 99 | 100 | # Initialize variables 101 | publisher=MicrosoftWindowsDesktop 102 | offer=windows-10 103 | sku=19h1-evd 104 | version=$(az vm image list -l $location -p $publisher -f $offer -s $sku --all --query '[0].version' -o tsv) 105 | image_source="$publisher:$offer:$sku:$version" 106 | image_template_name=myw10template 107 | script_url=https://raw.githubusercontent.com/TomHickling/WVD-Images/master/1.AzureImageBuilder/SetupGoldenImage.ps1 108 | # Create image template with image as output 109 | az image builder create -n $image_template_name -g $rg --image-source $image_source \ 110 | --scripts $script_url --managed-image-destinations myimage=westeurope 111 | # Create image template with SIG as output (NOT working, claims that 'Location westeurope,westus is not a valid subscription location') 112 | az image builder create -n $image_template_name -g $rg --image-source $image_source \ 113 | --scripts $script_url --managed-image-destinations my_shared_gallery/my_image_def=westeurope,westus 114 | 115 | # List/show 116 | az image builder list -g $rg --query '[].{Name:name,ProvisioningState:provisioningState,RunState:runState}' -o table 117 | az image builder show -n $image_template_name -g $rg --query 'lastRunStatus' 118 | 119 | # Run 120 | az image builder run -n $image_template_name -g $rg --no-wait 121 | 122 | # See completed runs 123 | az image builder show-runs -n $image_template_name -g $rg -o table 124 | 125 | # Delete 126 | image_template_name=AIBzq6mrjjnzh6na 127 | az image builder delete -g $rg -n $image_template_name 128 | 129 | 130 | ############### 131 | # Image / SIG # 132 | ############### 133 | 134 | az image list -g $rg -o table 135 | az image show -g $rg -n $imagename --query id 136 | az image delete -g $rg -n $imagename 137 | 138 | az sig list -g $rg -o table 139 | sig_name=my_shared_gallery 140 | az sig image-definition list -g $rg --gallery-name $sig_name -o table 141 | def_name=my_image_def 142 | az sig image-version list -g $rg --gallery-name $sig_name -i $def_name -o table 143 | 144 | 145 | ########### 146 | # Cleanup # 147 | ########### 148 | az group delete -n $rg -y --no-wait 149 | -------------------------------------------------------------------------------- /aks_baseline.azcli: -------------------------------------------------------------------------------- 1 | # Steps documented in https://github.com/mspnp/aks-secure-baseline 2 | 3 | export DOMAIN_NAME="contoso.com" 4 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -out appgw.crt -keyout appgw.key -subj "/CN=bicycle.${DOMAIN_NAME}/O=Contoso Bicycle" 5 | openssl pkcs12 -export -out appgw.pfx -in appgw.crt -inkey appgw.key -passout pass: 6 | export APP_GATEWAY_LISTENER_CERTIFICATE_AKS_BASELINE=$(cat appgw.pfx | base64 | tr -d '\n') 7 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -out traefik-ingress-internal-aks-ingress-tls.crt -keyout traefik-ingress-internal-aks-ingress-tls.key -subj "/CN=*.aks-ingress.${DOMAIN_NAME}/O=Contoso Aks Ingress" 8 | export AKS_INGRESS_CONTROLLER_CERTIFICATE_BASE64_AKS_BASELINE=$(cat traefik-ingress-internal-aks-ingress-tls.crt | base64 | tr -d '\n') 9 | -------------------------------------------------------------------------------- /akv.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # November 2020 4 | # 5 | # Examples for adding certificates to AKV 6 | ############################################################################ 7 | 8 | # Variables 9 | akv_name=erjositoKeyvault 10 | 11 | #################################### 12 | # Import cert (.pem/.key) into AKV # 13 | #################################### 14 | 15 | # Variables 16 | pem_file="./yourcert.pem or .crt" 17 | key_file="./yourkey.key" 18 | key_password="your_key_password" 19 | cert_name=akv_cert_name 20 | 21 | # Combine .pem and .key in one pfx file (pkcs#12) 22 | pfx_file=".${pem_file}.pfx" 23 | openssl pkcs12 -export -in $pem_file -inkey $key_file -out $pfx_file -passin pass:$key_password -passout pass:$key_password 24 | openssl pkcs12 -export -in $pem_file -inkey $key_file -out $pfx_file -passout pass:$key_password # No password for private key 25 | 26 | # Add certificate 27 | az keyvault certificate import --vault-name $akv_name -n $cert_name \ 28 | -f $pfx_file --password $key_password 29 | 30 | # Verify 31 | az keyvault certificate list --vault-name $akv_name -o table 32 | az keyvault certificate show -n $cert_name --vault-name $akv_name -o table 33 | 34 | ##################### 35 | # Self-signed cert # 36 | ##################### 37 | 38 | # Variables 39 | cert_name=myselfsignedcert 40 | 41 | # Generate default cert policy 42 | default_policy="$(az keyvault certificate get-default-policy)" 43 | # Change cert subject 44 | default_policy=$(echo $default_policy | jq '.x509CertificateProperties.subject = "CN=selfsigned.cloudtrooper.net"') 45 | # Generate self-signed cert 46 | az keyvault certificate create --vault-name $akv_name -n $cert_name -p $default_policy 47 | 48 | ############################## 49 | # Generate cert with certbot # 50 | ############################## 51 | 52 | # Install certbot and/or azure plugin 53 | # For example: https://github.com/dlapiduz/certbot-azure 54 | 55 | # Get SP with permission to Azure DNS zone 56 | purpose=certbot 57 | dns_zone="cloudtrooper.net" 58 | keyvault_appid_secret_name=$purpose-sp-appid 59 | keyvault_password_secret_name=$purpose-sp-secret 60 | sp_app_id=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_appid_secret_name --query 'value' -o tsv) 61 | sp_app_secret=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_password_secret_name --query 'value' -o tsv) 62 | # If either is blank, create new SP with the required name 63 | if [[ -z "$sp_app_id" ]] || [[ -z "$sp_app_secret" ]] 64 | then 65 | # Create new SP 66 | sp_name=$purpose 67 | sp_output=$(az ad sp create-for-rbac --name $sp_name --skip-assignment 2>/dev/null) 68 | sp_app_id=$(echo $sp_output | jq -r '.appId') 69 | sp_app_secret=$(echo $sp_output | jq -r '.password') 70 | az keyvault secret set --vault-name $keyvault_name --name $keyvault_appid_secret_name --value $sp_app_id 71 | az keyvault secret set --vault-name $keyvault_name --name $keyvault_password_secret_name --value $sp_app_secret 72 | # Assign Azure RBAC roles (example a RG) or AKV policy (example certificate/secret get if the SP should be able to retrieve certs) 73 | dns_zone_id=$(az network dns zone list --query "[?name=='$dns_zone'].id" -o tsv) 74 | if [[ -n "$dns_zone_id" ]] 75 | then 76 | echo "DNS zone $dns_zone found, resource ID $dns_zone_id, creating role assignment..." 77 | az role assignment create --scope $dns_zone_id --assignee $sp_app_id --role "DNS Zone Contributor" 78 | dns_zone_rg=$(az network dns zone list --query "[?name=='$dns_zone'].resourceGroup" -o tsv) 79 | else 80 | echo "DNS zone $dns_zone not found" 81 | fi 82 | fi 83 | 84 | # Create auth and cleanup scripts for certbot 85 | auth_script="/tmp/certbot_auth.sh" 86 | cat << 'EOF' > $auth_script 87 | #!/bin/bash 88 | echo "Receiving values from certbot:" 89 | echo " - CERTBOT_VALIDATION: $CERTBOT_VALIDATION" 90 | echo " - CERTBOT_DOMAIN: $CERTBOT_DOMAIN" 91 | DNS_ZONE_NAME=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)') 92 | DNS_ZONE_RG=$(az network dns zone list --query "[?name=='$DNS_ZONE_NAME'].resourceGroup" -o tsv) 93 | echo " - DNS ZONE: $DNS_ZONE_NAME" 94 | echo " - DNS RG: $DNS_ZONE_RG" 95 | suffix=".${DNS_ZONE_NAME}" 96 | record_name=_acme-challenge.${CERTBOT_DOMAIN%"$suffix"} 97 | echo "Creating record $record_name in DNS zone $DNS_ZONE_NAME..." 98 | az network dns record-set txt create -n "$record_name" -z "$DNS_ZONE_NAME" -g $DNS_ZONE_RG --ttl 30 99 | az network dns record-set txt add-record -n "$record_name" -z "$DNS_ZONE_NAME" -g "$DNS_ZONE_RG" -v "$CERTBOT_VALIDATION" 100 | EOF 101 | chmod +x $auth_script 102 | cleanup_script="/tmp/certbot_cleanup.sh" 103 | cat << 'EOF' > $cleanup_script 104 | #!/bin/bash 105 | echo "Receiving values from certbot:" 106 | echo " - CERTBOT_VALIDATION: $CERTBOT_VALIDATION" 107 | echo " - CERTBOT_DOMAIN: $CERTBOT_DOMAIN" 108 | DNS_ZONE_NAME=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)') 109 | DNS_ZONE_RG=$(az network dns zone list --query "[?name=='$DNS_ZONE_NAME'].resourceGroup" -o tsv) 110 | echo " - DNS ZONE: $DNS_ZONE_NAME" 111 | echo " - DNS RG: $DNS_ZONE_RG" 112 | suffix=".${DNS_ZONE_NAME}" 113 | record_name=_acme-challenge.${CERTBOT_DOMAIN%"$suffix"} 114 | echo "Deleting record $record_name from DNS zone $DNS_ZONE_NAME..." 115 | az network dns record-set txt delete -n "$record_name" -z "$DNS_ZONE_NAME" -g "$DNS_ZONE_RG" 116 | EOF 117 | chmod +x $cleanup_script 118 | 119 | # Generate cert with certbot 120 | email_address="jomore@microsoft.com" 121 | sudo certbot certonly -n -d certbot.cloudtrooper.net --manual -m $email_address --preferred-challenges=dns \ 122 | --manual-public-ip-logging-ok \ 123 | --manual-auth-hook $auth_script --manual-cleanup-hook $cleanup_script 124 | 125 | ##################### 126 | # Cert with Web App # 127 | ##################### 128 | 129 | # This script creates a webapp and assigns a pre-existing cert in AKV 130 | 131 | # Variables 132 | rg=certtest 133 | location=westeurope 134 | svcplan_name=webappplan 135 | app_name=web$RANDOM 136 | image=gcr.io/kuar-demo/kuard-amd64:blue 137 | tcp_port=8080 138 | dns_zone_name=cloudtrooper.net 139 | app_dns_name=$app_name 140 | domain="${app_dns_name}.${dns_zone}" 141 | email_address=jomore@microsoft.com 142 | id_name=certbotid 143 | id_rg=acicertbot 144 | 145 | # Create cert with ACI 146 | id_resid=$(az identity show -n $id_name -g $id_rg --query id -o tsv) 147 | az container create -n certbot -g $rg -l $location --image erjosito/certbot-azcli:1.0 --assign-identity $id_resid \ 148 | -e "DOMAIN=$domain" "EMAIL=$email_address" "AKV=$akv_name" 149 | cert_name=$(echo $domain | tr -d '.') # the container will create a cert with the domain name removing the dots (.) 150 | 151 | # Create Web App 152 | az group create -n $rg -l $location 153 | az appservice plan create -n $svcplan_name -g $rg --sku B1 --is-linux 154 | az webapp create -n $app_name -g $rg -p $svcplan_name --deployment-container-image-name $image 155 | az webapp config appsettings set -n $app_name -g $rg --settings "WEBSITES_PORT=${tcp_port}" 156 | az keyvault set-policy -n $akv_name --spn abfa0a7c-a6b6-4736-8310-5855508787cd \ 157 | --secret-permissions get \ 158 | --key-permissions get \ 159 | --certificate-permissions get 160 | az webapp config ssl import -n $app_name -g $rg --key-vault $akv_name --key-vault-certificate-name $cert_name 161 | cert_thumbprint=$(az webapp config ssl list -g $rg --query '[0].thumbprint' -o tsv) 162 | az webapp restart -n $app_name -g $rg 163 | app_hostname=$(az webapp show -n $app_name -g $rg --query defaultHostName -o tsv) 164 | # Update DNS name 165 | dns_zone_rg=$(az network dns zone list --query "[?name=='$dns_zone_name'].resourceGroup" -o tsv) 166 | echo "Adding CNAME record ${app_dns_name}.${dns_zone_name} for Webapp $app_hostname" 167 | az network dns record-set cname set-record -z $dns_zone_name -g $dns_zone_rg -n $app_dns_name -c $app_hostname 168 | app_fqdn="${app_dns_name}.${dns_zone_name}" 169 | # Add custom domain to web app 170 | az webapp config hostname add --webapp-name $app_name -g $rg --hostname $app_fqdn 171 | az webapp config ssl bind -n $app_name -g $rg --certificate-thumbprint $cert_thumbprint --ssl-type SNI 172 | az webapp update -n $app_name -g $rg --https-only true 173 | # Test 174 | echo "Visit with your browser the URL https://${app_fqdn}" 175 | 176 | ########### 177 | # DANGER! # 178 | ########### 179 | 180 | # Cleanup 181 | az group delete -n $rg -y --no-wait 182 | az keyvault certificate delete --vault-name $akv_name -n $cert_name 183 | sleep 5 184 | az keyvault certificate purge --vault-name $akv_name -n $cert_name 185 | az network dns record-set cname delete -z $dns_zone_name -g $dns_zone_rg -n $app_dns_name -y 186 | -------------------------------------------------------------------------------- /allsubnet_private.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # Get the vnet list 3 | vnet_list=$(az network vnet list --query "[].id" -o tsv) 4 | echo "$(echo $vnet_list | wc -l) vnets found" 5 | # Process line by line 6 | echo "$vnet_list" | while IFS= read -r vnet; do 7 | echo "Processing VNet $vnet..." 8 | # Get the vnet name 9 | vnet_name=$(az network vnet show --id $vnet --query "name" -o tsv) 10 | # Get the resource group name 11 | rg_name=$(az network vnet show --id $vnet --query "resourceGroup" -o tsv) 12 | # Get the subnet list 13 | subnet_list=$(az network vnet subnet list --vnet-name $vnet_name -g $rg_name --query "[].name" -o tsv) 14 | echo "$(echo $subnet_list | wc -l) subnets found" 15 | # Process line by line 16 | echo "$subnet_list" | while IFS= read -r subnet; do 17 | echo " - Processing subnet $subnet in VNet $vnet_name..." 18 | # Disable outbound access 19 | echo " * Disabling outbound access in subnet $subnet..." 20 | az network vnet subnet update -n $subnet --vnet-name $vnet_name -g $rg_name --default-outbound false -o none 21 | # Get current status 22 | featureStatus=$(az network vnet subnet show -n $subnet --vnet-name $vnet_name -g $rg_name --query defaultOutboundAccess -o tsv) 23 | echo " * Outbound access status in subnet $subnet is now $featureStatus" 24 | # if [[ "$featureStatus" -eq "false" ]]; then 25 | # echo " - Outbound access already disabled in subnet $subnet, status is $featureStatus" 26 | # else 27 | # echo " - Disabling outbound access in subnet $subnet..." 28 | # az network vnet subnet update -n $subnet --vnet-name $vnet_name -g $rg_name --default-outbound false -o none 29 | # fi 30 | done 31 | done -------------------------------------------------------------------------------- /ampls.azcli: -------------------------------------------------------------------------------- 1 | ############################################# 2 | # To test Azure Monitor Private Link Scopes 3 | # 4 | # Jose Moreno, March 2022 5 | ############################################# 6 | 7 | # Variables 8 | rg=amplstest 9 | location=westeurope 10 | logws_name=amplstest$RANDOM 11 | num_spokes=4 12 | vm_size=Standard_B1s 13 | 14 | # Create Hub with DNS server 15 | az group create -n $rg -l $location -o none 16 | # - Enabling OS IP fwding everywhere, even if it is not really needed 17 | cloudinit_file=/tmp/cloudinit.txt 18 | cat < $cloudinit_file 19 | #cloud-config 20 | package_upgrade: true 21 | packages: 22 | - dnsmasq 23 | EOF 24 | az vm create -n hub -g $rg -l $location --image ubuntuLTS --generate-ssh-keys --size $vm_size --public-ip-address hub-pip --public-ip-sku Standard \ 25 | --vnet-name hub --vnet-address-prefix 192.168.0.0/24 --subnet vm --subnet-address-prefix 192.168.0.0/26 --custom-data $cloudinit_file -o none --no-wait 26 | # Create spokes 27 | for spoke_id in $(seq 1 ${num_spokes}) 28 | do 29 | az vm create -n "spoke${spoke_id}" -g $rg -l $location --image ubuntuLTS --generate-ssh-keys --size $vm_size --public-ip-address "${spoke_id}-pip" --public-ip-sku Standard \ 30 | --vnet-name "spoke${spoke_id}" --vnet-address-prefix "192.168.${spoke_id}.0/24" --subnet vm --subnet-address-prefix "192.168.${spoke_id}.0/26" -o none --no-wait 31 | done 32 | # Create peerings and configure VNet 33 | hub_nic_id=$(az vm show -n hub -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 34 | hub_ip=$(az network nic show --ids $hub_nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) 35 | for spoke_id in $(seq 1 ${num_spokes}) 36 | do 37 | az network vnet update -n "spoke${spoke_id}" -g $rg --dns-servers $hub_ip -o none 38 | az network vnet peering create -n "hubtospoke${spoke_id}" -g $rg --vnet-name hub --remote-vnet "spoke${spoke_id}" --allow-vnet-access --allow-forwarded-traffic -o none 39 | az network vnet peering create -n "spoke${spoke_id}tohub" -g $rg --vnet-name "spoke${spoke_id}" --remote-vnet hub --allow-vnet-access --allow-forwarded-traffic -o none 40 | done 41 | az monitor log-analytics workspace create -g $rg -n $logws_name -o none 42 | logws_id=$(az resource list -g $rg -n $logws_name --query '[].id' -o tsv) 43 | logws_customerid=$(az monitor log-analytics workspace show -n $logws_name -g $rg --query customerId -o tsv) 44 | logws_key=$(az monitor log-analytics workspace get-shared-keys -n $logws_name -g $rg --query 'primarySharedKey' -o tsv) 45 | 46 | # Create DNS Zones, and link them to the hub VNet 47 | for zone in privatelink.agentsvc.azure-automation.net privatelink.blob.core.windows.net privatelink.monitor.azure.com privatelink.ods.opinsights.azure.com privatelink.oms.opinsights.azure.com 48 | do 49 | az network private-dns zone create -n $zone -g $rg --no-wait -o none 50 | done 51 | for zone in privatelink.agentsvc.azure-automation.net privatelink.blob.core.windows.net privatelink.monitor.azure.com privatelink.ods.opinsights.azure.com privatelink.oms.opinsights.azure.com 52 | do 53 | az network private-dns link vnet create -g $rg -z $zone -n hub --virtual-network hub --registration-enabled false -o none 54 | done 55 | 56 | # Create Private Link Scope and associate with AzMonitor 57 | az monitor private-link-scope create -n ampls -g $rg -o none 58 | az monitor private-link-scope scoped-resource create -n $logws_name --linked-resource $logws_id -g $rg --scope-name amls -o none 59 | 60 | # Create Private Endpoint 61 | ampls_id=$(az monitor private-link-scope show -n ampls -g $rg --query id -o tsv) 62 | az network vnet subnet create -g $rg --vnet-name hub -n endpoints --address-prefix "192.168.0.64/26" -o none 63 | az network vnet subnet update -n endpoints -g $rg --vnet-name hub --disable-private-endpoint-network-policies true -o none 64 | az network private-endpoint create -n ampls -g $rg --vnet-name hub --subnet endpoints --private-connection-resource-id $ampls_id --connection-name ampls -l $location --group-id azuremonitor -o none 65 | 66 | # Create Zone Groups 67 | zone=privatelink.agentsvc.azure-automation.net 68 | zone_dash=$(echo $zone | tr '.' '-') 69 | az network private-endpoint dns-zone-group create --endpoint-name ampls -g $rg -n default --zone-name $zone_dash --private-dns-zone $zone -o none 70 | for zone in privatelink.blob.core.windows.net privatelink.monitor.azure.com privatelink.ods.opinsights.azure.com privatelink.oms.opinsights.azure.com 71 | do 72 | zone_dash=$(echo $zone | tr '.' '-') 73 | az network private-endpoint dns-zone-group add --endpoint-name ampls -g $rg -n default --zone-name $zone_dash --private-dns-zone $zone -o none 74 | done 75 | 76 | # Refresh DNS server configuration in VMs (we updated the VNets after the VMs were up) 77 | for spoke_id in $(seq 1 ${num_spokes}) 78 | do 79 | pip_name="${spoke_id}-pip" 80 | pip=$(az network public-ip show -n $pip_name -g $rg --query 'ipAddress' -o tsv) 81 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no "$pip" "sudo systemctl restart systemd-networkd" 82 | done 83 | 84 | # Associate VMs to AzMonitor 85 | az vm extension set -n OmsAgentForLinux -g $rg --vm-name hub --publisher Microsoft.EnterpriseCloud.Monitoring --protected-settings "{\"workspaceKey\":\"${logws_key}\"}" --settings "{\"workspaceId\":\"${logws_customerid}\"}" --no-wait -o none 86 | for spoke_id in $(seq 1 ${num_spokes}) 87 | do 88 | az vm extension set -n OmsAgentForLinux -g $rg --vm-name spoke${spoke_id} --publisher Microsoft.EnterpriseCloud.Monitoring --protected-settings "{\"workspaceKey\":\"${logws_key}\"}" --settings "{\"workspaceId\":\"${logws_customerid}\"}" --no-wait -o none 89 | done 90 | 91 | # Query 92 | query='Heartbeat 93 | | where TimeGenerated > ago(15m) 94 | | extend PrivateIP = tostring(ComputerPrivateIPs[0]) 95 | | summarize count() by Computer, ComputerIP, PrivateIP' 96 | az monitor log-analytics query -w $logws_customerid --analytics-query $query -o tsv 97 | 98 | # Diagnostics 99 | az network private-endpoint dns-zone-group list --endpoint-name ampls -g $rg 100 | for zone in privatelink.agentsvc.azure-automation.net privatelink.blob.core.windows.net privatelink.monitor.azure.com privatelink.ods.opinsights.azure.com privatelink.oms.opinsights.azure.com 101 | do 102 | az network private-dns record-set a list -z $zone -g $rg --query '[].[aRecords[0].ipv4Address, fqdn]' -o tsv 103 | done 104 | 105 | 106 | ########### 107 | # Cleanup # 108 | # DANGER! # 109 | ########### 110 | 111 | # az group delete -y --no-wait -n $rg -------------------------------------------------------------------------------- /apim.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # February 2024 4 | # 5 | # The script creates APIM in different configurations 6 | # 7 | ############################################################################ 8 | 9 | 10 | # Variables 11 | rg=apim 12 | location=eastus2 13 | vnet_name=apimvnet 14 | vnet_prefix=10.13.76.0/24 15 | apim_subnet_name=apim 16 | apim_subnet_prefix=10.13.76.0/26 17 | apim_sku=Developer # The Premium SKU offers multi-region on top 18 | apim_vnet_type=Internal 19 | apim_publisher_email=admin@contoso.com 20 | apim_publisher_name=Contoso 21 | 22 | ###################### 23 | # Kubernetes cluster # 24 | ###################### 25 | 26 | # Create RG for AKS engine cluster 27 | az group create -n $rg -l $location 28 | 29 | ######## 30 | # APIM # 31 | ######## 32 | 33 | # Find existing APIM or create one 34 | apim_name=$(az apim list -g $rg --query '[0].name' -o tsv) 35 | if [[ -z "$apim_name" ]] 36 | then 37 | apim_name=apim$RANDOM 38 | echo "Creating APIM ${apim_name}..." 39 | az apim create -n $apim_name -g $rg --publisher-email $apim_publisher_email --publisher-name $apim_publisher_name --sku-name $apim_sku --virtual-network $apim_vnet_type 40 | else 41 | echo "APIM $apim_name found in resource group" 42 | fi 43 | 44 | # az network vnet subnet create -g $rg -n $apim_subnet_name --vnet-name $vnet_name --address-prefix $apim_subnet_prefix 45 | # apim_subnet_id=$(az network vnet subnet show -n $apim_subnet_name --vnet-name $vnet_name -g $rg --query id -o tsv) 46 | # And this another 23m 47 | # az apim update -n $apim_name -g $rg \ 48 | # --set virtualNetworkType=$apim_vnet_type \ 49 | # --set virtualNetworkConfiguration.subnetResourceId=$apim_subnet_id 50 | 51 | # Create product and API 52 | az apim product create -g $rg --service-name $apim_name \ 53 | --product-id MyApis --product-name MyAPIs --description "My API" --legal-terms MyTerms \ 54 | --subscription-required false --approval-required false --subscriptions-limit 8 --state "published" 55 | az apim api create -g $rg --service-name $apim_name --api-id SqlApi --path '/api' --display-name 'SQL API' 56 | az apim product api add -n $apim_name -g $rg --product-id MyApis --api-id SqlApi 57 | az apim api operation create -g $rg --service-name $apim_name --api-id SqlApi --display-name 'SQL API' --operation-id ip --url-template /ip --method GET 58 | az apim api operation create -g $rg --service-name $apim_name --api-id SqlApi --display-name 'Healthcheck' --operation-id healthcheck --url-template /healthcheck --method GET 59 | 60 | # Add a Gateway (portal) and get its key and config URL 61 | gw_key= 62 | gw_config_url= 63 | 64 | # Deploy Gateway to k8s 65 | az k8s-extension create --cluster-type connectedClusters --cluster-name $arc_name -g $rg \ 66 | --name apimgw --extension-type Microsoft.ApiManagement.Gateway \ 67 | --scope namespace --target-namespace apim \ 68 | --configuration-settings gateway.endpoint="$gw_config_url" \ 69 | --configuration-protected-settings gateway.authKey="$gw_key" \ 70 | --configuration-settings service.type='LoadBalancer' --release-train preview 71 | 72 | # Verify extension state 73 | az k8s-extension show --cluster-type connectedClusters --cluster-name $arc_name --resource-group $rg --name apimgw -o table 74 | az k8s-extension list --cluster-type connectedClusters --cluster-name $arc_name --resource-group $rg -o table 75 | 76 | # Delete extension 77 | # az k8s-extension delete --cluster-type connectedClusters --cluster-name $arc_name --resource-group $rg --name apimgw -y 78 | 79 | # Create test DB for the API 80 | sql_server_name=sqlserver$RANDOM 81 | sql_db_name=mydb 82 | sql_username=azure 83 | sql_password=Microsoft123! 84 | az group create -n $rg -l $location 85 | az sql server create -n $sql_server_name -g $rg -l $location --admin-user "$sql_username" --admin-password "$sql_password" 86 | az sql db create -n $sql_db_name -s $sql_server_name -g $rg -e Basic -c 5 --no-wait 87 | sql_server_fqdn=$(az sql server show -n $sql_server_name -g $rg -o tsv --query fullyQualifiedDomainName) && echo $sql_server_fqdn 88 | 89 | # Create backend using the SQL API image 90 | yaml_file=/tmp/sqlapi.yml 91 | cat < $yaml_file 92 | apiVersion: v1 93 | kind: Secret 94 | metadata: 95 | name: sqlpassword 96 | type: Opaque 97 | stringData: 98 | password: $sql_password 99 | --- 100 | apiVersion: apps/v1 101 | kind: Deployment 102 | metadata: 103 | labels: 104 | run: api 105 | name: api 106 | spec: 107 | replicas: 1 108 | selector: 109 | matchLabels: 110 | run: api 111 | template: 112 | metadata: 113 | labels: 114 | run: api 115 | spec: 116 | containers: 117 | - image: fasthacks/sqlapi:1.0 118 | name: api 119 | ports: 120 | - containerPort: 8080 121 | protocol: TCP 122 | env: 123 | - name: SQL_SERVER_USERNAME 124 | value: "$sql_username" 125 | - name: SQL_SERVER_FQDN 126 | value: "$sql_server_fqdn" 127 | - name: SQL_SERVER_PASSWORD 128 | valueFrom: 129 | secretKeyRef: 130 | name: sqlpassword 131 | key: password 132 | restartPolicy: Always 133 | --- 134 | apiVersion: v1 135 | kind: Service 136 | metadata: 137 | name: api 138 | spec: 139 | type: LoadBalancer 140 | ports: 141 | - port: 8080 142 | targetPort: 8080 143 | selector: 144 | run: api 145 | EOF 146 | kubectl apply -f $yaml_file 147 | 148 | # Get public IP and test /api/healthcheck endpoint 149 | api_pip=$(kubectl get svc/api -n default -o json | jq -rc '.status.loadBalancer.ingress[0].ip' 2>/dev/null) && echo $api_pip 150 | curl -s4 "http://${api_pip}:8080/api/healthcheck" 151 | curl -s4 "http://${api_pip}:8080/api/sqlversion" # SQL Server firewall would have to be updated 152 | 153 | # Create backend in portal, FQDN should be api.default.svc.cluster.local 154 | 155 | # Test APIM 156 | 157 | 158 | ############### 159 | # Diagnostics # 160 | ############### 161 | 162 | az apim list -g $rg -o table 163 | 164 | ############### 165 | # DANGER ZONE # 166 | ############### 167 | 168 | # az group delete -n $rg -y --no-wait -------------------------------------------------------------------------------- /appgw_v1.ps1: -------------------------------------------------------------------------------- 1 | # Environment generic variables 2 | $location = "eastus2" 3 | $rg = "josetest-appgwv1" 4 | $vnet_name = "josetest-vnet" 5 | $vnet_prefix = "192.168.0.0/16" 6 | # AppGW variables 7 | $appgw_name = "appgwv1-WAFMedium" 8 | $appgw_sku = "WAF_Medium" # Allowed values: Standard_Large, Standard_Medium, Standard_Small, Standard_v2, WAF_Large, WAF_Medium, WAF_v2. Default: Standard_Medium. 9 | $subnet_name = $appgw_name 10 | $subnet_prefix = "192.168.3.0/24" 11 | $pip_name = $appgw_name + "-pip" 12 | # Migrated AppGwv2 variables 13 | $appgwv2_name = "appgwv2" 14 | $subnetv2_name = $appgwv2_name 15 | $subnetv2_prefix = "192.168.5.0/24" 16 | 17 | # Create environment 18 | echo "Creating RG and VNet..." 19 | az group create -n $rg -l $location -o none 20 | az network vnet create -n $vnet_name -g $rg --address-prefix $vnet_prefix -o none 21 | 22 | # Create AppGW v1 23 | echo "Application Gateway v1..." 24 | az network vnet subnet create --vnet-name $vnet_name -n $subnet_name --address-prefixes $subnet_prefix -g $rg -o none 25 | az network public-ip create -n $pip_name -g $rg --sku Basic -o none 26 | az network application-gateway create -n $appgw_name -g $rg --sku $appgw_sku --public-ip-address $pip_name --vnet-name $vnet_name --subnet $subnet_name --frontend-port 80 --http-settings-port 80 --http-settings-protocol "Http" --routing-rule-type "Basic" --servers "1.2.3.4" -o none 27 | az network application-gateway stop -n $appgw_name -g $rg -o none 28 | 29 | # Create AppGW v2 (the rule priority argument is required) 30 | # az network vnet subnet create --vnet-name $vnet_name -n $subnet_name --address-prefixes $subnet_prefix -g $rg -o none 31 | # az network public-ip create -n $pip_name -g $rg --sku Standard -o none 32 | # az network application-gateway create -n $appgw_name -g $rg --sku $appgw_sku --public-ip-address $pip_name --vnet-name $vnet_name --subnet $subnet_name --frontend-port 80 --http-settings-port 80 --http-settings-protocol "Http" --routing-rule-type "Basic" --servers "1.2.3.4" --priority 1000 -o none 33 | # az network application-gateway stop -n $appgw_name -g $rg -o none 34 | 35 | #################### 36 | # Migrate v1 to v2 # 37 | #################### 38 | 39 | # Script installation errors out if Az modules installed?? (works for me!) 40 | if (Get-InstalledScript -Name "AzureAppGWMigration") { 41 | echo "Updating migration script..." 42 | Update-Script -Name AzureAppGWMigration 43 | } else { 44 | echo "Installing migration script..." 45 | Install-Script -Name AzureAppGWMigration 46 | } 47 | 48 | # Manual installation 49 | # $nupkg_url = "https://www.powershellgallery.com/api/v2/package/AzureAppGWMigration/1.0.11" 50 | # $nupkg_path = "C:\Users\jomore\Downloads\azureappgw-migration.1.0.11.nupkg" 51 | # $script_folder = "C:\Users\jomore\Downloads" 52 | # $script_name = "AzureAppGWMigration.ps1" 53 | # $script_path = $script_folder + "\" + $script_name 54 | # Invoke-WebRequest -Uri $nupkg_url -OutFile $nupkg_path 55 | # Unblock-File -Path $nupkg_path 56 | # Expand-Archive -Path $nupkg_path -Destination $script_folder 57 | # cd $script_folder 58 | 59 | # 60 | 61 | # Get some values first 62 | az network vnet subnet create --vnet-name $vnet_name -n $subnetv2_name --address-prefixes $subnetv2_prefix -g $rg -o none 63 | $appgw = Get-AzApplicationGateway -Name $appgw_name -ResourceGroupName $rg 64 | 65 | # Create PIP 66 | $appgw2_pip_name = "appgw2-pip" 67 | $ip = @{ 68 | Name = $appgw2_pip_name 69 | ResourceGroupName = $rg 70 | Location = $location 71 | Sku = 'Standard' 72 | AllocationMethod = 'Static' 73 | IpAddressVersion = 'IPv4' 74 | #Zone = 1,2,3 75 | } 76 | $appgw2_pip = New-AzPublicIpAddress @ip 77 | 78 | 79 | # https://learn.microsoft.com/en-us/azure/application-gateway/migrate-v1-v2 80 | AzureAppGWMigration -resourceId $appgw.Id ` 81 | -subnetAddressRange $subnetv2_prefix ` 82 | -PublicIpResourceId $appgw2_pip.Id ` 83 | -appgwName $appgwv2_name ` 84 | -AppGwResourceGroupName $rg ` 85 | -validateMigration -enableAutoScale 86 | 87 | # Errors: 88 | # 1. Az.Resources missing -------------------------------------------------------------------------------- /arc_data.sh: -------------------------------------------------------------------------------- 1 | # Get password from keyvault and define variables 2 | # https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios-new/002-create-data-controller.md 3 | akv_name=erjositoKeyvault 4 | password=$(az keyvault secret show -n defaultPassword --vault-name $akv_name --query value -o tsv) 5 | export AZDATA_USERNAME=jose 6 | export AZDATA_PASSWORD=$password 7 | export ACCEPT_EULA=yes 8 | export REGISTRY_USERNAME="22cda7bb-2eb1-419e-a742-8710c313fe79" 9 | export REGISTRY_PASSWORD="cb892016-5c33-4135-acbf-7b15bc8cb0f7" 10 | 11 | # Install azdata 12 | # https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios-new/001-install-client-tools.md#step-1-install-azdata 13 | # For example, for Ubuntu 20.04: 14 | apt-get update 15 | apt-get install -y curl apt-transport-https unixodbc libkrb5-dev libssl1.1 16 | curl -SL https://private-repo.microsoft.com/python/azure-arc-data/private-preview-aug-2020-new/ubuntu-focal/azdata-cli_20.1.1-1~focal_all.deb -o azdata-cli_20.1.1-1~focal_all.deb 17 | dpkg -i azdata-cli_20.1.1-1~focal_all.deb 18 | apt-get -f install 19 | 20 | # Deploy controller 21 | rg=arcdata 22 | location=westeurope 23 | az group create -n $rg -l $location 24 | subscription_id=$(az account show --query id -o tsv) 25 | azdata arc dc create --profile-name azure-arc-aks-premium-storage \ 26 | --namespace arc --name arc \ 27 | --subscription $subscription_id \ 28 | --resource-group $rg --location $location \ 29 | --connectivity-mode indirect 30 | azdata arc dc status show 31 | azdata arc dc endpoint list -o table # Notice the Grafana and Kibana endpoints 32 | 33 | # Login 34 | azdata login --namespace arc 35 | 36 | # Deploy Azure SQL Database 37 | # https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios-new/003-create-sqlmiaa-instance.md 38 | sqldb_name=mysqldb 39 | azdata arc sql mi create -n $sqldb_name \ 40 | --storage-class-data managed-premium --storage-class-logs managed-premium 41 | azdata arc sql mi list 42 | azdata arc sql mi show -n $sqldb_name 43 | 44 | # Upload info to AzMonitor 45 | # https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios-new/007-upload-metrics-and-logs-to-Azure-Monitor.md 46 | # 1. Create SP and assign 'Monitoring Metrics Publisher role' 47 | az role assignment create --assignee $sp_app_id --role 'Monitoring Metrics Publisher' --scope subscriptions/$subscription_id 48 | # az role assignment create --assignee $sp_app_id --role 'Contributor' --scope subscriptions/$subscription_id 49 | echo $SPN_CLIENT_ID 50 | echo $SPN_CLIENT_SECRET 51 | # 2. Create LA workspace 52 | logws_name=log$RANDOM 53 | az monitor log-analytics workspace create -g $rg -n $logws_name 54 | export WORKSPACE_ID=$(az monitor log-analytics workspace show -n $logws_name -g $rg --query customerId -o tsv) 55 | export WORKSPACE_SHARED_KEY=$(az monitor log-analytics workspace get-shared-keys -g $rg -n $logws_name --query primarySharedKey -o tsv) 56 | export SPN_AUTHORITY='https://login.microsoftonline.com' 57 | export SPN_TENANT_ID=$(az account show --query tenantId -o tsv) 58 | # 3. upload metrics/logs 59 | azdata arc dc upload --path metrics.json # One-time task, you should crontab this 60 | azdata arc dc upload --path logs.json # One-time task, you should crontab this 61 | 62 | 63 | # Deploy Posgres DB 64 | # https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios-new/004-create-Postgres-instances.md 65 | postgres_name=myps 66 | azdata arc postgres server create -n $postgres_name --workers 2 \ 67 | --storage-class-data managed-premium --storage-class-logs managed-premium 68 | azdata arc postgres server list 69 | azdata arc postgres server endpoint list -n $postgres_name 70 | -------------------------------------------------------------------------------- /arm_api.azcli: -------------------------------------------------------------------------------- 1 | # How to measure the remaining operations before throttling in the ARM API 2 | 3 | # Variables 4 | subscription_id=$(az account show --query id -o tsv) 5 | testrg=blahblah123 6 | 7 | # Writes 8 | remaining_writes=$(az rest --method PUT --uri "/subscriptions/${subscription_id}/resourcegroups/${testrg}?api-version=2021-04-01" --body '{"location": "westeurope"}' --debug 2>&1 | grep remaining | cut -d"'" -f 4) 9 | # Reads 10 | remaining_reads=$(az rest --method GET --uri "/subscriptions/${subscription_id}/resourcegroups/${testrg}?api-version=2021-04-01" --debug 2>&1 | grep remaining | cut -d"'" -f 4) 11 | # Deletes 12 | remaining_deletes=$(az rest --method DELETE --uri "/subscriptions/${subscription_id}/resourcegroups/${testrg}?api-version=2021-04-01" --debug 2>&1 | grep remaining | cut -d"'" -f 4) 13 | # Summary 14 | echo "Remaining operations this hour:" 15 | echo " * Writes: $remaining_writes" 16 | echo " * Reads: $remaining_reads" 17 | echo " * Deletes: $remaining_deletes" 18 | -------------------------------------------------------------------------------- /azdevops.azcli: -------------------------------------------------------------------------------- 1 | ############################################### 2 | # Azure Devops with Azure CLI 3 | # 4 | # Sample commands with the azure-devops extension 5 | # 6 | # Jose Moreno, November 2020 7 | ############################################### 8 | 9 | az devops boards -h 10 | -------------------------------------------------------------------------------- /azfnc2cosmosdb-rules.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | ######################################### 4 | # Script to get possible outbound IP addresses 5 | # from Azure Function and configure them 6 | # as allowed IPs in a Cosmos DB. 7 | # 8 | # 9 | # Jose Moreno, June 2024 10 | ########################################## 11 | 12 | # Get arguments 13 | for i in "$@" 14 | do 15 | case $i in 16 | -f=*|--function=*) 17 | function="${i#*=}" 18 | shift # past argument=value 19 | ;; 20 | -c=*|--cosmosdb=*) 21 | cosmosdb="${i#*=}" 22 | shift # past argument=value 23 | ;; 24 | -g=*|-rg=*|--resourcegroup=*) # Assumes same RG for Azure Function and CosmosDB 25 | rg="${i#*=}" 26 | shift # past argument=value 27 | ;; 28 | esac 29 | done 30 | set -- "${POSITIONAL[@]}" # restore positional parameters 31 | 32 | # Verify that all parameters have been provided 33 | if [[ -z $function || -z $cosmosdb || -z $rg ]]; then 34 | echo "ERROR: Usage: $0 -f|--function= -c|--cosmosdb= -g|--resourcegroup=" 35 | exit 1 36 | fi 37 | 38 | # Verify that the resources exist with the Azure CLI (assuming we are already authenticated and in the right subscription) 39 | function_id=$(az functionapp show --name $function --resource-group $rg --query id --output tsv) 40 | if [[ -z $function_id ]]; then 41 | echo "ERROR: Azure Function $function not found in resource group $rg" 42 | exit 1 43 | else 44 | echo "DEBUG: Azure Function found with ID $function_id" 45 | fi 46 | cosmosdb_id=$(az cosmosdb show --name $cosmosdb --resource-group $rg --query id --output tsv --only-show-errors) 47 | if [[ -z $cosmosdb_id ]]; then 48 | echo "ERROR: Cosmos DB $cosmosdb not found in resource group $rg" 49 | exit 1 50 | else 51 | echo "DEBUG: Cosmos DB found with ID $cosmosdb_id" 52 | fi 53 | 54 | # Get the possible outbound IP addresses for the Azure Function 55 | function_ips=$(az functionapp show --resource-group $rg --name $function --query possibleOutboundIpAddresses --output tsv --only-show-errors) 56 | echo "DEBUG: Azure Function $function has the following possible outbound IP addresses: $function_ips" 57 | 58 | # Get the current firewall rules for the Cosmos DB 59 | cosmosdb_ips_current=$(az cosmosdb show -n $cosmosdb -g $rg --only-show-errors -o json | jq -r '.ipRules | .[] | .ipAddressOrRange' | paste -sd "," -) 60 | cosmosdb_ips_new="${cosmosdb_ips_current},${function_ips}" 61 | # Eliminate duplicates in the comma-separated list of values 62 | cosmosdb_ips_new=$(echo $cosmosdb_ips_new | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//') 63 | echo "DEBUG: Updating Cosmos DB $cosmosdb firewall rules to: $cosmosdb_ips_new" 64 | az cosmosdb update -n $cosmosdb -g $rg --ip-range-filter "$cosmosdb_ips_new" -o none --only-show-errors 65 | -------------------------------------------------------------------------------- /azsearch.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # March 2020 4 | # 5 | # Azure search useful commands 6 | # 7 | ############################################################################ 8 | 9 | # Variables 10 | rg=kmoh 11 | azsearch_name=cloudtrooper 12 | 13 | # Azure Search 14 | az search service list -g $rg -o table 15 | az search admin-key show --service-name $azsearch_name -g $rg 16 | 17 | # Delete Azure Search 18 | # az search service delete -n $azsearch_name -g $rg -y 19 | 20 | # Azure Cognitive Services 21 | az cognitiveservices account list -g $rg -o table 22 | -------------------------------------------------------------------------------- /cdn2afd.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Sample to move a custom domain from Azure CDN to AFD Standard/Premium 3 | # 4 | # Jose Moreno, 2023 5 | ############################################################################### 6 | 7 | 8 | # Variables 9 | rg=afdtest 10 | fqdn=api.cloudtrooper.net 11 | host=$(echo $fqdn | cut -d. -f1) 12 | cdn_name=migrationtest 13 | afd_name=migrationtestafd 14 | cdn_test_prot=http 15 | afd_test_prot=https 16 | test_path=/api/healthcheck 17 | akv_name=erjositoKeyvault 18 | secret_name=cloudtroopernet 19 | dns_zone_name=cloudtrooper.net 20 | wait_interval=5 21 | 22 | # We assume that both Azure CDN and AFD are created 23 | echo "Finding out information. CDN and AFD IDs:" 24 | afd_id=$(az afd profile show --profile-name $afd_name -g $rg --query id -o tsv) && echo $afd_id 25 | cdn_id=$(az cdn profile show -n $cdn_name -g $rg -o tsv --query id) && echo $cdn_id 26 | 27 | # Getting custom domains for Azure CDN 28 | cdn_endpoint=$(az cdn endpoint list -g $rg --profile-name $cdn_name --query '[0].name' -o tsv) 29 | echo "Endpoint '$cdn_endpoint' found in CDN '$cdn_name'" 30 | cdn_endpoint_fqdn=$(az cdn endpoint show -n $cdn_endpoint -g $rg --profile-name $cdn_name --query hostName -o tsv) 31 | echo "Hostname for endpoint '$cdn_endpoint' is '$cdn_endpoint_fqdn'" 32 | custom_domain_id=$(az cdn custom-domain list -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name --query "[?hostName=='$fqdn'].id" -o tsv) 33 | custom_domain_name=$(az cdn custom-domain list -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name --query "[?hostName=='$fqdn'].name" -o tsv) 34 | if [[ -z "$custom_domain_name" ]]; then 35 | echo "Custom domain $custom_domain_name not found for FQDN $fqdn. Exiting..." 36 | exit 1 37 | else 38 | echo "Custom domain $custom_domain_name found for FQDN $fqdn" 39 | fi 40 | 41 | # Getting info from AFD 42 | afd_endpoint_name=$(az afd endpoint list -g $rg --profile-name $afd_name --query '[0].name' -o tsv) 43 | echo "Endpoint '$afd_endpoint_name' found in AFD profile '$afd_name'" 44 | afd_endpoint_fqdn=$(az afd endpoint show --endpoint-name $afd_endpoint_name --profile-name $afd_name -g $rg --query hostName -o tsv) 45 | echo "Endpoint '$afd_endpoint_name' has hostname '$afd_endpoint_fqdn'" 46 | afd_route_name=$(az afd route list --profile-name $afd_name -g $rg --endpoint-name $afd_endpoint_name -o tsv --query '[0].name') 47 | echo "Route '$afd_route_name' found in AFD profile '$afd_name', endpoint '$afd_endpoint_name'" 48 | fqdn_dash=$(echo $fqdn | tr "." "-") 49 | # Get the domain name from AFD if not found in the CDN 50 | if [[ -z "$custom_domain_name" ]]; then 51 | custom_domain_name=$(az afd custom-domain list -g $rg --profile-name $afd_name -o tsv --query "[?hostName=='$fqdn'].name") 52 | custom_domain_id=$(az afd custom-domain list -g $rg --profile-name $afd_name -o tsv --query "[?hostName=='$fqdn'].id") 53 | echo "Custom domain $custom_domain_name found for FQDN $fqdn" 54 | fi 55 | 56 | # Making sure secret exists in AFD 57 | afd_secret_id=$(az afd secret show -g $rg --profile-name $afd_name --secret-name $secret_name --query id -o tsv) 58 | if [[ -z "$afd_secret_id" ]]; then 59 | echo "Secret $secret_name not found in AFD $afd_name. Creating it..." 60 | subscription_id=$(az account show --query id -o tsv) 61 | akv_secret_id="/subscriptions/$subscription_id/resourceGroups/$rg/providers/Microsoft.KeyVault/vaults/$akv_name/secrets/$akv_secret_name" 62 | az afd secret create -g $rg --profile-name $afd_name --secret-name $secret_name --use-latest-version --secret-source $akv_secret_id -o none 63 | afd_secret_id=$(az afd secret show -g $rg --profile-name $afd_name --secret-name $secret_name --query id -o tsv) 64 | else 65 | echo "Secret '$secret_name' found in AFD '$afd_name'" 66 | fi 67 | 68 | # Get RG for DNS zone 69 | dns_rg=$(az network dns zone list --query "[?name=='$dns_zone_name'].resourceGroup" -o tsv) 70 | if [[ -z "$dns_rg" ]]; then 71 | echo "DNS zone $dns_zone_name not found. Exiting..." 72 | # exit 1 73 | else 74 | echo "DNS zone $dns_zone_name found in resource group $dns_rg" 75 | fi 76 | 77 | # The test function expects the test protocol (http/https) as parameter 78 | def test_app() { 79 | test_prot=$1 80 | url="${test_prot}://${fqdn}${test_path}" 81 | curl -o /dev/null -s -w "%{http_code}\n" $url 82 | } 83 | 84 | # Verify existing app through CDN 85 | return_code=$(test_app $cdn_test_prot) 86 | if [[ "$return_code" -ne "200" ]]; then 87 | echo "Error testing app. Exiting..." 88 | # exit 1 89 | else 90 | echo "App tested successfully, return code is $return_code" 91 | fi 92 | 93 | ################### 94 | # Migration start # 95 | ################### 96 | 97 | # Changing DNS 98 | start_time=`date +%s` 99 | echo "Starting migration at $(date)" 100 | echo "Changing DNS to point to AFD (from '$cdn_endpoint_fqdn' to '$afd_endpoint_fqdn')..." 101 | az network dns record-set cname remove-record -g $dns_rg -z $dns_zone_name -n $host -c $cdn_endpoint_fqdn --keep-empty-record-set -o none 102 | az network dns record-set cname set-record -g $dns_rg -z $dns_zone_name -n $host -c $afd_endpoint_fqdn --ttl 3600 -o none 103 | echo "DNS changed to '$afd_endpoint_fqdn'. Time elapsed: $((`date +%s` - $start_time)) seconds" 104 | 105 | # Delete custom domain from CDN 106 | echo "Deleting custom domain $custom_domain_name from CDN. This will not work until the DNS change is propagated." 107 | custom_domain_id=$(az cdn custom-domain show -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name -n $fqdn_dash --query id -o tsv) 108 | try_count=0 109 | until [[ -z "$custom_domain_id" ]] 110 | do 111 | ((try_count++)) 112 | echo "Trying to delete CDN custom domain (try $try_count). Time elapsed: $((`date +%s` - $start_time)) seconds..." 113 | az cdn custom-domain delete -n $custom_domain_name -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name -o none 2>/dev/null 114 | sleep $wait_interval 115 | custom_domain_id=$(az cdn custom-domain show -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name -n $fqdn_dash --query id -o tsv 2>/dev/null) 116 | done 117 | echo "Custom domain deleted from CDN. Time elapsed: $((`date +%s` - $start_time)) seconds" 118 | 119 | # Add custom domain to AFD 120 | echo "Adding custom domain $fqdn to AFD $afd_name. This will not work if the custom domain is still active in Azure CDN." 121 | state='' 122 | try_count=0 123 | until [[ "$state" == "Succeeded" ]] 124 | do 125 | ((try_count++)) 126 | echo "Trying to create custom domain $fqdn_dash in AFD $afd_name (try $try_count). Time elapsed $((`date +%s` - $start_time)) seconds..." 127 | az afd custom-domain create -g $rg --profile-name $afd_name --custom-domain-name $fqdn_dash --host-name $fqdn --certificate-type CustomerCertificate --secret $secret_name --minimum-tls-version TLS12 -o none 128 | sleep $wait_interval 129 | state=$(az afd custom-domain show -g $rg --profile-name $afd_name --custom-domain-name $fqdn_dash --query deploymentStatus -o tsv) 130 | done 131 | echo "Custom domain added to AFD, state is '$state'. Adding custom domain to AFD route '$afd_route_name'..." 132 | az afd route update -g $rg --profile-name $afd_name --endpoint-name $afd_endpoint_name --route-name $afd_route_name --custom-domains $fqdn_dash -o none 133 | echo "Custom domain added to AFD route. Time elapsed: $((`date +%s` - $start_time)) seconds" 134 | 135 | # Testing app 136 | return_code=$(test_app $afd_test_prot) 137 | try_count=0 138 | until [[ "$return_code" == "200" ]] 139 | do 140 | ((try_count++)) 141 | echo "Testing app (try $try_count). Return code: $return_code. Time elapsed: $((`date +%s` - $start_time)) seconds..." 142 | sleep $wait_interval 143 | return_code=$(test_app $afd_test_prot) 144 | done 145 | run_time=$(expr `date +%s` - $start_time) 146 | ((minutes=${run_time}/60)) 147 | ((seconds=${run_time}%60)) 148 | echo "Application working successfully on AFD now (return code $return_code)! Total time elapsed: $minutes minutes and $seconds seconds" 149 | 150 | ###################### 151 | # Revert back to CDN # 152 | ###################### 153 | 154 | echo "Reverting DNS..." 155 | az network dns record-set cname remove-record -g $dns_rg -z $dns_zone_name -n $host -c $afd_endpoint_fqdn --keep-empty-record-set -o none 156 | az network dns record-set cname set-record -g $dns_rg -z $dns_zone_name -n $host -c $cdn_endpoint_fqdn --ttl 3600 -o none 157 | echo "Removing custom domain from AFD..." 158 | az afd custom-domain create -g $rg --profile-name $afd_name --custom-domain-name dummy --host-name "dummy.${dns_zone_name}" --certificate-type CustomerCertificate --secret $secret_name --minimum-tls-version TLS12 -o none 159 | az afd route update -g $rg --profile-name $afd_name --endpoint-name $afd_endpoint_name --route-name $afd_route_name --custom-domains dummy -o none 160 | az afd custom-domain delete -g $rg --profile-name $afd_name --custom-domain-name $fqdn_dash -y -o none 161 | echo "Creating custom domain in CDN..." 162 | custom_domain_id='' 163 | until [[ -n "$custom_domain_id" ]] 164 | do 165 | echo "Attempting to create CDN custom domain..." 166 | az cdn custom-domain create -n $fqdn_dash -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name --hostname $fqdn -o none 167 | sleep $wait_interval 168 | custom_domain_id=$(az cdn custom-domain show -g $rg --endpoint-name $cdn_endpoint --profile-name $cdn_name -n $fqdn_dash --query id -o tsv 2>/dev/null) 169 | done 170 | echo "Custom domain added to CDN" 171 | return_code=$(test_app $cdn_test_prot) 172 | if [[ "$return_code" -ne "200" ]]; then 173 | echo "Error testing app. Exiting..." 174 | # exit 1 175 | else 176 | echo "App tested successfully back on CDN, return code is $return_code" 177 | fi 178 | -------------------------------------------------------------------------------- /cloudinit-apache2.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | runcmd: 3 | - apt-get update 4 | - apt-get install -y apache2 -------------------------------------------------------------------------------- /cloudinit-httpd.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | package_upgrade: true 3 | packages: 4 | - apache2 5 | - hping3 6 | -------------------------------------------------------------------------------- /cloudinit-iptables.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | runcmd: 3 | - sysctl -w net.ipv4.ip_forward=1 4 | - sysctl -w net.ipv6.conf.all.forwarding=1 5 | - iptables -A FORWARD -j ACCEPT 6 | - iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 7 | -------------------------------------------------------------------------------- /cloudinit-whoami.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | runcmd: 3 | - apt update && apt install -y python3-pip 4 | - pip3 install flask 5 | - wget https://raw.githubusercontent.com/erjosito/azcli/master/myip.py -O /root/myip.py 6 | - python3 /root/myip.py & -------------------------------------------------------------------------------- /cosmosdb_rest.azcli: -------------------------------------------------------------------------------- 1 | # This is a way to use the REST API to get the data from Cosmos DB 2 | # For more info: https://learn.microsoft.com/rest/api/cosmos-db/access-control-on-cosmosdb-resources 3 | 4 | # Variables 5 | rg=translation 6 | cosmosdb="reviewchecklist" 7 | dbname=checklist 8 | baseUrl="https://$cosmosdb.documents.azure.com/" 9 | verb="get" 10 | resourceType="colls" # 'dbs' or 'colls' or 'docs' 11 | resourceLink="dbs/$dbname/colls" # 'dbs' or 'dbs/$dbname/colls' or 'dbs/$dbname/colls/$collname/docs' 12 | resourceId="" 13 | 14 | # Main 15 | masterKey=$(az cosmosdb keys list --name $cosmosdb -g $rg --query primaryMasterKey --output tsv) 16 | echo "Masterkey: $masterKey" 17 | now=$(env LANG=en_US TZ=GMT date '+%a, %d %b %Y %T %Z') 18 | echo "Date: " $now 19 | signature="$(printf "%s" "$verb\n$resourceType\n$resourceId\n$now" | tr '[A-Z]' '[a-z]')\n\n" 20 | echo "Signature: $signature" 21 | hexKey=$(printf "$masterKey" | base64 --decode | hexdump -v -e '/1 "%02x"') 22 | echo "Hex key: " $hexKey 23 | hashedSignature=$(printf "$signature" | openssl dgst -sha256 -mac hmac -macopt hexkey:$hexKey -binary | base64) 24 | echo "Hashed signature: $hashedSignature" 25 | authString="type=master&ver=1.0&sig=$hashedSignature" 26 | echo "Auth string: $authString" 27 | urlEncodedAuthString=$(printf "$authString" | sed 's/=/%3d/g' | sed 's/&/%26/g' | sed 's/+/%2b/g' | sed 's/\//%2f/g') 28 | echo "URL encoded auth string: $urlEncodedAuthString" 29 | url="$baseUrl$resourceLink" 30 | echo "URL: $url" 31 | curl --request $verb -H "x-ms-date: $now" -H "x-ms-version: 2018-12-31" -H "x-ms-documentdb-isquery: true" -H "Content-Type: application/query+json" -H "Authorization: $urlEncodedAuthString" $url 32 | -------------------------------------------------------------------------------- /domain.azcli: -------------------------------------------------------------------------------- 1 | ############################################### 2 | # Create new Domain from freenom.com, 3 | # and wildcard cert from LetsEncrypt. 4 | # 5 | # Goal is to offer some infrastructure 6 | # to test without domains/certs. 7 | # 8 | # Prereqs: 9 | # - rapidapi key (stored in AKV): create in rapidapi.com 10 | ############################################### 11 | 12 | ####################### 13 | # Work in progress!!! # 14 | ####################### 15 | 16 | # Auxiliary functions 17 | urlencode() { 18 | # urlencode 19 | old_lc_collate=$LC_COLLATE 20 | LC_COLLATE=C 21 | local length="${#1}" 22 | for (( i = 0; i < length; i++ )); do 23 | local c="${1:$i:1}" 24 | case $c in 25 | [a-zA-Z0-9.~_-]) printf '%s' "$c" ;; 26 | *) printf '%%%02X' "'$c" ;; 27 | esac 28 | done 29 | LC_COLLATE=$old_lc_collate 30 | } 31 | 32 | urldecode() { 33 | # urldecode 34 | local url_encoded="${1//+/ }" 35 | printf '%b' "${url_encoded//%/\\x}" 36 | } 37 | 38 | # Generate free domain 39 | # domainr API doc: https://domainr.com/docs/api 40 | 41 | # Get rapidapi key from AKV 42 | rapidapi_akv_name=erjositoKeyVault 43 | akv_rapidapi_key_secret_name=freenom-username 44 | # Use this to store your rapidapi key in AKV for the first time 45 | # az keyvault secret set -n $akv_rapidapi_key_secret_name --value 'your_rapid_api_key' --vault-name $freenom_akv_name 46 | rapidapi_key=$(az keyvault secret show --vault-name $freenom_akv_name -n $akv_rapidapi_key_secret_name --query 'value' -o tsv) 47 | if [[ -z "$rapidapi_key" ]] 48 | then 49 | echo "ERROR: No RapidAPI key found in Key Vault $rapidapi_akv_name" 50 | # exit 1 51 | else 52 | echo "INFO: RapidAPI key retrieved successfully from AKV $rapidapi_akv_name" 53 | fi 54 | 55 | # free domainr service 56 | domainr_host="domainr.p.rapidapi.com" 57 | 58 | # Check domain status 59 | unique_string=$RANDOM 60 | domain_suffix="gq" 61 | domain="azlab-${unique_string}.${domain_suffix}" 62 | domain_encoded=$(urlencode $domain) 63 | domain_status=$(curl -s -X GET "https://${domainr_host}/v2/status?mashape-key=${rapidapi_key}&domain=${domain_encoded}") 64 | echo $domain_status 65 | request_status=$(echo $domain_status | jq -r '.status') 66 | if [[ "$request_status" == "error" ]] 67 | then 68 | echo "ERROR: $(echo $domain_status | jq -r '.error')" 69 | else 70 | echo "INFO: blah blah" 71 | fi -------------------------------------------------------------------------------- /flowlogs-ADXdashboard.json: -------------------------------------------------------------------------------- 1 | {"$schema":"https://dataexplorer.azure.com/static/d/schema/35/dashboard.json","id":"52df25dd-e6f1-4fa3-8b47-3f0df0bcfce5","eTag":"mYLlREA+Wd2NN50CPHHa2g==","schema_version":"35","title":"NSG Flow Logs","autoRefresh":{"enabled":false},"sharedQueries":[],"tiles":[{"id":"db482abb-7ccc-4347-9c03-b45d7f80aa7c","title":"Firewall logs","visualType":"card","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":0,"y":0,"width":3,"height":3},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"firewallLogs_structured\n| summarize count()\n"},"visualOptions":{"hideTileTitle":false,"multiStat__textSize":"auto","multiStat__valueColumn":{"type":"infer"},"colorRulesDisabled":false,"colorRules":[],"colorStyle":"light"}},{"id":"335bd73f-0c4c-4244-bc2c-ad1464b3fde9","title":"NSG Flow Logs","visualType":"card","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":3,"y":0,"width":3,"height":3},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| summarize count()"},"visualOptions":{"hideTileTitle":false,"multiStat__textSize":"auto","multiStat__valueColumn":{"type":"infer"},"colorRulesDisabled":false,"colorRules":[],"colorStyle":"light"}},{"id":"4ceef164-e264-422c-ba1f-e34ecc49754f","title":"Top TCP flows","visualType":"table","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":6,"y":0,"width":16,"height":6},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| summarize SumBytesSrcToDst=sum(BytesSrcToDst), SumBytesDstToSrc=sum(BytesDstToSrc) by srcIP,dstIP,Protocol,dstPort,Decision\n| extend SumBytes = SumBytesDstToSrc + SumBytesSrcToDst\n| top 10 by SumBytes desc "},"visualOptions":{"hideTileTitle":false,"table__enableRenderLinks":true,"colorRules":[],"colorRulesDisabled":true,"colorStyle":"light","crossFilterDisabled":false,"drillthroughDisabled":false,"crossFilter":[],"drillthrough":[],"table__renderLinks":[]}},{"id":"b0fb3e75-36dc-416f-be38-5670a3aedfc6","title":"Drop/Allowed bytes","visualType":"bar","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":16,"y":6,"width":7,"height":7},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| summarize SumBytesSrcToDst = sum(BytesSrcToDst), SumBytesDstToSrc = sum(BytesDstToSrc) by Decision \n"},"visualOptions":{"hideTileTitle":false,"multipleYAxes":{"base":{"id":"-1","label":"","columns":[],"yAxisMaximumValue":null,"yAxisMinimumValue":null,"yAxisScale":"linear","horizontalLines":[]},"additional":[],"showMultiplePanels":false},"hideLegend":false,"xColumnTitle":"","xColumn":{"type":"infer"},"yColumns":{"type":"infer"},"seriesColumns":{"type":"infer"},"xAxisScale":"linear","verticalLine":"","crossFilterDisabled":false,"drillthroughDisabled":false,"crossFilter":[],"drillthrough":[]}},{"id":"e88982ac-1cca-4cf3-9766-6a59f84687cc","title":"Inbound/Outbound bytes","visualType":"bar","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":16,"y":13,"width":7,"height":7},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| summarize SumBytesSrcToDst = sum(BytesSrcToDst), SumBytesDstToSrc = sum(BytesDstToSrc) by Direction \n"},"visualOptions":{"hideTileTitle":false,"multipleYAxes":{"base":{"id":"-1","label":"","columns":[],"yAxisMaximumValue":null,"yAxisMinimumValue":null,"yAxisScale":"linear","horizontalLines":[]},"additional":[],"showMultiplePanels":false},"hideLegend":false,"xColumnTitle":"","xColumn":{"type":"infer"},"yColumns":{"type":"infer"},"seriesColumns":{"type":"infer"},"xAxisScale":"linear","verticalLine":"","crossFilterDisabled":false,"drillthroughDisabled":false,"crossFilter":[],"drillthrough":[]}},{"id":"5643347c-636b-451f-9a3f-224f15345295","title":"Private/Public traffic distribution","visualType":"column","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":0,"y":3,"width":6,"height":6},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs \n| extend SrcIPisPrivate = ipv4_is_in_any_range(srcIP, dynamic([\"10.0.0.0/8\", \"192.168.0.0/16\", \"172.16.0.0/12\"]))\n| extend DstIPisPrivate = ipv4_is_in_any_range(dstIP, dynamic([\"10.0.0.0/8\", \"192.168.0.0/16\", \"172.16.0.0/12\"]))\n| where isnotnull(SrcIPisPrivate) and isnotnull(DstIPisPrivate) \n| extend FlowType = iff(SrcIPisPrivate and DstIPisPrivate, \"PrivateToPrivate\", iff(SrcIPisPrivate and not(DstIPisPrivate), \"PrivateToPublic\",iff(DstIPisPrivate and not(SrcIPisPrivate), \"PublicToPrivate\", \"PublicToPublic\")))\n| summarize SumBytesSrcToDst=sum(BytesSrcToDst), SumBytesDstToSrc=sum(BytesDstToSrc) by FlowType\n"},"visualOptions":{"hideTileTitle":false,"multipleYAxes":{"base":{"id":"-1","label":"","columns":[],"yAxisMaximumValue":null,"yAxisMinimumValue":null,"yAxisScale":"linear","horizontalLines":[]},"additional":[],"showMultiplePanels":false},"hideLegend":false,"xColumnTitle":"","xColumn":{"type":"infer"},"yColumns":{"type":"infer"},"seriesColumns":{"type":"infer"},"xAxisScale":"linear","verticalLine":"","crossFilterDisabled":false,"drillthroughDisabled":false,"crossFilter":[],"drillthrough":[]}},{"id":"e2a6fa9f-dfdc-48aa-8977-e38c2a556283","title":"Missing Src or Dst IP","visualType":"card","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":0,"y":9,"width":3,"height":3},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| extend EmptySrcIP = (strlen(srcIP)==0)\n| extend EmptyDstIP = (strlen(dstIP)==0)\n| where EmptySrcIP or EmptyDstIP\n| summarize FlowsWithNoSrcOrDst=count()"},"visualOptions":{"hideTileTitle":false,"multiStat__textSize":"auto","multiStat__valueColumn":{"type":"infer"},"colorRulesDisabled":false,"colorRules":[],"colorStyle":"light"}},{"id":"a4daf615-289b-4e3d-ab91-2c60866c3647","title":"Top protocols","visualType":"pie","pageId":"3d5ae887-54dc-4322-ac00-094b3e7a136d","layout":{"x":6,"y":6,"width":10,"height":9},"query":{"kind":"inline","dataSource":{"kind":"inline","dataSourceId":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7"},"usedVariables":[],"text":"flowLogs\n| extend ProtAndPort = strcat(iff(Protocol==\"T\", \"TCP\", iff(Protocol==\"U\", \"UDP\", \"Unknown\")),\"-\", dstPort)\n| summarize SumBytesSrcToDst=sum(BytesSrcToDst), SumBytesDstToSrc=sum(BytesDstToSrc) by ProtAndPort\n| top 10 by SumBytesSrcToDst+SumBytesSrcToDst\n"},"visualOptions":{"hideTileTitle":false,"hideLegend":false,"xColumn":{"type":"infer"},"yColumns":{"type":"infer"},"seriesColumns":{"type":"infer"},"crossFilterDisabled":false,"drillthroughDisabled":false,"labelDisabled":false,"pie__label":["name","percentage"],"tooltipDisabled":false,"pie__tooltip":["name","percentage","value"],"pie__orderBy":"size","pie__kind":"pie","pie__topNSlices":null,"crossFilter":[],"drillthrough":[]}}],"parameters":[{"kind":"duration","id":"f72264bb-cffd-468f-a9e3-496303de4f4f","displayName":"Time range","beginVariableName":"_startTime","endVariableName":"_endTime","defaultValue":{"kind":"dynamic","count":1,"unit":"hours"},"showOnPages":{"kind":"all"}}],"dataSources":[{"id":"1975debb-54f3-485b-9cd8-fd4b8dedc8b7","name":"Network Logs","clusterUri":"https://nwlogs.eastus2.kusto.windows.net/","database":"nwlogs","kind":"manual-kusto","scopeId":"kusto"}],"pages":[{"name":"Page 1","id":"3d5ae887-54dc-4322-ac00-094b3e7a136d"}]} -------------------------------------------------------------------------------- /flowlogs.kql: -------------------------------------------------------------------------------- 1 | // Create table to receive raw logs from Azure Storage (over Event Grid) 2 | .create table ['rawFlowLogs'] (['records']:dynamic, ['EventProcessedUtcTime']:datetime, ['PartitionId']:int, ['EventEnqueuedUtcTime']:datetime) 3 | 4 | ///////////////////// 5 | // VNet Flow Logs // 6 | ///////////////////// 7 | 8 | // Create Mapping function (VNet Flow Logs) 9 | .create-or-alter function 10 | with (docstring = 'Parses raw flowlogs records into strongly-typed columns', folder = 'FlowLogs') 11 | FlowLogMapping() { 12 | rawFlowLogs 13 | | mv-expand records = records 14 | | project Time = todatetime(records["time"]), 15 | macAddress = tostring(records["macAddress"]), 16 | category = tostring(records["category"]), 17 | flowLogVersion = toint(records["flowLogVersion"]), 18 | flowLogGUID = tostring(records["flowLogGUID"]), 19 | flowLogResourceID = tostring(records["flowLogGUID"]), 20 | targetResourceID = tostring(records["targetResourceID"]), 21 | vnetResourceID = tostring(records["vnetResourceID"]), 22 | nicResourceID = tostring(records["nicResourceID"]), 23 | vmResourceID = tostring(records["vmResrouceID"]), 24 | location = tostring(records["location"]), 25 | zone = tostring(records["zone"]), 26 | flowRecords = todynamic(records["flowRecords"]) 27 | | mv-expand flowRecords 28 | | extend fields = split(flowRecords, ',') 29 | | extend Timestamp = tostring(fields[0]), 30 | SourceIP = tostring(fields[1]), 31 | DestinationIP = tostring(fields[2]), 32 | SrcPort = tostring(fields[3]), 33 | DstPort = tostring(fields[4]), 34 | L4Protocol = tostring(fields[5]), 35 | Direction = tostring(fields[6]), 36 | State = tostring(fields[7]), 37 | Encryption = tostring(fields[8]), 38 | PacketsSrcToDst = toint(fields[9]), 39 | BytesSrcToDst = toint(fields[10]), 40 | PacketDstToSrc = toint(fields[11]), 41 | BytesDstToSrc = toint(fields[12]) 42 | | project-away flowRecords 43 | } 44 | 45 | // Create target table for VNet Flow Logs 46 | .create table flowLogs ( 47 | Time:datetime, 48 | macAddress:string, 49 | category:string, 50 | flowLogVersion:int, 51 | flowLogGUID:string, 52 | flowLogResourceID:string, 53 | targetResourceID:string, 54 | vnetResourceID:string, 55 | vmResourceID:string, 56 | nicResourceID:string, 57 | location:string, 58 | zone:string, 59 | flowRecords:dynamic, 60 | Timestamp:string, 61 | SourceIP:string, 62 | DestinationIP:string, 63 | SrcPort:string, 64 | DstPort:string, 65 | L4Protocol:string, 66 | Direction:string, 67 | State:string, 68 | Encryption:string, 69 | PacketsSrcToDst:int, 70 | BytesSrcToDst:int, 71 | PacketsDstToSrc:int, 72 | BytesDstToSrc:int 73 | ) 74 | 75 | // Update policy for rawFlowLogs 76 | .alter table flowLogs policy update 77 | @'[{ "IsEnabled": true, "Source": "rawFlowLogs", "Query": "FlowLogMapping()", "IsTransactional": false, "PropagateIngestionProperties": false}]' 78 | 79 | //////////////////// 80 | // NSG Flow Logs // 81 | //////////////////// 82 | 83 | // Create Mapping function (NSG Flow Logs) 84 | .create-or-alter function 85 | with (docstring = 'Parses raw flowlogs records into strongly-typed columns', folder = 'FlowLogs') 86 | FlowLogMapping() { 87 | rawFlowLogs 88 | | mv-expand records = records 89 | | take 1 90 | | project Time = todatetime(records["time"]), 91 | macAddress = tostring(records["macAddress"]), 92 | category = tostring(records["category"]), 93 | flowLogVersion = toint(records["properties"]["Version"]), 94 | nsgResourceID = tostring(records["resourceId"]), 95 | flows = todynamic(records["properties"]["flows"]) 96 | | mv-expand flows 97 | | extend rule = tostring(flows["rule"]) 98 | | extend flowsFlows = todynamic(flows["flows"]) 99 | | mv-expand flowsFlows 100 | | extend macAddress2=tostring(flowsFlows["mac"]), 101 | flowsFlowsTuples = todynamic(flowsFlows["flowTuples"]) 102 | | mv-expand flowsFlowsTuples 103 | | extend fields = split(flowsFlowsTuples, ',') 104 | | extend Timestamp = tostring(fields[0]), 105 | srcIP = tostring(fields[1]), 106 | dstIP = tostring(fields[2]), 107 | srcPort = tostring(fields[3]), 108 | dstPort = tostring(fields[4]), 109 | Protocol = tostring(fields[5]), 110 | Direction = tostring(fields[6]), 111 | Decision = tostring(fields[7]), 112 | State = tostring(fields[8]), 113 | PacketsSrcToDst = toint(fields[9]), 114 | BytesSrcToDst = toint(fields[10]), 115 | PacketDstToSrc = toint(fields[11]), 116 | BytesDstToSrc = toint(fields[12]) 117 | | project-away flows, flowsFlows, flowsFlowsTuples,fields 118 | } 119 | 120 | // Create target table for NSG Flow Logs 121 | .drop table flowLogs 122 | .create table flowLogs ( 123 | Time:datetime, 124 | macAddress:string, 125 | category:string, 126 | flowLogVersion:int, 127 | nsgResourceID:string, 128 | flows:dynamic, 129 | rule:string, 130 | flowsFlows:dynamic, 131 | macAddress2:string, 132 | flowsFlowsTuples:dynamic, 133 | Timestamp:string, 134 | srcIP:string, 135 | dstIP:string, 136 | srcPort:string, 137 | dstPort:string, 138 | Protocol:string, 139 | Direction:string, 140 | Decision:string, 141 | State:string, 142 | PacketsSrcToDst:int, 143 | BytesSrcToDst:int, 144 | PacketsDstToSrc:int, 145 | BytesDstToSrc:int 146 | ) 147 | 148 | // Update policy for rawFlowLogs 149 | .alter table flowLogs policy update 150 | @'[{ "IsEnabled": true, "Source": "rawFlowLogs", "Query": "FlowLogMapping()", "IsTransactional": false, "PropagateIngestionProperties": false}]' -------------------------------------------------------------------------------- /flowlogs_index.ndjson: -------------------------------------------------------------------------------- 1 | {"attributes":{"fieldAttrs":"{\"@timestamp\":{\"count\":1},\"_index\":{\"count\":2},\"_score\":{\"count\":2},\"message\":{\"count\":3}}","fieldFormatMap":"{}","fields":"[]","name":"nsg-flow-logs-dataview","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"nsg-flow-logs-*","typeMeta":"{}"},"coreMigrationVersion":"8.6.2","created_at":"2023-03-16T14:49:45.128Z","id":"130ee8d1-b379-4eb2-bbcc-16375396dc49","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2023-03-17T09:09:53.528Z","version":"WzEzMTQsMV0="} 2 | {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} -------------------------------------------------------------------------------- /fortigate.azcli: -------------------------------------------------------------------------------- 1 | # Variables 2 | rg=fortigate 3 | location=westeurope 4 | vnet_name=hub 5 | vnet_prefix=10.1.0.0/16 6 | vnet_prefix_long='10.1.0.0 255.255.0.0' 7 | rs_subnet_name=RouteServerSubnet # Fixed name 8 | rs_subnet_prefix=10.1.0.0/24 9 | rs_subnet_prefix_long='10.1.0.0 255.255.255.0' 10 | rs_name=hubrs 11 | hub_vm_subnet_name=vm 12 | hub_vm_subnet_prefix=10.1.10.0/24 13 | # GatewaySubnet 14 | gw_subnet_prefix=10.1.254.0/24 15 | # Fortigate NVA 16 | publisher=fortinet 17 | offer=fortinet_fortigate-vm_v5 18 | sku=fortinet_fg-vm 19 | # fg_vm_size=Standard_F2s # F2s is the size recommended in the marketplace UI, but seems to only support 2 NICs? 20 | # fg_vm_size=Standard_F2s_v2 # F2s_v2 supports 2 NICs only as well 21 | fg_vm_size=Standard_B2ms # B2ms is not supported from a performance perspective, it supports 2 VMs though 22 | fg_username=$(whoami) 23 | fg_password=Microsoft123! 24 | hub_fgext_subnet_name=fgext # External 25 | hub_fgext_subnet_prefix=10.1.1.0/24 26 | hub_fgext_nsg_name=hubfgext-nsg 27 | hub_fgint_subnet_name=fgint # Internal 28 | hub_fgint_subnet_prefix=10.1.2.0/24 29 | hub_fgint_nsg_name=hubfgint-nsg 30 | hub_fgpro_subnet_name=fgpro # Protected 31 | hub_fgpro_subnet_prefix=10.1.3.0/24 32 | hub_fgpro_nsg_name=hubfgpro-nsg 33 | hub_fg_asn=65001 34 | hub_fg1_name=fg1 35 | hub_fg1_bgp_ip=10.1.2.11 36 | hub_fg1_pro_ip=10.1.3.11 37 | hub_fg2_name=fg2 38 | hub_fg2_bgp_ip=10.1.2.12 39 | hub_fg2_pro_ip=10.1.3.12 40 | 41 | #################### 42 | # Helper functions # 43 | #################### 44 | 45 | # Auxiliary function to get the first IP of a subnet (default gateway) 46 | function first_ip(){ 47 | subnet=$1 48 | IP=$(echo $subnet | cut -d/ -f 1) 49 | IP_HEX=$(printf '%.2X%.2X%.2X%.2X\n' `echo $IP | sed -e 's/\./ /g'`) 50 | NEXT_IP_HEX=$(printf %.8X `echo $(( 0x$IP_HEX + 1 ))`) 51 | NEXT_IP=$(printf '%d.%d.%d.%d\n' `echo $NEXT_IP_HEX | sed -r 's/(..)/0x\1 /g'`) 52 | echo "$NEXT_IP" 53 | } 54 | 55 | ################################# 56 | # Hub VNet, optionally with ARS # 57 | ################################# 58 | 59 | # Create Vnet 60 | az group create -n $rg -l $location 61 | az network vnet create -g $rg -n $vnet_name --address-prefix $vnet_prefix --subnet-name $rs_subnet_name --subnet-prefix $rs_subnet_prefix 62 | 63 | # Create additional subnets (no subnet can be created while the route server is being provisioned, same as VNGs) 64 | az network vnet subnet create -n $hub_fgext_subnet_name --address-prefix $hub_fgext_subnet_prefix --vnet-name $vnet_name -g $rg 65 | az network vnet subnet create -n $hub_fgint_subnet_name --address-prefix $hub_fgint_subnet_prefix --vnet-name $vnet_name -g $rg 66 | az network vnet subnet create -n $hub_fgpro_subnet_name --address-prefix $hub_fgpro_subnet_prefix --vnet-name $vnet_name -g $rg 67 | az network vnet subnet create -n $hub_vm_subnet_name --address-prefix $hub_vm_subnet_prefix --vnet-name $vnet_name -g $rg 68 | az network vnet subnet create -n GatewaySubnet --address-prefix $gw_subnet_prefix --vnet-name $vnet_name -g $rg 69 | 70 | # Create Route Server 71 | # rs_subnet_id=$(az network vnet subnet show -n $rs_subnet_name --vnet-name $vnet_name -g $rg --query id -o tsv) 72 | # az network routeserver create -n $rs_name -g $rg --hosted-subnet $rs_subnet_id -l $location 73 | # az network routeserver update -n $rs_name -g $rg --allow-b2b-traffic true # Optional 74 | # # If you need to delete it to recreate it again 75 | # # az network routeserver delete -n $rs_name -g $rg -y # Danger Zone! 76 | # # Get info (once created) 77 | # rs_ip1=$(az network routeserver show -n $rs_name -g $rg --query 'virtualRouterIps[0]' -o tsv) && echo $rs_ip1 78 | # rs_ip2=$(az network routeserver show -n $rs_name -g $rg --query 'virtualRouterIps[1]' -o tsv) && echo $rs_ip2 79 | # rs_asn=$(az network routeserver show -n $rs_name -g $rg --query 'virtualRouterAsn' -o tsv) && echo $rs_asn 80 | 81 | # Create test VM in hub 82 | az vm create -n hubvm -g $rg -l $location --image ubuntuLTS --generate-ssh-keys \ 83 | --public-ip-address hubvm-pip --vnet-name $vnet_name --size Standard_B1s --subnet $hub_vm_subnet_name 84 | hub_vm_ip=$(az network public-ip show -n hubvm-pip --query ipAddress -o tsv -g $rg) && echo $hub_vm_ip 85 | hub_vm_nic_id=$(az vm show -n hubvm -g "$rg" --query 'networkProfile.networkInterfaces[0].id' -o tsv) && echo $hub_vm_nic_id 86 | hub_vm_private_ip=$(az network nic show --ids $hub_vm_nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) && echo $hub_vm_private_ip 87 | 88 | #################### 89 | # Create Fortigate # 90 | #################### 91 | 92 | # Default gateways 93 | hub_fgext_default_gw=$(first_ip $hub_fgext_subnet_prefix) && echo $hub_fgext_default_gw # External 94 | hub_fgint_default_gw=$(first_ip $hub_fgint_subnet_prefix) && echo $hub_fgint_default_gw # Internal 95 | hub_fgpro_default_gw=$(first_ip $hub_fgpro_subnet_prefix) && echo $hub_fgpro_default_gw # Protected 96 | 97 | # Create hub Fortigate with 3 NICs 98 | version=$(az vm image list -p $publisher -f $offer -s $sku --all --query '[-1].version' -o tsv) && echo $version 99 | az vm image terms accept --urn ${publisher}:${offer}:${sku}:${version} 100 | 101 | # NSGs (104.21.25.86 and 172.67.133.228 are the addresses of ifconfig.co) 102 | az network nsg create -n $hub_fgext_nsg_name -g $rg -l $location 103 | myip=$(curl -s4 ifconfig.co) && echo $myip 104 | az network nsg rule create -n Internet2VnetInbound --nsg-name $hub_fgext_nsg_name -g $rg \ 105 | --protocol '*' --access Allow --priority 1010 --direction Inbound \ 106 | --source-address-prefixes 8.8.8.8/32 104.21.25.86/32 172.67.133.228/32 --source-port-ranges '*' \ 107 | --destination-address-prefixes VirtualNetwork --destination-port-ranges '*' 108 | az network nsg rule create -n SSHInbound --nsg-name $hub_fgext_nsg_name -g $rg \ 109 | --protocol 'TCP' --access Allow --priority 1020 --direction Inbound \ 110 | --source-address-prefixes "${myip}/32" --source-port-ranges '*' \ 111 | --destination-address-prefixes VirtualNetwork --destination-port-ranges '22' 112 | az network nsg create -n $hub_fgint_nsg_name -g $rg -l $location 113 | az network nsg rule create -n Vnet2InternetInbound --nsg-name $hub_fgint_nsg_name -g $rg \ 114 | --protocol '*' --access Allow --priority 1010 --direction Inbound \ 115 | --source-address-prefixes VirtualNetwork --source-port-ranges '*' \ 116 | --destination-address-prefixes Internet --destination-port-ranges '*' 117 | az network nsg rule create -n Internet2VnetOutbound --nsg-name $hub_fgint_nsg_name -g $rg \ 118 | --protocol '*' --access Allow --priority 1010 --direction Outbound \ 119 | --source-address-prefixes 8.8.8.8/32 104.21.25.86/32 172.67.133.228/32 --source-port-ranges '*' \ 120 | --destination-address-prefixes VirtualNetwork --destination-port-ranges '*' 121 | # PIP 122 | az network public-ip create -g $rg -n "${hub_fg1_name}-pip" --sku basic --allocation-method Static 123 | # NICs 124 | az network nic create -n "${hub_fg1_name}-nic0" -g $rg --vnet-name $vnet_name --subnet $hub_fgext_subnet_name --network-security-group "$hub_fgext_nsg_name" --public-ip-address "${hub_fg1_name}-pip" --ip-forwarding 125 | az network nic create -n "${hub_fg1_name}-nic1" -g $rg --vnet-name $vnet_name --subnet $hub_fgint_subnet_name --network-security-group "$hub_fgint_nsg_name" --private-ip-address $hub_fg1_bgp_ip --ip-forwarding 126 | az network nic create -n "${hub_fg1_name}-nic2" -g $rg --vnet-name $vnet_name --subnet $hub_fgpro_subnet_name --network-security-group "$hub_fgint_nsg_name" --private-ip-address $hub_fg1_pro_ip --ip-forwarding 127 | # Fortigate VM 128 | az vm create -n $hub_fg1_name -g $rg -l $location --size $fg_vm_size \ 129 | --image ${publisher}:${offer}:${sku}:${version} \ 130 | --admin-username "$fg_username" --admin-password $fg_password --authentication-type all --generate-ssh-keys \ 131 | --nics "${hub_fg1_name}-nic0" "${hub_fg1_name}-nic1" "${hub_fg1_name}-nic2" 132 | # Test access over SSH 133 | hub_fg1_ip=$(az network public-ip show -n "${hub_fg1_name}-pip" --query ipAddress -o tsv -g $rg) && echo $hub_fg1_ip 134 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no "$hub_fg1_ip" "get system interface physical" 135 | 136 | # Get license keys (previously stored in an AKV) 137 | keyvault_name=erjositoKeyvault 138 | license1_secret_name=fortigatelicense1 139 | license2_secret_name=fortigatelicense2 140 | license1=$(az keyvault secret show --vault-name $keyvault_name -n $license1_secret_name --query 'value' -o tsv) 141 | license2=$(az keyvault secret show --vault-name $keyvault_name -n $license2_secret_name --query 'value' -o tsv) 142 | if [[ -n "$license1" ]] && [[ -n "$license2" ]] 143 | then 144 | echo "Fortigate licenses successfully retrieved from Azure Key Vault $keyvault_name" 145 | else 146 | echo "Fortigate licenses could NOT be retrieved from Azure Key Vault $keyvault_name" 147 | fi 148 | 149 | # Install license in Fortigate NVA 150 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no "$hub_fg1_ip" "exec forticarrier-license $license1" 151 | 152 | # Config BGP 153 | ssh -o BatchMode=yes -o StrictHostKeyChecking=no "$hub_fg1_ip" < -s ') 27 | sys.exit() 28 | else: 29 | print ('Getting secret', secret_name, 'from Azure Key Vault', akv_name) 30 | # Get secret 31 | akv_uri = f"https://{akv_name}.vault.azure.net" 32 | credential = DefaultAzureCredential() 33 | client = SecretClient(vault_url=akv_uri, credential=credential) 34 | secret_value = client.get_secret(secret_name) 35 | 36 | # Debug: print secret 37 | print('Secret value:', secret_value.value) 38 | 39 | if __name__ == "__main__": 40 | main(sys.argv[1:]) 41 | -------------------------------------------------------------------------------- /hubandspoke_simple.azcli: -------------------------------------------------------------------------------- 1 | # Create a simple hub and spoke environment 2 | # Used for FTA Live sessions 3 | 4 | # Variables 5 | rg=ftalive 6 | location=westeurope 7 | hub_vnet_name=hubvnet 8 | hub_vnet_prefix=10.1.0.0/24 9 | hub_subnet_name=vm 10 | hub_subnet_prefix=10.1.0.0/26 11 | spoke_vnet_name=spokevnet 12 | spoke_vnet_prefix=10.1.1.0/24 13 | spoke_subnet_name=vm 14 | spoke_subnet_prefix=10.1.1.0/26 15 | vm_size=Standard_B1s 16 | hub_vm_name=hubvm 17 | spoke_vm_name=spokevm 18 | spoke_rt_name=spokert 19 | 20 | # Create environment 21 | echo "Creating RG and VNets..." 22 | az group create -n $rg -l $location -o none 23 | az network vnet create -g $rg -n $hub_vnet_name --address-prefix $hub_vnet_prefix --subnet-name $hub_subnet_name --subnet-prefix $hub_subnet_prefix -l $location -o none 24 | az network vnet create -g $rg -n $spoke_vnet_name --address-prefix $spoke_vnet_prefix --subnet-name $spoke_subnet_name --subnet-prefix $spoke_subnet_prefix -l $location -o none 25 | echo "Creating Virtual machines..." 26 | az vm create -n $hub_vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys -o none --public-ip-sku Standard \ 27 | --public-ip-address "${hub_vm_name}-pip" --vnet-name $hub_vnet_name --size $vm_size --subnet $hub_subnet_name -l $location --no-wait 28 | az vm create -n $spoke_vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys -o none --public-ip-sku Standard \ 29 | --public-ip-address "${spoke_vm_name}-pip" --vnet-name $spoke_vnet_name --size $vm_size --subnet $spoke_subnet_name -l $location --no-wait 30 | echo "Peering VNets..." 31 | az network vnet peering create -n hub2spoke -g $rg --vnet-name $hub_vnet_name --remote-vnet $spoke_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 32 | az network vnet peering create -n spoke2hub -g $rg --vnet-name $spoke_vnet_name --remote-vnet $hub_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 33 | echo "Configuring VMs..." 34 | hub_nic_id=$(az vm show -n $hub_vm_name -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 35 | az network nic update --ids $hub_nic_id --ip-forwarding -o none 36 | hub_private_ip=$(az network nic show --ids $hub_nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) 37 | az network route-table create -n $spoke_rt_name -g $rg -l $location -o none 38 | az network route-table route create --route-table-name $spoke_rt_name -g $rg --address-prefix '0.0.0.0/0' -n default --next-hop-type VirtualAppliance --next-hop-ip-address $hub_private_ip -o none 39 | myip=$(curl -s4 ifconfig.co) 40 | az network route-table route create -n mypc -g $rg --route-table-name $spoke_rt_name --address-prefix "${myip}/32" --next-hop-type Internet -o none 41 | az network vnet subnet update -g $rg --vnet-name $spoke_vnet_name -n $spoke_subnet_name --route-table $spoke_rt_name -o none 42 | hub_pip=$(az network public-ip show -n "${hub_vm_name}-pip" -g $rg --query 'ipAddress' -o tsv) 43 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hub_pip "sudo sysctl -w net.ipv4.ip_forward=1" 44 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hub_pip "sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE" 45 | 46 | # Update route table with new IP (might be necessary if the local PIP changed) 47 | myip=$(curl -s4 ifconfig.co) 48 | az network route-table route update -n mypc -g $rg --route-table-name $spoke_rt_name --address-prefix "${myip}/32" --next-hop-type Internet -o none 49 | 50 | # Update hub VM NSG 51 | hub_nsg_id=$(az network nic show --ids $hub_nic_id --query 'networkSecurityGroup.id' -o tsv) 52 | hub_nsg_name=$(echo $hub_nsg_id | cut -d/ -f 9) 53 | az network nsg rule create --nsg-name $hub_nsg_name -g $rg -n Allow_Inbound_From_RFC1918 --priority 1010 \ 54 | --access Allow --protocol '*' --source-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --direction Inbound \ 55 | --destination-address-prefixes '*' --destination-port-ranges '*' -o none 56 | 57 | # Diagnostics 58 | hub_nic_id=$(az vm show -n $hub_vm_name -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 59 | spoke_nic_id=$(az vm show -n $spoke_vm_name -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 60 | hub_pip=$(az network public-ip show -n "${hub_vm_name}-pip" -g $rg --query 'ipAddress' -o tsv) 61 | spoke_pip=$(az network public-ip show -n "${spoke_vm_name}-pip" -g $rg --query 'ipAddress' -o tsv) 62 | az vm list-ip-addresses -o table -g $rg 63 | az network route-table list -g $rg -o table 64 | az network route-table route list --route-table-name $spoke_rt_name -g $rg -o table 65 | az network nic show-effective-route-table --ids $hub_nic_id -o table 66 | az network nic show-effective-route-table --ids $spoke_nic_id -o table 67 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hub_pip "curl -s4 ifconfig.co" 68 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hub_pip "sysctl net.ipv4.ip_forward" 69 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hub_pip "sudo iptables -L -t nat" 70 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $spoke_pip "curl -s4 ifconfig.co" 71 | az network vnet subnet show -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --query routeTable -o tsv 72 | 73 | # Cleanup 74 | # az group delete -y -n $rg --no-wait 75 | -------------------------------------------------------------------------------- /ipranges.sh: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # March 2020 4 | # 5 | # The script reads a JSON file with Azure's IP addresses and does something 6 | # with it, in this case confiuguring UDRs 7 | ############################################################################ 8 | 9 | # Configure routes to Azure Service (Azure Batch mgmt nodes in this example) 10 | # Variables 11 | rg=myrg 12 | rt=myroutetable 13 | printonly=yes 14 | # URLs 15 | url1=https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519 16 | url2=$(curl -Lfs "${url1}" | grep -Eoi ']+>' | grep -Eo 'href="[^\"]+"' | grep "download.microsoft.com/download/" | grep -m 1 -Eo '(http|https)://[^"]+') 17 | prefixes_json=$(curl -s $url2) 18 | # All categories 19 | categories=$(echo $prefixes_json | jq -rc '.values[] | .name') 20 | # Find category for a prefix 21 | prefix="20.36.105.0" 22 | echo $prefixes_json | jq -rc ".values[] | select(.properties.addressPrefixes[] | contains (\"$prefix\")) | .name" 23 | # Find complete prefix, for a partial one 24 | echo $prefixes_json | grep -i $prefix 25 | # Find prefixes for a category 26 | category=BatchNodeManagement.WestEurope 27 | prefixes=$(echo $prefixes_json | jq -rc ".values[] | select(.name | contains (\"$category\")) | .properties.addressPrefixes[]") 28 | echo $prefixes 29 | # Browse prefixes 30 | i=0 31 | while IFS= read -r prefix; do 32 | echo "$((i++)): $prefix" 33 | done <<< "$prefixes" 34 | 35 | # Check 36 | az network route-table route list -g $rg --route-table-name $rt -o table -------------------------------------------------------------------------------- /linuxnva_autoconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ -f /root/azure.nva.sas ]] && [[ -f /root/azure.nva.account ]] && [[ -f /root/azure.nva.guid ]] 3 | then 4 | echo "INFO: Configuration files found" 5 | sas=$(sudo cat /root/azure.nva.sas) 6 | storage_account_name=$(sudo cat /root/azure.nva.account) 7 | storage_container_name=$(sudo cat /root/azure.nva.guid) 8 | wget "https://${storage_account_name}.blob.core.windows.net/${storage_container_name}/ipsec.conf?${sas}" -O ./ipsec.conf 9 | wget "https://${storage_account_name}.blob.core.windows.net/${storage_container_name}/ipsec.secrets?${sas}" -O ./ipsec.secrets 10 | wget "https://${storage_account_name}.blob.core.windows.net/${storage_container_name}/bird.conf?${sas}" -O ./bird.conf 11 | wget "https://${storage_account_name}.blob.core.windows.net/${storage_container_name}/vti.csv?${sas}" -O ./vti.csv.new 12 | if [[ -n "$(diff ./ipsec.conf /etc/ipsec.conf)" ]] || [[ -n "$(diff ./ipsec.secrets /etc/ipsec.secrets)" ]] 13 | then 14 | sudo cp ./ipsec.conf /etc/ipsec.conf 15 | sudo cp ./ipsec.secrets /etc/ipsec.secrets 16 | sudo systemctl restart ipsec 17 | fi 18 | if [[ -n "$(diff ./bird.conf /etc/bird/bird.conf)" ]] 19 | then 20 | sudo cp ./bird.conf /etc/bird/bird.conf 21 | sudo systemctl restart bird 22 | fi 23 | touch ./vti.csv 24 | if [[ -n "$(diff ./vti.csv.new ./vti.csv)" ]] 25 | then 26 | while read line; do 27 | local_pip=$(echo "$line" | cut -d, -f 1) 28 | local_ip=$(echo "$line" | cut -d, -f 2) 29 | remote_pip=$(echo "$line" | cut -d, -f 3) 30 | remote_ip=$(echo "$line" | cut -d, -f 4) 31 | if_name=$(echo "$line" | cut -d, -f 5) 32 | if_mark=$(echo "$line" | cut -d, -f 6) 33 | sudo ip tunnel add "$if_name" local "$local_ip" remote "$remote_pip" mode vti key "$if_mark" 34 | sudo ip link set up dev "$if_name" 35 | sudo sysctl -w "net.ipv4.conf.${if_name}.disable_policy=1" 36 | sudo ip route add "${remote_ip}/32" dev "${if_name}" 37 | sudo sed -i 's/# install_routes = yes/install_routes = no/' /etc/strongswan.d/charon.conf 38 | sudo systemctl restart ipsec 39 | done <./vti.csv.new 40 | mv ./vti.csv.new ./vti.csv 41 | else 42 | rm ./vti.csv.new 43 | fi 44 | else 45 | echo "ERROR: Configuration files not found" 46 | fi -------------------------------------------------------------------------------- /mrt2azmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os, sys, getopt, json, collections, requests 4 | import datetime, hashlib, hmac, base64 5 | import mrtparse 6 | from azure.keyvault.secrets import SecretClient 7 | from azure.identity import DefaultAzureCredential 8 | 9 | # Default values 10 | default_mrt_file = "/tmp/bird-mrtdump_bgp" 11 | consolidated_mrt_file = "/var/log/bird.mrt" 12 | temp_mrt_file = "/tmp/bird-mrtdump_bgp.tmp" 13 | log_type = 'BgpAnalytics' 14 | send_keepalives = True 15 | 16 | # Build signature to authenticate message 17 | # See https://docs.microsoft.com/azure/azure-monitor/logs/data-collector-api 18 | def build_signature(customer_id, shared_key, date, content_length, method, content_type, resource): 19 | x_headers = 'x-ms-date:' + date 20 | string_to_hash = method + "\n" + str(content_length) + "\n" + content_type + "\n" + x_headers + "\n" + resource 21 | bytes_to_hash = bytes(string_to_hash, encoding="utf-8") 22 | decoded_key = base64.b64decode(shared_key) 23 | encoded_hash = base64.b64encode(hmac.new(decoded_key, bytes_to_hash, digestmod=hashlib.sha256).digest()).decode() 24 | authorization = "SharedKey {}:{}".format(customer_id,encoded_hash) 25 | return authorization 26 | 27 | # Build and send a request to the POST API 28 | # See https://docs.microsoft.com/azure/azure-monitor/logs/data-collector-api 29 | def post_data(customer_id, shared_key, body, log_type): 30 | method = 'POST' 31 | content_type = 'application/json' 32 | resource = '/api/logs' 33 | rfc1123date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') 34 | content_length = len(body) 35 | signature = build_signature(customer_id, shared_key, rfc1123date, content_length, method, content_type, resource) 36 | uri = 'https://' + customer_id + '.ods.opinsights.azure.com' + resource + '?api-version=2016-04-01' 37 | # Headers 38 | headers = { 39 | 'content-type': content_type, 40 | 'Authorization': signature, 41 | 'Log-Type': log_type, 42 | 'x-ms-date': rfc1123date 43 | } 44 | # Send POST request 45 | response = requests.post(uri,data=body, headers=headers) 46 | if (response.status_code >= 200 and response.status_code <= 299): 47 | print('INFO: API request to Azure Monitor accepted') 48 | else: 49 | print(f'ERROR: Response code: {response.status_code}, Response body: {response.content}') 50 | 51 | # Only 1-level JSON is accepted by Log Analytics 52 | def flatten(d, parent_key=None, items=None): 53 | if items == None: 54 | items = {} 55 | # print(f'Called flatten on dictionary {str(d)}, parent_key is {parent_key}, items is {str(items)}') 56 | for key in d: 57 | # print(f'Processing key {key}, type is {str(type(d[key]))}...') 58 | if parent_key == None: 59 | new_key = key 60 | else: 61 | new_key = parent_key + '_' + key 62 | if type(d[key]) == collections.OrderedDict: 63 | items = flatten (d[key], parent_key=new_key, items=items) 64 | elif type(d[key]) == list: 65 | if len(d[key]) > 0: 66 | if type(d[key][0]) == collections.OrderedDict: 67 | i=0 68 | for element in d[key]: 69 | element_parent_key = new_key + '_' + str(i) 70 | items = flatten(d[key][i], parent_key=element_parent_key, items=items) 71 | i += 1 72 | else: 73 | # If it is a value, such as a list of ASN in the ASpath, concat everything 74 | if key == "value": 75 | separator = ' ' 76 | items[new_key] = separator.join(map(str, d[key])) 77 | # Otherwise it might be a code/translation value pair 78 | elif len(d[key]) == 2: 79 | items[new_key + '_code'] = d[key][0] 80 | items[new_key] = d[key][1] 81 | # Otherwise, concatenate too 82 | else: 83 | separator = ' ' 84 | items[new_key] = separator.join(map(str, d[key])) 85 | else: 86 | items[new_key] = d[key] 87 | # print (json.dumps(items)) 88 | return items 89 | 90 | # Main 91 | def main(argv): 92 | # Get arguments 93 | akv_name = None 94 | mrt_file = default_mrt_file 95 | dry_run = False 96 | try: 97 | opts, args = getopt.getopt(argv,"hdv:f:",["help", "dry-run", "vault-name=", "mrt-file="]) 98 | except getopt.GetoptError: 99 | print ('Options: -v -f ') 100 | sys.exit(2) 101 | for opt, arg in opts: 102 | if opt == '-h': 103 | print ('Options: -v -f ') 104 | sys.exit() 105 | if opt in ("-d", "--dry-run"): 106 | print ("INFO: running in dry-run mode") 107 | dry_run = True 108 | elif opt in ("-v", "--vault-name"): 109 | akv_name = arg 110 | elif opt in ("-f", "--mrt-file"): 111 | mrt_file = arg 112 | # Print vault name 113 | if (akv_name == None): 114 | print ('Options: -v -f ') 115 | sys.exit() 116 | else: 117 | print ('INFO: Getting configuration from Azure Key Vault', akv_name) 118 | # Get secrets 119 | akv_uri = f"https://{akv_name}.vault.azure.net" 120 | credential = DefaultAzureCredential() 121 | client = SecretClient(vault_url=akv_uri, credential=credential) 122 | logws_id = client.get_secret('bgp-logws-id').value 123 | logws_key = client.get_secret('bgp-logws-key').value 124 | 125 | # Debug: print configuration 126 | print('INFO: Log Analytics workspace is', logws_id, 'and key is', logws_key) 127 | 128 | # Only do something if file is actually not empty 129 | if os.stat(mrt_file).st_size > 0: 130 | 131 | # Move mrt_file to temp_mrt_file, and append it to the consolidated_mrt_file 132 | os.system(f'cat {mrt_file} >{temp_mrt_file}') 133 | os.system(f'> {mrt_file}') 134 | os.system(f'cat {temp_mrt_file} >> {consolidated_mrt_file}') 135 | 136 | # Analyze temp MRT file and dump JSON into a flattened string variable 137 | body='[' 138 | entry_no = 0 139 | keepalive_no = 0 140 | for entry in mrtparse.Reader(temp_mrt_file): 141 | entry_no += 1 142 | # Do not log keepalives 143 | add_entry = True 144 | try: 145 | if entry.data['bgp_message']['type'][1] == 'KEEPALIVE': 146 | add_entry = send_keepalives 147 | keepalive_no += 1 148 | except: 149 | pass 150 | if add_entry: 151 | bgp_entry=flatten(entry.data) 152 | bgp_entry['raw']=str(json.dumps(entry.data)) # Add raw JSON for troubleshooting 153 | if dry_run: 154 | print(bgp_entry) 155 | # First run 156 | if body == '[': 157 | body += json.dumps(bgp_entry) 158 | # After the first run, append a comma and a line break to keep JSON syntax 159 | else: 160 | body += ',\n' 161 | body += json.dumps(bgp_entry) 162 | body += ']' 163 | 164 | if dry_run: 165 | # Print the JSON variable 166 | # print('INFO: Dry-run mode. Data to send:') 167 | # print(body) 168 | print(f'INFO: {entry_no} BGP messages analyzed, out of which {keepalive_no} were keepalives') 169 | else: 170 | # Send message to Azure Monitor 171 | post_data(logws_id, logws_key, body, log_type) 172 | else: 173 | print (f'INFO: MRT file {mrt_file} is empty, not sending any logs') 174 | 175 | if __name__ == "__main__": 176 | main(sys.argv[1:]) 177 | -------------------------------------------------------------------------------- /myip.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket, struct 3 | import sys 4 | import time 5 | import warnings 6 | import requests 7 | from flask import Flask 8 | from flask import request 9 | from flask import jsonify 10 | 11 | # Return True if IP address is valid 12 | def is_valid_ipv4_address(address): 13 | try: 14 | socket.inet_pton(socket.AF_INET, address) 15 | except AttributeError: # no inet_pton here, sorry 16 | try: 17 | socket.inet_aton(address) 18 | except socket.error: 19 | return False 20 | return address.count('.') == 3 21 | except socket.error: # not a valid address 22 | return False 23 | return True 24 | 25 | # Get IP for a DNS name 26 | def get_ip(d): 27 | try: 28 | return socket.gethostbyname(d) 29 | except Exception: 30 | return False 31 | 32 | app = Flask(__name__) 33 | 34 | # Get IP addresses of DNS servers 35 | def get_dns_ips(): 36 | dns_ips = [] 37 | with open('/etc/resolv.conf') as fp: 38 | for cnt, line in enumerate(fp): 39 | columns = line.split() 40 | if columns[0] == 'nameserver': 41 | ip = columns[1:][0] 42 | if is_valid_ipv4_address(ip): 43 | dns_ips.append(ip) 44 | return dns_ips 45 | 46 | # Get default gateway 47 | def get_default_gateway(): 48 | """Read the default gateway directly from /proc.""" 49 | with open("/proc/net/route") as fh: 50 | for line in fh: 51 | fields = line.strip().split() 52 | if fields[1] != '00000000' or not int(fields[3], 16) & 2: 53 | continue 54 | 55 | return socket.inet_ntoa(struct.pack(" $server_cloudinit_filename 54 | #cloud-config 55 | runcmd: 56 | - apt update && apt install -y python3-pip nginx 57 | - pip3 install flask 58 | - wget https://raw.githubusercontent.com/erjosito/azcli/master/myip.py -O /root/myip.py 59 | - python3 /root/myip.py & 60 | EOF 61 | echo "Creating server VM..." 62 | az vm create -n server -g $rg --image UbuntuLTS --generate-ssh-keys --size $vm_size \ 63 | --vnet-name $server_vnet_name --subnet $server_subnet_name --nsg server-nsg --public-ip-address server-pip \ 64 | --custom-data $server_cloudinit_filename -o none 65 | server_private_ip=$(az vm show -g $rg -n server -d --query privateIps -o tsv) && echo $server_private_ip 66 | 67 | # Create NVAs 68 | cat < $nva_cloudinit_file 69 | #cloud-config 70 | runcmd: 71 | - apt update && apt install -y bird strongswan 72 | - sysctl -w net.ipv4.ip_forward=1 73 | - sysctl -w net.ipv4.conf.all.accept_redirects=0 74 | - sysctl -w net.ipv4.conf.all.send_redirects=0 75 | EOF 76 | # NSG for onprem NVA 77 | echo "Creating NSG nva-nsg..." 78 | az network nsg create -n nva-nsg -g $rg -l $location -o none 79 | az network nsg rule create -n SSHin --nsg-name nva-nsg -g $rg --priority 1000 --destination-port-ranges 22 --access Allow --protocol Tcp -o none 80 | az network nsg rule create -n WebRFC1918in --nsg-name nva-nsg -g $rg --priority 1010 --destination-port-ranges 8080 --access Allow --protocol Tcp -o none 81 | az network nsg rule create -n ICMP --nsg-name nva-nsg -g $rg --priority 1020 --destination-port-ranges '*' --access Allow --protocol Icmp -o none 82 | echo "Creating VM nva01..." 83 | az vm create -n nva01 -g $rg -l $location --image ubuntuLTS --generate-ssh-keys \ 84 | --public-ip-address nva01_pip --public-ip-sku Standard --vnet-name $nva_vnet_name --size $nva_size --subnet $nva_subnet_name \ 85 | --custom-data $nva_cloudinit_file --nsg nva-nsg -o none 86 | nva01_nic_id=$(az vm show -n nva01 -g "$rg" --query 'networkProfile.networkInterfaces[0].id' -o tsv) 87 | az network nic update --ids $nva01_nic_id --ip-forwarding -o none 88 | echo "Creating VM nva02..." 89 | az vm create -n nva02 -g $rg -l $location --image ubuntuLTS --generate-ssh-keys \ 90 | --public-ip-address nva02_pip --public-ip-sku Standard --vnet-name $nva_vnet_name --size $nva_size --subnet $nva_subnet_name \ 91 | --custom-data $nva_cloudinit_file --nsg nva-nsg -o none 92 | nva02_nic_id=$(az vm show -n nva02 -g "$rg" --query 'networkProfile.networkInterfaces[0].id' -o tsv) 93 | az network nic update --ids $nva02_nic_id --ip-forwarding -o none 94 | 95 | # Create NVA LB and private link service 96 | echo "Creating LB..." 97 | az network lb create -g $rg -n nvalb --sku Standard --vnet-name $nva_vnet_name \ 98 | --frontend-ip-name frontend --subnet $nva_subnet_name --backend-pool-name nva -o none 99 | az network lb probe create -g $rg --lb-name nvalb -n port22 --protocol tcp --port 22 -o none 100 | az network lb rule create -n HAports -g $rg --lb-name nvalb --protocol All --frontend-port 0 --backend-port 0 \ 101 | --frontend-ip-name frontend --backend-pool-name nva --probe-name port22 -o none 102 | nvalb_backend_id=$(az network lb address-pool show -n nva --lb-name nvalb -g $rg --query id -o tsv) 103 | nvalb_lb_ip=$(az network lb frontend-ip show -n frontend --lb-name nvalb -g $rg --query privateIpAddress -o tsv) && echo "$nvalb_lb_ip" 104 | az network private-link-service create -n nvapls -g $rg --vnet-name $nva_vnet_name --subnet $pls_subnet_name \ 105 | --lb-name nvalb --lb-frontend-ip-configs frontend -o none 106 | pls_id=$(az network private-link-service show -n nvapls -g $rg --query id -o tsv) 107 | nva01_ipconfig_name=$(az network nic show --ids $nva01_nic_id --query 'ipConfigurations[0].name' -o tsv) 108 | nva01_nic_name=$(echo $nva01_nic_id | cut -d/ -f 9) 109 | az network nic ip-config address-pool add --nic-name $nva01_nic_name -g $rg --ip-config-name $nva01_ipconfig_name --lb-name nvalb --address-pool nva -o none 110 | nva02_ipconfig_name=$(az network nic show --ids $nva02_nic_id --query 'ipConfigurations[0].name' -o tsv) 111 | nva02_nic_name=$(echo $nva02_nic_id | cut -d/ -f 9) 112 | az network nic ip-config address-pool add --nic-name $nva02_nic_name -g $rg --ip-config-name $nva02_ipconfig_name --lb-name nvalb --address-pool nva -o none 113 | 114 | # Create private link endpoint in client vnet 115 | az network private-endpoint create -n nvape -g $rg --vnet-name $client_vnet_name --subnet $ple_subnet_name \ 116 | --private-connection-resource-id $pls_id --connection-name toNVApls --manual-request false -o none 117 | ple_nic_id=$(az network private-endpoint show -n nvape -g $rg --query 'networkInterfaces[0].id' -o tsv) 118 | ple_ip=$(az network nic show --ids $ple_nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) 119 | 120 | # Route traffic from client to server over Private Endpoint 121 | echo "Creating route table..." 122 | az network route-table create -n clientrt -g $rg -l $location -o none 123 | az network route-table route create -g $rg --route-table-name clientrt -n default \ 124 | --next-hop-type VirtualAppliance --address-prefix $server_vnet_prefix --next-hop-ip-address $ple_ip -o none 125 | az network vnet subnet update -n $client_subnet_name --vnet-name $client_vnet_name -g $rg --route-table clientrt -o none 126 | 127 | # Test 128 | client_pip=$(az network public-ip show -n client-pip -g $rg --query ipAddress -o tsv) 129 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $client_pip "nc -vz $server_private_ip 22" 130 | 131 | ############### 132 | # Diagnostics # 133 | ############### 134 | 135 | # Effective routes 136 | client_nic_id=$(az vm show -n client -g "$rg" --query 'networkProfile.networkInterfaces[0].id' -o tsv) 137 | az network nic show-effective-route-table --ids $client_nic_id -o table 138 | 139 | # LB 140 | az network lb address-pool address list -g $rg --lb-name nvalb --pool-name nva -o table 141 | az network nic show --ids $nva01_nic_id --query 'ipConfigurations[0].loadBalancerBackendAddressPools[0].id' -o tsv 142 | az network nic show --ids $nva02_nic_id --query 'ipConfigurations[0].loadBalancerBackendAddressPools[0].id' -o tsv -------------------------------------------------------------------------------- /routeserver-vmss-selfcontained-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Initialization 4 | log_file='/root/routeserver.log' 5 | 6 | # Function to add date to log message 7 | function adddate() { 8 | while IFS= read -r line; do 9 | printf '%s %s\n' "$(date --iso-8601=seconds)" "$line"; 10 | done 11 | } 12 | 13 | # Start 14 | echo "Starting configuration process..." | adddate >>$log_file 15 | # Read metadata 16 | metadata=$(curl -s -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01") 17 | # echo "$metadata" | adddate >>$log_file 18 | az login --identity -o none 19 | subscription_id=$(az account show --query id -o tsv) 20 | echo "Logged into subscription ID $subscription_id" | adddate >>$log_file 21 | myname=$(echo "$metadata" | jq -r '.compute.name') 22 | rg=$(echo "$metadata" | jq -r '.compute.resourceGroupName') 23 | 24 | # Get RS name (assuming it is the only one in the same RG) 25 | rs_name=$(az network routeserver list -g "$rg" --query '[0].name' -o tsv) 26 | 27 | # Get local IP and ASN 28 | myasn=$(grep 'local as' /etc/bird/bird.conf.template | head -1) 29 | myasn=$(echo "$myasn" | awk '{print $3}' | cut -d ';' -f 1) 30 | myip=$(hostname -I | tr -d ' ') 31 | 32 | # Sometimes cloudinit doesnt make it on time to create the /etc/bird/bird.conf.template file 33 | if [[ -z "$myasn" ]]; then 34 | sleep 30 35 | myasn=$(grep 'local as' /etc/bird/bird.conf.template | head -1) 36 | myasn=$(echo "$myasn" | awk '{print $3}' | cut -d ';' -f 1) 37 | fi 38 | 39 | if [[ -z "$myasn" ]] || [[ -z "$rs_name" ]] || [[ -z "$subscription_id" ]]; then 40 | echo "Could not retrieve required variables, exiting now..." | adddate >>$log_file 41 | exit 1 42 | fi 43 | 44 | # Look for another peer with the same IP 45 | existing_peer=$(az network routeserver peering list --routeserver "$rs_name" -g "$rg" --query "[?peerIp=='$myip']" -o json) 46 | existing_peer_name=$(echo "$existing_peer" | jq -r '.[0].name' 2>/dev/null) 47 | existing_peer_asn=$(echo "$existing_peer" | jq -r '.[0].peerAsn' 2>/dev/null) 48 | existing_peer_state=$(echo "$existing_peer" | jq -r '.[0].provisioningState' 2>/dev/null) 49 | if [[ -n $existing_peer_name ]]; then 50 | if [[ "$existing_peer_name" == "$myname" ]] && [[ "$existing_peer_asn" == "$myasn" ]]; then 51 | if [[ "$existing_peer_state" == "Failed" ]]; then 52 | echo "Peer $existing_peer_name already found in ARS $rs_name with state $existing_peer_state, deleting and recreating..." | adddate >>$log_file 53 | az network routeserver peering delete --routeserver "$rs_name" -g "$rg" -n "$existing_peer_name" -y -o none 54 | az network routeserver peering create --routeserver "$rs_name" -g "$rg" --peer-ip "$myip" --peer-asn "$myasn" -n "$myname" -o none 55 | else 56 | echo "Peer $existing_peer_name already found in ARS $rs_name with state $existing_peer_state, no need to do anything" | adddate >>$log_file 57 | fi 58 | else 59 | echo "Deleting existing peer $existing_peer_name with IP $myip and ASN $existing_peer_asn, does not match $myname and $myasn..." | adddate >>$log_file 60 | az network routeserver peering delete --routeserver "$rs_name" -g "$rg" -n "$existing_peer_name" -y -o none 61 | echo "Configuring ARS $rs_name in RG $rg to peer to $myname on IP address $myip and ASN $myasn..." | adddate >>$log_file 62 | az network routeserver peering create --routeserver "$rs_name" -g "$rg" --peer-ip "$myip" --peer-asn "$myasn" -n "$myname" -o none 63 | fi 64 | else 65 | # Look for an existing peer with the same name 66 | echo "No existing RS peer found with the IP address $myip" | adddate >>$log_file 67 | existing_peer=$(az network routeserver peering list --routeserver "$rs_name" -g "$rg" --query "[?name=='$myname']" -o json) 68 | existing_peer_ip=$(echo "$existing_peer" | jq -r '.[0].peerIp' 2>/dev/null) 69 | existing_peer_asn=$(echo "$existing_peer" | jq -r '.[0].peerAsn' 2>/dev/null) 70 | existing_peer_state=$(echo "$existing_peer" | jq -r '.[0].provisioningState' 2>/dev/null) 71 | if [[ -n $existing_peer_ip ]]; then 72 | if [[ "$existing_peer_ip" == "$myip" ]] && [[ "$existing_peer_asn" == "$myasn" ]]; then 73 | if [[ "$existing_peer_state" == "Failed" ]]; then 74 | echo "Peer $myname already found in ARS $rs_name with state $existing_peer_state, deleting and recreating..." | adddate >>$log_file 75 | az network routeserver peering delete --routeserver "$rs_name" -g "$rg" -n "$myname" -y -o none 76 | az network routeserver peering create --routeserver "$rs_name" -g "$rg" --peer-ip "$myip" --peer-asn "$myasn" -n "$myname" -o none 77 | else 78 | echo "Peer $existing_peer_name already found in ARS $rs_name, no need to do anything" | adddate >>$log_file 79 | fi 80 | else 81 | echo "Deleting existing peer $myname with IP $existing_peer_ip ASN $existing_peer_asn, does not match $myip and $myasn..." | adddate >>$log_file 82 | az network routeserver peering delete --routeserver "$rs_name" -g "$rg" -n "$myname" -y -o none 83 | echo "Configuring ARS $rs_name in RG $rg to peer to $myname on IP address $myip and ASN $myasn..." | adddate >>$log_file 84 | az network routeserver peering create --routeserver "$rs_name" -g "$rg" --peer-ip "$myip" --peer-asn "$myasn" -n "$myname" -o none 85 | fi 86 | # No peer was found with the same name or IP 87 | else 88 | echo "No existing RS peer found with the name $myname" | adddate >>$log_file 89 | echo "Configuring ARS $rs_name in RG $rg to peer to $myname on IP address $myip and ASN $myasn..." | adddate >>$log_file 90 | az network routeserver peering create --routeserver "$rs_name" -g "$rg" --peer-ip "$myip" --peer-asn "$myasn" -n "$myname" -o none 91 | fi 92 | fi 93 | 94 | # Update routes in bird.conf if the files have changed 95 | # First download the routes and compare to the existing ones 96 | routes_url=$(cat /root/routes_url) 97 | if [[ -e /root/routes.txt ]]; then 98 | mv /root/routes.txt /root/routes.old.txt 99 | else 100 | touch /root/routes.old.txt 101 | fi 102 | wget -q -O /root/routes.txt "$routes_url" 103 | route_no=$(cat /root/routes.txt | wc -l) 104 | echo "$route_no routes downloaded from $routes_url, adding now to BIRD configuration..." | adddate >>$log_file 105 | if cmp -s /root/routes.txt /root/routes.old.txt; then 106 | echo "No change in downloaded routes, nothing else to do." | adddate >>$log_file 107 | else 108 | file_name=/etc/bird/bird.conf 109 | cp /etc/bird/bird.conf.template $file_name 110 | default_gw=$(/sbin/ip route | awk '/default/ { print $3 }') 111 | line_no=$(grep -n '# Routes advertised' $file_name | cut -d: -f1) 112 | line_no=$((line_no+1)) 113 | routes=$(cat /root/routes.txt) 114 | for prefix in $routes; do 115 | echo "Adding route for $prefix to BIRD configuration..." | adddate >>$log_file 116 | sed -i "${line_no}i\\ route $prefix via ${default_gw};" "$file_name" 117 | done 118 | systemctl restart bird 119 | fi 120 | rm /root/routes.old.txt 121 | 122 | # Cleanup not used adjacencies from ARS. Get private IP addresses of the VMSS 123 | vmss_name=$(echo "$metadata" | jq -r '.compute.vmScaleSetName') 124 | vmss_ips=$(az vmss nic list --vmss-name "$vmss_name" -g "$rg" --query '[].ipConfigurations[].privateIpAddress' -o tsv) 125 | peer_ips=$(az network routeserver peering list --routeserver "$rs_name" -g "$rg" --query '[].peerIp' -o tsv) 126 | for peer_ip in $peer_ips; do 127 | echo "Seeing if RS peer $peer_ip can be deleted..." | adddate >>$log_file 128 | match="false" 129 | for vmss_ip in $vmss_ips; do 130 | if [[ "$peer_ip" == "$vmss_ip" ]]; then 131 | match="true" 132 | fi 133 | done 134 | # If no match was found, it means that there is a BGP peer for some IP that does not exist in the VMSS 135 | if [[ "$match" == "false" ]]; then 136 | rs_peer=$(az network routeserver peering list --routeserver "$rs_name" -g "$rg" --query "[?peerIp=='$peer_ip']" -o json) 137 | rs_peer_name=$(echo "$rs_peer" | jq -r '.[0].name' 2>/dev/null) 138 | if [[ -n "$rs_peer_name" ]]; then 139 | echo "Deleting BGP peer $rs_peer_name with IP address $peer_ip..." | adddate >>$log_file 140 | az network routeserver peering delete --routeserver "$rs_name" -g "$rg" -n "$rs_peer_name" -y -o none 141 | else 142 | echo "Could not find name for BGP peer with IP address $peer_ip" | adddate >>$log_file 143 | fi 144 | fi 145 | done 146 | -------------------------------------------------------------------------------- /routeserver-vmss-selfcontained-healthcheck.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket, struct 3 | import sys 4 | import time 5 | import warnings 6 | import requests 7 | from flask import Flask 8 | from flask import request 9 | from flask import jsonify 10 | 11 | # Gets the web port out of an environment variable, or defaults to 8080 12 | def get_web_port(): 13 | web_port=os.environ.get('PORT') 14 | if web_port==None or not web_port.isnumeric(): 15 | print("Using default port 8080") 16 | web_port=8080 17 | else: 18 | print("Port supplied as environment variable:", web_port) 19 | return web_port 20 | 21 | app = Flask(__name__) 22 | 23 | # Flask route for healthchecks 24 | @app.route("/api/healthcheck", methods=['GET']) 25 | def healthcheck(): 26 | if request.method == 'GET': 27 | try: 28 | output_stream = os.popen('birdc show protocols | grep rs0 | awk \'{print $6}\'') 29 | rs0_status = output_stream.read() 30 | output_stream = os.popen('birdc show protocols | grep rs1 | awk \'{print $6}\'') 31 | rs1_status = output_stream.read() 32 | rs0_status = rs0_status.rstrip('\n') 33 | rs1_status = rs1_status.rstrip('\n') 34 | if (rs0_status == "Established") and (rs1_status == "Established"): 35 | return_code = 200 36 | else: 37 | return_code = 503 38 | msg = { 39 | 'health': 'OK', 40 | 'rs0_status': rs0_status, 41 | 'rs1_status': rs1_status 42 | } 43 | return jsonify(msg), return_code 44 | except Exception as e: 45 | return jsonify(str(e)), 500 46 | 47 | # Flask route to run config 48 | @app.route("/api/config", methods=['GET']) 49 | def config(): 50 | if request.method == 'GET': 51 | try: 52 | output_stream = os.popen('/root/routeserver-vmss-selfcontained-config.sh') 53 | output = output_stream.read() 54 | msg = { 55 | 'health': 'OK', 56 | 'config_output': output.rstrip('\n'), 57 | } 58 | return jsonify(msg) 59 | except Exception as e: 60 | return jsonify(str(e)) 61 | 62 | 63 | # Ignore warnings 64 | with warnings.catch_warnings(): 65 | warnings.simplefilter("ignore") 66 | 67 | # Set web port 68 | web_port=get_web_port() 69 | 70 | app.run(host='0.0.0.0', port=web_port, debug=True, use_reloader=False) 71 | -------------------------------------------------------------------------------- /routeserver-vmss-selfcontained-routes.txt: -------------------------------------------------------------------------------- 1 | 192.168.0.0/16 2 | -------------------------------------------------------------------------------- /sample_azure_config_msi.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | azureCloudConfig: 3 | cloud: "AzurePublicCloud" 4 | tenantId: "" 5 | subscriptionId: "" 6 | useManagedIdentityExtension: true 7 | userAssignedIdentityID: "" 8 | userAgent: "kube-egress-gateway-controller" 9 | resourceGroup: "" 10 | location: "" 11 | gatewayLoadBalancerName: "kubeegressgateway-ilb" 12 | loadBalancerResourceGroup: "" 13 | vnetName: "" 14 | vnetResourceGroup: "" 15 | subnetName: "" 16 | 17 | -------------------------------------------------------------------------------- /sas.azcli: -------------------------------------------------------------------------------- 1 | # Create a SAS and download blob to test it 2 | 3 | # Variables 4 | subscription_id=e7da9914-9b05-4891-893c-546cb7b0422e 5 | storage_account=myobjects 6 | container_name=templates 7 | blob_path='KubernetesMain.json' 8 | 9 | # Check we are in the right subscription 10 | current_subscription_id=$(az account show --query id -o tsv) 11 | if [[ "${current_subscription_id}" == "${subscription_id}" ]] 12 | then 13 | echo "Subscription seems to be correct (${current_subscription_id})" 14 | else 15 | az account set -s $subscription_id 16 | fi 17 | 18 | # Calculate dates, get storage key, generate SAS 19 | expiry=`date -u -d "30 minutes" '+%Y-%m-%dT%H:%MZ'` 20 | key=$(az storage account keys list -n myobjects --query '[0].value' -o tsv) 21 | sas=$(az storage container generate-sas --account-name $storage_account --account-key $key \ 22 | -n $container_name --https-only --permissions r --expiry=$expiry -o tsv) 23 | 24 | # Retrieve blob URL and download blob 25 | # az storage blob show -n $blob_path -c $container_name --account-name $storage_account --account-key $key 26 | blob_url=$(az storage blob url -n $blob_path -c $container_name --account-name $storage_account --account-key $key -o tsv) 27 | url=${blob_url}?${sas} 28 | echo "Downloading from ${url}..." 29 | wget $url -O /tmp/workspace.json 30 | -------------------------------------------------------------------------------- /service-tags.azcli: -------------------------------------------------------------------------------- 1 | ################################################# 2 | # Commands to look into defined service tags 3 | # Two use cases: 4 | # 1. Find the service tag for an IP 5 | # 2. Look for the IPs in a set of service tags 6 | ################################################# 7 | 8 | 9 | # Commands are restricted to a certain location 10 | location=westeurope 11 | 12 | # Find to wich service tag an IP belongs 13 | # This is doing a basic contains, not too sophisticated 14 | az network list-service-tags -l $location -o json | jq -r '.values[] | select(.properties.addressPrefixes[] | contains("13.92.84.128")) | .name' 15 | 16 | # Find the IPs of a service tag 17 | az network list-service-tags -l $location -o json | jq -r '.values[] | select(.name == "GatewayManager.EastUS") | [.name,.properties.addressPrefixes]' 18 | az network list-service-tags -l $location -o json | jq -r '.values[] | select(.name | contains("GatewayManager")) | [.name,.properties.addressPrefixes]' 19 | -------------------------------------------------------------------------------- /sqlmi.azcli: -------------------------------------------------------------------------------- 1 | # From https://docs.microsoft.com/en-us/azure/sql-database/scripts/sql-database-create-configure-managed-instance-cli 2 | 3 | randomIdentifier=$RANDOM 4 | resource="sqlmi" 5 | location="westeurope" 6 | vnet="vnet-$randomIdentifier" 7 | subnet="subnet-$randomIdentifier" 8 | nsg="nsg-$randomIdentifier" 9 | route="route-$randomIdentifier" 10 | instance="instance-$randomIdentifier" 11 | 12 | login="azure" 13 | password='samplePassword123!' 14 | 15 | echo "Using resource group $resource with login: $login, password: $password..." 16 | 17 | echo "Creating $resource..." 18 | az group create --name $resource --location "$location" 19 | 20 | echo "Creating $vnet with $subnet..." 21 | az network vnet create --name $vnet --resource-group $resource --location "$location" --address-prefixes 10.0.0.0/16 22 | az network vnet subnet create --name $subnet --resource-group $resource --vnet-name $vnet --address-prefixes 10.0.0.0/24 23 | 24 | echo "Creating $nsg..." 25 | az network nsg create --name $nsg --resource-group $resource --location "$location" 26 | 27 | az network nsg rule create --name "allow_management_inbound" --nsg-name $nsg --priority 100 --resource-group $resource --access Allow --destination-address-prefixes 10.0.0.0/24 --destination-port-ranges 9000 9003 1438 1440 1452 --direction Inbound --protocol Tcp --source-address-prefixes '*' --source-port-ranges '*' 28 | az network nsg rule create --name "allow_misubnet_inbound" --nsg-name $nsg --priority 200 --resource-group $resource --access Allow --destination-address-prefixes 10.0.0.0/24 --destination-port-ranges '*' --direction Inbound --protocol '*' --source-address-prefixes 10.0.0.0/24 --source-port-ranges '*' 29 | az network nsg rule create --name "allow_health_probe_inbound" --nsg-name $nsg --priority 300 --resource-group $resource --access Allow --destination-address-prefixes 10.0.0.0/24 --destination-port-ranges '*' --direction Inbound --protocol '*' --source-address-prefixes AzureLoadBalancer --source-port-ranges '*' 30 | az network nsg rule create --name "allow_management_outbound" --nsg-name $nsg --priority 1100 --resource-group $resource --access Allow --destination-address-prefixes AzureCloud --destination-port-ranges 443 12000 --direction Outbound --protocol Tcp --source-address-prefixes 10.0.0.0/24 --source-port-ranges '*' 31 | az network nsg rule create --name "allow_misubnet_outbound" --nsg-name $nsg --priority 200 --resource-group $resource --access Allow --destination-address-prefixes 10.0.0.0/24 --destination-port-ranges '*' --direction Outbound --protocol '*' --source-address-prefixes 10.0.0.0/24 --source-port-ranges '*' 32 | 33 | echo "Creating $route..." 34 | az network route-table create --name $route --resource-group $resource --location "$location" 35 | 36 | az network route-table route create --address-prefix 0.0.0.0/0 --name "primaryToMIManagementService" --next-hop-type Internet --resource-group $resource --route-table-name $route 37 | az network route-table route create --address-prefix 10.0.0.0/24 --name "ToLocalClusterNode" --next-hop-type VnetLocal --resource-group $resource --route-table-name $route 38 | 39 | echo "Configuring $subnet with $nsg and $route..." 40 | az network vnet subnet update --name $subnet --network-security-group $nsg --route-table $route --vnet-name $vnet --resource-group $resource 41 | 42 | az network vnet subnet update --resource-group $resource --name $subnet --vnet-name $vnet --delegations Microsoft.Sql/managedInstances 43 | # az network vnet subnet update --resource-group $resource --name $subnet --vnet-name $vnet --remove delegations 44 | 45 | echo "Creating $instance with $vnet and $subnet..." 46 | az sql mi create --admin-password $password --admin-user $login --name $instance --resource-group $resource --subnet $subnet --vnet-name $vnet --location "$location" -------------------------------------------------------------------------------- /ssh_rule.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/zsh 2 | 3 | ################################################# 4 | # Add SSH allow/deny rules to running VMs 5 | # It assumes that the NSG is in the same RG as the VM 6 | # It creates the rules with prio 100 7 | # 8 | # Jose Moreno, March 2021 9 | ################################################# 10 | 11 | # Function to inject a deny rule for SSH 12 | function deny_ssh () { 13 | while IFS= read -r vm; do 14 | ssh_vm_name=$(echo $vm | cut -f1 -d$'\t') 15 | ssh_rg=$(echo $vm | cut -f2 -d$'\t') 16 | echo "Getting NSG for VM $ssh_vm_name in RG $ssh_rg..." 17 | ssh_nic_id=$(az vm show -n $ssh_vm_name -g $ssh_rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 18 | ssh_nsg_id=$(az network nic show --ids $ssh_nic_id --query 'networkSecurityGroup.id' -o tsv) 19 | if [[ -z "$ssh_nsg_id" ]] 20 | then 21 | echo "No NSG could be found for NIC $ssh_nic_id" 22 | else 23 | ssh_nsg_name=$(basename $ssh_nsg_id) 24 | echo "Adding SSH-deny rule to NSG $ssh_nsg_name for VM $ssh_vm_name in RG $ssh_rg..." 25 | az network nsg rule create -n "${rule_prefix}SSH" --nsg-name $ssh_nsg_name -g $ssh_rg --priority $rule_prio --destination-port-ranges 22 --access Deny --protocol Tcp -o none 26 | az network nsg rule create -n "${rule_prefix}RDP" --nsg-name $ssh_nsg_name -g $ssh_rg --priority $(($rule_prio+1)) --destination-port-ranges 3389 --access Deny --protocol Tcp -o none 27 | fi 28 | done <<< "$vm_list" 29 | } 30 | 31 | # Function to inject an allow rule for SSH 32 | function allow_ssh () { 33 | while IFS= read -r vm; do 34 | ssh_vm_name=$(echo $vm | cut -f1 -d$'\t') 35 | ssh_rg=$(echo $vm | cut -f2 -d$'\t') 36 | echo "Getting NSG for VM $ssh_vm_name in RG $ssh_rg..." 37 | ssh_nic_id=$(az vm show -n $ssh_vm_name -g $ssh_rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 38 | ssh_nsg_id=$(az network nic show --ids $ssh_nic_id --query 'networkSecurityGroup.id' -o tsv) 39 | if [[ -z "$ssh_nsg_id" ]] 40 | then 41 | echo "No NSG could be found for NIC $ssh_nic_id" 42 | else 43 | ssh_nsg_name=$(basename $ssh_nsg_id) 44 | echo "Adding SSH-allow rule to NSG $ssh_nsg_name for VM $ssh_vm_name in RG $ssh_rg..." 45 | az network nsg rule create -n "${rule_prefix}SSH" --nsg-name $ssh_nsg_name -g $ssh_rg --priority $rule_prio --destination-port-ranges 22 --access Allow --protocol Tcp -o none 46 | az network nsg rule create -n "${rule_prefix}RDP" --nsg-name $ssh_nsg_name -g $ssh_rg --priority $(($rule_prio+1)) --destination-port-ranges 3389 --access Allow --protocol Tcp -o none 47 | fi 48 | done <<< "$vm_list" 49 | } 50 | 51 | # Function to inject an allow rule for SSH for only the current IP address 52 | function allow_1ip_ssh () { 53 | echo "Getting current IP..." 54 | myip=$(curl -s4 ifconfig.co) 55 | echo "Current IP is $myip" 56 | while IFS= read -r vm; do 57 | ssh_vm_name=$(echo $vm | cut -f1 -d$'\t') 58 | ssh_rg=$(echo $vm | cut -f2 -d$'\t') 59 | echo "Getting NSG for VM $ssh_vm_name in RG $ssh_rg..." 60 | ssh_nic_id=$(az vm show -n $ssh_vm_name -g $ssh_rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 61 | ssh_nsg_id=$(az network nic show --ids $ssh_nic_id --query 'networkSecurityGroup.id' -o tsv) 62 | if [[ -z "$ssh_nsg_id" ]] 63 | then 64 | echo "No NSG could be found for NIC $ssh_nic_id" 65 | else 66 | ssh_nsg_name=$(basename $ssh_nsg_id) 67 | echo "Adding allow rule for SSH and RDP to NSG $ssh_nsg_name for VM $ssh_vm_name in RG $ssh_rg (for IP address $myip)..." 68 | az network nsg rule create -n "${rule_prefix}Mgmt" --nsg-name $ssh_nsg_name -g $ssh_rg --priority $rule_prio \ 69 | --source-address-prefixes "${myip}/32" --destination-port-ranges 22 3389 --access Allow --protocol Tcp -o none 70 | fi 71 | done <<< "$vm_list" 72 | } 73 | 74 | # Function to inject an allow rule for SSH 75 | function delete_ssh_rule () { 76 | while IFS= read -r vm; do 77 | ssh_vm_name=$(echo $vm | cut -f1 -d$'\t') 78 | ssh_rg=$(echo $vm | cut -f2 -d$'\t') 79 | echo "Getting NSG for VM $ssh_vm_name in RG $ssh_rg..." 80 | ssh_nic_id=$(az vm show -n $ssh_vm_name -g $ssh_rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 81 | ssh_nsg_id=$(az network nic show --ids $ssh_nic_id --query 'networkSecurityGroup.id' -o tsv) 82 | if [[ -z "$ssh_nsg_id" ]] 83 | then 84 | echo "No NSG could be found for NIC $ssh_nic_id" 85 | else 86 | ssh_nsg_name=$(basename $ssh_nsg_id) 87 | echo "Deleting SSH-allow rule from NSG $ssh_nsg_name for VM $ssh_vm_name in RG $ssh_rg..." 88 | az network nsg rule delete -n "${rule_prefix}SSH" --nsg-name $ssh_nsg_name -g $ssh_rg -o none 89 | az network nsg rule delete -n "${rule_prefix}RDP" --nsg-name $ssh_nsg_name -g $ssh_rg -o none 90 | fi 91 | done <<< "$vm_list" 92 | } 93 | 94 | # Variables 95 | rule_prefix=auto 96 | rule_prio=100 97 | 98 | # Get arguments 99 | scope_rg='' 100 | action='' 101 | for i in "$@" 102 | do 103 | case $i in 104 | -g=*|--resource-group=*) 105 | scope_rg="${i#*=}" 106 | shift # past argument=value 107 | ;; 108 | -a=*|--action=*) 109 | action="${i#*=}" 110 | shift # past argument=value 111 | ;; 112 | esac 113 | done 114 | set -- "${POSITIONAL[@]}" # restore positional parameters 115 | 116 | # Check there is an action 117 | if [[ -z "$action" ]] 118 | then 119 | echo "ERROR: You need to specify an action with -a/--action, and optionally a resource group with -g/--resource-group" 120 | exit 1 121 | fi 122 | 123 | # Create VM list 124 | subscription=$(az account show --query name -o tsv) 125 | if [[ -z $scope_rg ]] 126 | then 127 | echo "Getting the list of VMs powered on in subscription $subscription..." 128 | vm_list=$(az vm list -o tsv -d --query "[?powerState=='VM running'].[name,resourceGroup]") 129 | else 130 | echo "Getting the list of VMs powered on in subscription $subscription and resource group $scope_rg..." 131 | vm_list=$(az vm list -g $scope_rg -o tsv -d --query "[?powerState=='VM running'].[name,resourceGroup]") 132 | fi 133 | echo "$(echo $vm_list | wc -l) VMs found" 134 | 135 | # Run action 136 | case $action in 137 | allow|Allow|permit|Permit) 138 | allow_ssh 139 | ;; 140 | deny|Deny|drop|Drop) 141 | deny_ssh 142 | ;; 143 | delete|remove) 144 | delete_ssh_rule 145 | ;; 146 | allow1|permit1|allow1ip|permit1ip) 147 | allow_1ip_ssh 148 | ;; 149 | esac 150 | -------------------------------------------------------------------------------- /storage_logs.azcli: -------------------------------------------------------------------------------- 1 | storage_account=myobjects 2 | container_name=templates 3 | key=$(az storage account keys list -n myobjects --query '[0].value' -o tsv) 4 | log_container_name=$(az storage container show -n '$logs' --account-name $storage_account --account-key $key --query name -o tsv) 5 | if [[ -z ${log_container_name} ]] 6 | then 7 | echo "Are you sure that you have activated audit trail logs for the storage account ${storage_account}?" 8 | else 9 | echo "Container with audit logs found, retrieving log list..." 10 | # Use this line for all logs 11 | full_blob_list=$(az storage blob list -c '$logs' --account-name $storage_account --account-key $key --query '[].name' -o tsv) 12 | echo "Found these log files:" 13 | az storage blob list -c '$logs' --account-name $storage_account --account-key $key -o table 14 | # Use this line for only the last log file 15 | filtered_blob_list=$(az storage blob list -c '$logs' --account-name $storage_account --account-key $key --query '[-1].name' -o tsv) 16 | # Process the retrieved logs one by one 17 | echo "Printing only the last log file:" 18 | temp_filename=/tmp/blog.log 19 | echo $full_blob_list | while read blob_name ; do 20 | if [[ -n "${blob_name}" ]] 21 | then 22 | az storage blob download -n $blob_name -c '$logs' --account-name $storage_account --account-key $key --no-progress -f $temp_filename >/dev/null 23 | cat $temp_filename | while read line ; do 24 | version=$(echo $line | cut -d';' -f 1) 25 | # Only v2.0 26 | if [[ ${version} == "2.0" ]] 27 | then 28 | timestamp=$(echo $line | cut -d';' -f 2) 29 | operation=$(echo $line | cut -d';' -f 3) 30 | accessed_blob_url=$(echo $line | cut -d';' -f 12) 31 | accessed_blob_name=$(echo $line | cut -d';' -f 13) 32 | accessed_container=$(echo $blob_url | cut -d'/' -f 4) 33 | accessed_container=$(echo $accessed_container | cut -d'?' -f 1) 34 | agent=$(echo $line | cut -d';' -f 30) 35 | else 36 | timestamp=$(echo $line | cut -d';' -f 2) 37 | operation=$(echo $line | cut -d';' -f 3) 38 | accessed_blob_url=$(echo $line | cut -d';' -f 12) 39 | accessed_blob_name=$(echo $line | cut -d';' -f 13) 40 | accessed_container=$(echo $blob_url | cut -d'/' -f 4) 41 | accessed_container=$(echo $accessed_container | cut -d'?' -f 1) 42 | agent=$(echo $line | cut -d';' -f 30) 43 | fi 44 | # Only print certain operations 45 | if [[ ${operation} == 'PutBlob' ]] || [[ ${operation} == 'GetBlob' ]] 46 | then 47 | # Do not print access to the logs (this tool for example) 48 | if [[ ${container} != '$logs' ]] 49 | then 50 | # echo $line # Uncommnet for debugging 51 | printf "** %.25s %4s %.10s %.40s %.15s %.60s\n" $timestamp $version $operation $agent $accessed_container $accessed_blob_name 52 | fi 53 | fi 54 | done 55 | rm $temp_filename 56 | fi 57 | done 58 | fi -------------------------------------------------------------------------------- /tag_batch.sh: -------------------------------------------------------------------------------- 1 | # Example: tag_batch myrg Microsoft.Network/vpnSites mytag myvalue 2 | function tag_type { 3 | rg=$1 4 | resource_type=$2 5 | tag_key=$3 6 | tag_value=$4 7 | echo "Getting $resource_type resources in resource group $rg..." 8 | resource_id_list=$(az resource list -g $rg --resource-type "$resource_type" --query '[].id' -o tsv) 9 | if [[ -n "$resource_id_list" ]]; then 10 | while IFS= read -r resource_id; do 11 | echo "Tagging resource $resource_id with $tag_key:$tag_value..." 12 | az resource tag -i --ids $resource_id --tags $tag_key=$tag_value >/dev/null 13 | done <<< "$resource_id_list" 14 | else 15 | echo "No $resource_type resources found in resource group $rg" 16 | fi 17 | } 18 | 19 | tag_rg=vwanlab2 20 | mytag_key=test 21 | tag_type $rg 'Microsoft.Network/vpnSites' $mytag_key 'vpnsite' 22 | tag_type $rg 'Microsoft.Network/virtualHubs' $mytag_key 'hub' 23 | tag_type $rg 'Microsoft.Network/vpnGateways' $mytag_key 'vpngw' 24 | tag_type $rg 'Microsoft.Network/azureFirewalls' $mytag_key 'fw' 25 | tag_type $rg 'Microsoft.Network/virtualWans' $mytag_key 'vwan' 26 | tag_type $rg 'Microsoft.Network/firewallPolicies' $mytag_key 'fwpolicy' 27 | tag_type $rg 'Microsoft.OperationalInsights/workspaces' $mytag_key 'log' -------------------------------------------------------------------------------- /translations/en_US/LC_MESSAGES/acsengine.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erjosito/azcli/62ca5bf237b00073d4830aac80ded958ec38b824/translations/en_US/LC_MESSAGES/acsengine.mo -------------------------------------------------------------------------------- /translations/en_US/LC_MESSAGES/acsengine.po: -------------------------------------------------------------------------------- 1 | # English translations for aks-engine package. 2 | # Copyright (C) 2017 3 | # This file is distributed under the same license as the aks-engine package. 4 | # Jiangtian Li , 2017. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: acsengine\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2017-07-25 00:04+0000\n" 11 | "PO-Revision-Date: 2017-07-24 17:23-0700\n" 12 | "Last-Translator: Jiangtian Li \n" 13 | "Language-Team: English\n" 14 | "Language: en_US\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 2.0.3\n" 20 | 21 | #: pkg/operations/kubernetesupgrade/upgrader.go:202 22 | #: pkg/operations/kubernetesupgrade/upgrader.go:217 23 | #, c-format 24 | msgid "Error generating upgrade template: %s" 25 | msgstr "Error generating upgrade template: %s" 26 | 27 | #: pkg/acsengine/engine.go:251 28 | #, c-format 29 | msgid "Error reading file %s, Error: %s" 30 | msgstr "Error reading file %s, Error: %s" 31 | 32 | #: pkg/operations/kubernetesupgrade/upgradecluster.go:76 33 | #, c-format 34 | msgid "Error while querying ARM for resources: %+v" 35 | msgstr "Error while querying ARM for resources: %+v" 36 | 37 | #: pkg/acsengine/transform/transform.go:121 38 | #: pkg/acsengine/transform/transform.go:129 39 | #, c-format 40 | msgid "Found 2 resources with type %s in the template. There should only be 1" 41 | msgstr "Found 2 resources with type %s in the template. There should only be 1" 42 | 43 | #: pkg/acsengine/transform/transform.go:158 44 | #, c-format 45 | msgid "" 46 | "Found no resources with type %s in the template. There should have been 1" 47 | msgstr "" 48 | "Found no resources with type %s in the template. There should have been 1" 49 | 50 | #: pkg/operations/kubernetesupgrade/upgradeagentnode.go:125 51 | #, c-format 52 | msgid "Node was not ready within %v" 53 | msgstr "Node was not ready within %v" 54 | 55 | #: pkg/api/apiloader.go:205 pkg/api/apiloader.go:210 pkg/api/apiloader.go:225 56 | #: pkg/api/apiloader.go:230 57 | #, c-format 58 | msgid "The selected orchestrator version '%s' is not supported" 59 | msgstr "The selected orchestrator version '%s' is not supported" 60 | 61 | #: pkg/operations/kubernetesupgrade/upgrader.go:120 62 | #, c-format 63 | msgid "Total count of master VMs: %d exceeded expected count: %d" 64 | msgstr "Total count of master VMs: %d exceeded expected count: %d" 65 | 66 | #: pkg/operations/kubernetesupgrade/upgradecluster.go:110 67 | #, c-format 68 | msgid "Upgrade to Kubernetes version %s is not supported" 69 | msgstr "Upgrade to Kubernetes version %s is not supported" 70 | 71 | #: pkg/acsengine/filesaver.go:26 72 | #, c-format 73 | msgid "error creating directory '%s': %s" 74 | msgstr "error creating directory '%s': %s" 75 | 76 | #: pkg/acsengine/engine.go:2545 77 | #, c-format 78 | msgid "error executing template for file %s: %v" 79 | msgstr "error executing template for file %s: %v" 80 | 81 | #: pkg/operations/kubernetesupgrade/upgrader.go:78 82 | #: pkg/operations/kubernetesupgrade/upgrader.go:391 83 | #, c-format 84 | msgid "error generating upgrade template: %s" 85 | msgstr "error generating upgrade template: %s" 86 | 87 | #: pkg/acsengine/engine.go:2540 88 | #, c-format 89 | msgid "error parsing file %s: %v" 90 | msgstr "error parsing file %s: %v" 91 | 92 | #: pkg/api/apiloader.go:32 93 | #, c-format 94 | msgid "error reading file %s: %s" 95 | msgstr "error reading file %s: %s" 96 | 97 | #: pkg/operations/kubernetesupgrade/upgrader.go:385 98 | #, c-format 99 | msgid "failed to initialize template generator: %s" 100 | msgstr "failed to initialize template generator: %s" 101 | 102 | #: pkg/api/apiloader.go:342 pkg/api/apiloader.go:369 103 | #, c-format 104 | msgid "invalid version %s for conversion back from unversioned object" 105 | msgstr "invalid version %s for conversion back from unversioned object" 106 | 107 | #: pkg/acsengine/engine.go:399 108 | #, c-format 109 | msgid "orchestrator '%s' is unsupported" 110 | msgstr "orchestrator '%s' is unsupported" 111 | 112 | #: pkg/acsengine/engine.go:195 113 | #, c-format 114 | msgid "template file %s does not exist" 115 | msgstr "template file %s does not exist" 116 | 117 | #: pkg/api/apiloader.go:184 118 | #, c-format 119 | msgid "unrecognized APIVersion '%s'" 120 | msgstr "unrecognized APIVersion '%s'" 121 | 122 | #: pkg/api/apiloader.go:272 123 | #, c-format 124 | msgid "" 125 | "unrecognized APIVersion in LoadContainerServiceForAgentPoolOnlyCluster '%s'" 126 | msgstr "" 127 | "unrecognized APIVersion in LoadContainerServiceForAgentPoolOnlyCluster '%s'" 128 | 129 | #: pkg/acsengine/engine.go:2534 130 | #, c-format 131 | msgid "yaml file %s does not exist" 132 | msgstr "yaml file %s does not exist" 133 | -------------------------------------------------------------------------------- /velocloud.azcli: -------------------------------------------------------------------------------- 1 | # Variables 2 | rg=velocloud 3 | location=westeurope 4 | hub_vnet_name=hub 5 | hub_vnet_prefix=10.1.0.0/24 6 | hub_velo_subnet_public_name=velopub 7 | hub_velo_subnet_public_prefix=10.1.0.0/26 8 | hub_velo_subnet_private_name=velopriv 9 | hub_velo_subnet_private_prefix=10.1.0.64/26 10 | hub_velo1_name=velo1 11 | hub_velo1_nic_public_name=velo1nic1 12 | hub_velo1_nic_private_name=velo1nic2 13 | hub_velo1_pip_name=velo1pip 14 | spoke1_vnet_name=spoke1 15 | spoke1_vnet_prefix=10.1.11.0/24 16 | spoke1_vm_subnet_name=vm 17 | spoke1_vm_subnet_prefix=10.1.11.0/26 18 | spoke2_vnet_name=spoke2 19 | spoke2_vnet_prefix=10.1.12.0/24 20 | spoke2_vm_subnet_name=vm 21 | spoke2_vm_subnet_prefix=10.1.12.0/26 22 | velo_publisher=vmware-inc 23 | velo_offer=sol-42222-bbj 24 | velo_sku=vmware_sdwan_4x 25 | velo_version=4.2.1 26 | velo_ignore_cert_errors='true' 27 | velo_nsg_name=velonsg 28 | velo_cloudinit=/tmp/velocloudinit.txt 29 | velo_vm_size=Standard_B2ms 30 | sshkey=$(cat ~/.ssh/id_rsa.pub) 31 | keyvault_name=erjositoKeyvault 32 | velo1_token_secret_name=velocloudtoken1 33 | velo_vco_secret_name=velovco 34 | 35 | # Create RG and VNets 36 | echo "Creating RG and VNets..." 37 | az group create -n $rg -l $location -o none 38 | az network vnet create -n $hub_vnet_name -g $rg --address-prefixes $hub_vnet_prefix --subnet-name $hub_velo_subnet_public_name --subnet-prefixes $hub_velo_subnet_public_prefix -o none 39 | az network vnet subnet create -g $rg -n $hub_velo_subnet_private_name --vnet-name $hub_vnet_name --address-prefix $hub_velo_subnet_private_prefix -o none 40 | az network vnet create -n $spoke1_vnet_name -g $rg --address-prefixes $spoke1_vnet_prefix --subnet-name $spoke1_vm_subnet_name --subnet-prefixes $spoke1_vm_subnet_prefix -o none 41 | az network vnet create -n $spoke2_vnet_name -g $rg --address-prefixes $spoke2_vnet_prefix --subnet-name $spoke2_vm_subnet_name --subnet-prefixes $spoke2_vm_subnet_prefix -o none 42 | az network vnet peering create -n hubtospoke1 -g $rg --vnet-name $hub_vnet_name --remote-vnet $spoke1_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 43 | az network vnet peering create -n spoke1tohub -g $rg --vnet-name $spoke1_vnet_name --remote-vnet $hub_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 44 | az network vnet peering create -n hubtospoke2 -g $rg --vnet-name $hub_vnet_name --remote-vnet $spoke2_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 45 | az network vnet peering create -n spoke2tohub -g $rg --vnet-name $spoke2_vnet_name --remote-vnet $hub_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none 46 | 47 | # Create VeloCloud NVA 48 | echo "Creating public IP, NSG and NICs..." 49 | az network public-ip create -g $rg -n $hub_velo1_pip_name --sku standard --allocation-method static -o none 50 | az network nsg create -n $velo_nsg_name -g $rg -o none 51 | az network nsg rule create --nsg-name $velo_nsg_name -g $rg -n VCMP --priority 1000 --destination-port-ranges 2426 --access Allow --protocol Udp -o none 52 | az network nsg rule create --nsg-name $velo_nsg_name -g $rg -n SSH --priority 1010 --destination-port-ranges 22 --access Allow --protocol Tcp -o none 53 | az network nsg rule create --nsg-name $velo_nsg_name -g $rg -n SNMP --priority 1020 --destination-port-ranges 161 --access Allow --protocol Udp -o none 54 | az network nic create -n "$hub_velo1_nic_public_name" -g $rg --vnet-name $hub_vnet_name --subnet $hub_velo_subnet_public_name --public-ip-address "$hub_velo1_pip_name" --ip-forwarding --network-security-group $velo_nsg_name -o none 55 | az network nic create -n "$hub_velo1_nic_private_name" -g $rg --vnet-name $hub_vnet_name --subnet $hub_velo_subnet_private_name --ip-forwarding --network-security-group $velo_nsg_name -o none 56 | # Get license keys (previously stored in an Azure Key Vault) 57 | velo1_token=$(az keyvault secret show --vault-name $keyvault_name -n $velo1_token_secret_name --query 'value' -o tsv) 58 | vco=$(az keyvault secret show --vault-name $keyvault_name -n $velo_vco_secret_name --query 'value' -o tsv) 59 | if [[ -n "$velo1_token" ]] && [[ -n "$vco" ]] 60 | then 61 | echo "VeloCloud license and VCO successfully retrieved from Azure Key Vault $keyvault_name" 62 | else 63 | echo "VeloCloud license and VCO could NOT be retrieved from Azure Key Vault $keyvault_name" 64 | fi 65 | cat < $velo_cloudinit 66 | #cloud-config 67 | password: Velocloud123 68 | chpasswd: { expire: False } 69 | ssh_pwauth: True 70 | velocloud: 71 | vce: 72 | management_interface: false 73 | vco: $vco 74 | activation_code: $velo1_token 75 | vco_ignore_cert_errors: $velo_ignore_cert_errors 76 | EOF 77 | velo_image_urn="${velo_publisher}:${velo_offer}:${velo_sku}:${velo_version}" 78 | echo "Accepting image terms and creating VM..." 79 | az vm image terms accept --urn "$velo_image_urn" -o none 80 | az vm create -n $hub_velo1_name -g $rg -l $location --image "$velo_image_urn" --size $velo_vm_size \ 81 | --admin-username "azure-user" --admin-password "Velocloud123" --authentication-type all --generate-ssh-keys \ 82 | --nics "$hub_velo1_nic_private_name" "$hub_velo1_nic_public_name" --custom-data $velo_cloudinit -o none 83 | 84 | -------------------------------------------------------------------------------- /vmss-iperf.azcli: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # July 2024 4 | # 5 | # The script creates two VMSS of Ubuntu VMs running iperf. One will act as 6 | # client and the other one as server. 7 | ############################################################################ 8 | 9 | # Variables 10 | rg=vmss 11 | location=eastus2 12 | vnet1_name=vnet1 13 | vnet1_prefix=10.13.76.0/24 14 | subnet1_name=vm 15 | subnet1_prefix=10.13.76.0/24 16 | vnet2_name=vnet2 17 | vnet2_prefix=10.13.77.0/24 18 | subnet2_name=vm 19 | subnet2_prefix=10.13.77.0/24 20 | vmss1_name=client 21 | vmss1_cloudinit_file=/tmp/client.txt 22 | vmss2_name=server 23 | vmss2_cloudinit_file=/tmp/server.txt 24 | vm_size=Standard_B1s 25 | vmss_image=Ubuntu2204 26 | vmss_instance_count=3 27 | 28 | # Create cloudinit files 29 | cat <<'EOF' > $vmss1_cloudinit_file 30 | #cloud-config 31 | packages: 32 | - iperf3 33 | write_files: 34 | - content: | 35 | #!/bin/bash 36 | 37 | # Controls whether printing debugging info 38 | debug=no 39 | dev_name=eth0 40 | remote_cidr=__subnet2_prefix__ 41 | 42 | # Get local IP address and split it in ip and mask 43 | local_cidr=$(ip address show dev $dev_name | grep inet | grep $dev_name | awk '{print $2}') 44 | if [[ "$debug" == "yes" ]]; then echo "Local CIDR: $local_cidr"; fi 45 | local_ip=$(echo $local_cidr | cut -d/ -f 1) 46 | local_mask_int=$(echo $local_cidr | cut -d/ -f 2) 47 | if [[ "$debug" == "yes" ]]; then echo "Local IP address: $local_ip, mask: $local_mask_int"; fi 48 | 49 | # Convert the /24 mask to 255.255.255.0 format 50 | cidr_to_netmask() { 51 | local cidr=$1 52 | local mask="" 53 | local full_octets=$(( cidr / 8 )) 54 | local partial_octet=$(( cidr % 8 )) 55 | for ((i=0; i<4; i++)); do 56 | if [ $i -lt $full_octets ]; then 57 | mask+="255" 58 | elif [ $i -eq $full_octets ]; then 59 | mask+=$(( 256 - (1 << (8 - partial_octet)) )) 60 | else 61 | mask+="0" 62 | fi 63 | [ $i -lt 3 ] && mask+="." 64 | done 65 | echo $mask 66 | } 67 | local_mask=$(cidr_to_netmask $local_mask_int) 68 | if [[ "$debug" == "yes" ]]; then echo "Local mask: $local_mask"; fi 69 | 70 | # Convert to hex, and then to dec 71 | local_ip_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $local_ip | sed -e 's/\./ /g'`) 72 | local_mask_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $local_mask | sed -e 's/\./ /g'`) 73 | if [[ "$debug" == "yes" ]]; then echo "Local IP address in hex: $local_ip_hex, mask: $local_mask_hex"; fi 74 | 75 | # Now we can get the subnet with a binary AND between the IP and the mask 76 | local_subnet_hex=$(printf %.8X `echo $(( 0x$local_ip_hex & 0x$local_mask_hex ))`) 77 | if [[ "$debug" == "yes" ]]; then echo "Local subnet in hex: $local_subnet_hex"; fi 78 | 79 | # Now we have the offset 80 | local_offset=$(printf %.8X `echo $(( 0x$local_ip_hex - 0x$local_subnet_hex ))`) 81 | if [[ "$debug" == "yes" ]]; then echo "Local offset: $local_offset"; fi 82 | 83 | # Before adding it to the remote subnet, we need to convert it to hex too 84 | remote_subnet=$(echo $remote_cidr | cut -d/ -f 1) 85 | if [[ "$debug" == "yes" ]]; then echo "Remote subnet address: $remote_subnet"; fi 86 | remote_subnet_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $remote_subnet | sed -e 's/\./ /g'`) 87 | if [[ "$debug" == "yes" ]]; then echo "Remote subnet address in hex: $remote_subnet_hex"; fi 88 | 89 | # Now we can have the remote IP in hex adding the offset to the remote subnet 90 | remote_ip_hex=$(printf %.8X `echo $(( 0x$remote_subnet_hex + 0x$local_offset ))`) 91 | if [[ "$debug" == "yes" ]]; then echo "Remote IP address in hex: $remote_ip_hex"; fi 92 | 93 | # Finally we convert the remote IP in hex to an IPv4 format 94 | remote_ip=$(printf '%d.%d.%d.%d\n' `echo $remote_ip_hex | sed -r 's/(..)/0x\1 /g'`) 95 | if [[ "$debug" == "yes" ]]; then echo "Remote IP address: $remote_ip"; fi 96 | 97 | # Output 98 | echo $remote_ip 99 | path: /root/get_remote_ip.sh 100 | runcmd: 101 | - iperf3 -c $(/usr/bin/bash /root/get_remote_ip.sh) -tinf | tee /root/iperf.log 102 | EOF 103 | # Replace token "__remote_subnet_prefix__" with the right value 104 | sed -i "s|__subnet2_prefix__|${subnet2_prefix}|g" $vmss1_cloudinit_file 105 | cat < $vmss2_cloudinit_file 106 | #cloud-config 107 | packages: 108 | - iperf3 109 | runcmd: 110 | - iperf3 -s | tee /root/iperf.log 111 | EOF 112 | 113 | 114 | # Create RG, VNets and subnets 115 | az group create -n $rg -o none 116 | az network vnet create -g $rg -n $vnet1_name --address-prefixes $vnet1_prefix --subnet-name $subnet1_name --subnet-prefixes $subnet1_prefix -o none 117 | az network vnet create -g $rg -n $vnet2_name --address-prefixes $vnet2_prefix --subnet-name $subnet2_name --subnet-prefixes $subnet2_prefix -o none 118 | 119 | # Create server VMSS first 120 | az vmss create -n $vmss2_name -g $rg -l $location --image $vmss_image --generate-ssh-keys \ 121 | --vnet-name $vnet2_name --subnet $subnet2_name --public-ip-per-vm --orchestration-mode Uniform \ 122 | --vm-sku ${vm_size} --custom-data "$vmss2_cloudinit_file" --instance-count $vmss_instance_count -o none 123 | 124 | # Create client VMSS 125 | az vmss create -n $vmss1_name -g $rg -l $location --image $vmss_image --generate-ssh-keys \ 126 | --vnet-name $vnet1_name --subnet $subnet1_name --public-ip-per-vm --orchestration-mode Uniform \ 127 | --vm-sku ${vm_size} --custom-data "$vmss1_cloudinit_file" --instance-count $vmss_instance_count -o none 128 | 129 | 130 | ############### 131 | # Scale out # 132 | ############### 133 | # Scale out 134 | vmss_instance_count=20 135 | az vmss scale -n $vmss1_name -g $rg --new-capacity $vmss_instance_count --no-wait 136 | az vmss scale -n $vmss2_name -g $rg --new-capacity $vmss_instance_count --no-wait 137 | # Scale up 138 | vm_size=Standard_F2s_v2 139 | az vmss update -n $vmss1_name -g $rg --vm-sku $vm_size -o none 140 | az vmss update-instances -n $vmss1_name -g $rg --instance-ids '*' -o none 141 | az vmss update -n $vmss2_name -g $rg --vm-sku $vm_size -o none 142 | az vmss update-instances -n $vmss2_name -g $rg --instance-ids '*' -o none 143 | 144 | ############### 145 | # Diagnostics # 146 | ############### 147 | 148 | az vmss list-instance-public-ips -n $vmss1_name -g $rg -o table 149 | az vmss list-instance-public-ips -n $vmss2_name -g $rg -o table 150 | az vmss list-instance-public-ips -n $vmss1_name -g $rg --query '[].{IP:ipAddress, IPConfig:ipConfiguration.id}' -o table 151 | az vmss list-instance-public-ips -n $vmss2_name -g $rg --query '[].{IP:ipAddress, IPConfig:ipConfiguration.id}' -o table 152 | az vmss nic list -g $rg --vmss-name $vmss1_name --query '[].{IP:ipConfigurations[0].privateIPAddress, instanceId:virtualMachine.id}' -o table 153 | az vmss nic list -g $rg --vmss-name $vmss2_name --query '[].{IP:ipConfigurations[0].privateIPAddress, instanceId:virtualMachine.id}' -o table 154 | 155 | -------------------------------------------------------------------------------- /vmss_iperf_targetip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Controls whether printing debugging info 4 | debug=no 5 | dev_name=eth0 6 | remote_cidr=10.0.0.0/24 7 | 8 | # Get local IP address and split it in ip and mask 9 | local_cidr=$(ip address show dev $dev_name | grep inet | grep $dev_name | awk '{print $2}') 10 | if [[ "$debug" == "yes" ]]; then echo "Local CIDR: $local_cidr"; fi 11 | local_ip=$(echo $local_cidr | cut -d/ -f 1) 12 | local_mask_int=$(echo $local_cidr | cut -d/ -f 2) 13 | if [[ "$debug" == "yes" ]]; then echo "Local IP address: $local_ip, mask: $local_mask_int"; fi 14 | 15 | # Convert the /24 mask to 255.255.255.0 format 16 | cidr_to_netmask() { 17 | local cidr=$1 18 | local mask="" 19 | local full_octets=$(( cidr / 8 )) 20 | local partial_octet=$(( cidr % 8 )) 21 | for ((i=0; i<4; i++)); do 22 | if [ $i -lt $full_octets ]; then 23 | mask+="255" 24 | elif [ $i -eq $full_octets ]; then 25 | mask+=$(( 256 - (1 << (8 - partial_octet)) )) 26 | else 27 | mask+="0" 28 | fi 29 | [ $i -lt 3 ] && mask+="." 30 | done 31 | echo $mask 32 | } 33 | local_mask=$(cidr_to_netmask $local_mask_int) 34 | if [[ "$debug" == "yes" ]]; then echo "Local mask: $local_mask"; fi 35 | 36 | # Convert to hex, and then to dec 37 | local_ip_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $local_ip | sed -e 's/\./ /g'`) 38 | local_mask_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $local_mask | sed -e 's/\./ /g'`) 39 | if [[ "$debug" == "yes" ]]; then echo "Local IP address in hex: $local_ip_hex, mask: $local_mask_hex"; fi 40 | 41 | # Now we can get the subnet with a binary AND between the IP and the mask 42 | local_subnet_hex=$(printf %.8X `echo $(( 0x$local_ip_hex & 0x$local_mask_hex ))`) 43 | if [[ "$debug" == "yes" ]]; then echo "Local subnet in hex: $local_subnet_hex"; fi 44 | 45 | # Now we have the offset 46 | local_offset=$(printf %.8X `echo $(( 0x$local_ip_hex - 0x$local_subnet_hex ))`) 47 | if [[ "$debug" == "yes" ]]; then echo "Local offset: $local_offset"; fi 48 | 49 | # Before adding it to the remote subnet, we need to convert it to hex too 50 | remote_subnet=$(echo $remote_cidr | cut -d/ -f 1) 51 | if [[ "$debug" == "yes" ]]; then echo "Remote subnet address: $remote_subnet"; fi 52 | remote_subnet_hex=$(printf '%.2X%.2X%.2X%.2X\n' `echo $remote_subnet | sed -e 's/\./ /g'`) 53 | if [[ "$debug" == "yes" ]]; then echo "Remote subnet address in hex: $remote_subnet_hex"; fi 54 | 55 | # Now we can have the remote IP in hex adding the offset to the remote subnet 56 | remote_ip_hex=$(printf %.8X `echo $(( 0x$remote_subnet_hex + 0x$local_offset ))`) 57 | if [[ "$debug" == "yes" ]]; then echo "Remote IP address in hex: $remote_ip_hex"; fi 58 | 59 | # Finally we convert the remote IP in hex to an IPv4 format 60 | remote_ip=$(printf '%d.%d.%d.%d\n' `echo $remote_ip_hex | sed -r 's/(..)/0x\1 /g'`) 61 | if [[ "$debug" == "yes" ]]; then echo "Remote IP address: $remote_ip"; fi 62 | 63 | # Output 64 | echo $remote_ip 65 | 66 | -------------------------------------------------------------------------------- /vnet.azcli: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Created by Jose Moreno 3 | # November 2022 4 | # 5 | # Some useful commands around Virtual 6 | # Networks in Azure 7 | ###################################### 8 | 9 | # Control 10 | location=eastus2euap 11 | create_spokes=no 12 | number_of_spokes=2 13 | simulate_onprem=no 14 | enable_encryption=true 15 | encryption_policy=dropUnencrypted # Can be allowUnencrypted or dropUnencrypted 16 | create_azfw=yes 17 | 18 | # Variables 19 | rg=vnettest 20 | hub_vnet_name=hub 21 | vnet_prefix=192.168.0.0/24 22 | hub_vm_subnet_name=vm 23 | hub_vm_subnet_prefix=192.168.0.64/26 24 | gw_subnet_prefix=192.168.0.0/26 25 | azfw_name=myazfw 26 | azfw_pip_name=myazfw-pip 27 | azfw_subnet_name=AzureFirewallSubnet 28 | azfw_subnet_prefix=192.168.0.128/26 29 | # vm_size=Standard_B1s # Note that a size with accelerated networking is required to support encryption 30 | vm_size=Standard_D4a_v4 # Note that a size with accelerated networking is required to support encryption 31 | 32 | ################### 33 | # Enable features # 34 | ################### 35 | 36 | function enable_nw_feature () { 37 | feature_name=$1 38 | state=$(az feature list -o table --query "[?contains(name, 'microsoft.network/$feature_name')].properties.state" -o tsv) 39 | if [[ "$state" == "Registered" ]] 40 | then 41 | echo "$feature_name is already registered" 42 | else 43 | echo "Registering feature $feature_name..." 44 | az feature register --name "$feature_name" --namespace microsoft.network 45 | state=$(az feature list -o table --query "[?contains(name, 'microsoft.network/$feature_name')].properties.state" -o tsv) 46 | echo "Waiting for feature $feature_name to finish registering..." 47 | wait_interval=15 48 | until [[ "$state" == "Registered" ]] 49 | do 50 | sleep $wait_interval 51 | state=$(az feature list -o table --query "[?contains(name, 'microsoft.network/$feature_name')].properties.state" -o tsv) 52 | echo "Current registration status for feature $feature_name is $state" 53 | done 54 | echo "Registering resource provider Microsoft.Network now..." 55 | az provider register --namespace Microsoft.Network 56 | fi 57 | } 58 | 59 | ################### 60 | # Start # 61 | ################### 62 | 63 | # Create RG 64 | echo "Creating resource group..." 65 | az group create -n $rg -l $location -o none 66 | 67 | # Create hub VNet and test VMs in the same or different AZs 68 | echo "Creating VNet and VMs..." 69 | az network vnet create -g $rg -n $hub_vnet_name --address-prefix $vnet_prefix --subnet-name $hub_vm_subnet_name --subnet-prefix $hub_vm_subnet_prefix -l $location \ 70 | --enable-encryption $enable_encryption --encryption-policy $encryption_policy -o none 71 | vm_name=hubvm1 72 | zone=1 73 | echo "Creating VM $vm_name in AZ $zone..." 74 | az vm create -n $vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys --nsg "${vm_name}-nsg" -o none -l $location --zone $zone \ 75 | --public-ip-address "${vm_name}-pip" --vnet-name $hub_vnet_name --size $vm_size --subnet $hub_vm_subnet_name --accelerated-networking 76 | vm_name=hubvm2 77 | zone=1 78 | echo "Creating VM $vm_name in AZ $zone..." 79 | az vm create -n $vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys --nsg "${vm_name}-nsg" -o none -l $location --zone $zone \ 80 | --public-ip-address "${vm_name}-pip" --vnet-name $hub_vnet_name --size $vm_size --subnet $hub_vm_subnet_name --accelerated-networking 81 | 82 | # Optionally create Azure Firewall 83 | if [[ "$create_azfw" == "yes" ]]; then 84 | az network vnet subnet create --vnet-name $hub_vnet_name --name AzureFirewallSubnet -g $rg --address-prefixes $azfw_subnet_prefix -o none 85 | az network public-ip create -g $rg -n $azfw_pip_name --sku standard --allocation-method static -l $location -o none 86 | azfw_ip=$(az network public-ip show -g $rg -n $azfw_pip_name --query ipAddress -o tsv) 87 | azfw_policy_name="${azfw_name}-policy" 88 | az network firewall policy create -n $azfw_policy_name -g $rg -o none 89 | az network firewall policy rule-collection-group create -n ruleset01 --policy-name $azfw_policy_name -g $rg --priority 1000 -o none 90 | # Any-to-any network rule 91 | echo "Creating network rule to allow all traffic..." 92 | az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \ 93 | --name mgmt --collection-priority 101 --action Allow --rule-name allowAny --rule-type NetworkRule --description "Allow any to any" \ 94 | --destination-addresses '*' --source-addresses '*' --ip-protocols Any --destination-ports '*' -o none 95 | # Create Azure Firewall 96 | echo "Creating Azure Firewall..." 97 | az network firewall create -n $azfw_name -g $rg --policy $azfw_policy_name -l $location -o none 98 | # Configure routing (ToDo) 99 | fi 100 | 101 | # Flow logs - create storage account 102 | storage_account_name=$(az storage account list -g $rg -o tsv --query "[?location=='$location'].name" | head -1) # Retrieve the storage account name if it already existed 103 | if [[ -z "$storage_account_name" ]]; then 104 | storage_account_name=$(echo "logs$RANDOM${location}" | cut -c1-24) # max 24 characters 105 | echo "No storage account found in $location, creating one..." 106 | az storage account create -n $storage_account_name -g $rg --sku Standard_LRS --kind StorageV2 -l $location -o none 107 | else 108 | echo "Storage account $storage_account_name created in $location, using it for NSG flow flogs" 109 | fi 110 | # Create Log Analytics workspace 111 | logws_name=$(az monitor log-analytics workspace list -g $rg --query '[].name' -o tsv 2>/dev/null) # Retrieve the WS name if it already existed 112 | if [[ -z "$logws_name" ]] 113 | then 114 | logws_name=log$RANDOM 115 | az monitor log-analytics workspace create -n $logws_name -g $rg -o none 116 | fi 117 | logws_id=$(az resource list -g $rg -n $logws_name --query '[].id' -o tsv) 118 | logws_customerid=$(az monitor log-analytics workspace show -n $logws_name -g $rg --query customerId -o tsv) 119 | # Enable flow logs in the VNet 120 | echo "Registering Microsoft.Insights RP..." 121 | az provider register --namespace Microsoft.Insights -o none 122 | vnet_id=$(az network vnet show -n $hub_vnet_name -g $rg --query id -o tsv) 123 | echo "Configuring VNet Flow Logs..." 124 | az network watcher flow-log create -l $location -n "flowlog-$location" -g $rg \ 125 | --vnet $vnet_id --storage-account $storage_account_name --log-version 2 --retention 7 \ 126 | --workspace $logws_id --interval 10 --traffic-analytics true -o none 127 | 128 | 129 | ###################### 130 | # Performance test # 131 | ###################### 132 | 133 | # iperf (hubvm2 will be the server) 134 | # Getting IPs... 135 | hubvm1_pip=$(az network public-ip show -n hubvm1-pip -g $rg --query ipAddress -o tsv) && echo $hubvm1_pip 136 | hubvm1_private_ip=$(az vm show -g $rg -n hubvm1 -d --query privateIps -o tsv) && echo $hubvm1_private_ip 137 | hubvm2_pip=$(az network public-ip show -n hubvm2-pip -g $rg --query ipAddress -o tsv) && echo $hubvm2_pip 138 | hubvm2_private_ip=$(az vm show -g $rg -n hubvm2 -d --query privateIps -o tsv) && echo $hubvm2_private_ip 139 | # Enable iperf server on hubvm1 140 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hubvm1_pip "sudo apt update && sudo apt install -y iperf3" 141 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hubvm2_pip "sudo apt update && sudo apt install -y iperf3" 142 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hubvm1_pip "iperf3 -s -D" 143 | ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $hubvm2_pip "iperf3 -c $hubvm1_private_ip" 144 | 145 | ################# 146 | # Diagnostics # 147 | ################# 148 | 149 | # Encryption settings 150 | az network vnet show -n $hub_vnet_name -g $rg --query encryption 151 | az network nic list -g $rg --query '[].{Name:name, EncryptionSupported:vnetEncryptionSupported,Location:location}' -o table 152 | 153 | # Flow logs 154 | az network watcher flow-log list -l $location -o table 155 | 156 | ################## 157 | # Start/Stop # 158 | ################## 159 | 160 | function stop_firewall() { 161 | echo "Stoping Azure Firewall ${azfw_name}..." 162 | az network firewall ip-config delete -f $azfw_name -n azfw-ipconfig -g $rg -o none 163 | az network firewall update -n $azfw_name -g $rg -o none 164 | } 165 | function start_firewall() { 166 | echo "Starting Azure Firewall ${azfw_name}..." 167 | az network firewall ip-config create -f $azfw_name -n azfw-ipconfig -g $rg --public-ip-address $azfw_pip_name --vnet-name $vnet_name -o none 168 | az network firewall update -n $azfw_name -g $rg -o none 169 | } 170 | function stop_vms() { 171 | vm_list=$(az vm list -o tsv -g "$rg" --query "[].name") 172 | while IFS= read -r vm_name; do 173 | echo "Deallocating Virtual Machine ${vm_name}..." 174 | az vm deallocate -g $rg -n "$vm_name" --no-wait -o none 175 | done <<< "$vm_list" 176 | } 177 | function start_vms() { 178 | vm_list=$(az vm list -o tsv -g "$rg" --query "[].name") 179 | while IFS= read -r vm_name; do 180 | echo "Starting Virtual Machine ${vm_name}..." 181 | az vm start -g $rg -n "$vm_name" --no-wait -o none 182 | done <<< "$vm_list" 183 | } 184 | function start_lab() { 185 | start_vms 186 | start_firewall 187 | } 188 | function stop_lab() { 189 | stop_vms 190 | stop_firewall 191 | } 192 | 193 | ################# 194 | # CLEANUP # 195 | ################# 196 | 197 | az network watcher flow-log delete -l $location -n flowlog-$location 198 | az group delete -y --no-wait -n $rg -------------------------------------------------------------------------------- /vwan-lab-create.sh: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Created by Jose Moreno 3 | # September 2020 4 | # 5 | # Creates a VWAN environment using the 2020-05-01 APIs for custom routing 6 | # It leverages the functions defined in 'vwan-functions.sh' 7 | # It takes a single parameter, the resource group name. Everything else 8 | # is defined as variables here or in vwan-functions.sh 9 | # The recommended way to use this is with source, like: 10 | # $ source ./vwan-lab-create.sh yourRGname 11 | ############################################################################ 12 | 13 | # Parameters 14 | if [[ -n "$1" ]] 15 | then 16 | rg=$1 17 | else 18 | rg=vwanlab # Default value if no param is provided 19 | fi 20 | 21 | # Variables 22 | rg_location=westeurope 23 | vwan_name=vwan 24 | 25 | ######################### 26 | # Create infrastructure # 27 | ######################### 28 | 29 | # RG 30 | echo "Creating resource group $rg in $rg_location..." 31 | az group create -n $rg -l $rg_location >/dev/null 32 | source ./vwan-functions.sh 33 | 34 | # vwan and hubs 35 | create_vwan $vwan_name 36 | create_hub 1 $vwan_name 37 | create_hub 2 $vwan_name 38 | 39 | # Gateways and branches 40 | # Creating 2 gateways in parallel normally ends up in one of them Failed 41 | # Going sequentially here 42 | create_vpngw 1 43 | create_csr 1 1 44 | wait_until_gw_finished hubvpn1 45 | 46 | create_vpngw 2 47 | create_csr 2 2 48 | wait_until_gw_finished hubvpn2 49 | 50 | # Example of a single-homed branch (VPN tunnels to 1 hub): 51 | configure_csr 1 1 52 | connect_branch 1 1 53 | 54 | # Example of a dual-homed branch (VPN tunnels to 2 hubs): 55 | # configure_csr_dualhomed 1 2 1 # Configures CSR 1 to connect to hubs 1 and 2 56 | # connect_branch 1 1 # Connect hub1 to branch1 57 | # connect_branch 2 1 # Connect hub2 to branch1 58 | 59 | # Example of a single-homed branch (VPN tunnels to 1 hub): 60 | configure_csr 2 2 61 | connect_branch 2 2 62 | 63 | # Example of a dual-homed branch (VPN tunnels to 2 hubs): 64 | # configure_csr_dualhomed 1 2 2 # Configures CSR 2 (branch2) to connect to hubs 1 and 2 65 | # connect_branch 1 2 # Connect hub1 to branch2 66 | # connect_branch 2 2 # Connect hub2 to branch2 67 | 68 | # Tunnel verification 69 | echo "Giving the IPsec tunnels 60s to start..." && sleep 60 70 | branch_cmd "show ip int brief" 71 | 72 | # Vnets 73 | create_spokes 1 2 74 | create_spokes 2 2 75 | 76 | # User spokes (aka nva spokes, aka indirect spokes) 77 | # We will use spoke3 as user hub (aka nva spoke) 78 | # create_userspoke 1 3 1 79 | # convert_to_nva 1 3 80 | # connect_userspoke 1 3 1 81 | # create_userspoke 2 3 1 82 | # convert_to_nva 2 3 83 | # connect_userspoke 2 3 1 84 | 85 | # Virtual Secure Hub 86 | create_azfw_policy 87 | create_fw 1 88 | create_fw 2 89 | 90 | # Logging from all VPN gateways and Azure Firewalls 91 | create_logs 92 | 93 | ################## 94 | # Custom Routing # 95 | ################## 96 | 97 | # Create route tables 98 | # create_rt hub1 hub1VnetRT vnet 99 | # create_rt hub2 hub2VnetRT vnet 100 | 101 | # Modify vnet connections 102 | # cx_set_rt hub1 spoke11 hub1VnetRT hub2/defaultRouteTable,hub2/hub2VnetRT 103 | # cx_set_prop_labels hub1 spoke11 104 | # cx_set_rt hub1 spoke12 hub1VnetRT hub2/defaultRouteTable,hub2/hub2VnetRT 105 | # cx_set_prop_labels hub1 spoke12 106 | # cx_set_rt hub2 spoke21 hub2VnetRT hub1/defaultRouteTable,hub1/hub1VnetRT 107 | # cx_set_prop_labels hub2 spoke21 108 | # cx_set_rt hub2 spoke22 hub2VnetRT hub1/defaultRouteTable,hub1/hub1VnetRT 109 | # cx_set_prop_labels hub2 spoke22 110 | 111 | # Modify vpn connections 112 | # vpncx_set_prop_rt 1 branch1 hub2/defaultRouteTable,hub2/hub2VnetRT 113 | # vpncx_set_prop_rt 1 branch2 hub2/defaultRouteTable,hub2/hub2VnetRT 114 | # vpncx_set_prop_rt 2 branch1 hub1/defaultRouteTable,hub1/hub1VnetRT 115 | # vpncx_set_prop_rt 2 branch2 hub1/defaultRouteTable,hub1/hub1VnetRT 116 | 117 | # Delete vpn prop labels 118 | # vpncx_set_prop_labels hubvpn1 branch1 119 | # vpncx_set_prop_labels hubvpn1 branch2 120 | # vpncx_set_prop_labels hubvpn2 branch1 121 | # vpncx_set_prop_labels hubvpn2 branch2 122 | 123 | # Set vpn connections to default propagation 124 | # vpncx_set_prop_rt 1 branch1 hub1/defaultRouteTable,hub1/hub1VnetRT default,vnet 125 | # vpncx_set_prop_rt 1 branch2 hub1/defaultRouteTable,hub1/hub1VnetRT default,vnet 126 | # vpncx_set_prop_rt 2 branch1 hub2/defaultRouteTable,hub2/hub2VnetRT default,vnet 127 | # vpncx_set_prop_rt 2 branch2 hub2/defaultRouteTable,hub2/hub2VnetRT default,vnet 128 | 129 | 130 | # Create static routes in route tables for Secure Virtual Hub 131 | # rt_add_route 1 defaultRouteTable "10.0.0.0/8" "$(get_azfw_id 1)" 132 | # rt_add_route 2 defaultRouteTable "10.0.0.0/8" "$(get_azfw_id 2)" 133 | # rt_add_route 1 hub1VnetRT "0.0.0.0/0" "$(get_azfw_id 1)" 134 | # rt_add_route 2 hub2VnetRT "0.0.0.0/0" "$(get_azfw_id 2)" 135 | 136 | # Create static routes in route tables for NVA routing 137 | # rt_add_route 1 defaultRouteTable "$(get_spoke_prefix 1 5 1)" "$(get_vnetcx_id 1 5)" 138 | # rt_add_route 2 defaultRouteTable "$(get_spoke_prefix 1 5 1)" "$(get_vnetcx_id 1 5)" 139 | 140 | # Create static routes in connections for NVA routing 141 | # cx_add_routes 1 spoke15 "$(get_spoke_prefix 1 5 1)" "$(get_spoke_ip 1 5)" 142 | 143 | # Done 144 | echo "Your VWAN $vwan_name in resource group $rg is ready" 145 | -------------------------------------------------------------------------------- /webapp.azcli: -------------------------------------------------------------------------------- 1 | # Resource group 2 | rg=appweblab 3 | location=westeurope 4 | az group create -n $rg -l $location 5 | create_appgw=yes 6 | private_link=no 7 | aad_auth=yes 8 | 9 | # Create Windows Web App for API 10 | svcplan_name=webappplan 11 | app_name_api=api-$RANDOM 12 | az appservice plan create -n $svcplan_name -g $rg --sku B1 13 | # Update svc plan if required 14 | az appservice plan update -n $svcplan_name -g $rg --sku S1 15 | # Create web app (see `az webapp list-runtimes` for the runtimes) 16 | az webapp create -n $app_name_api -g $rg -p $svcplan_name -r "aspnet|V4.7" 17 | app_url=$(az webapp show -n $app_name_api -g $rg --query defaultHostName -o tsv) 18 | echo "Web app url is https://${app_url}" 19 | 20 | # Load app 21 | app_file_url=https://raw.githubusercontent.com/jelledruyts/InspectorGadget/master/Page/default.aspx 22 | app_file_name=default.aspx 23 | wget $app_file_url -O $app_file_name 24 | creds=($(az webapp deployment list-publishing-profiles -n $app_name_api -g $rg --query "[?contains(publishMethod, 'FTP')].[publishUrl,userName,userPWD]" --output tsv)) 25 | # curl -T $app_file_name -u ${creds[1]}:${creds[2]} ${creds[0]}/ 26 | curl -T $app_file_name -u ${creds[2]}:${creds[3]} ${creds[1]}/ 27 | echo "Check out this URL: http://${app_url}/${app_file_name}" 28 | 29 | # Private Link 30 | if [[ "$private_link" == "yes" ]] 31 | then 32 | # Azure SQL 33 | sql_server_name=myserver$RANDOM 34 | sql_db_name=mydb 35 | sql_username=azure 36 | sql_password=Microsoft123! 37 | az sql server create -n $sql_server_name -g $rg -l $location --admin-user $sql_username --admin-password $sql_password 38 | az sql db create -n $sql_db_name -s $sql_server_name -g $rg -e Basic -c 5 --no-wait 39 | # Optionally test for serverless SKU 40 | # az sql db update -g $rg -s $sql_server_name -n $sql_db_name --edition GeneralPurpose --min-capacity 1 --capacity 4 --family Gen5 --compute-model Serverless --auto-pause-delay 1440 41 | sql_server_fqdn=$(az sql server show -n $sql_server_name -g $rg -o tsv --query fullyQualifiedDomainName) 42 | 43 | # Create Vnet 44 | vnet_name=myvnet 45 | vnet_prefix=192.168.0.0/16 46 | subnet_sql_name=sql 47 | subnet_sql_prefix=192.168.2.0/24 48 | subnet_webapp_be_name=webapp-be 49 | subnet_webapp_be_prefix=192.168.5.0/24 50 | az network vnet create -g $rg -n $vnet_name --address-prefix $vnet_prefix -l $location 51 | az network vnet subnet create -g $rg --vnet-name $vnet_name -n $subnet_sql_name --address-prefix $subnet_sql_prefix 52 | az network vnet subnet create -g $rg --vnet-name $vnet_name -n $subnet_webapp_be_name --address-prefix $subnet_webapp_be_prefix 53 | 54 | # Create vnet integration 55 | az webapp vnet-integration add -n $app_name_api -g $rg --vnet $vnet_name --subnet $subnet_webapp_be_name 56 | # Verify 57 | az webapp vnet-integration list -n $app_name_api -g $rg -o table 58 | 59 | # Update Firewall 60 | # az webapp show -n api-26567 -g $rg --query outboundIpAddresses 61 | # Creating one rule for each outbound IP: not implemented yet. Workaround: fully open 62 | az sql server firewall-rule create -g $rg -s $sql_server_name -n permitAny --start-ip-address "0.0.0.0" --end-ip-address "255.255.255.255" 63 | az sql server firewall-rule list -g $rg -s $sql_server_name -o table 64 | 65 | # Get connection string 66 | db_client_type=ado.net 67 | az sql db show-connection-string -n $sql_db_name -s $sql_server_name -c $db_client_type -o tsv | awk '{sub(//,"'$sql_username'")}1' | awk '{sub(//,"'$sql_password'")}1' 68 | 69 | # Send Query over the web app GUI to SELECT CONNECTIONPROPERTY('client_net_address'). This should work, since it is going over the public IP at this time 70 | 71 | # Create SQL private endpoint (note that there is no integration with private DNS from the CLI) 72 | endpoint_name=mysqlep 73 | sql_server_id=$(az sql server show -n $sql_server_name -g $rg -o tsv --query id) 74 | az network vnet subnet update -n $subnet_sql_name -g $rg --vnet-name $vnet_name --disable-private-endpoint-network-policies true 75 | az network private-endpoint create -n $endpoint_name -g $rg --vnet-name $vnet_name --subnet $subnet_sql_name --private-connection-resource-id $sql_server_id --group-ids sqlServer --connection-name sqlConnection 76 | # Get private endpoint ip 77 | nic_id=$(az network private-endpoint show -n $endpoint_name -g $rg --query 'networkInterfaces[0].id' -o tsv) 78 | sql_endpoint_ip=$(az network nic show --ids $nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) 79 | echo "Private IP address for SQL server ${sql_server_name}: ${sql_endpoint_ip}" 80 | 81 | # Create Azure DNS private zone and records: database.windows.net 82 | dns_zone_name=database.windows.net 83 | az network private-dns zone create -n $dns_zone_name -g $rg 84 | az network private-dns link vnet create -g $rg -z $dns_zone_name -n myDnsLink --virtual-network $vnet_name --registration-enabled false 85 | # Create record (private dns zone integration not working in the CLI) 86 | az network private-dns record-set a create -n $sql_server_name -z $dns_zone_name -g $rg 87 | az network private-dns record-set a add-record --record-set-name $sql_server_name -z $dns_zone_name -g $rg -a $sql_endpoint_ip 88 | # Verification: list recordsets in the zone 89 | az network private-dns record-set list -z $dns_zone_name -g $rg -o table 90 | az network private-dns record-set a show -n $sql_server_name -z $dns_zone_name -g $rg --query aRecords -o table 91 | 92 | # Create DNS server VM 93 | subnet_dns_name=dns-vm 94 | subnet_dns_prefix=192.168.53.0/24 95 | az network vnet subnet create -g $rg --vnet-name $vnet_name -n $subnet_dns_name --address-prefix $subnet_dns_prefix 96 | dnsserver_name=dnsserver 97 | dnsserver_pip_name=dns-vm-pip 98 | dnsserver_size=Standard_D2_v3 99 | az vm create -n $dnsserver_name -g $rg --vnet-name $vnet_name --subnet $subnet_dns_name --public-ip-address $dnsserver_pip_name --generate-ssh-keys --image ubuntuLTS --priority Low --size $dnsserver_size --no-wait 100 | dnsserver_ip=$(az network public-ip show -n $dnsserver_pip_name -g $rg --query ipAddress -o tsv) 101 | dnsserver_nic_id=$(az vm show -n $dnsserver_name -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv) 102 | dnsserver_privateip=$(az network nic show --ids $dnsserver_nic_id --query 'ipConfigurations[0].privateIpAddress' -o tsv) 103 | echo "DNS server deployed to $dnsserver_privateip, $dnsserver_ip" 104 | echo "IP configuration of the VM:" 105 | ssh $dnsserver_ip "ip a" 106 | ssh $dnsserver_ip "sudo apt -y install apache2 dnsmasq" 107 | 108 | # Configure web app for DNS - Option 1: 109 | # DNS server as server for the vnet (required only if not setting the app setting) 110 | az network vnet update -n $vnet_name -g $g --dns-servers $dnsserver_privateip 111 | # Bounce the vnet integration to take the new DNS config 112 | az webapp vnet-integration delete -n $app_name_api -g $rg 113 | az webapp vnet-integration add -n $app_name_api -g $rg --vnet $vnet_name --subnet $subnet_webapp_be_name 114 | 115 | # Configure web app for DNS - Option 2: 116 | # Change web app DNS settings (https://www.azuretechguy.com/how-to-change-the-dns-server-in-azure-app-service) 117 | az webapp config appsettings set -n $app_name_api -g $rg --settings "WEBSITE_DNS_SERVER=${dnsserver_privateip} 118 | az webapp restart -n $app_name_api -g $rg 119 | 120 | # Send Query over the app to SELECT CONNECTIONPROPERTY('client_net_address'). Now we should be using the private IP 121 | 122 | fi 123 | 124 | if [[ "$create_appgw" == "yes "]] 125 | then 126 | appgw_pipname=appgwpip 127 | dnsname=kuardgw 128 | dnszone=cloudtrooper.net 129 | dnsrg=dns 130 | appgw_name=appgw 131 | sku=Standard_v2 132 | cookie=Disabled 133 | backenddnsname=webapp 134 | backendfqdn="$backenddnsname"."$dnszone" 135 | vnet_name=appgw 136 | vnet_prefix=10.0.0.0/16 137 | appgw_subnet_name=AppGateway 138 | appgw_subnet_prefix=10.0.0.0/24 139 | aci_subnet_name=aci 140 | aci_subnet_prefix=10.0.1.0/24 141 | appgw_nsg_name=appgw 142 | log_storage_account=appgwlog$RANDOM 143 | 144 | # Create vnet 145 | az network vnet create -n $vnet_name -g $rg --address-prefix $vnet_prefix --subnet-name $appgw_subnet_name --subnet-prefix $appgw_subnet_prefix 146 | 147 | # Alternatively create subnet in the vnet 148 | # az network vnet subnet create --vnet-name $vnet_name --name $appgw_subnet_name -g $rg --address-prefixes 10.13.123.0/24 149 | 150 | # Create PIP 151 | allocation_method=Static 152 | az network public-ip create -g $rg -n $appgw_pipname --sku Standard --allocation-method $allocation_method 153 | #fqdn=$(az network public-ip show -g $rg -n $appgw_pipname --query dnsSettings.fqdn -o tsv) 154 | 155 | # Create GW with sample config for port 80 156 | az network application-gateway create -g $rg -n $appgw_name --capacity 2 --sku $sku \ 157 | --frontend-port 80 --routing-rule-type basic \ 158 | --http-settings-port 80 --http-settings-protocol Http \ 159 | --public-ip-address $appgw_pipname --vnet-name $vnet_name --subnet $appgw_subnet_name \ 160 | --servers "$app_url" \ 161 | --no-wait 162 | fi 163 | 164 | if [[ "$aad_auth" == "yes" ]] 165 | then 166 | # Update app with new callback url 167 | # cluster_fqdn=$(az aro show -n $cluster_name -g $rg --query clusterProfile.domain -o tsv) 168 | domain=$(az aro list -g $rg --query '[0].clusterProfile.domain' -o tsv) 169 | location=$(az aro list -g $rg --query '[0].location' -o tsv) 170 | cluster_fqdn=https://oauth-openshift.apps.${domain}.${location}.aroapp.io/oauth2callback/AAD 171 | echo "Running command: az ad app update --id $app_id --reply-urls \"${cluster_fqdn}\"" 172 | az ad app update --id $app_id --reply-urls "${cluster_fqdn}" 173 | fi --------------------------------------------------------------------------------