Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions terraform/azure_ad/foofrix.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# FooFrix identities. Subscription roles are in azure_foofrix. RELOPS-2548.
# Create the client secret outside Terraform and store it in 1Password.
resource "azuread_application" "foofrix" {
display_name = "sp-foofrix-azure-devtest"
owners = data.azuread_group.relops.members
notes = "GCP provisioning identity for the FooFrix Azure DevTest Subscription. RELOPS-2548."
}

resource "azuread_service_principal" "foofrix" {
client_id = azuread_application.foofrix.client_id
owners = data.azuread_group.relops.members
tags = concat(["name:sp-foofrix-azure-devtest"], local.sp_tags)
}

resource "azuread_group" "platform_performance" {
display_name = "Platform Performance"
security_enabled = true
mail_enabled = false
description = "Managed by RelOps - Platform Performance team"
}

resource "azuread_group_member" "platform_performance" {
for_each = {
dpalmeiro = "2e8c6f6d-9dae-42b3-a193-5ea7c4b09cb5"
jlink = "d76c0d0a-537a-42a3-9ac7-0d96caa8e054"
}
group_object_id = azuread_group.platform_performance.object_id
member_object_id = each.value
}

resource "azuread_application" "foofrix_image_build" {
display_name = "sp-foofrix-image-build"
owners = data.azuread_group.relops.members
notes = "FooFrix Windows image builds from worker-images. RELOPS-2570."
}

resource "azuread_service_principal" "foofrix_image_build" {
client_id = azuread_application.foofrix_image_build.client_id
owners = data.azuread_group.relops.members
tags = concat(["name:sp-foofrix-image-build"], local.sp_tags)
}

resource "azuread_application_federated_identity_credential" "foofrix_image_build" {
application_id = azuread_application.foofrix_image_build.id
display_name = "github-worker-images-foofrix"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://token.actions.githubusercontent.com"
subject = "repo:mozilla-platform-ops/worker-images:environment:foofrix-image-build"
}
87 changes: 87 additions & 0 deletions terraform/azure_foofrix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# FooFrix Azure subscription

