From be7e91cf1da8ae62d5b4e93806c8832ea1a13397 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:26:18 +0000 Subject: [PATCH 1/6] Initial plan From 24f18bdc019bb80ce14067f4b48e5142a287b43e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:32:45 +0000 Subject: [PATCH 2/6] Add Terraform module for ACSC Windows Hardening deployment Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- README.md | 60 +++-- terraform/.gitignore | 22 ++ terraform/README.md | 365 +++++++++++++++++++++++++++ terraform/main.tf | 178 +++++++++++++ terraform/outputs.tf | 86 +++++++ terraform/policy.tf | 393 +++++++++++++++++++++++++++++ terraform/terraform.tfvars.example | 28 ++ terraform/variables.tf | 81 ++++++ 8 files changed, 1197 insertions(+), 16 deletions(-) create mode 100644 terraform/.gitignore create mode 100644 terraform/README.md create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/policy.tf create mode 100644 terraform/terraform.tfvars.example create mode 100644 terraform/variables.tf diff --git a/README.md b/README.md index ef9f23b..ec40b6b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,13 @@ This project implements security configurations based on the [ACSC Hardening Mic ├── scripts/ # Deployment and utility scripts │ ├── Deploy-ACSCToAzure.ps1 │ └── New-ACSCMachineConfigurationPackage.ps1 +├── terraform/ # Terraform deployment module +│ ├── main.tf # Main Terraform configuration +│ ├── variables.tf # Input variables +│ ├── outputs.tf # Output values +│ ├── policy.tf # Policy definitions and assignments +│ ├── terraform.tfvars.example # Example configuration +│ └── README.md # Terraform documentation ├── build-release.ps1 # Automated build script └── docs/ # Documentation ``` @@ -183,29 +190,50 @@ flowchart TD ## Quick Start -### Option 1: Automated Build via GitHub Actions (Recommended) +### Option 1: Terraform Deployment (Recommended) -The repository includes a GitHub Actions workflow for automated builds: +The easiest way to deploy is using the Terraform module, which automates the entire process: ```bash -# Tag a release version -git tag v1.0.0 -git push origin v1.0.0 - -# GitHub Actions will automatically: -# - Compile DSC configurations -# - Create Machine Configuration packages -# - Generate SHA256 hashes -# - Create a GitHub Release with all artifacts +cd terraform +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your Azure subscription details + +terraform init +terraform plan +terraform apply ``` -Download the release artifacts and deploy to Azure using the provided scripts. +The Terraform module automatically: +- Downloads packages from GitHub releases +- Creates storage account and uploads packages +- Generates SAS tokens +- Creates and assigns Azure Policies +- Configures managed identities and roles + +See [terraform/README.md](terraform/README.md) for detailed documentation. + +### Option 2: PowerShell Deployment with GitHub Release + +Deploy using the PowerShell script with automatic GitHub release download: -### Option 2: Local Build +```powershell +.\scripts\Deploy-ACSCToAzure.ps1 ` + -SubscriptionId "your-subscription-id" ` + -ResourceGroupName "your-resource-group" ` + -StorageAccountName "yourstorageaccount" ` + -UseGitHubRelease +``` + +### Option 3: Local Build and Deploy 1. **Build packages locally** - ```powershell - # Creates packages in ./release directory + ```bash + # Tag a release version + git tag v1.0.0 + git push origin v1.0.0 + + # Or build locally .\build-release.ps1 -Version "1.0.0" ``` @@ -219,7 +247,7 @@ Download the release artifacts and deploy to Azure using the provided scripts. 3. **Monitor compliance** in Azure Policy dashboard (20-30 minutes for initial evaluation) -### Option 3: Manual Setup +### Option 4: Manual Setup 1. **Install required modules** ```powershell diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..5877961 --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,22 @@ +# Terraform files +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.* +*.tfplan +*.tfvars +!terraform.tfvars.example + +# Crash log files +crash.log +crash.*.log + +# Override files +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# CLI configuration files +.terraformrc +terraform.rc diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..55f3d83 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,365 @@ +# ACSC Windows Hardening - Terraform Module + +This Terraform module automates the deployment of Azure Machine Configuration policies for implementing ACSC (Australian Cyber Security Centre) Windows hardening guidelines. + +## Features + +The module automatically: +1. ✅ Creates an Azure Storage Account for DSC MOF packages +2. ✅ Downloads and uploads MOF files from GitHub releases +3. ✅ Generates SAS token links for secure package access +4. ✅ Creates Azure Policy machine configuration definitions +5. ✅ Creates Azure Policy assignments with managed identities +6. ✅ Assigns required roles to managed identities + +## Prerequisites + +- Terraform >= 1.0 +- Azure CLI or authenticated Azure service principal +- Azure subscription with appropriate permissions: + - Storage Account Contributor + - Policy Contributor + - Role Assignment Administrator +- Target Windows VMs must have: + - Managed identity enabled + - Guest Configuration extension installed (auto-installed by policy) + +## Quick Start + +### 1. Configure Variables + +Copy the example variables file and customize it: + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars` with your values: + +```hcl +subscription_id = "your-subscription-id" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" # Must be globally unique +``` + +### 2. Initialize Terraform + +```bash +terraform init +``` + +### 3. Plan Deployment + +```bash +terraform plan +``` + +### 4. Apply Configuration + +```bash +terraform apply +``` + +### 5. Monitor Compliance + +After deployment, monitor compliance in the Azure Portal: +- Navigate to **Azure Policy** → **Compliance** +- Initial evaluation takes 20-30 minutes +- Configurations are auto-corrected every 15 minutes (if using ApplyAndAutoCorrect mode) + +## Variables + +### Required Variables + +| Variable | Type | Description | +|----------|------|-------------| +| `subscription_id` | string | Azure Subscription ID | +| `resource_group_name` | string | Name of the Azure Resource Group | +| `storage_account_name` | string | Storage account name (3-24 chars, lowercase alphanumeric, globally unique) | + +### Optional Variables + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `location` | string | `"Australia East"` | Azure region for resources | +| `container_name` | string | `"acsc-machine-configuration"` | Storage container name | +| `github_repo` | string | `"devnomadic/ACSC-WindowsHardening"` | GitHub repository for releases | +| `release_version` | string | `""` (latest) | GitHub release version (e.g., "v1.0.0") | +| `configuration_level` | string | `"All"` | Configuration level: `HighPriority`, `MediumPriority`, or `All` | +| `assignment_type` | string | `"ApplyAndAutoCorrect"` | Assignment type: `ApplyAndMonitor` or `ApplyAndAutoCorrect` | +| `sas_token_expiry_years` | number | `2` | SAS token validity period in years | +| `tags` | map(string) | See below | Resource tags | + +**Default Tags:** +```hcl +{ + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" +} +``` + +## Outputs + +### Storage Information + +| Output | Description | +|--------|-------------| +| `storage_account_name` | Name of the created storage account | +| `storage_account_id` | Resource ID of the storage account | +| `container_name` | Name of the storage container | +| `high_priority_package_url` | URL of the High Priority package blob | +| `medium_priority_package_url` | URL of the Medium Priority package blob | + +### Package Information (Sensitive) + +| Output | Description | +|--------|-------------| +| `high_priority_content_uri` | SAS token URI for High Priority package | +| `medium_priority_content_uri` | SAS token URI for Medium Priority package | +| `high_priority_content_hash` | SHA256 hash of High Priority package | +| `medium_priority_content_hash` | SHA256 hash of Medium Priority package | + +### Policy Information + +| Output | Description | +|--------|-------------| +| `high_priority_policy_id` | Resource ID of High Priority policy definition | +| `medium_priority_policy_id` | Resource ID of Medium Priority policy definition | +| `high_priority_assignment_id` | Resource ID of High Priority policy assignment | +| `medium_priority_assignment_id` | Resource ID of Medium Priority policy assignment | +| `high_priority_managed_identity_principal_id` | Principal ID of managed identity for High Priority | +| `medium_priority_managed_identity_principal_id` | Principal ID of managed identity for Medium Priority | + +### Release Information + +| Output | Description | +|--------|-------------| +| `release_version` | GitHub release version deployed | +| `release_name` | GitHub release name | + +## Usage Examples + +### Deploy All Configurations (Default) + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +configuration_level = "All" +assignment_type = "ApplyAndAutoCorrect" +``` + +### Deploy Only High Priority Configuration + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +configuration_level = "HighPriority" +``` + +### Deploy Specific Release Version + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +release_version = "v1.0.0" +``` + +### Audit-Only Mode (No Enforcement) + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +assignment_type = "ApplyAndMonitor" +``` + +## Authentication + +### Option 1: Azure CLI + +```bash +az login +az account set --subscription "your-subscription-id" +terraform apply +``` + +### Option 2: Service Principal + +```bash +export ARM_CLIENT_ID="your-client-id" +export ARM_CLIENT_SECRET="your-client-secret" +export ARM_SUBSCRIPTION_ID="your-subscription-id" +export ARM_TENANT_ID="your-tenant-id" +terraform apply +``` + +### Option 3: Managed Identity (Azure VM/Azure DevOps) + +Terraform automatically uses managed identity when running in Azure. + +## Assignment Types + +### ApplyAndMonitor +- Configuration is applied **once** when the policy is first assigned +- Drift is **monitored** but not automatically corrected +- Non-compliant resources must be **manually remediated** +- Use for testing or when you want manual control + +### ApplyAndAutoCorrect (Recommended) +- Configuration is applied initially +- Agent checks compliance **every 15 minutes** +- Drift is **automatically corrected** +- Provides continuous compliance enforcement +- Recommended for production environments + +## Configuration Levels + +### HighPriority +Implements critical ACSC recommendations: +- Application control +- Credential protection (Credential Guard) +- Attack Surface Reduction (ASR) +- User Account Control (UAC) +- Secure Boot +- And more... + +### MediumPriority +Implements additional hardening: +- Account lockout policy +- Password policy +- BitLocker encryption +- Windows Firewall +- PowerShell security +- And more... + +### All +Deploys both High and Medium priority configurations. + +## Compliance Monitoring + +### Azure Portal +1. Navigate to **Azure Policy** → **Compliance** +2. Filter by resource group or policy name +3. View detailed compliance status per resource + +### Azure CLI +```bash +# Check policy compliance state +az policy state list --resource-group rg-acsc-hardening + +# View guest configuration assignments +az vm guest-assignment list --resource-group rg-acsc-hardening +``` + +### PowerShell +```powershell +# Check policy compliance +Get-AzPolicyState -ResourceGroupName "rg-acsc-hardening" + +# View guest configuration assignments +Get-AzGuestConfigurationAssignment -ResourceGroupName "rg-acsc-hardening" +``` + +## Troubleshooting + +### Storage Account Name Already Exists + +**Error:** `Storage account name is already taken` + +**Solution:** Storage account names must be globally unique. Choose a different name: +```hcl +storage_account_name = "stacschardening2024" +``` + +### Policy Assignment Identity Not Created + +**Error:** Managed identity was not automatically created + +**Solution:** This can occur due to Azure API delays. Wait 5-10 minutes and run: +```bash +terraform apply -refresh-only +``` + +### Compliance Not Showing + +**Issue:** Policy shows as "Not started" after 30 minutes + +**Solution:** +1. Ensure VMs have managed identity enabled +2. Check Guest Configuration extension is installed +3. Verify VMs are Windows (not Linux) +4. Check Azure Policy evaluation status + +### SAS Token Expired + +**Issue:** Configuration downloads fail with 403 errors + +**Solution:** Re-run terraform to generate new SAS tokens: +```bash +terraform apply -replace="data.azurerm_storage_account_blob_container_sas.high_priority[0]" +``` + +## Clean Up + +To remove all resources created by this module: + +```bash +terraform destroy +``` + +**Note:** This will: +- Delete policy assignments +- Delete policy definitions +- Delete storage account and all packages +- Remove role assignments + +## Security Considerations + +- Storage account uses **private containers** (no public access) +- SAS tokens are generated with **read-only** permissions +- SAS tokens are marked as **sensitive** in Terraform outputs +- Managed identities follow **principle of least privilege** +- TLS 1.2 is enforced for storage account + +## Cost Estimation + +Approximate monthly costs (Australia East region): + +| Resource | Estimated Cost | +|----------|----------------| +| Storage Account (LRS) | ~$0.50/month | +| Policy Assignments | Free | +| Guest Configuration Extension | Free | +| **Total** | **~$0.50/month** | + +*Note: Actual costs may vary based on storage usage and data transfer.* + +## Comparison with PowerShell Deployment + +| Feature | PowerShell Script | Terraform Module | +|---------|-------------------|------------------| +| Infrastructure as Code | ❌ | ✅ | +| State Management | ❌ | ✅ | +| Idempotent | Partial | ✅ | +| Version Control Friendly | ❌ | ✅ | +| CI/CD Integration | Moderate | Easy | +| Rollback Support | ❌ | ✅ | +| Cross-platform | Windows only | All platforms | + +## Support + +- **Issues:** [GitHub Issues](https://github.com/devnomadic/ACSC-WindowsHardening/issues) +- **ACSC Guidelines:** [ACSC Windows Hardening](https://www.cyber.gov.au/resources-business-and-government/maintaining-devices-and-systems/system-hardening-and-administration/system-hardening/hardening-microsoft-windows-10-and-windows-11-workstations) +- **Azure Policy:** [Azure Machine Configuration Documentation](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview) + +## Contributing + +Contributions are welcome! Please submit pull requests or open issues for bugs and feature requests. + +## License + +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..19a2c3c --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,178 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + http = { + source = "hashicorp/http" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + features {} + subscription_id = var.subscription_id +} + +# Data source to get GitHub release information +data "http" "github_release" { + url = var.release_version != "" ? "https://api.github.com/repos/${var.github_repo}/releases/tags/${var.release_version}" : "https://api.github.com/repos/${var.github_repo}/releases/latest" + + request_headers = { + Accept = "application/vnd.github+json" + User-Agent = "Terraform-ACSC-Deploy" + } +} + +locals { + release_data = jsondecode(data.http.github_release.response_body) + + # Extract asset download URLs + assets = { + for asset in local.release_data.assets : + asset.name => asset.browser_download_url + } + + # Identify specific assets + high_priority_package = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))][0] + medium_priority_package = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))][0] + high_priority_hash = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip\\.sha256$", name))][0] + medium_priority_hash = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip\\.sha256$", name))][0] + high_priority_policy = [for name, url in local.assets : { name = name, url = url } if name == "acsc-high-priority-policy.json"][0] + medium_priority_policy = [for name, url in local.assets : { name = name, url = url } if name == "acsc-medium-priority-policy.json"][0] + + deploy_high_priority = var.configuration_level == "HighPriority" || var.configuration_level == "All" + deploy_medium_priority = var.configuration_level == "MediumPriority" || var.configuration_level == "All" +} + +# Download package files +data "http" "high_priority_package" { + count = local.deploy_high_priority ? 1 : 0 + url = local.high_priority_package.url +} + +data "http" "medium_priority_package" { + count = local.deploy_medium_priority ? 1 : 0 + url = local.medium_priority_package.url +} + +# Download hash files +data "http" "high_priority_hash" { + count = local.deploy_high_priority ? 1 : 0 + url = local.high_priority_hash.url +} + +data "http" "medium_priority_hash" { + count = local.deploy_medium_priority ? 1 : 0 + url = local.medium_priority_hash.url +} + +# Download policy files +data "http" "high_priority_policy" { + count = local.deploy_high_priority ? 1 : 0 + url = local.high_priority_policy.url +} + +data "http" "medium_priority_policy" { + count = local.deploy_medium_priority ? 1 : 0 + url = local.medium_priority_policy.url +} + +# Get existing resource group or create new one +data "azurerm_resource_group" "main" { + name = var.resource_group_name +} + +# Storage account for DSC MOF packages +resource "azurerm_storage_account" "acsc" { + name = var.storage_account_name + resource_group_name = data.azurerm_resource_group.main.name + location = data.azurerm_resource_group.main.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "StorageV2" + + # Security settings + min_tls_version = "TLS1_2" + allow_nested_items_to_be_public = false + + tags = var.tags +} + +# Private container for Machine Configuration packages +resource "azurerm_storage_container" "acsc" { + name = var.container_name + storage_account_name = azurerm_storage_account.acsc.name + container_access_type = "private" +} + +# Upload High Priority package +resource "azurerm_storage_blob" "high_priority_package" { + count = local.deploy_high_priority ? 1 : 0 + name = "ACSCHighPriorityHardening.zip" + storage_account_name = azurerm_storage_account.acsc.name + storage_container_name = azurerm_storage_container.acsc.name + type = "Block" + source_content = data.http.high_priority_package[0].response_body +} + +# Upload Medium Priority package +resource "azurerm_storage_blob" "medium_priority_package" { + count = local.deploy_medium_priority ? 1 : 0 + name = "ACSCMediumPriorityHardening.zip" + storage_account_name = azurerm_storage_account.acsc.name + storage_container_name = azurerm_storage_container.acsc.name + type = "Block" + source_content = data.http.medium_priority_package[0].response_body +} + +# Generate SAS token for High Priority package +data "azurerm_storage_account_blob_container_sas" "high_priority" { + count = local.deploy_high_priority ? 1 : 0 + connection_string = azurerm_storage_account.acsc.primary_connection_string + container_name = azurerm_storage_container.acsc.name + + start = timestamp() + expiry = timeadd(timestamp(), format("%dh", var.sas_token_expiry_years * 8760)) + + permissions { + read = true + add = false + create = false + write = false + delete = false + list = false + } +} + +# Generate SAS token for Medium Priority package +data "azurerm_storage_account_blob_container_sas" "medium_priority" { + count = local.deploy_medium_priority ? 1 : 0 + connection_string = azurerm_storage_account.acsc.primary_connection_string + container_name = azurerm_storage_container.acsc.name + + start = timestamp() + expiry = timeadd(timestamp(), format("%dh", var.sas_token_expiry_years * 8760)) + + permissions { + read = true + add = false + create = false + write = false + delete = false + list = false + } +} + +locals { + high_priority_content_uri = local.deploy_high_priority ? "${azurerm_storage_blob.high_priority_package[0].url}${data.azurerm_storage_account_blob_container_sas.high_priority[0].sas}" : "" + medium_priority_content_uri = local.deploy_medium_priority ? "${azurerm_storage_blob.medium_priority_package[0].url}${data.azurerm_storage_account_blob_container_sas.medium_priority[0].sas}" : "" + + # Extract SHA256 hash from downloaded hash file (format: "HASH FILENAME") + high_priority_content_hash = local.deploy_high_priority ? trimspace(split(" ", data.http.high_priority_hash[0].response_body)[0]) : "" + medium_priority_content_hash = local.deploy_medium_priority ? trimspace(split(" ", data.http.medium_priority_hash[0].response_body)[0]) : "" +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..57c8b30 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,86 @@ +output "storage_account_name" { + description = "Name of the storage account containing DSC packages" + value = azurerm_storage_account.acsc.name +} + +output "storage_account_id" { + description = "Resource ID of the storage account" + value = azurerm_storage_account.acsc.id +} + +output "container_name" { + description = "Name of the storage container" + value = azurerm_storage_container.acsc.name +} + +output "high_priority_package_url" { + description = "URL of the High Priority package blob" + value = local.deploy_high_priority ? azurerm_storage_blob.high_priority_package[0].url : null +} + +output "medium_priority_package_url" { + description = "URL of the Medium Priority package blob" + value = local.deploy_medium_priority ? azurerm_storage_blob.medium_priority_package[0].url : null +} + +output "high_priority_content_uri" { + description = "SAS token URI for High Priority package (valid for 2 years)" + value = local.deploy_high_priority ? local.high_priority_content_uri : null + sensitive = true +} + +output "medium_priority_content_uri" { + description = "SAS token URI for Medium Priority package (valid for 2 years)" + value = local.deploy_medium_priority ? local.medium_priority_content_uri : null + sensitive = true +} + +output "high_priority_content_hash" { + description = "SHA256 hash of the High Priority package" + value = local.deploy_high_priority ? local.high_priority_content_hash : null +} + +output "medium_priority_content_hash" { + description = "SHA256 hash of the Medium Priority package" + value = local.deploy_medium_priority ? local.medium_priority_content_hash : null +} + +output "high_priority_policy_id" { + description = "Resource ID of the High Priority policy definition" + value = local.deploy_high_priority ? azurerm_policy_definition.high_priority[0].id : null +} + +output "medium_priority_policy_id" { + description = "Resource ID of the Medium Priority policy definition" + value = local.deploy_medium_priority ? azurerm_policy_definition.medium_priority[0].id : null +} + +output "high_priority_assignment_id" { + description = "Resource ID of the High Priority policy assignment" + value = local.deploy_high_priority ? azurerm_resource_group_policy_assignment.high_priority[0].id : null +} + +output "medium_priority_assignment_id" { + description = "Resource ID of the Medium Priority policy assignment" + value = local.deploy_medium_priority ? azurerm_resource_group_policy_assignment.medium_priority[0].id : null +} + +output "high_priority_managed_identity_principal_id" { + description = "Principal ID of the managed identity for High Priority policy" + value = local.deploy_high_priority ? azurerm_resource_group_policy_assignment.high_priority[0].identity[0].principal_id : null +} + +output "medium_priority_managed_identity_principal_id" { + description = "Principal ID of the managed identity for Medium Priority policy" + value = local.deploy_medium_priority ? azurerm_resource_group_policy_assignment.medium_priority[0].identity[0].principal_id : null +} + +output "release_version" { + description = "GitHub release version deployed" + value = local.release_data.tag_name +} + +output "release_name" { + description = "GitHub release name" + value = local.release_data.name +} diff --git a/terraform/policy.tf b/terraform/policy.tf new file mode 100644 index 0000000..db7515e --- /dev/null +++ b/terraform/policy.tf @@ -0,0 +1,393 @@ +# Azure Policy Definition for High Priority Configuration +resource "azurerm_policy_definition" "high_priority" { + count = local.deploy_high_priority ? 1 : 0 + name = "acsc-high-priority-hardening" + policy_type = "Custom" + mode = "Indexed" + display_name = "ACSC High Priority Windows Hardening" + description = "Ensures Windows machines comply with Australian Cyber Security Centre (ACSC) high priority hardening guidelines" + + metadata = jsonencode({ + category = "Guest Configuration" + version = "1.0.0" + guestConfiguration = { + name = "ACSCHighPriorityHardening" + version = "1.0.0.0" + contentType = "Custom" + contentUri = local.high_priority_content_uri + contentHash = local.high_priority_content_hash + assignmentType = var.assignment_type + configurationParameter = {} + } + }) + + parameters = jsonencode({ + contentUri = { + type = "String" + metadata = { + displayName = "Guest configuration content URI" + description = "URI to the guest configuration content package" + } + } + contentHash = { + type = "String" + metadata = { + displayName = "Guest configuration content hash" + description = "SHA256 hash of the guest configuration content package" + } + } + effect = { + type = "String" + allowedValues = [ + "AuditIfNotExists", + "DeployIfNotExists", + "Disabled" + ] + defaultValue = "DeployIfNotExists" + metadata = { + displayName = "Effect" + description = "Enable or disable the execution of this policy" + } + } + assignmentType = { + type = "String" + allowedValues = [ + "Audit", + "ApplyAndMonitor", + "ApplyAndAutoCorrect" + ] + defaultValue = var.assignment_type + metadata = { + displayName = "Assignment Type" + description = "Specifies the assignment type for the guest configuration" + } + } + }) + + policy_rule = jsonencode({ + if = { + allOf = [ + { + field = "type" + equals = "Microsoft.Compute/virtualMachines" + }, + { + anyOf = [ + { + field = "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration" + exists = true + } + ] + } + ] + } + then = { + effect = "[parameters('effect')]" + details = { + type = "Microsoft.GuestConfiguration/guestConfigurationAssignments" + name = "ACSCHighPriorityHardening" + + existenceCondition = { + allOf = [ + { + field = "Microsoft.GuestConfiguration/guestConfigurationAssignments/complianceStatus" + equals = "Compliant" + } + ] + } + + deployment = { + properties = { + mode = "incremental" + parameters = { + vmName = { + value = "[field('name')]" + } + location = { + value = "[field('location')]" + } + contentUri = { + value = "[parameters('contentUri')]" + } + contentHash = { + value = "[parameters('contentHash')]" + } + assignmentType = { + value = "[parameters('assignmentType')]" + } + } + template = { + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = { + vmName = { type = "string" } + location = { type = "string" } + contentUri = { type = "string" } + contentHash = { type = "string" } + assignmentType = { type = "string" } + } + resources = [ + { + apiVersion = "2018-11-20" + type = "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments" + name = "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/ACSCHighPriorityHardening')]" + location = "[parameters('location')]" + properties = { + guestConfiguration = { + name = "ACSCHighPriorityHardening" + version = "1.0.0.0" + contentUri = "[parameters('contentUri')]" + contentHash = "[parameters('contentHash')]" + assignmentType = "[parameters('assignmentType')]" + configurationParameter = [] + } + } + } + ] + } + } + } + } + } + }) +} + +# Azure Policy Definition for Medium Priority Configuration +resource "azurerm_policy_definition" "medium_priority" { + count = local.deploy_medium_priority ? 1 : 0 + name = "acsc-medium-priority-hardening" + policy_type = "Custom" + mode = "Indexed" + display_name = "ACSC Medium Priority Windows Hardening" + description = "Ensures Windows machines comply with Australian Cyber Security Centre (ACSC) medium priority hardening guidelines" + + metadata = jsonencode({ + category = "Guest Configuration" + version = "1.0.0" + guestConfiguration = { + name = "ACSCMediumPriorityHardening" + version = "1.0.0.0" + contentType = "Custom" + contentUri = local.medium_priority_content_uri + contentHash = local.medium_priority_content_hash + assignmentType = var.assignment_type + configurationParameter = {} + } + }) + + parameters = jsonencode({ + contentUri = { + type = "String" + metadata = { + displayName = "Guest configuration content URI" + description = "URI to the guest configuration content package" + } + } + contentHash = { + type = "String" + metadata = { + displayName = "Guest configuration content hash" + description = "SHA256 hash of the guest configuration content package" + } + } + effect = { + type = "String" + allowedValues = [ + "AuditIfNotExists", + "DeployIfNotExists", + "Disabled" + ] + defaultValue = "DeployIfNotExists" + metadata = { + displayName = "Effect" + description = "Enable or disable the execution of this policy" + } + } + assignmentType = { + type = "String" + allowedValues = [ + "Audit", + "ApplyAndMonitor", + "ApplyAndAutoCorrect" + ] + defaultValue = var.assignment_type + metadata = { + displayName = "Assignment Type" + description = "Specifies the assignment type for the guest configuration" + } + } + }) + + policy_rule = jsonencode({ + if = { + allOf = [ + { + field = "type" + equals = "Microsoft.Compute/virtualMachines" + }, + { + anyOf = [ + { + field = "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration" + exists = true + } + ] + } + ] + } + then = { + effect = "[parameters('effect')]" + details = { + type = "Microsoft.GuestConfiguration/guestConfigurationAssignments" + name = "ACSCMediumPriorityHardening" + + existenceCondition = { + allOf = [ + { + field = "Microsoft.GuestConfiguration/guestConfigurationAssignments/complianceStatus" + equals = "Compliant" + } + ] + } + + deployment = { + properties = { + mode = "incremental" + parameters = { + vmName = { + value = "[field('name')]" + } + location = { + value = "[field('location')]" + } + contentUri = { + value = "[parameters('contentUri')]" + } + contentHash = { + value = "[parameters('contentHash')]" + } + assignmentType = { + value = "[parameters('assignmentType')]" + } + } + template = { + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = { + vmName = { type = "string" } + location = { type = "string" } + contentUri = { type = "string" } + contentHash = { type = "string" } + assignmentType = { type = "string" } + } + resources = [ + { + apiVersion = "2018-11-20" + type = "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments" + name = "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/ACSCMediumPriorityHardening')]" + location = "[parameters('location')]" + properties = { + guestConfiguration = { + name = "ACSCMediumPriorityHardening" + version = "1.0.0.0" + contentUri = "[parameters('contentUri')]" + contentHash = "[parameters('contentHash')]" + assignmentType = "[parameters('assignmentType')]" + configurationParameter = [] + } + } + } + ] + } + } + } + } + } + }) +} + +# Policy Assignment for High Priority Configuration +resource "azurerm_resource_group_policy_assignment" "high_priority" { + count = local.deploy_high_priority ? 1 : 0 + name = "acsc-high-priority-assignment" + resource_group_id = data.azurerm_resource_group.main.id + policy_definition_id = azurerm_policy_definition.high_priority[0].id + display_name = "ACSC High Priority Hardening Assignment" + description = "Assigns ACSC High Priority Windows Hardening configuration to all Windows VMs in the resource group" + + parameters = jsonencode({ + contentUri = { + value = local.high_priority_content_uri + } + contentHash = { + value = local.high_priority_content_hash + } + effect = { + value = "DeployIfNotExists" + } + assignmentType = { + value = var.assignment_type + } + }) + + location = data.azurerm_resource_group.main.location + + identity { + type = "SystemAssigned" + } +} + +# Policy Assignment for Medium Priority Configuration +resource "azurerm_resource_group_policy_assignment" "medium_priority" { + count = local.deploy_medium_priority ? 1 : 0 + name = "acsc-medium-priority-assignment" + resource_group_id = data.azurerm_resource_group.main.id + policy_definition_id = azurerm_policy_definition.medium_priority[0].id + display_name = "ACSC Medium Priority Hardening Assignment" + description = "Assigns ACSC Medium Priority Windows Hardening configuration to all Windows VMs in the resource group" + + parameters = jsonencode({ + contentUri = { + value = local.medium_priority_content_uri + } + contentHash = { + value = local.medium_priority_content_hash + } + effect = { + value = "DeployIfNotExists" + } + assignmentType = { + value = var.assignment_type + } + }) + + location = data.azurerm_resource_group.main.location + + identity { + type = "SystemAssigned" + } +} + +# Role Assignment for High Priority Policy - Guest Configuration Resource Contributor +resource "azurerm_role_assignment" "high_priority" { + count = local.deploy_high_priority ? 1 : 0 + scope = data.azurerm_resource_group.main.id + role_definition_id = "/subscriptions/${var.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + principal_id = azurerm_resource_group_policy_assignment.high_priority[0].identity[0].principal_id + + depends_on = [ + azurerm_resource_group_policy_assignment.high_priority + ] +} + +# Role Assignment for Medium Priority Policy - Guest Configuration Resource Contributor +resource "azurerm_role_assignment" "medium_priority" { + count = local.deploy_medium_priority ? 1 : 0 + scope = data.azurerm_resource_group.main.id + role_definition_id = "/subscriptions/${var.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + principal_id = azurerm_resource_group_policy_assignment.medium_priority[0].identity[0].principal_id + + depends_on = [ + azurerm_resource_group_policy_assignment.medium_priority + ] +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..88233e8 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,28 @@ +# Azure Subscription Configuration +subscription_id = "your-subscription-id-here" +resource_group_name = "rg-acsc-hardening" +location = "Australia East" + +# Storage Account Configuration +# Must be globally unique, 3-24 characters, lowercase letters and numbers only +storage_account_name = "stacschardening" +container_name = "acsc-machine-configuration" + +# GitHub Release Configuration +github_repo = "devnomadic/ACSC-WindowsHardening" +release_version = "" # Leave empty for latest release, or specify like "v1.0.0" + +# Deployment Configuration +configuration_level = "All" # Options: HighPriority, MediumPriority, All +assignment_type = "ApplyAndAutoCorrect" # Options: ApplyAndMonitor, ApplyAndAutoCorrect + +# SAS Token Configuration +sas_token_expiry_years = 2 + +# Resource Tags +tags = { + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" + CostCenter = "Security" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..ef59bbd --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,81 @@ +variable "subscription_id" { + description = "Azure Subscription ID" + type = string +} + +variable "resource_group_name" { + description = "Name of the Azure Resource Group" + type = string +} + +variable "location" { + description = "Azure region for resources" + type = string + default = "Australia East" +} + +variable "storage_account_name" { + description = "Name of the storage account for DSC packages (must be globally unique, 3-24 chars, lowercase alphanumeric)" + type = string + + validation { + condition = can(regex("^[a-z0-9]{3,24}$", var.storage_account_name)) + error_message = "Storage account name must be 3-24 characters long, lowercase letters and numbers only." + } +} + +variable "container_name" { + description = "Name of the storage container for Machine Configuration packages" + type = string + default = "acsc-machine-configuration" +} + +variable "github_repo" { + description = "GitHub repository in format owner/repo" + type = string + default = "devnomadic/ACSC-WindowsHardening" +} + +variable "release_version" { + description = "GitHub release version to deploy (e.g., 'v1.0.0'). Leave empty for latest release." + type = string + default = "" +} + +variable "configuration_level" { + description = "Configuration level to deploy: HighPriority, MediumPriority, or All" + type = string + default = "All" + + validation { + condition = contains(["HighPriority", "MediumPriority", "All"], var.configuration_level) + error_message = "Configuration level must be HighPriority, MediumPriority, or All." + } +} + +variable "assignment_type" { + description = "Guest Configuration assignment type" + type = string + default = "ApplyAndAutoCorrect" + + validation { + condition = contains(["ApplyAndMonitor", "ApplyAndAutoCorrect"], var.assignment_type) + error_message = "Assignment type must be ApplyAndMonitor or ApplyAndAutoCorrect." + } +} + +variable "sas_token_expiry_years" { + description = "Number of years before SAS token expires" + type = number + default = 2 +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = { + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" + } +} From 5f23db6a967a389715ef9bc96c95daea13ecd06e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:34:49 +0000 Subject: [PATCH 3/6] Fix Terraform module: Add error handling for asset lookup and update output descriptions Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- terraform/main.tf | 22 +++++++++++++++------- terraform/outputs.tf | 4 ++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/terraform/main.tf b/terraform/main.tf index 19a2c3c..19a2193 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -37,13 +37,21 @@ locals { asset.name => asset.browser_download_url } - # Identify specific assets - high_priority_package = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))][0] - medium_priority_package = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))][0] - high_priority_hash = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip\\.sha256$", name))][0] - medium_priority_hash = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip\\.sha256$", name))][0] - high_priority_policy = [for name, url in local.assets : { name = name, url = url } if name == "acsc-high-priority-policy.json"][0] - medium_priority_policy = [for name, url in local.assets : { name = name, url = url } if name == "acsc-medium-priority-policy.json"][0] + # Identify specific assets with error handling + high_priority_package_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))] + medium_priority_package_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))] + high_priority_hash_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip\\.sha256$", name))] + medium_priority_hash_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip\\.sha256$", name))] + high_priority_policy_list = [for name, url in local.assets : { name = name, url = url } if name == "acsc-high-priority-policy.json"] + medium_priority_policy_list = [for name, url in local.assets : { name = name, url = url } if name == "acsc-medium-priority-policy.json"] + + # Extract first element safely + high_priority_package = length(local.high_priority_package_list) > 0 ? local.high_priority_package_list[0] : { name = "", url = "" } + medium_priority_package = length(local.medium_priority_package_list) > 0 ? local.medium_priority_package_list[0] : { name = "", url = "" } + high_priority_hash = length(local.high_priority_hash_list) > 0 ? local.high_priority_hash_list[0] : { name = "", url = "" } + medium_priority_hash = length(local.medium_priority_hash_list) > 0 ? local.medium_priority_hash_list[0] : { name = "", url = "" } + high_priority_policy = length(local.high_priority_policy_list) > 0 ? local.high_priority_policy_list[0] : { name = "", url = "" } + medium_priority_policy = length(local.medium_priority_policy_list) > 0 ? local.medium_priority_policy_list[0] : { name = "", url = "" } deploy_high_priority = var.configuration_level == "HighPriority" || var.configuration_level == "All" deploy_medium_priority = var.configuration_level == "MediumPriority" || var.configuration_level == "All" diff --git a/terraform/outputs.tf b/terraform/outputs.tf index 57c8b30..fad0a25 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -24,13 +24,13 @@ output "medium_priority_package_url" { } output "high_priority_content_uri" { - description = "SAS token URI for High Priority package (valid for 2 years)" + description = "SAS token URI for High Priority package (expires based on sas_token_expiry_years variable)" value = local.deploy_high_priority ? local.high_priority_content_uri : null sensitive = true } output "medium_priority_content_uri" { - description = "SAS token URI for Medium Priority package (valid for 2 years)" + description = "SAS token URI for Medium Priority package (expires based on sas_token_expiry_years variable)" value = local.deploy_medium_priority ? local.medium_priority_content_uri : null sensitive = true } From 5a11af24f979074b09597eb50599cd127e4adfbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:36:57 +0000 Subject: [PATCH 4/6] Add validation for GitHub assets and improve hash parsing robustness Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- terraform/main.tf | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/terraform/main.tf b/terraform/main.tf index 19a2193..febd207 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -55,6 +55,19 @@ locals { deploy_high_priority = var.configuration_level == "HighPriority" || var.configuration_level == "All" deploy_medium_priority = var.configuration_level == "MediumPriority" || var.configuration_level == "All" + + # Validate that required assets were found + validate_high_assets = local.deploy_high_priority && ( + local.high_priority_package.url == "" || + local.high_priority_hash.url == "" || + local.high_priority_policy.url == "" + ) ? tobool("Error: High Priority assets not found in GitHub release. Ensure the release contains ACSCHighPriorityHardening package files.") : true + + validate_medium_assets = local.deploy_medium_priority && ( + local.medium_priority_package.url == "" || + local.medium_priority_hash.url == "" || + local.medium_priority_policy.url == "" + ) ? tobool("Error: Medium Priority assets not found in GitHub release. Ensure the release contains ACSCMediumPriorityHardening package files.") : true } # Download package files @@ -119,6 +132,9 @@ resource "azurerm_storage_container" "acsc" { } # Upload High Priority package +# Note: Using source_content loads the ZIP file into Terraform state. +# For very large packages (>100MB), consider using a separate deployment script +# or Azure CLI commands instead. Current ACSC packages are typically <10MB. resource "azurerm_storage_blob" "high_priority_package" { count = local.deploy_high_priority ? 1 : 0 name = "ACSCHighPriorityHardening.zip" @@ -129,6 +145,9 @@ resource "azurerm_storage_blob" "high_priority_package" { } # Upload Medium Priority package +# Note: Using source_content loads the ZIP file into Terraform state. +# For very large packages (>100MB), consider using a separate deployment script +# or Azure CLI commands instead. Current ACSC packages are typically <10MB. resource "azurerm_storage_blob" "medium_priority_package" { count = local.deploy_medium_priority ? 1 : 0 name = "ACSCMediumPriorityHardening.zip" @@ -180,7 +199,17 @@ locals { high_priority_content_uri = local.deploy_high_priority ? "${azurerm_storage_blob.high_priority_package[0].url}${data.azurerm_storage_account_blob_container_sas.high_priority[0].sas}" : "" medium_priority_content_uri = local.deploy_medium_priority ? "${azurerm_storage_blob.medium_priority_package[0].url}${data.azurerm_storage_account_blob_container_sas.medium_priority[0].sas}" : "" - # Extract SHA256 hash from downloaded hash file (format: "HASH FILENAME") - high_priority_content_hash = local.deploy_high_priority ? trimspace(split(" ", data.http.high_priority_hash[0].response_body)[0]) : "" - medium_priority_content_hash = local.deploy_medium_priority ? trimspace(split(" ", data.http.medium_priority_hash[0].response_body)[0]) : "" + # Extract SHA256 hash from downloaded hash file (format: "HASH FILENAME" or "HASH") + # Split on whitespace and take the first element which should be the hash + high_priority_content_hash = local.deploy_high_priority ? ( + length(split(" ", data.http.high_priority_hash[0].response_body)) > 0 ? + trimspace(split(" ", data.http.high_priority_hash[0].response_body)[0]) : + trimspace(data.http.high_priority_hash[0].response_body) + ) : "" + + medium_priority_content_hash = local.deploy_medium_priority ? ( + length(split(" ", data.http.medium_priority_hash[0].response_body)) > 0 ? + trimspace(split(" ", data.http.medium_priority_hash[0].response_body)[0]) : + trimspace(data.http.medium_priority_hash[0].response_body) + ) : "" } From 0bb8d94f37110997e8707ea060367a4c0eac7bfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:06:35 +0000 Subject: [PATCH 5/6] Reorganize Terraform modules: nest Azure and AWS under terraform directory Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- README.md | 29 +- terraform/README.md | 393 +++--------------- terraform/aws/README.md | 170 ++++++++ terraform/{ => azure}/.gitignore | 0 terraform/azure/README.md | 365 ++++++++++++++++ terraform/{ => azure}/main.tf | 0 terraform/{ => azure}/outputs.tf | 0 terraform/{ => azure}/policy.tf | 0 .../{ => azure}/terraform.tfvars.example | 0 terraform/{ => azure}/variables.tf | 0 10 files changed, 617 insertions(+), 340 deletions(-) create mode 100644 terraform/aws/README.md rename terraform/{ => azure}/.gitignore (100%) create mode 100644 terraform/azure/README.md rename terraform/{ => azure}/main.tf (100%) rename terraform/{ => azure}/outputs.tf (100%) rename terraform/{ => azure}/policy.tf (100%) rename terraform/{ => azure}/terraform.tfvars.example (100%) rename terraform/{ => azure}/variables.tf (100%) diff --git a/README.md b/README.md index ec40b6b..afd5c2c 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,17 @@ This project implements security configurations based on the [ACSC Hardening Mic ├── scripts/ # Deployment and utility scripts │ ├── Deploy-ACSCToAzure.ps1 │ └── New-ACSCMachineConfigurationPackage.ps1 -├── terraform/ # Terraform deployment module -│ ├── main.tf # Main Terraform configuration -│ ├── variables.tf # Input variables -│ ├── outputs.tf # Output values -│ ├── policy.tf # Policy definitions and assignments -│ ├── terraform.tfvars.example # Example configuration -│ └── README.md # Terraform documentation +├── terraform/ # Terraform deployment modules +│ ├── azure/ # Azure-specific module +│ │ ├── main.tf # Main Terraform configuration +│ │ ├── variables.tf # Input variables +│ │ ├── outputs.tf # Output values +│ │ ├── policy.tf # Policy definitions and assignments +│ │ ├── terraform.tfvars.example # Example configuration +│ │ └── README.md # Azure module documentation +│ ├── aws/ # AWS-specific module (coming soon) +│ │ └── README.md # AWS module documentation +│ └── README.md # Terraform modules overview ├── build-release.ps1 # Automated build script └── docs/ # Documentation ``` @@ -194,8 +198,9 @@ flowchart TD The easiest way to deploy is using the Terraform module, which automates the entire process: +**Azure Deployment:** ```bash -cd terraform +cd terraform/azure cp terraform.tfvars.example terraform.tfvars # Edit terraform.tfvars with your Azure subscription details @@ -204,6 +209,14 @@ terraform plan terraform apply ``` +**AWS Deployment (Coming Soon):** +```bash +cd terraform/aws +# See terraform/aws/README.md for upcoming implementation +``` + +See [terraform/README.md](terraform/README.md) for detailed documentation on both modules. + The Terraform module automatically: - Downloads packages from GitHub releases - Creates storage account and uploads packages diff --git a/terraform/README.md b/terraform/README.md index 55f3d83..e5d5035 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,365 +1,94 @@ -# ACSC Windows Hardening - Terraform Module +# ACSC Windows Hardening - Terraform Modules -This Terraform module automates the deployment of Azure Machine Configuration policies for implementing ACSC (Australian Cyber Security Centre) Windows hardening guidelines. +This directory contains Terraform modules for deploying ACSC (Australian Cyber Security Centre) Windows hardening configurations to cloud environments. -## Features +## Available Modules -The module automatically: -1. ✅ Creates an Azure Storage Account for DSC MOF packages -2. ✅ Downloads and uploads MOF files from GitHub releases -3. ✅ Generates SAS token links for secure package access -4. ✅ Creates Azure Policy machine configuration definitions -5. ✅ Creates Azure Policy assignments with managed identities -6. ✅ Assigns required roles to managed identities +### Azure Module -## Prerequisites - -- Terraform >= 1.0 -- Azure CLI or authenticated Azure service principal -- Azure subscription with appropriate permissions: - - Storage Account Contributor - - Policy Contributor - - Role Assignment Administrator -- Target Windows VMs must have: - - Managed identity enabled - - Guest Configuration extension installed (auto-installed by policy) - -## Quick Start +The Azure module deploys ACSC hardening policies using Azure Machine Configuration (formerly Azure Policy Guest Configuration). -### 1. Configure Variables +**Location:** [`azure/`](azure/) -Copy the example variables file and customize it: +**Features:** +- Azure Storage Account for DSC MOF packages +- Automatic GitHub release integration +- Azure Policy definitions and assignments +- Managed identities and RBAC configuration +- Support for both High and Medium priority configurations +**Quick Start:** ```bash +cd azure cp terraform.tfvars.example terraform.tfvars -``` - -Edit `terraform.tfvars` with your values: - -```hcl -subscription_id = "your-subscription-id" -resource_group_name = "rg-acsc-hardening" -storage_account_name = "stacschardening" # Must be globally unique -``` - -### 2. Initialize Terraform - -```bash +# Edit terraform.tfvars with your Azure subscription details terraform init -``` - -### 3. Plan Deployment - -```bash terraform plan -``` - -### 4. Apply Configuration - -```bash terraform apply ``` -### 5. Monitor Compliance - -After deployment, monitor compliance in the Azure Portal: -- Navigate to **Azure Policy** → **Compliance** -- Initial evaluation takes 20-30 minutes -- Configurations are auto-corrected every 15 minutes (if using ApplyAndAutoCorrect mode) - -## Variables - -### Required Variables - -| Variable | Type | Description | -|----------|------|-------------| -| `subscription_id` | string | Azure Subscription ID | -| `resource_group_name` | string | Name of the Azure Resource Group | -| `storage_account_name` | string | Storage account name (3-24 chars, lowercase alphanumeric, globally unique) | - -### Optional Variables - -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `location` | string | `"Australia East"` | Azure region for resources | -| `container_name` | string | `"acsc-machine-configuration"` | Storage container name | -| `github_repo` | string | `"devnomadic/ACSC-WindowsHardening"` | GitHub repository for releases | -| `release_version` | string | `""` (latest) | GitHub release version (e.g., "v1.0.0") | -| `configuration_level` | string | `"All"` | Configuration level: `HighPriority`, `MediumPriority`, or `All` | -| `assignment_type` | string | `"ApplyAndAutoCorrect"` | Assignment type: `ApplyAndMonitor` or `ApplyAndAutoCorrect` | -| `sas_token_expiry_years` | number | `2` | SAS token validity period in years | -| `tags` | map(string) | See below | Resource tags | - -**Default Tags:** -```hcl -{ - Environment = "Production" - ManagedBy = "Terraform" - Purpose = "ACSC-Windows-Hardening" -} -``` - -## Outputs - -### Storage Information - -| Output | Description | -|--------|-------------| -| `storage_account_name` | Name of the created storage account | -| `storage_account_id` | Resource ID of the storage account | -| `container_name` | Name of the storage container | -| `high_priority_package_url` | URL of the High Priority package blob | -| `medium_priority_package_url` | URL of the Medium Priority package blob | - -### Package Information (Sensitive) - -| Output | Description | -|--------|-------------| -| `high_priority_content_uri` | SAS token URI for High Priority package | -| `medium_priority_content_uri` | SAS token URI for Medium Priority package | -| `high_priority_content_hash` | SHA256 hash of High Priority package | -| `medium_priority_content_hash` | SHA256 hash of Medium Priority package | - -### Policy Information - -| Output | Description | -|--------|-------------| -| `high_priority_policy_id` | Resource ID of High Priority policy definition | -| `medium_priority_policy_id` | Resource ID of Medium Priority policy definition | -| `high_priority_assignment_id` | Resource ID of High Priority policy assignment | -| `medium_priority_assignment_id` | Resource ID of Medium Priority policy assignment | -| `high_priority_managed_identity_principal_id` | Principal ID of managed identity for High Priority | -| `medium_priority_managed_identity_principal_id` | Principal ID of managed identity for Medium Priority | +See [azure/README.md](azure/README.md) for detailed documentation. -### Release Information +### AWS Module -| Output | Description | -|--------|-------------| -| `release_version` | GitHub release version deployed | -| `release_name` | GitHub release name | +The AWS module deploys ACSC hardening policies using AWS Systems Manager and related services. -## Usage Examples +**Location:** [`aws/`](aws/) -### Deploy All Configurations (Default) +**Status:** 🚧 Coming Soon -```hcl -subscription_id = "12345678-1234-1234-1234-123456789012" -resource_group_name = "rg-acsc-hardening" -storage_account_name = "stacschardening" -configuration_level = "All" -assignment_type = "ApplyAndAutoCorrect" -``` - -### Deploy Only High Priority Configuration - -```hcl -subscription_id = "12345678-1234-1234-1234-123456789012" -resource_group_name = "rg-acsc-hardening" -storage_account_name = "stacschardening" -configuration_level = "HighPriority" -``` - -### Deploy Specific Release Version - -```hcl -subscription_id = "12345678-1234-1234-1234-123456789012" -resource_group_name = "rg-acsc-hardening" -storage_account_name = "stacschardening" -release_version = "v1.0.0" -``` +**Planned Features:** +- S3 bucket for DSC MOF packages +- AWS Systems Manager State Manager associations +- AWS Config Rules for compliance monitoring +- IAM roles and policies for EC2 instances +- Support for both High and Medium priority configurations -### Audit-Only Mode (No Enforcement) - -```hcl -subscription_id = "12345678-1234-1234-1234-123456789012" -resource_group_name = "rg-acsc-hardening" -storage_account_name = "stacschardening" -assignment_type = "ApplyAndMonitor" -``` - -## Authentication - -### Option 1: Azure CLI - -```bash -az login -az account set --subscription "your-subscription-id" -terraform apply -``` - -### Option 2: Service Principal - -```bash -export ARM_CLIENT_ID="your-client-id" -export ARM_CLIENT_SECRET="your-client-secret" -export ARM_SUBSCRIPTION_ID="your-subscription-id" -export ARM_TENANT_ID="your-tenant-id" -terraform apply -``` +## Module Comparison -### Option 3: Managed Identity (Azure VM/Azure DevOps) +| Feature | Azure Module | AWS Module | +|---------|-------------|------------| +| Storage | Azure Storage Account | S3 Bucket | +| Configuration Management | Azure Machine Configuration | Systems Manager State Manager | +| Compliance/Policy | Azure Policy | AWS Config Rules | +| Authentication | Managed Identity | IAM Instance Profile | +| Package Distribution | SAS Token URLs | Pre-signed URLs | +| Auto-remediation | ApplyAndAutoCorrect mode | State Manager associations | -Terraform automatically uses managed identity when running in Azure. - -## Assignment Types - -### ApplyAndMonitor -- Configuration is applied **once** when the policy is first assigned -- Drift is **monitored** but not automatically corrected -- Non-compliant resources must be **manually remediated** -- Use for testing or when you want manual control - -### ApplyAndAutoCorrect (Recommended) -- Configuration is applied initially -- Agent checks compliance **every 15 minutes** -- Drift is **automatically corrected** -- Provides continuous compliance enforcement -- Recommended for production environments - -## Configuration Levels - -### HighPriority -Implements critical ACSC recommendations: -- Application control -- Credential protection (Credential Guard) -- Attack Surface Reduction (ASR) -- User Account Control (UAC) -- Secure Boot -- And more... - -### MediumPriority -Implements additional hardening: -- Account lockout policy -- Password policy -- BitLocker encryption -- Windows Firewall -- PowerShell security -- And more... - -### All -Deploys both High and Medium priority configurations. - -## Compliance Monitoring - -### Azure Portal -1. Navigate to **Azure Policy** → **Compliance** -2. Filter by resource group or policy name -3. View detailed compliance status per resource - -### Azure CLI -```bash -# Check policy compliance state -az policy state list --resource-group rg-acsc-hardening - -# View guest configuration assignments -az vm guest-assignment list --resource-group rg-acsc-hardening -``` - -### PowerShell -```powershell -# Check policy compliance -Get-AzPolicyState -ResourceGroupName "rg-acsc-hardening" - -# View guest configuration assignments -Get-AzGuestConfigurationAssignment -ResourceGroupName "rg-acsc-hardening" -``` - -## Troubleshooting - -### Storage Account Name Already Exists - -**Error:** `Storage account name is already taken` - -**Solution:** Storage account names must be globally unique. Choose a different name: -```hcl -storage_account_name = "stacschardening2024" -``` - -### Policy Assignment Identity Not Created - -**Error:** Managed identity was not automatically created - -**Solution:** This can occur due to Azure API delays. Wait 5-10 minutes and run: -```bash -terraform apply -refresh-only -``` - -### Compliance Not Showing - -**Issue:** Policy shows as "Not started" after 30 minutes - -**Solution:** -1. Ensure VMs have managed identity enabled -2. Check Guest Configuration extension is installed -3. Verify VMs are Windows (not Linux) -4. Check Azure Policy evaluation status - -### SAS Token Expired - -**Issue:** Configuration downloads fail with 403 errors - -**Solution:** Re-run terraform to generate new SAS tokens: -```bash -terraform apply -replace="data.azurerm_storage_account_blob_container_sas.high_priority[0]" -``` - -## Clean Up - -To remove all resources created by this module: - -```bash -terraform destroy -``` - -**Note:** This will: -- Delete policy assignments -- Delete policy definitions -- Delete storage account and all packages -- Remove role assignments - -## Security Considerations - -- Storage account uses **private containers** (no public access) -- SAS tokens are generated with **read-only** permissions -- SAS tokens are marked as **sensitive** in Terraform outputs -- Managed identities follow **principle of least privilege** -- TLS 1.2 is enforced for storage account +## Prerequisites -## Cost Estimation +### Azure Module +- Terraform >= 1.0 +- Azure CLI or Service Principal authentication +- Azure subscription with Policy Contributor permissions -Approximate monthly costs (Australia East region): +### AWS Module +- Terraform >= 1.0 +- AWS CLI or IAM credentials +- AWS account with appropriate permissions +- EC2 instances with SSM Agent installed -| Resource | Estimated Cost | -|----------|----------------| -| Storage Account (LRS) | ~$0.50/month | -| Policy Assignments | Free | -| Guest Configuration Extension | Free | -| **Total** | **~$0.50/month** | +## Contributing -*Note: Actual costs may vary based on storage usage and data transfer.* +Contributions are welcome! Please: +1. Fork the repository +2. Create a feature branch +3. Submit a pull request with detailed description +4. Ensure all configurations are tested -## Comparison with PowerShell Deployment +## License -| Feature | PowerShell Script | Terraform Module | -|---------|-------------------|------------------| -| Infrastructure as Code | ❌ | ✅ | -| State Management | ❌ | ✅ | -| Idempotent | Partial | ✅ | -| Version Control Friendly | ❌ | ✅ | -| CI/CD Integration | Moderate | Easy | -| Rollback Support | ❌ | ✅ | -| Cross-platform | Windows only | All platforms | +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. ## Support -- **Issues:** [GitHub Issues](https://github.com/devnomadic/ACSC-WindowsHardening/issues) -- **ACSC Guidelines:** [ACSC Windows Hardening](https://www.cyber.gov.au/resources-business-and-government/maintaining-devices-and-systems/system-hardening-and-administration/system-hardening/hardening-microsoft-windows-10-and-windows-11-workstations) -- **Azure Policy:** [Azure Machine Configuration Documentation](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview) - -## Contributing - -Contributions are welcome! Please submit pull requests or open issues for bugs and feature requests. +For issues related to: +- **Azure implementation:** See [azure/README.md](azure/README.md) +- **AWS implementation:** See [aws/README.md](aws/README.md) (when available) +- **General questions:** Open an issue in the GitHub repository -## License +## References -This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. +- [ACSC Windows Hardening Guidelines](https://www.cyber.gov.au/resources-business-and-government/maintaining-devices-and-systems/system-hardening-and-administration/system-hardening/hardening-microsoft-windows-10-and-windows-11-workstations) +- [Azure Machine Configuration Documentation](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview) +- [AWS Systems Manager Documentation](https://docs.aws.amazon.com/systems-manager/) diff --git a/terraform/aws/README.md b/terraform/aws/README.md new file mode 100644 index 0000000..5360fea --- /dev/null +++ b/terraform/aws/README.md @@ -0,0 +1,170 @@ +# ACSC Windows Hardening - AWS Terraform Module + +🚧 **Status: Under Development** + +This module will automate the deployment of ACSC (Australian Cyber Security Centre) Windows hardening configurations to AWS EC2 instances using AWS Systems Manager and related services. + +## Planned Features + +The AWS module will provide: + +1. ✨ **S3 Bucket for DSC Packages** - Secure storage for MOF files with versioning +2. ✨ **GitHub Release Integration** - Automatic download and upload of hardening packages +3. ✨ **Pre-signed URL Generation** - Secure access to DSC packages for EC2 instances +4. ✨ **Systems Manager Documents** - SSM documents for DSC configuration application +5. ✨ **State Manager Associations** - Automated configuration application and drift remediation +6. ✨ **Compliance Monitoring** - AWS Config rules for continuous compliance checking +7. ✨ **IAM Roles and Policies** - Proper permissions for EC2 instances + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────┐ +│ GitHub Release │ +│ (DSC MOF Packages + Hashes) │ +└────────────────────┬────────────────────────────────────┘ + │ + ↓ (Terraform downloads) +┌─────────────────────────────────────────────────────────┐ +│ S3 Bucket │ +│ - High Priority Package (ZIP) │ +│ - Medium Priority Package (ZIP) │ +│ - Pre-signed URLs (configurable expiry) │ +└────────────────────┬────────────────────────────────────┘ + │ + ↓ (EC2 instances download) +┌─────────────────────────────────────────────────────────┐ +│ Systems Manager State Manager │ +│ - SSM Document (AWS-ApplyDSCMofs) │ +│ - State Manager Associations │ +│ - Schedule: Auto-remediation (configurable) │ +└────────────────────┬────────────────────────────────────┘ + │ + ↓ (applies configuration) +┌─────────────────────────────────────────────────────────┐ +│ Windows EC2 Instances │ +│ - SSM Agent installed │ +│ - IAM Instance Profile attached │ +│ - ACSC hardening configurations applied │ +└────────────────────┬────────────────────────────────────┘ + │ + ↓ (reports compliance) +┌─────────────────────────────────────────────────────────┐ +│ AWS Config / Systems Manager │ +│ Compliance Dashboard │ +└─────────────────────────────────────────────────────────┘ +``` + +## Prerequisites (Planned) + +- Terraform >= 1.0 +- AWS CLI configured or IAM credentials +- AWS account with appropriate permissions: + - S3 bucket creation and management + - Systems Manager access + - IAM role creation + - EC2 instance management +- Target Windows EC2 instances must have: + - SSM Agent installed and running + - IAM instance profile with Systems Manager permissions + - Network access to AWS Systems Manager endpoints + +## Planned Variables + +```hcl +variable "region" { + description = "AWS region for resources" + type = string + default = "ap-southeast-2" # Sydney +} + +variable "s3_bucket_name" { + description = "Name of the S3 bucket for DSC packages" + type = string +} + +variable "configuration_level" { + description = "Configuration level: HighPriority, MediumPriority, or All" + type = string + default = "All" +} + +variable "remediation_schedule" { + description = "Schedule expression for State Manager association" + type = string + default = "rate(30 minutes)" +} + +variable "target_tag_key" { + description = "EC2 tag key to target instances" + type = string + default = "ACSC-Hardening" +} + +variable "target_tag_value" { + description = "EC2 tag value to target instances" + type = string + default = "Enabled" +} +``` + +## Implementation Differences from Azure + +### AWS Limitations +- **No Auto-install:** Unlike Azure's Guest Configuration extension, AWS requires SSM Agent to be pre-installed +- **Limited DSC Support:** AWS Systems Manager has basic DSC support compared to Azure Machine Configuration +- **Manual Tagging:** EC2 instances must be tagged to be included in State Manager associations +- **No Native Policy Engine:** AWS Config rules are used for compliance, but lack Azure Policy's deployment capabilities + +### AWS Advantages +- **Pre-signed URLs:** More flexible URL expiry management than Azure SAS tokens +- **CloudWatch Integration:** Built-in logging and monitoring +- **Cross-region Replication:** Easy S3 replication for multi-region deployments +- **Parameter Store Integration:** Secure storage for configuration parameters + +## Roadmap + +- [ ] Create S3 bucket with encryption and versioning +- [ ] Implement GitHub release download and upload logic +- [ ] Generate pre-signed URLs for S3 objects +- [ ] Create SSM documents for DSC application +- [ ] Create State Manager associations +- [ ] Implement AWS Config rules for compliance +- [ ] Create IAM roles and policies +- [ ] Add variables and outputs +- [ ] Write comprehensive documentation +- [ ] Add usage examples +- [ ] Test with Windows Server 2016/2019/2022 + +## Contributing + +Interested in helping build the AWS module? Contributions are welcome! + +Areas where help is needed: +- SSM Document creation for DSC +- AWS Config rule development +- Testing on various Windows Server versions +- Documentation and examples + +## Comparison with Azure Module + +| Feature | Azure | AWS (Planned) | +|---------|-------|---------------| +| Storage | Azure Storage Account | S3 Bucket | +| Configuration | Machine Configuration | State Manager | +| Compliance | Azure Policy | AWS Config Rules | +| Identity | Managed Identity | IAM Instance Profile | +| Distribution | SAS Token | Pre-signed URL | +| Auto-install | Yes | No (requires SSM Agent) | +| Drift Correction | Every 15 minutes | Configurable schedule | + +## References + +- [AWS Systems Manager State Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state.html) +- [AWS-ApplyDSCMofs](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state-manager-using-mof-file.html) +- [AWS Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html) +- [Azure Module Documentation](../azure/README.md) + +--- + +**Note:** This is a placeholder for the upcoming AWS implementation. Check back soon for updates! diff --git a/terraform/.gitignore b/terraform/azure/.gitignore similarity index 100% rename from terraform/.gitignore rename to terraform/azure/.gitignore diff --git a/terraform/azure/README.md b/terraform/azure/README.md new file mode 100644 index 0000000..55f3d83 --- /dev/null +++ b/terraform/azure/README.md @@ -0,0 +1,365 @@ +# ACSC Windows Hardening - Terraform Module + +This Terraform module automates the deployment of Azure Machine Configuration policies for implementing ACSC (Australian Cyber Security Centre) Windows hardening guidelines. + +## Features + +The module automatically: +1. ✅ Creates an Azure Storage Account for DSC MOF packages +2. ✅ Downloads and uploads MOF files from GitHub releases +3. ✅ Generates SAS token links for secure package access +4. ✅ Creates Azure Policy machine configuration definitions +5. ✅ Creates Azure Policy assignments with managed identities +6. ✅ Assigns required roles to managed identities + +## Prerequisites + +- Terraform >= 1.0 +- Azure CLI or authenticated Azure service principal +- Azure subscription with appropriate permissions: + - Storage Account Contributor + - Policy Contributor + - Role Assignment Administrator +- Target Windows VMs must have: + - Managed identity enabled + - Guest Configuration extension installed (auto-installed by policy) + +## Quick Start + +### 1. Configure Variables + +Copy the example variables file and customize it: + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars` with your values: + +```hcl +subscription_id = "your-subscription-id" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" # Must be globally unique +``` + +### 2. Initialize Terraform + +```bash +terraform init +``` + +### 3. Plan Deployment + +```bash +terraform plan +``` + +### 4. Apply Configuration + +```bash +terraform apply +``` + +### 5. Monitor Compliance + +After deployment, monitor compliance in the Azure Portal: +- Navigate to **Azure Policy** → **Compliance** +- Initial evaluation takes 20-30 minutes +- Configurations are auto-corrected every 15 minutes (if using ApplyAndAutoCorrect mode) + +## Variables + +### Required Variables + +| Variable | Type | Description | +|----------|------|-------------| +| `subscription_id` | string | Azure Subscription ID | +| `resource_group_name` | string | Name of the Azure Resource Group | +| `storage_account_name` | string | Storage account name (3-24 chars, lowercase alphanumeric, globally unique) | + +### Optional Variables + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `location` | string | `"Australia East"` | Azure region for resources | +| `container_name` | string | `"acsc-machine-configuration"` | Storage container name | +| `github_repo` | string | `"devnomadic/ACSC-WindowsHardening"` | GitHub repository for releases | +| `release_version` | string | `""` (latest) | GitHub release version (e.g., "v1.0.0") | +| `configuration_level` | string | `"All"` | Configuration level: `HighPriority`, `MediumPriority`, or `All` | +| `assignment_type` | string | `"ApplyAndAutoCorrect"` | Assignment type: `ApplyAndMonitor` or `ApplyAndAutoCorrect` | +| `sas_token_expiry_years` | number | `2` | SAS token validity period in years | +| `tags` | map(string) | See below | Resource tags | + +**Default Tags:** +```hcl +{ + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" +} +``` + +## Outputs + +### Storage Information + +| Output | Description | +|--------|-------------| +| `storage_account_name` | Name of the created storage account | +| `storage_account_id` | Resource ID of the storage account | +| `container_name` | Name of the storage container | +| `high_priority_package_url` | URL of the High Priority package blob | +| `medium_priority_package_url` | URL of the Medium Priority package blob | + +### Package Information (Sensitive) + +| Output | Description | +|--------|-------------| +| `high_priority_content_uri` | SAS token URI for High Priority package | +| `medium_priority_content_uri` | SAS token URI for Medium Priority package | +| `high_priority_content_hash` | SHA256 hash of High Priority package | +| `medium_priority_content_hash` | SHA256 hash of Medium Priority package | + +### Policy Information + +| Output | Description | +|--------|-------------| +| `high_priority_policy_id` | Resource ID of High Priority policy definition | +| `medium_priority_policy_id` | Resource ID of Medium Priority policy definition | +| `high_priority_assignment_id` | Resource ID of High Priority policy assignment | +| `medium_priority_assignment_id` | Resource ID of Medium Priority policy assignment | +| `high_priority_managed_identity_principal_id` | Principal ID of managed identity for High Priority | +| `medium_priority_managed_identity_principal_id` | Principal ID of managed identity for Medium Priority | + +### Release Information + +| Output | Description | +|--------|-------------| +| `release_version` | GitHub release version deployed | +| `release_name` | GitHub release name | + +## Usage Examples + +### Deploy All Configurations (Default) + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +configuration_level = "All" +assignment_type = "ApplyAndAutoCorrect" +``` + +### Deploy Only High Priority Configuration + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +configuration_level = "HighPriority" +``` + +### Deploy Specific Release Version + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +release_version = "v1.0.0" +``` + +### Audit-Only Mode (No Enforcement) + +```hcl +subscription_id = "12345678-1234-1234-1234-123456789012" +resource_group_name = "rg-acsc-hardening" +storage_account_name = "stacschardening" +assignment_type = "ApplyAndMonitor" +``` + +## Authentication + +### Option 1: Azure CLI + +```bash +az login +az account set --subscription "your-subscription-id" +terraform apply +``` + +### Option 2: Service Principal + +```bash +export ARM_CLIENT_ID="your-client-id" +export ARM_CLIENT_SECRET="your-client-secret" +export ARM_SUBSCRIPTION_ID="your-subscription-id" +export ARM_TENANT_ID="your-tenant-id" +terraform apply +``` + +### Option 3: Managed Identity (Azure VM/Azure DevOps) + +Terraform automatically uses managed identity when running in Azure. + +## Assignment Types + +### ApplyAndMonitor +- Configuration is applied **once** when the policy is first assigned +- Drift is **monitored** but not automatically corrected +- Non-compliant resources must be **manually remediated** +- Use for testing or when you want manual control + +### ApplyAndAutoCorrect (Recommended) +- Configuration is applied initially +- Agent checks compliance **every 15 minutes** +- Drift is **automatically corrected** +- Provides continuous compliance enforcement +- Recommended for production environments + +## Configuration Levels + +### HighPriority +Implements critical ACSC recommendations: +- Application control +- Credential protection (Credential Guard) +- Attack Surface Reduction (ASR) +- User Account Control (UAC) +- Secure Boot +- And more... + +### MediumPriority +Implements additional hardening: +- Account lockout policy +- Password policy +- BitLocker encryption +- Windows Firewall +- PowerShell security +- And more... + +### All +Deploys both High and Medium priority configurations. + +## Compliance Monitoring + +### Azure Portal +1. Navigate to **Azure Policy** → **Compliance** +2. Filter by resource group or policy name +3. View detailed compliance status per resource + +### Azure CLI +```bash +# Check policy compliance state +az policy state list --resource-group rg-acsc-hardening + +# View guest configuration assignments +az vm guest-assignment list --resource-group rg-acsc-hardening +``` + +### PowerShell +```powershell +# Check policy compliance +Get-AzPolicyState -ResourceGroupName "rg-acsc-hardening" + +# View guest configuration assignments +Get-AzGuestConfigurationAssignment -ResourceGroupName "rg-acsc-hardening" +``` + +## Troubleshooting + +### Storage Account Name Already Exists + +**Error:** `Storage account name is already taken` + +**Solution:** Storage account names must be globally unique. Choose a different name: +```hcl +storage_account_name = "stacschardening2024" +``` + +### Policy Assignment Identity Not Created + +**Error:** Managed identity was not automatically created + +**Solution:** This can occur due to Azure API delays. Wait 5-10 minutes and run: +```bash +terraform apply -refresh-only +``` + +### Compliance Not Showing + +**Issue:** Policy shows as "Not started" after 30 minutes + +**Solution:** +1. Ensure VMs have managed identity enabled +2. Check Guest Configuration extension is installed +3. Verify VMs are Windows (not Linux) +4. Check Azure Policy evaluation status + +### SAS Token Expired + +**Issue:** Configuration downloads fail with 403 errors + +**Solution:** Re-run terraform to generate new SAS tokens: +```bash +terraform apply -replace="data.azurerm_storage_account_blob_container_sas.high_priority[0]" +``` + +## Clean Up + +To remove all resources created by this module: + +```bash +terraform destroy +``` + +**Note:** This will: +- Delete policy assignments +- Delete policy definitions +- Delete storage account and all packages +- Remove role assignments + +## Security Considerations + +- Storage account uses **private containers** (no public access) +- SAS tokens are generated with **read-only** permissions +- SAS tokens are marked as **sensitive** in Terraform outputs +- Managed identities follow **principle of least privilege** +- TLS 1.2 is enforced for storage account + +## Cost Estimation + +Approximate monthly costs (Australia East region): + +| Resource | Estimated Cost | +|----------|----------------| +| Storage Account (LRS) | ~$0.50/month | +| Policy Assignments | Free | +| Guest Configuration Extension | Free | +| **Total** | **~$0.50/month** | + +*Note: Actual costs may vary based on storage usage and data transfer.* + +## Comparison with PowerShell Deployment + +| Feature | PowerShell Script | Terraform Module | +|---------|-------------------|------------------| +| Infrastructure as Code | ❌ | ✅ | +| State Management | ❌ | ✅ | +| Idempotent | Partial | ✅ | +| Version Control Friendly | ❌ | ✅ | +| CI/CD Integration | Moderate | Easy | +| Rollback Support | ❌ | ✅ | +| Cross-platform | Windows only | All platforms | + +## Support + +- **Issues:** [GitHub Issues](https://github.com/devnomadic/ACSC-WindowsHardening/issues) +- **ACSC Guidelines:** [ACSC Windows Hardening](https://www.cyber.gov.au/resources-business-and-government/maintaining-devices-and-systems/system-hardening-and-administration/system-hardening/hardening-microsoft-windows-10-and-windows-11-workstations) +- **Azure Policy:** [Azure Machine Configuration Documentation](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview) + +## Contributing + +Contributions are welcome! Please submit pull requests or open issues for bugs and feature requests. + +## License + +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. diff --git a/terraform/main.tf b/terraform/azure/main.tf similarity index 100% rename from terraform/main.tf rename to terraform/azure/main.tf diff --git a/terraform/outputs.tf b/terraform/azure/outputs.tf similarity index 100% rename from terraform/outputs.tf rename to terraform/azure/outputs.tf diff --git a/terraform/policy.tf b/terraform/azure/policy.tf similarity index 100% rename from terraform/policy.tf rename to terraform/azure/policy.tf diff --git a/terraform/terraform.tfvars.example b/terraform/azure/terraform.tfvars.example similarity index 100% rename from terraform/terraform.tfvars.example rename to terraform/azure/terraform.tfvars.example diff --git a/terraform/variables.tf b/terraform/azure/variables.tf similarity index 100% rename from terraform/variables.tf rename to terraform/azure/variables.tf From 3506a2ca3e8a2552830472a02cbd008f5d40bd52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:21:51 +0000 Subject: [PATCH 6/6] Implement AWS Terraform module with S3, SSM, and IAM Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- README.md | 24 ++-- terraform/README.md | 31 +++-- terraform/aws/.gitignore | 22 +++ terraform/aws/README.md | 174 ++++++++++++++++++------ terraform/aws/iam.tf | 55 ++++++++ terraform/aws/main.tf | 179 +++++++++++++++++++++++++ terraform/aws/outputs.tf | 89 ++++++++++++ terraform/aws/ssm.tf | 148 ++++++++++++++++++++ terraform/aws/terraform.tfvars.example | 37 +++++ terraform/aws/variables.tf | 89 ++++++++++++ 10 files changed, 788 insertions(+), 60 deletions(-) create mode 100644 terraform/aws/.gitignore create mode 100644 terraform/aws/iam.tf create mode 100644 terraform/aws/main.tf create mode 100644 terraform/aws/outputs.tf create mode 100644 terraform/aws/ssm.tf create mode 100644 terraform/aws/terraform.tfvars.example create mode 100644 terraform/aws/variables.tf diff --git a/README.md b/README.md index afd5c2c..7eec3ea 100644 --- a/README.md +++ b/README.md @@ -209,20 +209,28 @@ terraform plan terraform apply ``` -**AWS Deployment (Coming Soon):** +**AWS Deployment:** ```bash cd terraform/aws -# See terraform/aws/README.md for upcoming implementation +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your AWS configuration + +terraform init +terraform plan +terraform apply + +# Tag EC2 instances to receive hardening +aws ec2 create-tags --resources i-INSTANCE-ID --tags Key=ACSC-Hardening,Value=Enabled ``` See [terraform/README.md](terraform/README.md) for detailed documentation on both modules. -The Terraform module automatically: -- Downloads packages from GitHub releases -- Creates storage account and uploads packages -- Generates SAS tokens -- Creates and assigns Azure Policies -- Configures managed identities and roles +The Terraform modules automatically: +- Download packages from GitHub releases +- Create storage (Azure Storage Account / S3 Bucket) and upload packages +- Generate secure access tokens/permissions +- Create and configure policies/associations +- Set up managed identities and roles See [terraform/README.md](terraform/README.md) for detailed documentation. diff --git a/terraform/README.md b/terraform/README.md index e5d5035..8837e23 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -35,25 +35,39 @@ The AWS module deploys ACSC hardening policies using AWS Systems Manager and rel **Location:** [`aws/`](aws/) -**Status:** 🚧 Coming Soon +**Status:** ✅ Available -**Planned Features:** -- S3 bucket for DSC MOF packages -- AWS Systems Manager State Manager associations -- AWS Config Rules for compliance monitoring -- IAM roles and policies for EC2 instances +**Features:** +- S3 bucket for DSC MOF packages with encryption and versioning +- Automatic GitHub release integration +- SSM Documents for DSC configuration application +- State Manager associations for automated remediation +- IAM roles and instance profiles for EC2 instances - Support for both High and Medium priority configurations +**Quick Start:** +```bash +cd aws +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your AWS configuration +terraform init +terraform plan +terraform apply +``` + +See [aws/README.md](aws/README.md) for detailed documentation. + ## Module Comparison | Feature | Azure Module | AWS Module | |---------|-------------|------------| | Storage | Azure Storage Account | S3 Bucket | | Configuration Management | Azure Machine Configuration | Systems Manager State Manager | -| Compliance/Policy | Azure Policy | AWS Config Rules | +| Compliance/Policy | Azure Policy | Systems Manager Compliance | | Authentication | Managed Identity | IAM Instance Profile | -| Package Distribution | SAS Token URLs | Pre-signed URLs | +| Package Distribution | SAS Token URLs | S3 Direct Access | | Auto-remediation | ApplyAndAutoCorrect mode | State Manager associations | +| Extension Auto-install | Yes | No (SSM Agent required) | ## Prerequisites @@ -66,6 +80,7 @@ The AWS module deploys ACSC hardening policies using AWS Systems Manager and rel - Terraform >= 1.0 - AWS CLI or IAM credentials - AWS account with appropriate permissions +- Windows EC2 instances with SSM Agent installed - EC2 instances with SSM Agent installed ## Contributing diff --git a/terraform/aws/.gitignore b/terraform/aws/.gitignore new file mode 100644 index 0000000..5877961 --- /dev/null +++ b/terraform/aws/.gitignore @@ -0,0 +1,22 @@ +# Terraform files +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.* +*.tfplan +*.tfvars +!terraform.tfvars.example + +# Crash log files +crash.log +crash.*.log + +# Override files +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# CLI configuration files +.terraformrc +terraform.rc diff --git a/terraform/aws/README.md b/terraform/aws/README.md index 5360fea..0fd6348 100644 --- a/terraform/aws/README.md +++ b/terraform/aws/README.md @@ -1,20 +1,19 @@ # ACSC Windows Hardening - AWS Terraform Module -🚧 **Status: Under Development** +✅ **Status: Available** -This module will automate the deployment of ACSC (Australian Cyber Security Centre) Windows hardening configurations to AWS EC2 instances using AWS Systems Manager and related services. +This module automates the deployment of ACSC (Australian Cyber Security Centre) Windows hardening configurations to AWS EC2 instances using AWS Systems Manager and related services. -## Planned Features +## Features -The AWS module will provide: +The AWS module provides: -1. ✨ **S3 Bucket for DSC Packages** - Secure storage for MOF files with versioning -2. ✨ **GitHub Release Integration** - Automatic download and upload of hardening packages -3. ✨ **Pre-signed URL Generation** - Secure access to DSC packages for EC2 instances -4. ✨ **Systems Manager Documents** - SSM documents for DSC configuration application -5. ✨ **State Manager Associations** - Automated configuration application and drift remediation -6. ✨ **Compliance Monitoring** - AWS Config rules for continuous compliance checking -7. ✨ **IAM Roles and Policies** - Proper permissions for EC2 instances +1. ✅ **S3 Bucket for DSC Packages** - Secure storage for MOF files with versioning +2. ✅ **GitHub Release Integration** - Automatic download and upload of hardening packages +3. ✅ **SSM Documents** - SSM documents for DSC configuration application +4. ✅ **State Manager Associations** - Automated configuration application and drift remediation +5. ✅ **IAM Roles and Policies** - Proper permissions for EC2 instances +6. 🔄 **Compliance Monitoring** - Use AWS Config rules for continuous compliance checking (manual setup) ## Architecture Overview @@ -55,7 +54,71 @@ The AWS module will provide: └─────────────────────────────────────────────────────────┘ ``` -## Prerequisites (Planned) +## Quick Start + +### 1. Configure Variables + +Copy the example variables file and customize it: + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars` with your values: + +```hcl +region = "ap-southeast-2" +s3_bucket_name = "acsc-hardening-packages" # Must be globally unique +``` + +### 2. Initialize Terraform + +```bash +terraform init +``` + +### 3. Plan Deployment + +```bash +terraform plan +``` + +### 4. Apply Configuration + +```bash +terraform apply +``` + +### 5. Tag EC2 Instances + +Tag your Windows EC2 instances to receive hardening: + +```bash +aws ec2 create-tags --resources i-1234567890abcdef0 --tags Key=ACSC-Hardening,Value=Enabled +``` + +Or via Terraform: + +```hcl +resource "aws_instance" "windows" { + # ... other configuration ... + + iam_instance_profile = module.acsc_hardening.iam_instance_profile_name + + tags = { + ACSC-Hardening = "Enabled" + } +} +``` + +### 6. Monitor Compliance + +Check compliance in AWS Systems Manager Console: +- Navigate to **Systems Manager** → **State Manager** +- View association execution history +- Check compliance status + +## Prerequisites - Terraform >= 1.0 - AWS CLI configured or IAM credentials @@ -66,10 +129,11 @@ The AWS module will provide: - EC2 instance management - Target Windows EC2 instances must have: - SSM Agent installed and running - - IAM instance profile with Systems Manager permissions + - IAM instance profile with Systems Manager permissions (created by module) - Network access to AWS Systems Manager endpoints + - Tagged with the target tag (e.g., `ACSC-Hardening=Enabled`) -## Planned Variables +## Variables ```hcl variable "region" { @@ -122,49 +186,71 @@ variable "target_tag_value" { - **Cross-region Replication:** Easy S3 replication for multi-region deployments - **Parameter Store Integration:** Secure storage for configuration parameters -## Roadmap - -- [ ] Create S3 bucket with encryption and versioning -- [ ] Implement GitHub release download and upload logic -- [ ] Generate pre-signed URLs for S3 objects -- [ ] Create SSM documents for DSC application -- [ ] Create State Manager associations -- [ ] Implement AWS Config rules for compliance -- [ ] Create IAM roles and policies -- [ ] Add variables and outputs -- [ ] Write comprehensive documentation -- [ ] Add usage examples -- [ ] Test with Windows Server 2016/2019/2022 +## Module Files -## Contributing +- **main.tf** - S3 bucket, GitHub release integration, package upload +- **iam.tf** - IAM roles, policies, and instance profile for EC2 +- **ssm.tf** - SSM documents and State Manager associations +- **variables.tf** - Input variables with validation +- **outputs.tf** - Output values (S3 URLs, SSM document names, IAM resources) +- **terraform.tfvars.example** - Example configuration -Interested in helping build the AWS module? Contributions are welcome! +## How It Works -Areas where help is needed: -- SSM Document creation for DSC -- AWS Config rule development -- Testing on various Windows Server versions -- Documentation and examples +1. **GitHub Release Integration**: Downloads DSC packages from latest GitHub release +2. **S3 Upload**: Uploads packages to encrypted, versioned S3 bucket +3. **IAM Setup**: Creates role and instance profile for EC2 instances +4. **SSM Documents**: Creates documents that download from S3 and apply DSC +5. **State Manager**: Schedules regular execution on tagged EC2 instances +6. **Compliance**: Execution logs stored in S3, viewable in Systems Manager console ## Comparison with Azure Module -| Feature | Azure | AWS (Planned) | -|---------|-------|---------------| +| Feature | Azure | AWS | +|---------|-------|-----| | Storage | Azure Storage Account | S3 Bucket | | Configuration | Machine Configuration | State Manager | -| Compliance | Azure Policy | AWS Config Rules | +| Compliance | Azure Policy | Systems Manager Compliance | | Identity | Managed Identity | IAM Instance Profile | -| Distribution | SAS Token | Pre-signed URL | +| Distribution | SAS Token | S3 Direct Access | | Auto-install | Yes | No (requires SSM Agent) | -| Drift Correction | Every 15 minutes | Configurable schedule | +| Drift Correction | Every 15 minutes | Configurable (default: 30 min) | + +## Troubleshooting + +### EC2 Instances Not Receiving Configuration + +**Issue:** State Manager association shows no targets + +**Solution:** +1. Verify EC2 instance has correct tag: `ACSC-Hardening=Enabled` +2. Ensure SSM Agent is installed and running +3. Attach the IAM instance profile created by this module +4. Verify network connectivity to Systems Manager endpoints + +### DSC Application Fails + +**Issue:** SSM document execution fails + +**Solution:** +1. Check CloudWatch Logs or S3 logs for error details +2. Verify Windows PowerShell 5.1+ is installed +3. Ensure AWS Tools for PowerShell is available +4. Check S3 bucket permissions in IAM role + +### Package Download Fails + +**Issue:** Unable to download from S3 + +**Solution:** +1. Verify IAM role has S3 read permissions +2. Check S3 bucket policy doesn't block access +3. Ensure EC2 instance can reach S3 endpoints ## References - [AWS Systems Manager State Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state.html) -- [AWS-ApplyDSCMofs](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state-manager-using-mof-file.html) +- [AWS Systems Manager Run Command](https://docs.aws.amazon.com/systems-manager/latest/userguide/execute-remote-commands.html) - [AWS Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html) - [Azure Module Documentation](../azure/README.md) - ---- - -**Note:** This is a placeholder for the upcoming AWS implementation. Check back soon for updates! +- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) diff --git a/terraform/aws/iam.tf b/terraform/aws/iam.tf new file mode 100644 index 0000000..602d726 --- /dev/null +++ b/terraform/aws/iam.tf @@ -0,0 +1,55 @@ +# IAM role for EC2 instances to access S3 and Systems Manager +resource "aws_iam_role" "ec2_ssm" { + name = "ACSCHardeningEC2SSMRole" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + } + ] + }) + + tags = var.tags +} + +# Attach AWS managed policy for Systems Manager +resource "aws_iam_role_policy_attachment" "ssm_managed_policy" { + role = aws_iam_role.ec2_ssm.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +# Custom policy for S3 access to DSC packages +resource "aws_iam_role_policy" "s3_dsc_access" { + name = "ACSCHardeningS3Access" + role = aws_iam_role.ec2_ssm.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:ListBucket" + ] + Resource = [ + aws_s3_bucket.acsc.arn, + "${aws_s3_bucket.acsc.arn}/*" + ] + } + ] + }) +} + +# Instance profile for EC2 instances +resource "aws_iam_instance_profile" "ec2_ssm" { + name = "ACSCHardeningEC2SSMProfile" + role = aws_iam_role.ec2_ssm.name + tags = var.tags +} diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf new file mode 100644 index 0000000..b33e0cb --- /dev/null +++ b/terraform/aws/main.tf @@ -0,0 +1,179 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + http = { + source = "hashicorp/http" + version = "~> 3.0" + } + } +} + +provider "aws" { + region = var.region +} + +# Data source to get GitHub release information +data "http" "github_release" { + url = var.release_version != "" ? "https://api.github.com/repos/${var.github_repo}/releases/tags/${var.release_version}" : "https://api.github.com/repos/${var.github_repo}/releases/latest" + + request_headers = { + Accept = "application/vnd.github+json" + User-Agent = "Terraform-ACSC-Deploy-AWS" + } +} + +locals { + release_data = jsondecode(data.http.github_release.response_body) + + # Extract asset download URLs + assets = { + for asset in local.release_data.assets : + asset.name => asset.browser_download_url + } + + # Identify specific assets with error handling + high_priority_package_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))] + medium_priority_package_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip$", name)) && !can(regex("\\.sha256$", name))] + high_priority_hash_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCHighPriorityHardening.*\\.zip\\.sha256$", name))] + medium_priority_hash_list = [for name, url in local.assets : { name = name, url = url } if can(regex("ACSCMediumPriorityHardening.*\\.zip\\.sha256$", name))] + + # Extract first element safely + high_priority_package = length(local.high_priority_package_list) > 0 ? local.high_priority_package_list[0] : { name = "", url = "" } + medium_priority_package = length(local.medium_priority_package_list) > 0 ? local.medium_priority_package_list[0] : { name = "", url = "" } + high_priority_hash = length(local.high_priority_hash_list) > 0 ? local.high_priority_hash_list[0] : { name = "", url = "" } + medium_priority_hash = length(local.medium_priority_hash_list) > 0 ? local.medium_priority_hash_list[0] : { name = "", url = "" } + + deploy_high_priority = var.configuration_level == "HighPriority" || var.configuration_level == "All" + deploy_medium_priority = var.configuration_level == "MediumPriority" || var.configuration_level == "All" + + # Validate that required assets were found + validate_high_assets = local.deploy_high_priority && ( + local.high_priority_package.url == "" || + local.high_priority_hash.url == "" + ) ? tobool("Error: High Priority assets not found in GitHub release. Ensure the release contains ACSCHighPriorityHardening package files.") : true + + validate_medium_assets = local.deploy_medium_priority && ( + local.medium_priority_package.url == "" || + local.medium_priority_hash.url == "" + ) ? tobool("Error: Medium Priority assets not found in GitHub release. Ensure the release contains ACSCMediumPriorityHardening package files.") : true +} + +# Download package files +data "http" "high_priority_package" { + count = local.deploy_high_priority ? 1 : 0 + url = local.high_priority_package.url +} + +data "http" "medium_priority_package" { + count = local.deploy_medium_priority ? 1 : 0 + url = local.medium_priority_package.url +} + +# Download hash files +data "http" "high_priority_hash" { + count = local.deploy_high_priority ? 1 : 0 + url = local.high_priority_hash.url +} + +data "http" "medium_priority_hash" { + count = local.deploy_medium_priority ? 1 : 0 + url = local.medium_priority_hash.url +} + +# S3 bucket for DSC MOF packages +resource "aws_s3_bucket" "acsc" { + bucket = var.s3_bucket_name + tags = var.tags +} + +# Enable versioning +resource "aws_s3_bucket_versioning" "acsc" { + count = var.enable_versioning ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + + versioning_configuration { + status = "Enabled" + } +} + +# Enable encryption +resource "aws_s3_bucket_server_side_encryption_configuration" "acsc" { + count = var.enable_encryption ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +# Block public access +resource "aws_s3_bucket_public_access_block" "acsc" { + bucket = aws_s3_bucket.acsc.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# Upload High Priority package +resource "aws_s3_object" "high_priority_package" { + count = local.deploy_high_priority ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + key = "packages/ACSCHighPriorityHardening.zip" + content = data.http.high_priority_package[0].response_body + content_type = "application/zip" + tags = var.tags +} + +# Upload Medium Priority package +resource "aws_s3_object" "medium_priority_package" { + count = local.deploy_medium_priority ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + key = "packages/ACSCMediumPriorityHardening.zip" + content = data.http.medium_priority_package[0].response_body + content_type = "application/zip" + tags = var.tags +} + +# Upload High Priority hash +resource "aws_s3_object" "high_priority_hash" { + count = local.deploy_high_priority ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + key = "packages/ACSCHighPriorityHardening.zip.sha256" + content = data.http.high_priority_hash[0].response_body + content_type = "text/plain" + tags = var.tags +} + +# Upload Medium Priority hash +resource "aws_s3_object" "medium_priority_hash" { + count = local.deploy_medium_priority ? 1 : 0 + bucket = aws_s3_bucket.acsc.id + key = "packages/ACSCMediumPriorityHardening.zip.sha256" + content = data.http.medium_priority_hash[0].response_body + content_type = "text/plain" + tags = var.tags +} + +locals { + # Extract SHA256 hash from downloaded hash file + high_priority_content_hash = local.deploy_high_priority ? ( + length(split(" ", data.http.high_priority_hash[0].response_body)) > 0 ? + trimspace(split(" ", data.http.high_priority_hash[0].response_body)[0]) : + trimspace(data.http.high_priority_hash[0].response_body) + ) : "" + + medium_priority_content_hash = local.deploy_medium_priority ? ( + length(split(" ", data.http.medium_priority_hash[0].response_body)) > 0 ? + trimspace(split(" ", data.http.medium_priority_hash[0].response_body)[0]) : + trimspace(data.http.medium_priority_hash[0].response_body) + ) : "" +} diff --git a/terraform/aws/outputs.tf b/terraform/aws/outputs.tf new file mode 100644 index 0000000..e341a2c --- /dev/null +++ b/terraform/aws/outputs.tf @@ -0,0 +1,89 @@ +output "s3_bucket_name" { + description = "Name of the S3 bucket containing DSC packages" + value = aws_s3_bucket.acsc.id +} + +output "s3_bucket_arn" { + description = "ARN of the S3 bucket" + value = aws_s3_bucket.acsc.arn +} + +output "s3_bucket_region" { + description = "Region of the S3 bucket" + value = aws_s3_bucket.acsc.region +} + +output "high_priority_package_s3_key" { + description = "S3 key of the High Priority package" + value = local.deploy_high_priority ? aws_s3_object.high_priority_package[0].key : null +} + +output "medium_priority_package_s3_key" { + description = "S3 key of the Medium Priority package" + value = local.deploy_medium_priority ? aws_s3_object.medium_priority_package[0].key : null +} + +output "high_priority_content_hash" { + description = "SHA256 hash of the High Priority package" + value = local.deploy_high_priority ? local.high_priority_content_hash : null +} + +output "medium_priority_content_hash" { + description = "SHA256 hash of the Medium Priority package" + value = local.deploy_medium_priority ? local.medium_priority_content_hash : null +} + +output "ssm_document_high_priority_name" { + description = "Name of the SSM Document for High Priority configuration" + value = local.deploy_high_priority ? aws_ssm_document.high_priority_dsc[0].name : null +} + +output "ssm_document_medium_priority_name" { + description = "Name of the SSM Document for Medium Priority configuration" + value = local.deploy_medium_priority ? aws_ssm_document.medium_priority_dsc[0].name : null +} + +output "ssm_association_high_priority_id" { + description = "ID of the State Manager Association for High Priority" + value = local.deploy_high_priority ? aws_ssm_association.high_priority[0].id : null +} + +output "ssm_association_medium_priority_id" { + description = "ID of the State Manager Association for Medium Priority" + value = local.deploy_medium_priority ? aws_ssm_association.medium_priority[0].id : null +} + +output "iam_role_name" { + description = "Name of the IAM role for EC2 instances" + value = aws_iam_role.ec2_ssm.name +} + +output "iam_role_arn" { + description = "ARN of the IAM role for EC2 instances" + value = aws_iam_role.ec2_ssm.arn +} + +output "iam_instance_profile_name" { + description = "Name of the IAM instance profile for EC2 instances" + value = aws_iam_instance_profile.ec2_ssm.name +} + +output "iam_instance_profile_arn" { + description = "ARN of the IAM instance profile for EC2 instances" + value = aws_iam_instance_profile.ec2_ssm.arn +} + +output "release_version" { + description = "GitHub release version deployed" + value = local.release_data.tag_name +} + +output "release_name" { + description = "GitHub release name" + value = local.release_data.name +} + +output "target_tag" { + description = "Tag that EC2 instances must have to receive hardening" + value = "${var.target_tag_key}=${var.target_tag_value}" +} diff --git a/terraform/aws/ssm.tf b/terraform/aws/ssm.tf new file mode 100644 index 0000000..049ab5d --- /dev/null +++ b/terraform/aws/ssm.tf @@ -0,0 +1,148 @@ +# Data source for current AWS account +data "aws_caller_identity" "current" {} + +# SSM Document for applying High Priority DSC configuration +resource "aws_ssm_document" "high_priority_dsc" { + count = local.deploy_high_priority ? 1 : 0 + name = "ACSC-HighPriority-DSC" + document_type = "Command" + document_format = "JSON" + tags = var.tags + + content = jsonencode({ + schemaVersion = "2.2" + description = "Apply ACSC High Priority Windows Hardening DSC Configuration" + parameters = { + s3BucketName = { + type = "String" + description = "S3 bucket containing DSC package" + default = var.s3_bucket_name + } + s3Key = { + type = "String" + description = "S3 key for DSC package" + default = "packages/ACSCHighPriorityHardening.zip" + } + } + mainSteps = [ + { + action = "aws:runPowerShellScript" + name = "downloadAndApplyDSC" + inputs = { + runCommand = [ + "$ErrorActionPreference = 'Stop'", + "Write-Output 'Downloading DSC package from S3...'", + "$tempDir = Join-Path $env:TEMP 'ACSC-DSC'", + "New-Item -ItemType Directory -Path $tempDir -Force | Out-Null", + "$zipPath = Join-Path $tempDir 'ACSCHighPriorityHardening.zip'", + "Read-S3Object -BucketName '{{ s3BucketName }}' -Key '{{ s3Key }}' -File $zipPath", + "Write-Output 'Extracting DSC package...'", + "$extractPath = Join-Path $tempDir 'extracted'", + "Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force", + "Write-Output 'Applying DSC configuration...'", + "$mofFile = Get-ChildItem -Path $extractPath -Filter '*.mof' -Recurse | Select-Object -First 1", + "if ($mofFile) {", + " Start-DscConfiguration -Path $mofFile.DirectoryName -Wait -Verbose -Force", + " Write-Output 'DSC configuration applied successfully'", + "} else {", + " throw 'No MOF file found in package'", + "}", + "Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue" + ] + } + } + ] + }) +} + +# SSM Document for applying Medium Priority DSC configuration +resource "aws_ssm_document" "medium_priority_dsc" { + count = local.deploy_medium_priority ? 1 : 0 + name = "ACSC-MediumPriority-DSC" + document_type = "Command" + document_format = "JSON" + tags = var.tags + + content = jsonencode({ + schemaVersion = "2.2" + description = "Apply ACSC Medium Priority Windows Hardening DSC Configuration" + parameters = { + s3BucketName = { + type = "String" + description = "S3 bucket containing DSC package" + default = var.s3_bucket_name + } + s3Key = { + type = "String" + description = "S3 key for DSC package" + default = "packages/ACSCMediumPriorityHardening.zip" + } + } + mainSteps = [ + { + action = "aws:runPowerShellScript" + name = "downloadAndApplyDSC" + inputs = { + runCommand = [ + "$ErrorActionPreference = 'Stop'", + "Write-Output 'Downloading DSC package from S3...'", + "$tempDir = Join-Path $env:TEMP 'ACSC-DSC'", + "New-Item -ItemType Directory -Path $tempDir -Force | Out-Null", + "$zipPath = Join-Path $tempDir 'ACSCMediumPriorityHardening.zip'", + "Read-S3Object -BucketName '{{ s3BucketName }}' -Key '{{ s3Key }}' -File $zipPath", + "Write-Output 'Extracting DSC package...'", + "$extractPath = Join-Path $tempDir 'extracted'", + "Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force", + "Write-Output 'Applying DSC configuration...'", + "$mofFile = Get-ChildItem -Path $extractPath -Filter '*.mof' -Recurse | Select-Object -First 1", + "if ($mofFile) {", + " Start-DscConfiguration -Path $mofFile.DirectoryName -Wait -Verbose -Force", + " Write-Output 'DSC configuration applied successfully'", + "} else {", + " throw 'No MOF file found in package'", + "}", + "Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue" + ] + } + } + ] + }) +} + +# State Manager Association for High Priority configuration +resource "aws_ssm_association" "high_priority" { + count = local.deploy_high_priority ? 1 : 0 + name = aws_ssm_document.high_priority_dsc[0].name + schedule_expression = var.remediation_schedule + + targets { + key = "tag:${var.target_tag_key}" + values = [var.target_tag_value] + } + + output_location { + s3_bucket_name = aws_s3_bucket.acsc.id + s3_key_prefix = "ssm-logs/high-priority" + } + + compliance_severity = "HIGH" +} + +# State Manager Association for Medium Priority configuration +resource "aws_ssm_association" "medium_priority" { + count = local.deploy_medium_priority ? 1 : 0 + name = aws_ssm_document.medium_priority_dsc[0].name + schedule_expression = var.remediation_schedule + + targets { + key = "tag:${var.target_tag_key}" + values = [var.target_tag_value] + } + + output_location { + s3_bucket_name = aws_s3_bucket.acsc.id + s3_key_prefix = "ssm-logs/medium-priority" + } + + compliance_severity = "MEDIUM" +} diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example new file mode 100644 index 0000000..ac47f14 --- /dev/null +++ b/terraform/aws/terraform.tfvars.example @@ -0,0 +1,37 @@ +# AWS Region Configuration +region = "ap-southeast-2" # Sydney + +# S3 Bucket Configuration +# Must be globally unique, 3-63 characters, lowercase letters, numbers, and hyphens +s3_bucket_name = "acsc-hardening-packages" + +# GitHub Release Configuration +github_repo = "devnomadic/ACSC-WindowsHardening" +release_version = "" # Leave empty for latest release, or specify like "v1.0.0" + +# Deployment Configuration +configuration_level = "All" # Options: HighPriority, MediumPriority, All + +# Remediation Schedule +# How often State Manager checks and applies configuration +remediation_schedule = "rate(30 minutes)" # Every 30 minutes + +# EC2 Instance Targeting +# Only EC2 instances with this tag will receive the hardening configuration +target_tag_key = "ACSC-Hardening" +target_tag_value = "Enabled" + +# S3 Configuration +enable_versioning = true +enable_encryption = true + +# Pre-signed URL Configuration +presigned_url_expiry_hours = 17520 # 2 years (730 days * 24 hours) + +# Resource Tags +tags = { + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" + CostCenter = "Security" +} diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf new file mode 100644 index 0000000..a2d401d --- /dev/null +++ b/terraform/aws/variables.tf @@ -0,0 +1,89 @@ +variable "region" { + description = "AWS region for resources" + type = string + default = "ap-southeast-2" # Sydney +} + +variable "s3_bucket_name" { + description = "Name of the S3 bucket for DSC packages (must be globally unique, 3-63 chars)" + type = string + + validation { + condition = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.s3_bucket_name)) + error_message = "S3 bucket name must be 3-63 characters long, lowercase letters, numbers, and hyphens only." + } +} + +variable "github_repo" { + description = "GitHub repository in format owner/repo" + type = string + default = "devnomadic/ACSC-WindowsHardening" +} + +variable "release_version" { + description = "GitHub release version to deploy (e.g., 'v1.0.0'). Leave empty for latest release." + type = string + default = "" +} + +variable "configuration_level" { + description = "Configuration level to deploy: HighPriority, MediumPriority, or All" + type = string + default = "All" + + validation { + condition = contains(["HighPriority", "MediumPriority", "All"], var.configuration_level) + error_message = "Configuration level must be HighPriority, MediumPriority, or All." + } +} + +variable "presigned_url_expiry_hours" { + description = "Number of hours before pre-signed URLs expire" + type = number + default = 17520 # 2 years +} + +variable "remediation_schedule" { + description = "Schedule expression for State Manager association (rate or cron)" + type = string + default = "rate(30 minutes)" + + validation { + condition = can(regex("^(rate|cron)\\(", var.remediation_schedule)) + error_message = "Schedule must be a valid rate() or cron() expression." + } +} + +variable "target_tag_key" { + description = "EC2 tag key to target instances for hardening" + type = string + default = "ACSC-Hardening" +} + +variable "target_tag_value" { + description = "EC2 tag value to target instances for hardening" + type = string + default = "Enabled" +} + +variable "enable_versioning" { + description = "Enable versioning on S3 bucket" + type = bool + default = true +} + +variable "enable_encryption" { + description = "Enable server-side encryption on S3 bucket" + type = bool + default = true +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = { + Environment = "Production" + ManagedBy = "Terraform" + Purpose = "ACSC-Windows-Hardening" + } +}