diff --git a/.gitignore b/.gitignore index 291736b..66fbdb2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,8 @@ override.tf.json .terraformrc terraform.rc -.vscode/settings.json \ No newline at end of file +.vscode/settings.json + +# Bicep build artifacts (ARM templates) +infra/bicep/**/*.json +!infra/bicep/**/*.parameters.json \ No newline at end of file diff --git a/infra/bicep/README.md b/infra/bicep/README.md index 95f4c10..c7b5c7b 100644 --- a/infra/bicep/README.md +++ b/infra/bicep/README.md @@ -1 +1,228 @@ -# Bicep IaC \ No newline at end of file +# Bicep IaC - Azure VM + Network + +This directory contains Azure Bicep Infrastructure as Code (IaC) for deploying a demo Windows Server virtual machine with associated networking resources in Azure. + +## What's Deployed + +The infrastructure includes: + +- **Resource Group**: Container for all Azure resources +- **Virtual Network (VNet)**: 10.0.0.0/16 address space +- **Subnet**: 10.0.0.0/24 address space (default subnet) +- **Network Security Group (NSG)**: With inbound rules for RDP (3389), HTTP (80), and HTTPS (443) +- **Public IP Address**: Static IP with DNS name for accessing the VM +- **Network Interface (NIC)**: Connects the VM to the VNet and public IP +- **Virtual Machine**: Windows Server 2022 Datacenter +- **Managed OS Disk**: StandardSSD_LRS for the OS disk + +All resources are deployed to **Sweden Central** region by default. + +## Prerequisites + +Before deploying, ensure you have: + +1. **Azure CLI** installed and authenticated + ```bash + az login + az account show # Verify you're in the correct subscription + ``` + +2. **Bicep CLI** installed (included with Azure CLI 2.20.0+) + ```bash + bicep --version + ``` + +3. **Azure Subscription** with appropriate permissions (Contributor or Owner role at subscription level) + +## File Structure + +``` +infra/bicep/ +├── main.bicep # Main template (subscription scope) +├── main.bicepparam # Parameter file with example values +├── modules/ +│ ├── network.bicep # Network infrastructure (VNet, Subnet, NSG) +│ └── vm.bicep # Virtual machine resources (VM, NIC, Public IP) +└── README.md # This file +``` + +## Deployment + +### Option 1: Deploy with Parameter File + +The easiest way to deploy is using the parameter file. You'll need to provide the admin password: + +```bash +# Navigate to the bicep directory +cd infra/bicep + +# Deploy (will prompt for adminPassword) +az deployment sub create \ + --location swedencentral \ + --template-file main.bicep \ + --parameters main.bicepparam + +# Or provide password inline (for automation) +az deployment sub create \ + --location swedencentral \ + --template-file main.bicep \ + --parameters main.bicepparam \ + --parameters adminPassword='YourSecurePassword123!' +``` + +### Option 2: Deploy with Individual Parameters + +You can override any parameters at deployment time: + +```bash +az deployment sub create \ + --location swedencentral \ + --template-file main.bicep \ + --parameters resourceGroupName='rg-my-demo' \ + --parameters adminUsername='myadmin' \ + --parameters adminPassword='MySecurePassword123!' \ + --parameters resourcePrefix='myvm' +``` + +### Option 3: Deploy with What-If (Preview Changes) + +Preview what resources will be created before deploying: + +```bash +az deployment sub what-if \ + --location swedencentral \ + --template-file main.bicep \ + --parameters main.bicepparam \ + --parameters adminPassword='YourSecurePassword123!' +``` + +## Validation + +### Local Validation (Fast) + +Validate Bicep syntax and compile to ARM template: + +```bash +# Validate main template +bicep build main.bicep + +# Validate network module +bicep build modules/network.bicep + +# Validate VM module +bicep build modules/vm.bicep +``` + +### Azure Validation (Comprehensive) + +Validate against Azure APIs (checks permissions, quotas, API versions): + +```bash +az deployment sub validate \ + --location swedencentral \ + --template-file main.bicep \ + --parameters main.bicepparam \ + --parameters adminPassword='YourSecurePassword123!' +``` + +## Connecting to Your VM + +After deployment completes, you'll receive outputs including the public IP address: + +1. **Get deployment outputs:** + ```bash + az deployment sub show \ + --name main \ + --query properties.outputs + ``` + +2. **Connect via RDP:** + - Open Remote Desktop Connection (mstsc.exe on Windows) + - Enter the public IP address or FQDN + - Login with the credentials you provided during deployment + +## Customization + +You can customize the deployment by modifying parameters: + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `location` | `swedencentral` | Azure region for resources | +| `resourceGroupName` | `rg-demo-vm` | Name of the resource group | +| `adminUsername` | `azureuser` | VM administrator username | +| `adminPassword` | *(required)* | VM administrator password (min 12 chars) | +| `vmSize` | `Standard_B2s` | VM size (2 vCPU, 4 GB RAM) | +| `osVersion` | `2022-Datacenter` | Windows Server version (2019 or 2022) | +| `resourcePrefix` | `demo` | Prefix for resource naming | + +## Clean Up + +To delete all deployed resources: + +```bash +# Delete the resource group (deletes all resources within it) +az group delete --name rg-demo-vm --yes --no-wait +``` + +## Cost Considerations + +This demo configuration uses low-cost resources: + +- **VM Size**: Standard_B2s (burstable B-series, ~$30-40/month) +- **OS Disk**: StandardSSD_LRS (128 GB, ~$5/month) +- **Public IP**: Standard SKU Static (~$3/month) +- **Networking**: VNet and NSG have no additional cost + +**Total estimated cost**: ~$40-50 USD/month when running continuously. + +To minimize costs: +- Stop (deallocate) the VM when not in use: `az vm deallocate --resource-group rg-demo-vm --name ` +- Delete resources when no longer needed + +## Security Notes + +⚠️ **This is a demo configuration**. For production use: + +1. **Restrict NSG rules**: Don't allow RDP from `*` (any source). Limit to your IP address or corporate network. +2. **Use Azure Bastion**: Instead of exposing RDP publicly, use Azure Bastion for secure access. +3. **Enable JIT Access**: Use Azure Security Center's Just-In-Time VM access. +4. **Key-based authentication**: Consider using Azure Key Vault for secrets management. +5. **Monitoring**: Enable Azure Monitor and diagnostic settings. +6. **Backup**: Configure Azure Backup for production VMs. + +## Troubleshooting + +### Bicep Build Errors + +```bash +# Check Bicep version +bicep --version + +# Upgrade Bicep +az bicep upgrade + +# Validate specific file +bicep build .bicep +``` + +### Deployment Errors + +```bash +# View deployment logs +az deployment sub show --name main + +# List all subscription deployments +az deployment sub list --output table +``` + +### Common Issues + +1. **Password complexity**: Ensure password meets Azure requirements (min 12 chars, includes uppercase, lowercase, number, special char) +2. **Quota limits**: Check your subscription quotas for the selected VM size and region +3. **Permissions**: Ensure you have Contributor or Owner role at subscription level + +## Additional Resources + +- [Azure Bicep Documentation](https://learn.microsoft.com/azure/azure-resource-manager/bicep/) +- [Azure VM Sizes](https://learn.microsoft.com/azure/virtual-machines/sizes) +- [Azure Naming Conventions](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming) diff --git a/infra/bicep/main.bicep b/infra/bicep/main.bicep new file mode 100644 index 0000000..906fa61 --- /dev/null +++ b/infra/bicep/main.bicep @@ -0,0 +1,87 @@ +// Main Bicep template for Azure VM + Network infrastructure +// Deployment scope: subscription (creates resource group) +targetScope = 'subscription' + +// Parameters +@description('Azure region for all resources') +param location string = 'swedencentral' + +@description('Name of the resource group to create') +@minLength(1) +@maxLength(90) +param resourceGroupName string = 'rg-demo-vm' + +@description('Administrator username for the VM') +@minLength(1) +@maxLength(20) +param adminUsername string = 'azureuser' + +@description('Administrator password for the VM') +@secure() +@minLength(12) +param adminPassword string + +@description('Virtual machine size') +param vmSize string = 'Standard_B2s' + +@description('Windows Server OS version') +@allowed([ + '2019-Datacenter' + '2022-Datacenter' +]) +param osVersion string = '2022-Datacenter' + +@description('Prefix for naming resources') +@minLength(3) +@maxLength(10) +param resourcePrefix string = 'demo' + +// Variables +var uniqueSuffix = uniqueString(subscription().subscriptionId, resourceGroupName) + +// Create resource group +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { + name: resourceGroupName + location: location +} + +// Deploy network infrastructure +module network 'modules/network.bicep' = { + scope: resourceGroup + name: 'networkDeployment' + params: { + location: location + resourcePrefix: resourcePrefix + uniqueSuffix: uniqueSuffix + } +} + +// Deploy virtual machine +module virtualMachine 'modules/vm.bicep' = { + scope: resourceGroup + name: 'vmDeployment' + params: { + location: location + resourcePrefix: resourcePrefix + uniqueSuffix: uniqueSuffix + adminUsername: adminUsername + adminPassword: adminPassword + vmSize: vmSize + osVersion: osVersion + subnetId: network.outputs.subnetId + nsgId: network.outputs.nsgId + } +} + +// Outputs +@description('Resource group name') +output resourceGroupName string = resourceGroup.name + +@description('Virtual machine name') +output vmName string = virtualMachine.outputs.vmName + +@description('Public IP address') +output publicIpAddress string = virtualMachine.outputs.publicIpAddress + +@description('Virtual network name') +output vnetName string = network.outputs.vnetName diff --git a/infra/bicep/main.bicepparam b/infra/bicep/main.bicepparam new file mode 100644 index 0000000..fd91d6d --- /dev/null +++ b/infra/bicep/main.bicepparam @@ -0,0 +1,26 @@ +// Parameter file for main.bicep +// This file contains example parameter values for demo deployment +using './main.bicep' + +// Azure region for resources +param location = 'swedencentral' + +// Resource group name +param resourceGroupName = 'rg-demo-vm' + +// VM administrator username +param adminUsername = 'azureuser' + +// VM administrator password - MUST be provided at deployment time +// Use: az deployment sub create --parameters main.bicepparam --parameters adminPassword='YourSecurePassword123!' +// Or use: az deployment sub create --parameters main.bicepparam (will prompt for password) +param adminPassword = readEnvironmentVariable('ADMIN_PASSWORD', '') + +// VM size - Standard_B2s is a low-cost general purpose size +param vmSize = 'Standard_B2s' + +// Windows Server version +param osVersion = '2022-Datacenter' + +// Resource naming prefix +param resourcePrefix = 'demo' diff --git a/infra/bicep/modules/network.bicep b/infra/bicep/modules/network.bicep new file mode 100644 index 0000000..6d9d7e8 --- /dev/null +++ b/infra/bicep/modules/network.bicep @@ -0,0 +1,105 @@ +// Network module: Virtual Network, Subnet, and Network Security Group +targetScope = 'resourceGroup' + +// Parameters +@description('Azure region for all resources') +param location string + +@description('Prefix for naming resources') +param resourcePrefix string + +@description('Unique suffix for resource names') +param uniqueSuffix string + +// Variables +var vnetName = '${resourcePrefix}-vnet-${uniqueSuffix}' +var subnetName = 'default' +var nsgName = '${resourcePrefix}-nsg-${uniqueSuffix}' +var vnetAddressPrefix = '10.0.0.0/16' +var subnetAddressPrefix = '10.0.0.0/24' + +// Network Security Group with default rules +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { + name: nsgName + location: location + properties: { + securityRules: [ + { + name: 'AllowRDP' + properties: { + description: 'Allow RDP traffic (demo only - restrict source in production)' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3389' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1000 + direction: 'Inbound' + } + } + { + name: 'AllowHTTP' + properties: { + description: 'Allow HTTP traffic (demo only)' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '80' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1001 + direction: 'Inbound' + } + } + { + name: 'AllowHTTPS' + properties: { + description: 'Allow HTTPS traffic (demo only)' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1002 + direction: 'Inbound' + } + } + ] + } +} + +// Virtual Network +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { + name: vnetName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + vnetAddressPrefix + ] + } + subnets: [ + { + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + networkSecurityGroup: { + id: networkSecurityGroup.id + } + } + } + ] + } +} + +// Outputs +@description('Virtual network name') +output vnetName string = virtualNetwork.name + +@description('Subnet resource ID') +output subnetId string = virtualNetwork.properties.subnets[0].id + +@description('Network security group resource ID') +output nsgId string = networkSecurityGroup.id diff --git a/infra/bicep/modules/vm.bicep b/infra/bicep/modules/vm.bicep new file mode 100644 index 0000000..08a5209 --- /dev/null +++ b/infra/bicep/modules/vm.bicep @@ -0,0 +1,138 @@ +// Virtual Machine module: Windows Server VM, Public IP, NIC, and Managed Disk +targetScope = 'resourceGroup' + +// Parameters +@description('Azure region for all resources') +param location string + +@description('Prefix for naming resources') +param resourcePrefix string + +@description('Unique suffix for resource names') +param uniqueSuffix string + +@description('Administrator username for the VM') +param adminUsername string + +@description('Administrator password for the VM') +@secure() +param adminPassword string + +@description('Virtual machine size') +param vmSize string + +@description('Windows Server OS version') +param osVersion string + +@description('Subnet resource ID') +param subnetId string + +@description('Network security group resource ID') +param nsgId string + +// Variables +var vmName = '${resourcePrefix}-vm-${uniqueSuffix}' +var nicName = '${resourcePrefix}-nic-${uniqueSuffix}' +var publicIpName = '${resourcePrefix}-pip-${uniqueSuffix}' +var osDiskName = '${vmName}-osdisk' + +// Public IP Address +resource publicIp 'Microsoft.Network/publicIPAddresses@2024-05-01' = { + name: publicIpName + location: location + sku: { + name: 'Standard' + } + properties: { + publicIPAllocationMethod: 'Static' + dnsSettings: { + domainNameLabel: toLower('${resourcePrefix}-${uniqueSuffix}') + } + } +} + +// Network Interface +resource networkInterface 'Microsoft.Network/networkInterfaces@2024-05-01' = { + name: nicName + location: location + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAllocationMethod: 'Dynamic' + subnet: { + id: subnetId + } + publicIPAddress: { + id: publicIp.id + } + } + } + ] + networkSecurityGroup: { + id: nsgId + } + } +} + +// Virtual Machine +resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = { + name: vmName + location: location + properties: { + hardwareProfile: { + vmSize: vmSize + } + osProfile: { + computerName: vmName + adminUsername: adminUsername + adminPassword: adminPassword + windowsConfiguration: { + enableAutomaticUpdates: true + patchSettings: { + patchMode: 'AutomaticByOS' + assessmentMode: 'ImageDefault' + } + } + } + storageProfile: { + imageReference: { + publisher: 'MicrosoftWindowsServer' + offer: 'WindowsServer' + sku: osVersion + version: 'latest' + } + osDisk: { + name: osDiskName + createOption: 'FromImage' + managedDisk: { + storageAccountType: 'StandardSSD_LRS' + } + caching: 'ReadWrite' + } + } + networkProfile: { + networkInterfaces: [ + { + id: networkInterface.id + } + ] + } + diagnosticsProfile: { + bootDiagnostics: { + enabled: true + } + } + } +} + +// Outputs +@description('Virtual machine name') +output vmName string = virtualMachine.name + +@description('Public IP address') +output publicIpAddress string = publicIp.properties.ipAddress + +@description('Fully qualified domain name') +output fqdn string = publicIp.properties.dnsSettings.fqdn