[FooFrix](https://foofrix.uc.r.appspot.com/?component=JS&suite=speedometer3)
runs agents that profile Firefox, test performance changes, build Firefox,
and produce patches. Its launcher, scheduler, queue, results, and dashboard
remain in GCP. This dedicated Azure DevTest subscription gives the GCP launcher
a target for Windows testing. It can create VMs from an image, run tests, and
remove the VMs when finished, including runs that last more than 24 hours.
Windows workers use the existing GCS queue and report results to
`gs://foofrix-findings`.

The harness source and image provisioning scripts are in
[dpalmeiro/foofrix](https://github.com/dpalmeiro/foofrix).

This Terraform stack manages the subscription, a resource group in Central US,
a Key Vault available for Windows worker secrets, and a managed identity for the VMs.
Terraform manages the `foofrix` Compute Gallery and its `win11_64_24h2` image
definition. It uses the existing FXCI Windows 11 24H2 properties: Windows, x64,
Hyper-V V2, generalized, and `MicrosoftWindowsDesktop/Windows-11/win11-24h2-avd`.
The worker-images workflow publishes image versions. A private `artifacts`
Blob Storage container holds Azure build and image files in Standard LRS storage.
The GCP launcher manages the VMs through `sp-foofrix-azure-devtest`. The application and
service principal are managed in `../azure_ad/foofrix.tf`.

| Identity | Access |
| --- | --- |
| Existing Relops group | Subscription Owner; Key Vault Administrator; blob read/write |
| `sp-foofrix-azure-devtest` | Subscription Contributor; Key Vault Secrets Officer; blob read/write |
| `id-foofrix-worker` | Read vault secrets; blob read/write |
| Platform Performance | Subscription Contributor; Key Vault Secrets Officer; blob read/write |
| `sp-foofrix-image-build` | Contributor on the build resource group and gallery; blob read; attach the build identity |
| `id-foofrix-image-build` | Blob read during image creation |

The GCP launcher uses a tenant ID, client ID, and client secret to
access Azure. The client secret is managed outside Terraform and stored in
1Password. VMs can use `id-foofrix-worker` to read secrets from Key Vault.
Access to the GCS queue and results bucket requires separate Google credentials.
Blob access uses these identities through the Storage Blob Data Contributor
role on the `artifacts` container. The provisioning service can manage gallery
images through its subscription Contributor role.

FooFrix uses the same Mozilla billing profile and invoice section as fuzzing.
The daily Actual Cost, Amortized Cost, and FOCUS exports in
`../azure_billing/finops.tf` include its costs in
`safinopsdata/cost-management`. Filter by the FooFrix subscription ID to report
its costs.

Related issue: [RELOPS-2548](https://mozilla-hub.atlassian.net/browse/RELOPS-2548).

## Image builds and access setup

Apply `azure_ad` before this stack. Create the subscription with a targeted plan
for `azurerm_subscription.foofrix`, then run a full plan and apply.

The build application uses GitHub OIDC with this exact subject:
`repo:mozilla-platform-ops/worker-images:environment:foofrix-image-build`.
RELOPS-2570 must create and protect that dedicated GitHub environment and use it
in the authorized FooFrix workflow before image builds start. This subject
permits jobs that use that environment; it does not identify a workflow file.
Use environment deployment rules and the team authorization check to control access.
The workflow needs `id-token: write` and audience `api://AzureADTokenExchange`.
No image-build client secret is needed.

Configure Packer to use the existing `image_build_resource_group` output for
temporary resources. Publish to `image_gallery_name` in
`image_gallery_resource_group`, using the definition from
`windows_image_definition_id`. The workflow logs in with `image_build_client_id`.
Attach `image_build_identity_id` to the temporary VM. The guest bootstrap must
use that managed identity to authenticate artifact downloads, with
`image_build_identity_client_id` to select it. The GitHub login does not provide
credentials inside the VM. Both build identities have read access to `artifacts`.

Platform Performance starts with Denis Palmeiro and Justin Link. Add Frank Doty,
Andrew Creskey, Jamie Nicol, Marc Leclair, Markus Stange, and Sky Ning after their
Entra accounts are created. Deliver Denis's temporary password through the
approved private process. He must change it and complete MFA enrollment.
Do not put credentials in Terraform or the PR.

Perf owns VM creation, deletion, and the FooFrix harness and GCP integration.
RelOps supplies the Azure resources and image-build path. RELOPS-2570 covers
Windows tooling, profiling support, startup, and image validation.

The GCS authentication design still needs agreement with Perf. If Azure managed
identity to Google Workload Identity Federation is selected, add the Entra
audience application in `azure_ad` and the Google trust and bucket grants in GCP.
Queue and state access to `foofrix-findings` in project `foofrix` needs reads,
updates, and deletes as well as uploads. Test results remain in GCS.
8 changes: 8 additions & 0 deletions terraform/azure_foofrix/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
backend "s3" {
bucket = "relops-tf-states"
key = "azure_foofrix.tfstate"
use_lockfile = true
region = "us-west-2"
}
}
125 changes: 125 additions & 0 deletions terraform/azure_foofrix/images.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
resource "azurerm_shared_image_gallery" "foofrix" {
name = "foofrix"
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
description = "Windows images for FooFrix performance agents."
tags = local.common_tags

depends_on = [azurerm_resource_provider_registration.this["Microsoft.Compute"]]
}

resource "azurerm_storage_account" "foofrix" {
name = "safoofrix${substr(azurerm_subscription.foofrix.subscription_id, 0, 8)}"
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
tags = local.common_tags

depends_on = [azurerm_resource_provider_registration.this["Microsoft.Storage"]]
}

resource "azurerm_storage_container" "artifacts" {
name = "artifacts"
storage_account_id = azurerm_storage_account.foofrix.id
container_access_type = "private"
}

resource "azurerm_role_assignment" "blob_contributor" {
for_each = {
provisioner = {
id = data.azuread_service_principal.foofrix.object_id
type = "ServicePrincipal"
}
worker = {
id = azurerm_user_assigned_identity.worker.principal_id
type = "ServicePrincipal"
}
platform_performance = {
id = data.azuread_group.platform_performance.object_id
type = "Group"
}
relops = {
id = data.azuread_group.relops.object_id
type = "Group"
}
}

scope = azurerm_storage_container.artifacts.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = each.value.id
principal_type = each.value.type
skip_service_principal_aad_check = each.value.type == "ServicePrincipal"
}

resource "azurerm_shared_image" "windows" {
name = "win11_64_24h2"
gallery_name = azurerm_shared_image_gallery.foofrix.name
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
os_type = "Windows"
architecture = "x64"
hyper_v_generation = "V2"
specialized = false
tags = local.common_tags

identifier {
publisher = "MicrosoftWindowsDesktop"
offer = "Windows-11"
sku = "win11-24h2-avd"
}
}

data "azuread_service_principal" "foofrix_image_build" {
display_name = "sp-foofrix-image-build"
}

resource "azurerm_resource_group" "image_build" {
name = "rg-foofrix-image-build"
location = local.location
tags = local.common_tags
}

resource "azurerm_user_assigned_identity" "image_build" {
name = "id-foofrix-image-build"
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
tags = local.common_tags

depends_on = [azurerm_resource_provider_registration.this["Microsoft.ManagedIdentity"]]
}

resource "azurerm_role_assignment" "image_build_contributor" {
for_each = {
build = azurerm_resource_group.image_build.id
gallery = azurerm_shared_image_gallery.foofrix.id
}
scope = each.value
role_definition_name = "Contributor"
principal_id = data.azuread_service_principal.foofrix_image_build.object_id
principal_type = "ServicePrincipal"
skip_service_principal_aad_check = true
}

resource "azurerm_role_assignment" "image_build_identity_operator" {
scope = azurerm_user_assigned_identity.image_build.id
role_definition_name = "Managed Identity Operator"
principal_id = data.azuread_service_principal.foofrix_image_build.object_id
principal_type = "ServicePrincipal"
skip_service_principal_aad_check = true
}

resource "azurerm_role_assignment" "image_build_blob_reader" {
for_each = {
builder = data.azuread_service_principal.foofrix_image_build.object_id
guest = azurerm_user_assigned_identity.image_build.principal_id
}
scope = azurerm_storage_container.artifacts.id
role_definition_name = "Storage Blob Data Reader"
principal_id = each.value
principal_type = "ServicePrincipal"
skip_service_principal_aad_check = true
}
51 changes: 51 additions & 0 deletions terraform/azure_foofrix/keyvault.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
resource "azurerm_user_assigned_identity" "worker" {
name = "id-foofrix-worker"
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
tags = local.common_tags

depends_on = [azurerm_resource_provider_registration.this["Microsoft.ManagedIdentity"]]
}

resource "azurerm_key_vault" "foofrix" {
name = "kv-foofrix-${substr(azurerm_subscription.foofrix.subscription_id, 0, 8)}"
resource_group_name = azurerm_resource_group.foofrix.name
location = local.location
tenant_id = local.tenant_id
sku_name = "standard"
rbac_authorization_enabled = true
purge_protection_enabled = true
tags = local.common_tags

depends_on = [azurerm_resource_provider_registration.this["Microsoft.KeyVault"]]
}

resource "azurerm_role_assignment" "relops_key_vault_administrator" {
scope = azurerm_key_vault.foofrix.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azuread_group.relops.object_id
principal_type = "Group"
}

resource "azurerm_role_assignment" "foofrix_secrets_officer" {
scope = azurerm_key_vault.foofrix.id
role_definition_name = "Key Vault Secrets Officer"
principal_id = data.azuread_service_principal.foofrix.object_id
principal_type = "ServicePrincipal"
skip_service_principal_aad_check = true
}

resource "azurerm_role_assignment" "worker_secrets_user" {
scope = azurerm_key_vault.foofrix.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.worker.principal_id
principal_type = "ServicePrincipal"
skip_service_principal_aad_check = true
}

resource "azurerm_role_assignment" "platform_performance_secrets_officer" {
scope = azurerm_key_vault.foofrix.id
role_definition_name = "Key Vault Secrets Officer"
principal_id = data.azuread_group.platform_performance.object_id
principal_type = "Group"
}
Loading
Loading