|
| 1 | +# Azure DevOps Agent Pool Backplane |
| 2 | + |
| 3 | +This backplane provides the necessary Azure infrastructure and permissions for managing Azure DevOps agent pools with elastic scaling. |
| 4 | + |
| 5 | +## Components |
| 6 | + |
| 7 | +- **Service Principal**: Azure AD application for Azure DevOps management |
| 8 | +- **Key Vault**: Secure storage for Azure DevOps Personal Access Token (PAT) |
| 9 | +- **Custom Role**: Role definition with permissions to read VMSS and Key Vault secrets |
| 10 | +- **Role Assignment**: Assigns the custom role to the service principal |
| 11 | + |
| 12 | +## Prerequisites |
| 13 | + |
| 14 | +Before deploying this backplane: |
| 15 | + |
| 16 | +1. Azure subscription with permissions to create: |
| 17 | + - Service principals |
| 18 | + - Key Vaults |
| 19 | + - Custom role definitions |
| 20 | + - Role assignments |
| 21 | + |
| 22 | +2. Azure DevOps Personal Access Token with scopes: |
| 23 | + - Agent Pools (Read & Manage) |
| 24 | + - Project & Team (Read, optional for project authorization) |
| 25 | + |
| 26 | +## Usage |
| 27 | + |
| 28 | +```hcl |
| 29 | +module "agent_pool_backplane" { |
| 30 | + source = "path/to/azuredevops/agent-pool/backplane" |
| 31 | +
|
| 32 | + service_principal_name = "sp-azure-devops-agent-pool" |
| 33 | + key_vault_name = "kv-azdevops-terraform" |
| 34 | + resource_group_name = "rg-azdevops-terraform" |
| 35 | + location = "westeurope" |
| 36 | + scope = "/subscriptions/12345678-1234-1234-1234-123456789012" |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +## Manual Steps After Deployment |
| 41 | + |
| 42 | +After the backplane is deployed, you must manually: |
| 43 | + |
| 44 | +1. **Store the PAT in Key Vault**: |
| 45 | +```bash |
| 46 | +az keyvault secret set \ |
| 47 | + --vault-name kv-azdevops-terraform \ |
| 48 | + --name azure-devops-pat \ |
| 49 | + --value "YOUR_PAT_TOKEN_HERE" |
| 50 | +``` |
| 51 | + |
| 52 | +2. **Create Service Principal Secret** (if using client credentials): |
| 53 | +```bash |
| 54 | +az ad app credential reset \ |
| 55 | + --id <service-principal-client-id> \ |
| 56 | + --append |
| 57 | +``` |
| 58 | + |
| 59 | +## Permissions Granted |
| 60 | + |
| 61 | +The custom role grants the service principal: |
| 62 | + |
| 63 | +- `Microsoft.KeyVault/vaults/secrets/read` - Read PAT from Key Vault |
| 64 | +- `Microsoft.Resources/subscriptions/resourceGroups/read` - List resource groups |
| 65 | +- `Microsoft.Compute/virtualMachineScaleSets/read` - Read VMSS information |
| 66 | + |
| 67 | +## Security Considerations |
| 68 | + |
| 69 | +- **PAT Rotation**: Rotate the PAT every 90 days minimum |
| 70 | +- **Least Privilege**: Service principal only has read access to VMSS |
| 71 | +- **Key Vault Access**: Limited to specific service principal and admin |
| 72 | +- **Scope**: Apply role at subscription or resource group level |
| 73 | + |
| 74 | +## Outputs |
| 75 | + |
| 76 | +- `service_principal_id`: Client ID for authentication |
| 77 | +- `service_principal_object_id`: Object ID for role assignments |
| 78 | +- `key_vault_name`: Key Vault name for PAT storage |
| 79 | +- `resource_group_name`: Resource group containing Key Vault |
| 80 | +- `role_definition_id`: Custom role definition ID |
| 81 | + |
| 82 | +## Troubleshooting |
| 83 | + |
| 84 | +### Service Principal Creation Failed |
| 85 | + |
| 86 | +**Cause**: Insufficient Azure AD permissions |
| 87 | + |
| 88 | +**Solution**: Ensure you have Application Administrator or Global Administrator role in Azure AD |
| 89 | + |
| 90 | +### Key Vault Access Denied |
| 91 | + |
| 92 | +**Cause**: Service principal lacks access policy |
| 93 | + |
| 94 | +**Solution**: Verify access policy in Key Vault grants Get and List permissions on secrets |
| 95 | + |
| 96 | +### Role Assignment Failed |
| 97 | + |
| 98 | +**Cause**: Insufficient permissions at target scope |
| 99 | + |
| 100 | +**Solution**: Ensure you have Owner or User Access Administrator role at the specified scope |
| 101 | + |
| 102 | +<!-- BEGIN_TF_DOCS --> |
| 103 | +## Requirements |
| 104 | + |
| 105 | +| Name | Version | |
| 106 | +|------|---------| |
| 107 | +| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3.0 | |
| 108 | +| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | ~> 2.0 | |
| 109 | +| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 4.51.0 | |
| 110 | + |
| 111 | +## Modules |
| 112 | + |
| 113 | +No modules. |
| 114 | + |
| 115 | +## Resources |
| 116 | + |
| 117 | +| Name | Type | |
| 118 | +|------|------| |
| 119 | +| [azuread_application.azure_devops](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application) | resource | |
| 120 | +| [azuread_service_principal.azure_devops](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal) | resource | |
| 121 | +| [azurerm_key_vault.devops](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | |
| 122 | +| [azurerm_resource_group.devops](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | |
| 123 | +| [azurerm_role_assignment.azure_devops_manager](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | |
| 124 | +| [azurerm_role_definition.azure_devops_agent_pool_manager](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition) | resource | |
| 125 | +| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | |
| 126 | +| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source | |
| 127 | + |
| 128 | +## Inputs |
| 129 | + |
| 130 | +| Name | Description | Type | Default | Required | |
| 131 | +|------|-------------|------|---------|:--------:| |
| 132 | +| <a name="input_key_vault_name"></a> [key\_vault\_name](#input\_key\_vault\_name) | Name of the Key Vault to store Azure DevOps PAT | `string` | n/a | yes | |
| 133 | +| <a name="input_location"></a> [location](#input\_location) | Azure region for resources | `string` | `"westeurope"` | no | |
| 134 | +| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group for Key Vault | `string` | n/a | yes | |
| 135 | +| <a name="input_scope"></a> [scope](#input\_scope) | Scope for the custom role definition (e.g., subscription ID) | `string` | n/a | yes | |
| 136 | +| <a name="input_service_principal_name"></a> [service\_principal\_name](#input\_service\_principal\_name) | Name of the service principal for Azure DevOps agent pool management | `string` | `"sp-azure-devops-agent-pool"` | no | |
| 137 | + |
| 138 | +## Outputs |
| 139 | + |
| 140 | +| Name | Description | |
| 141 | +|------|-------------| |
| 142 | +| <a name="output_key_vault_id"></a> [key\_vault\_id](#output\_key\_vault\_id) | ID of the Key Vault | |
| 143 | +| <a name="output_key_vault_name"></a> [key\_vault\_name](#output\_key\_vault\_name) | Name of the Key Vault | |
| 144 | +| <a name="output_resource_group_name"></a> [resource\_group\_name](#output\_resource\_group\_name) | Name of the resource group | |
| 145 | +| <a name="output_role_definition_id"></a> [role\_definition\_id](#output\_role\_definition\_id) | ID of the custom role definition | |
| 146 | +| <a name="output_service_principal_id"></a> [service\_principal\_id](#output\_service\_principal\_id) | Application (client) ID of the service principal | |
| 147 | +| <a name="output_service_principal_object_id"></a> [service\_principal\_object\_id](#output\_service\_principal\_object\_id) | Object ID of the service principal | |
| 148 | +<!-- END_TF_DOCS --> |
0 commit comments