ejk52515/Azure_AD_Terraform
GitHub: ejk52515/Azure_AD_Terraform
使用 Terraform 在 Azure 上以生产级安全标准自动化部署 Windows Server 2022 Active Directory 域控制器,通过托管标识、Key Vault RBAC 和 Bastion 实现零公网 IP 的安全架构。
Stars: 0 | Forks: 0
# Azure Active Directory 域控制器 — Terraform 部署
使用 Terraform 将 **Windows Server 2022 Active Directory 域控制器**部署到 Azure,并按照生产环境的标准进行配置:机密存储在 **Key Vault** 中,使用**系统分配的托管标识**进行无凭据身份验证,域控制器**没有公共 IP**,并通过 **Azure Bastion** 进行访问。
📹 **[视频演示](https://www.loom.com/share/7eaa256aae3e427e86986cd7cc5bb079)** — 详细介绍每个文件、设计决策以及在真实企业环境中需要更改的内容。
## 架构
```
graph TB
subgraph Internet
Admin[Admin Workstation]
end
subgraph "Azure — rg-ad-eli (southafricanorth)"
subgraph KV["Key Vault (RBAC-enabled)"]
S1[vm-admin-password]
S2[dsrm-password]
end
subgraph VNet["vnet-ad-eli — 10.0.0.0/16"]
subgraph BSub["AzureBastionSubnet — 10.0.2.0/26"]
Bastion[Azure Bastion
Standard SKU] end subgraph ADSub["snet-ad — 10.0.1.0/24"] NSG[NSG: RDP from VNet + mgmt IP only] VM[vm-ad-eli
DC + DNS
10.0.1.4
Managed Identity] end end end Admin -->|TLS / browser RDP| Bastion Bastion -->|RDP over VNet| VM VM -->|Managed Identity
Secrets User| KV Admin -.->|az CLI
Secrets Officer| KV style VM fill:#0078d4,color:#fff style Bastion fill:#107c10,color:#fff style KV fill:#5c2d91,color:#fff ``` **流程:**管理员通过 Bastion 经由 TLS 进行连接 — 域控制器没有公共 IP。VM 使用其自身的托管标识向 Key Vault 进行身份验证(对机密为只读)。管理员通过 Azure CLI 在带外填充机密(读/写)。Terraform state 存放在远程加密的 Azure Storage 后端中。 ## 部署内容 | 资源 | 名称 | 备注 | |---|---|---| | 资源组 | `rg-ad-eli` | 所有内容均嵌套在此处;一次 `destroy` 即可全部清理 | | 虚拟网络 | `vnet-ad-eli` | `10.0.0.0/16` | | AD 子网 | `snet-ad` | `10.0.1.0/24`;域控制器静态 IP 为 `10.0.1.4` | | Bastion 子网 | `AzureBastionSubnet` | `10.0.2.0/26`;名称为强制要求,最小长度为 /26 | | Key Vault | `kv-adlab-eli-01` | RBAC 授权;包含两个机密 | | NSG | `nsg-ad-eli` | 仅允许来自管理 IP + `VirtualNetwork` 标签的 RDP | | NIC | `nic-ad-eli` | **无公共 IP** | | VM | `vm-ad-eli` | Windows Server 2022,`Standard_DS1_v2`,系统分配的标识 | | Bastion | `bastion-ad-eli` | Standard SKU,拥有独立的公共 IP | | 扩展 | `install-ad-ds` | 安装 AD DS,提升为新林 `corp.eli.com` | ## 关键设计决策 **使用托管标识而非代码中的密码。**VM 将获得一个系统分配的托管标识,并被授予 `Key Vault Secrets User` 权限。大多数教程放在 `variables.tf` 中的密码变量已被完全移除 — 变量只是机密可能泄露的又一个地方。 **在 Key Vault 上选择 RBAC 而非访问策略。**`rbac_authorization_enabled = true` 将保管库权限置于与 Azure 其余部分相同的可审计控制平面上。两个作用域受限、最小权限的分配:管理员获得 `Secrets Officer`(读/写),VM 标识获得 `Secrets User`(只读)。 **数据平面与控制平面角色。**`Secrets Officer`/`Secrets User` 作用于保管库*内部*的机密 — 这有别于管理保管库*资源*的 `Contributor`。管理保管库与读取其机密是分开授权的。 **域控制器无公共 IP。**域控制器在互联网上不可达。访问是通过 Bastion 经由 TLS 进行的,因此唯一面向公众的暴露面是一个托管的、已打补丁的跳板机 — 绝不会在域控制器上开放 3389 端口。 **远程加密的 Terraform state。**state 以明文形式保存机密,因此它存放在访问受控的 Azure Storage 后端中,绝不存放在本地磁盘上。每个实验室在共享的 state 存储中使用独立的 state key。 **用于机密引导的分阶段应用。**由于机密数据源是在计划(plan)阶段读取的,因此构建是分阶段进行的:(1) 定向应用创建保管库 + 管理员访问权限,(2) 通过 CLI 设置机密,(3) 全面应用读取机密并构建所有内容。使用者无法读取尚不存在的机密 — 预配顺序非常重要。 ## 部署说明 前置条件:Azure CLI (`az login`),Terraform 1.3+,一个 Azure 订阅,以及一个远程 state 存储帐户。 ``` # 阶段 1 — vault + 仅您的访问权限 terraform init terraform apply -target="azurerm_key_vault.main" -target="azurerm_role_assignment.kv_admin" # 阶段 2 — 设置 secrets(首先等待约 60 秒以进行 RBAC 传播) az keyvault secret set --vault-name "kv-adlab-eli-01" --name "vm-admin-password" --value ''
az keyvault secret set --vault-name "kv-adlab-eli-01" --name "dsrm-password" --value ''
# 阶段 3 — 完整构建(约 25-30 分钟;仅 Bastion 需要 10-15 分钟)
terraform apply
```
连接方式:Azure Portal → `vm-ad-eli` → **Connect → Bastion** → `CORP\adadmin` + 管理员密码。
销毁(同日):`terraform destroy`。
## 证据
**干净的应用 — 11 个资源,扩展已完成且未报错:**

**域控制器没有公共 IP — 仅能通过 Bastion 访问:**

**`Get-ADDomain` 确认了健康的林 `corp.eli.com` (NetBIOS 为 `CORP`):**

**VM 系统分配的托管标识:**

**Key Vault RBAC — 管理员为 Secrets Officer,VM 标识为 Secrets User,两者的作用域均限制在保管库:**

**捕获并修复了提供程序弃用提示,随后完成干净验证:**

## 故障排除与经验总结(作为技能展示)
| 问题 | 原因 | 解决方案 / 经验 |
|---|---|---|
| 在 `-target` 上出现 `Too many command line arguments` | PowerShell 解析该标志的方式与 bash 不同 | 为每个地址加引号:`-target="azurerm_key_vault.main"` |
| 在应用后立即设置机密时出现 `403 Forbidden` | Azure RBAC 需要 30 秒到几分钟的时间进行传播 | 等待约 60 秒后重试 — 这是传播延迟,不是配置错误 |
| 第一次全面 `apply` 读取机密时会失败 | 数据源在计划阶段读取;当时机密尚不存在 | 分阶段应用 — 先建保管库,设置机密,然后再全面应用 |
| `enable_rbac_authorization` 弃用警告 | azurerm 4.x 重命名了该属性 | 在 5.0 彻底移除之前更新为 `rbac_authorization_enabled` |
| Bastion 无法部署到子网中 | 子网必须准确命名为 `AzureBastionSubnet`,最小长度为 /26 | 使用规定的字面名称和大小 |
| 扩展可能会报告 `Failed`(本文档中未出现) | AD 提升会在扩展运行期间重启 VM | 属于良性状态 — 请通过 `Get-ADDomain` 而非扩展状态来验证 |
| 重建时发生 Key Vault 名称冲突 | 软删除在销毁后会留下残影 | 设置 `purge_soft_delete_on_destroy = true`,或使用 `az keyvault purge` |
| `curl -s` 挂起并提示 `Uri:` | 在 PowerShell 中 `curl` 是 `Invoke-WebRequest` 的别名 | 请使用 `Invoke-RestMethod` 或 `curl.exe` |
## 生产环境加固 — 刻意超出本次范围
这些是为单日免费层级实验室**有意**省略的内容。了解这些差距以及如何弥补它们才是关键。
- **Key Vault 网络** — 实验室访问使用受 RBAC 限制的公共终结点。生产环境:使用私有终结点 + 网络 ACL,无公共可达性。
- **清除保护** — 已禁用,以允许在同日重建。生产环境:`purge_protection_enabled = true`。
- **state 中的机密** — 即使源自 Key Vault,密码也会在 Terraform state 和扩展设置中实体化(Terraform 必须使用该值)。生产环境:通过 VM 的托管标识在引导脚本内检索运行时机密,从而使该值永远不会进入 state。在此期间,远程加密后端可缓解静态暴露风险。
- **可观测性** — 此处无诊断设置。生产环境:将 VM 和 Key Vault 的诊断设置发送到 Log Analytics 工作区,以进行审计和告警。
- **弹性** — 单个林根域控制器仅适合实验室。生产环境:跨可用性区域的多个域控制器,并具有适当的 AD 站点拓扑。
## 文件
```
.
├── main.tf # All resources + AD DS extension
├── variables.tf # Inputs (no secrets)
├── outputs.tf # Names, IDs, access info (no secrets)
├── terraform.tfvars # Real values (git-ignored)
├── terraform.tfvars.example # Template (committed)
├── .gitignore # Excludes tfvars + state
└── screenshots/ # Deployment evidence
```
Standard SKU] end subgraph ADSub["snet-ad — 10.0.1.0/24"] NSG[NSG: RDP from VNet + mgmt IP only] VM[vm-ad-eli
DC + DNS
10.0.1.4
Managed Identity] end end end Admin -->|TLS / browser RDP| Bastion Bastion -->|RDP over VNet| VM VM -->|Managed Identity
Secrets User| KV Admin -.->|az CLI
Secrets Officer| KV style VM fill:#0078d4,color:#fff style Bastion fill:#107c10,color:#fff style KV fill:#5c2d91,color:#fff ``` **流程:**管理员通过 Bastion 经由 TLS 进行连接 — 域控制器没有公共 IP。VM 使用其自身的托管标识向 Key Vault 进行身份验证(对机密为只读)。管理员通过 Azure CLI 在带外填充机密(读/写)。Terraform state 存放在远程加密的 Azure Storage 后端中。 ## 部署内容 | 资源 | 名称 | 备注 | |---|---|---| | 资源组 | `rg-ad-eli` | 所有内容均嵌套在此处;一次 `destroy` 即可全部清理 | | 虚拟网络 | `vnet-ad-eli` | `10.0.0.0/16` | | AD 子网 | `snet-ad` | `10.0.1.0/24`;域控制器静态 IP 为 `10.0.1.4` | | Bastion 子网 | `AzureBastionSubnet` | `10.0.2.0/26`;名称为强制要求,最小长度为 /26 | | Key Vault | `kv-adlab-eli-01` | RBAC 授权;包含两个机密 | | NSG | `nsg-ad-eli` | 仅允许来自管理 IP + `VirtualNetwork` 标签的 RDP | | NIC | `nic-ad-eli` | **无公共 IP** | | VM | `vm-ad-eli` | Windows Server 2022,`Standard_DS1_v2`,系统分配的标识 | | Bastion | `bastion-ad-eli` | Standard SKU,拥有独立的公共 IP | | 扩展 | `install-ad-ds` | 安装 AD DS,提升为新林 `corp.eli.com` | ## 关键设计决策 **使用托管标识而非代码中的密码。**VM 将获得一个系统分配的托管标识,并被授予 `Key Vault Secrets User` 权限。大多数教程放在 `variables.tf` 中的密码变量已被完全移除 — 变量只是机密可能泄露的又一个地方。 **在 Key Vault 上选择 RBAC 而非访问策略。**`rbac_authorization_enabled = true` 将保管库权限置于与 Azure 其余部分相同的可审计控制平面上。两个作用域受限、最小权限的分配:管理员获得 `Secrets Officer`(读/写),VM 标识获得 `Secrets User`(只读)。 **数据平面与控制平面角色。**`Secrets Officer`/`Secrets User` 作用于保管库*内部*的机密 — 这有别于管理保管库*资源*的 `Contributor`。管理保管库与读取其机密是分开授权的。 **域控制器无公共 IP。**域控制器在互联网上不可达。访问是通过 Bastion 经由 TLS 进行的,因此唯一面向公众的暴露面是一个托管的、已打补丁的跳板机 — 绝不会在域控制器上开放 3389 端口。 **远程加密的 Terraform state。**state 以明文形式保存机密,因此它存放在访问受控的 Azure Storage 后端中,绝不存放在本地磁盘上。每个实验室在共享的 state 存储中使用独立的 state key。 **用于机密引导的分阶段应用。**由于机密数据源是在计划(plan)阶段读取的,因此构建是分阶段进行的:(1) 定向应用创建保管库 + 管理员访问权限,(2) 通过 CLI 设置机密,(3) 全面应用读取机密并构建所有内容。使用者无法读取尚不存在的机密 — 预配顺序非常重要。 ## 部署说明 前置条件:Azure CLI (`az login`),Terraform 1.3+,一个 Azure 订阅,以及一个远程 state 存储帐户。 ``` # 阶段 1 — vault + 仅您的访问权限 terraform init terraform apply -target="azurerm_key_vault.main" -target="azurerm_role_assignment.kv_admin" # 阶段 2 — 设置 secrets(首先等待约 60 秒以进行 RBAC 传播) az keyvault secret set --vault-name "kv-adlab-eli-01" --name "vm-admin-password" --value '
标签:Active Directory, AI合规, Azure, ECS, Plaso, Terraform, Windows Server, 系统运维