mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
474 lines
12 KiB
Markdown
474 lines
12 KiB
Markdown
---
|
|
name: arm-templates
|
|
description: Deploy Azure resources with ARM templates and Bicep. Create modular deployments and manage dependencies. Use when deploying Azure-native IaC.
|
|
license: MIT
|
|
metadata:
|
|
author: devops-skills
|
|
version: "1.0"
|
|
---
|
|
|
|
# ARM Templates & Bicep
|
|
|
|
Deploy Azure infrastructure with ARM templates and Bicep. Bicep is the recommended domain-specific language that compiles to ARM JSON, offering cleaner syntax, modules, and first-class tooling support.
|
|
|
|
## When to Use
|
|
|
|
- You need Azure-native Infrastructure as Code without third-party tooling.
|
|
- Your organization standardizes on Azure and wants tight portal integration.
|
|
- You need What-If analysis before deploying changes.
|
|
- You are migrating existing ARM JSON templates to Bicep for maintainability.
|
|
- You need deployment scopes at resource group, subscription, management group, or tenant level.
|
|
|
|
## Prerequisites
|
|
|
|
```bash
|
|
# Install Azure CLI
|
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
|
|
|
# Install Bicep CLI (bundled with Azure CLI 2.20+)
|
|
az bicep install
|
|
az bicep upgrade
|
|
|
|
# Verify installation
|
|
az bicep version
|
|
|
|
# Login and set subscription
|
|
az login
|
|
az account set --subscription "my-subscription-id"
|
|
```
|
|
|
|
## Bicep Fundamentals
|
|
|
|
### Resource Group Deployment with Virtual Network
|
|
|
|
```bicep
|
|
// main.bicep
|
|
@description('Azure region for all resources')
|
|
param location string = resourceGroup().location
|
|
|
|
@description('Environment name used for resource naming')
|
|
@allowed(['dev', 'staging', 'prod'])
|
|
param environment string = 'dev'
|
|
|
|
@description('Base name for all resources')
|
|
param baseName string
|
|
|
|
var vnetName = '${baseName}-${environment}-vnet'
|
|
var nsgName = '${baseName}-${environment}-nsg'
|
|
|
|
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-05-01' = {
|
|
name: nsgName
|
|
location: location
|
|
properties: {
|
|
securityRules: [
|
|
{
|
|
name: 'AllowHTTPS'
|
|
properties: {
|
|
priority: 100
|
|
direction: 'Inbound'
|
|
access: 'Allow'
|
|
protocol: 'Tcp'
|
|
sourcePortRange: '*'
|
|
destinationPortRange: '443'
|
|
sourceAddressPrefix: '*'
|
|
destinationAddressPrefix: '*'
|
|
}
|
|
}
|
|
{
|
|
name: 'DenyAllInbound'
|
|
properties: {
|
|
priority: 4096
|
|
direction: 'Inbound'
|
|
access: 'Deny'
|
|
protocol: '*'
|
|
sourcePortRange: '*'
|
|
destinationPortRange: '*'
|
|
sourceAddressPrefix: '*'
|
|
destinationAddressPrefix: '*'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
|
|
name: vnetName
|
|
location: location
|
|
properties: {
|
|
addressSpace: {
|
|
addressPrefixes: [
|
|
'10.0.0.0/16'
|
|
]
|
|
}
|
|
subnets: [
|
|
{
|
|
name: 'web-subnet'
|
|
properties: {
|
|
addressPrefix: '10.0.1.0/24'
|
|
networkSecurityGroup: {
|
|
id: nsg.id
|
|
}
|
|
}
|
|
}
|
|
{
|
|
name: 'app-subnet'
|
|
properties: {
|
|
addressPrefix: '10.0.2.0/24'
|
|
}
|
|
}
|
|
{
|
|
name: 'data-subnet'
|
|
properties: {
|
|
addressPrefix: '10.0.3.0/24'
|
|
privateEndpointNetworkPolicies: 'Enabled'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
output vnetId string = vnet.id
|
|
output webSubnetId string = vnet.properties.subnets[0].id
|
|
output appSubnetId string = vnet.properties.subnets[1].id
|
|
```
|
|
|
|
### VM Deployment with Managed Identity
|
|
|
|
```bicep
|
|
// vm.bicep
|
|
param location string = resourceGroup().location
|
|
param vmName string
|
|
param subnetId string
|
|
param adminUsername string = 'azureuser'
|
|
|
|
@secure()
|
|
param adminPublicKey string
|
|
|
|
resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = {
|
|
name: '${vmName}-nic'
|
|
location: location
|
|
properties: {
|
|
ipConfigurations: [
|
|
{
|
|
name: 'ipconfig1'
|
|
properties: {
|
|
privateIPAllocationMethod: 'Dynamic'
|
|
subnet: {
|
|
id: subnetId
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
resource vm 'Microsoft.Compute/virtualMachines@2023-07-01' = {
|
|
name: vmName
|
|
location: location
|
|
identity: {
|
|
type: 'SystemAssigned'
|
|
}
|
|
properties: {
|
|
hardwareProfile: {
|
|
vmSize: 'Standard_B2s'
|
|
}
|
|
osProfile: {
|
|
computerName: vmName
|
|
adminUsername: adminUsername
|
|
linuxConfiguration: {
|
|
disablePasswordAuthentication: true
|
|
ssh: {
|
|
publicKeys: [
|
|
{
|
|
path: '/home/${adminUsername}/.ssh/authorized_keys'
|
|
keyData: adminPublicKey
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
storageProfile: {
|
|
imageReference: {
|
|
publisher: 'Canonical'
|
|
offer: '0001-com-ubuntu-server-jammy'
|
|
sku: '22_04-lts-gen2'
|
|
version: 'latest'
|
|
}
|
|
osDisk: {
|
|
createOption: 'FromImage'
|
|
managedDisk: {
|
|
storageAccountType: 'Premium_LRS'
|
|
}
|
|
}
|
|
}
|
|
networkProfile: {
|
|
networkInterfaces: [
|
|
{
|
|
id: nic.id
|
|
}
|
|
]
|
|
}
|
|
diagnosticsProfile: {
|
|
bootDiagnostics: {
|
|
enabled: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
output vmPrincipalId string = vm.identity.principalId
|
|
output vmId string = vm.id
|
|
```
|
|
|
|
## Bicep Modules
|
|
|
|
### Module Definition
|
|
|
|
```bicep
|
|
// modules/storage.bicep
|
|
@description('Storage account name (3-24 chars, lowercase alphanumeric)')
|
|
param storageAccountName string
|
|
|
|
param location string = resourceGroup().location
|
|
param sku string = 'Standard_LRS'
|
|
|
|
@allowed(['Hot', 'Cool', 'Archive'])
|
|
param accessTier string = 'Hot'
|
|
|
|
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
|
name: storageAccountName
|
|
location: location
|
|
sku: {
|
|
name: sku
|
|
}
|
|
kind: 'StorageV2'
|
|
properties: {
|
|
accessTier: accessTier
|
|
supportsHttpsTrafficOnly: true
|
|
minimumTlsVersion: 'TLS1_2'
|
|
allowBlobPublicAccess: false
|
|
networkAcls: {
|
|
defaultAction: 'Deny'
|
|
bypass: 'AzureServices'
|
|
}
|
|
}
|
|
}
|
|
|
|
output storageAccountId string = storageAccount.id
|
|
output primaryBlobEndpoint string = storageAccount.properties.primaryEndpoints.blob
|
|
```
|
|
|
|
### Consuming Modules
|
|
|
|
```bicep
|
|
// main.bicep
|
|
param location string = resourceGroup().location
|
|
param environment string = 'prod'
|
|
|
|
module storage 'modules/storage.bicep' = {
|
|
name: 'storage-deployment'
|
|
params: {
|
|
storageAccountName: 'myapp${environment}sa'
|
|
location: location
|
|
sku: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
|
|
}
|
|
}
|
|
|
|
module vnet 'modules/network.bicep' = {
|
|
name: 'vnet-deployment'
|
|
params: {
|
|
location: location
|
|
environment: environment
|
|
}
|
|
}
|
|
|
|
// Reference module outputs
|
|
output storageBlobEndpoint string = storage.outputs.primaryBlobEndpoint
|
|
```
|
|
|
|
## ARM JSON Template Structure
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
|
"contentVersion": "1.0.0.0",
|
|
"parameters": {
|
|
"storageAccountName": {
|
|
"type": "string",
|
|
"metadata": {
|
|
"description": "Name of the storage account"
|
|
}
|
|
},
|
|
"location": {
|
|
"type": "string",
|
|
"defaultValue": "[resourceGroup().location]"
|
|
}
|
|
},
|
|
"variables": {
|
|
"storageSku": "Standard_LRS"
|
|
},
|
|
"resources": [
|
|
{
|
|
"type": "Microsoft.Storage/storageAccounts",
|
|
"apiVersion": "2023-01-01",
|
|
"name": "[parameters('storageAccountName')]",
|
|
"location": "[parameters('location')]",
|
|
"sku": {
|
|
"name": "[variables('storageSku')]"
|
|
},
|
|
"kind": "StorageV2",
|
|
"properties": {
|
|
"supportsHttpsTrafficOnly": true,
|
|
"minimumTlsVersion": "TLS1_2"
|
|
}
|
|
}
|
|
],
|
|
"outputs": {
|
|
"storageId": {
|
|
"type": "string",
|
|
"value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Deployment Commands
|
|
|
|
```bash
|
|
# Validate a Bicep template before deployment
|
|
az deployment group validate \
|
|
--resource-group mygroup \
|
|
--template-file main.bicep \
|
|
--parameters environment='prod' baseName='myapp'
|
|
|
|
# Preview changes with What-If
|
|
az deployment group what-if \
|
|
--resource-group mygroup \
|
|
--template-file main.bicep \
|
|
--parameters environment='prod' baseName='myapp'
|
|
|
|
# Deploy Bicep to resource group
|
|
az deployment group create \
|
|
--resource-group mygroup \
|
|
--template-file main.bicep \
|
|
--parameters environment='prod' baseName='myapp' \
|
|
--name "deploy-$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# Deploy ARM JSON with parameter file
|
|
az deployment group create \
|
|
--resource-group mygroup \
|
|
--template-file template.json \
|
|
--parameters @parameters.prod.json
|
|
|
|
# Subscription-level deployment (e.g., resource groups, policies)
|
|
az deployment sub create \
|
|
--location eastus \
|
|
--template-file subscription-level.bicep \
|
|
--parameters @params.json
|
|
|
|
# Management group deployment
|
|
az deployment mg create \
|
|
--management-group-id my-mg \
|
|
--location eastus \
|
|
--template-file mg-policy.bicep
|
|
|
|
# Export resource group to ARM JSON
|
|
az group export --name mygroup --output json > exported-template.json
|
|
|
|
# Decompile ARM JSON to Bicep
|
|
az bicep decompile --file exported-template.json
|
|
|
|
# Build Bicep to ARM JSON (for inspection)
|
|
az bicep build --file main.bicep --outfile main.json
|
|
|
|
# List deployments and their status
|
|
az deployment group list \
|
|
--resource-group mygroup \
|
|
--output table
|
|
|
|
# Delete a failed deployment
|
|
az deployment group delete \
|
|
--resource-group mygroup \
|
|
--name my-failed-deployment
|
|
```
|
|
|
|
## Parameter Files
|
|
|
|
```json
|
|
// parameters.prod.json
|
|
{
|
|
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
|
"contentVersion": "1.0.0.0",
|
|
"parameters": {
|
|
"environment": { "value": "prod" },
|
|
"baseName": { "value": "myapp" },
|
|
"adminPublicKey": {
|
|
"reference": {
|
|
"keyVault": {
|
|
"id": "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault}"
|
|
},
|
|
"secretName": "ssh-public-key"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Linked and Nested Templates
|
|
|
|
```bicep
|
|
// Deploy to a different resource group
|
|
module networkInSharedRg 'modules/network.bicep' = {
|
|
name: 'shared-network'
|
|
scope: resourceGroup('shared-networking-rg')
|
|
params: {
|
|
location: location
|
|
}
|
|
}
|
|
|
|
// Conditional deployment
|
|
param deployMonitoring bool = true
|
|
|
|
module monitoring 'modules/monitoring.bicep' = if (deployMonitoring) {
|
|
name: 'monitoring-deployment'
|
|
params: {
|
|
location: location
|
|
}
|
|
}
|
|
|
|
// Loop deployment
|
|
param storageAccounts array = [
|
|
{ name: 'logs', sku: 'Standard_LRS' }
|
|
{ name: 'data', sku: 'Standard_GRS' }
|
|
]
|
|
|
|
module storageLoop 'modules/storage.bicep' = [for account in storageAccounts: {
|
|
name: 'storage-${account.name}'
|
|
params: {
|
|
storageAccountName: '${baseName}${account.name}sa'
|
|
sku: account.sku
|
|
location: location
|
|
}
|
|
}]
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Cause | Fix |
|
|
|---------|-------|-----|
|
|
| `InvalidTemplate` error | Syntax error in ARM JSON or Bicep | Run `az bicep build` to check for compile errors |
|
|
| `ResourceNotFound` during deployment | Resource dependency not declared | Add `dependsOn` or use implicit references in Bicep |
|
|
| `DeploymentFailed` with quota error | Subscription quota exceeded | Request quota increase or use a different region |
|
|
| `AuthorizationFailed` | Insufficient RBAC permissions | Assign Contributor role on the target resource group |
|
|
| Parameter file secrets in source control | Secrets stored as plain text | Use Key Vault references in parameter files |
|
|
| Deployment takes very long | Large number of resources deployed serially | Use `dependsOn` carefully to allow parallel deployment |
|
|
| `What-If` shows unexpected deletions | Complete mode instead of Incremental | Use `--mode Incremental` (the default) to avoid deleting unmanaged resources |
|
|
| Bicep module not found | Incorrect relative path | Verify path is relative to the consuming file |
|
|
|
|
## Related Skills
|
|
|
|
- `terraform-azure` -- Multi-cloud IaC alternative with broader provider support.
|
|
- `azure-networking` -- VNet, NSG, and firewall configurations referenced in templates.
|
|
- `azure-vms` -- Virtual machine sizing and configuration details.
|
|
- `azure-aks` -- Kubernetes cluster definitions for Bicep/ARM.
|