This module creates infra to support exporting data from Worklytics to Azure Blob Storage.
It is intended for the Terraform Registry
(Worklytics/worklytics-export/azurerm).
If it does not meet your needs, feel free to directly copy the main.tf file into your own Terraform
configuration and adapt it to your requirements.
- Optional storage — an Azure storage account and/or blob container, unless you pass existing
names. Created accounts use TLS 1.2, HTTPS-only, infrastructure encryption, shared access keys
disabled, and
LRSunless you override replication. - Entra application + service principal with a federated identity credential that trusts your
Worklytics tenant's GCP service account (
issuer = https://accounts.google.com,subject = worklytics_tenant_id). - RBAC so that identity can write blobs in the export container (
Storage Blob Data Contributoron the container,Storage Blob Delegatoron the account).
Worklytics then exchanges a Google ID token for an Entra access token and writes export objects to the container.
from Terraform registry (once published):
module "worklytics-export" {
source = "Worklytics/worklytics-export/azurerm"
version = "~> 0.1.0"
# numeric ID of your Worklytics Tenant SA (21-digit unique ID, not the email)
worklytics_tenant_id = "123456789012345678901"
azure_tenant_id = "11111111-1111-1111-1111-111111111111"
resource_group_name = "worklytics"
}via GitHub:
module "worklytics-export" {
source = "git::https://github.com/worklytics/terraform-azurerm-worklytics-export/?ref=v0.1.0"
worklytics_tenant_id = "123456789012345678901"
azure_tenant_id = "11111111-1111-1111-1111-111111111111"
resource_group_name = "worklytics"
}The calling configuration must declare azurerm and azuread providers. This module does not
configure providers (so it can be composed into an existing Azure workspace).
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
provider "azuread" {
tenant_id = var.azure_tenant_id
}| Name | Required | Default | Description |
|---|---|---|---|
worklytics_tenant_id |
no | null |
21-digit unique ID of the Worklytics tenant GCP SA; null skips federation (pre-prod) |
azure_tenant_id |
yes | Entra tenant ID (for instructions / deep-link) | |
resource_group_name |
yes | Existing resource group for the storage account | |
storage_account_name |
no | null |
Reuse this account; otherwise one is created |
storage_container_name |
no | null |
Reuse this container; otherwise one is created |
account_replication_type |
no | LRS |
Replication for a created account (GRS / RAGRS / GZRS / RAGZRS recommended in production) |
infrastructure_encryption_enabled |
no | true |
Double-encrypt a created account (set at creation only) |
blob_diagnostics |
no | null |
Azure Monitor destination for blob logs; omit to skip logging |
location |
no | RG location | Region used only when creating a storage account |
worklytics_tenant_sa_email |
no | null |
SA email, documentation only |
resource_name_prefix |
no | worklytics-export- |
Prefix for created Entra / container names |
owners |
no | [] |
Entra object IDs set as owners of the application |
worklytics_host |
no | app.worklytics.co |
Hostname for connect TODOs / deep-links (prod by default; override for a custom domain) |
Your Worklytics tenant identity is the numeric unique ID of the tenant's GCP service account (the same value used by the AWS export and Azure import modules). The SA email cannot be used as the federated credential subject. Obtain the ID from the Worklytics app, or:
gcloud iam service-accounts describe EMAIL --format='value(uniqueId)'The storage account used as the export destination (created or reused).
The blob container Worklytics writes to. Compose with additional azurerm_* resources for
retention, extra RBAC, or a customer-managed key.
blob_services_resource_id is the ARM id of the account blob service
(…/blobServices/default). Pass it as target_resource_id on your own
azurerm_monitor_diagnostic_setting if you do not set blob_diagnostics.
blob_diagnostic_setting_id is set only when the module creates that setting.
Values for an account created by this module; null when you reuse an existing account
(configure those on the existing account, or pass a hardened account in).
Entra application (client) ID. Worklytics uses this when exchanging a Google ID token for an Azure access token.
Object ID of the service principal granted blob access. Compose with additional azurerm_role_assignment
resources if you use a customer-managed encryption key or extra locks.
Deep-link to finish setup in Worklytics (https://app.worklytics.co/analytics/data-export/connect?...
unless you set worklytics_host).
Rendered when todos_as_outputs = true.
This module is meant for use with Terraform 1.3+ and:
azurerm>= 4.0(storage + Azure RBAC)azuread>= 2.47(Entra app, service principal, federated identity credential)
Both providers are required: HashiCorp splits Azure Resource Manager from Entra ID. This module does not configure provider blocks; the caller must.
If you find incompatibilities, please open an issue.
Pass both names to skip storage creation and only grant Worklytics access:
module "worklytics-export" {
source = "Worklytics/worklytics-export/azurerm"
worklytics_tenant_id = "123456789012345678901"
azure_tenant_id = "11111111-1111-1111-1111-111111111111"
resource_group_name = "worklytics"
storage_account_name = "myexistingaccount"
storage_container_name = "worklytics-export"
}If you omit only storage_container_name, the module creates a private container on the existing
account.
These apply only to a storage account created by the module. If you pass
storage_account_name, configure them on that account (or compose extra resources using the
outputs below).
Infrastructure encryption is on by default for created accounts (infrastructure_encryption_enabled = true).
It can only be set at creation. Shared access keys are disabled (shared_access_key_enabled = false);
Worklytics uses Entra workload identity federation, not account keys.
Replication defaults to LRS. For production durability use geo-redundant storage:
module "worklytics-export" {
source = "Worklytics/worklytics-export/azurerm"
# ...
account_replication_type = "GRS" # or RAGRS, GZRS, RAGZRS
}Blob logging needs a destination you already own (Log Analytics workspace, another storage account, or Event Hub). It is off until you pass one:
module "worklytics-export" {
source = "Worklytics/worklytics-export/azurerm"
# ...
blob_diagnostics = {
log_analytics_workspace_id = azurerm_log_analytics_workspace.logs.id
}
}Or compose the diagnostic setting yourself (same destination types):
resource "azurerm_monitor_diagnostic_setting" "export_blobs" {
name = "worklytics-export-blobs"
target_resource_id = module.worklytics-export.blob_services_resource_id
log_analytics_workspace_id = azurerm_log_analytics_workspace.logs.id
enabled_log {
category = "StorageRead"
}
enabled_log {
category = "StorageWrite"
}
enabled_log {
category = "StorageDelete"
}
}Do not send diagnostics to the export storage account itself.
| Role | Scope | Why |
|---|---|---|
| Storage Blob Data Contributor | container | Write/overwrite export blobs |
| Storage Blob Delegator | storage account | User delegation keys used by Azure SDKs |
The federated credential trusts Google (accounts.google.com) as issuer and your
worklytics_tenant_id as subject, with audience api://AzureADTokenExchange.
This module is written and maintained by Worklytics, Co. and intended to guide our customers in setting up their own infra to export data from Worklytics to Azure Blob Storage.
As this is published as a Terraform module, we will strive to follow standard Terraform module structure and style conventions.
See examples/basic/ for a simple example of how to use this module.
Registry versions are git tags (vX.Y.Z) on main, not GitHub Releases. After a change is on
main and CI is green:
./tools/release.sh v0.1.0 --waitThat tags the current origin/main commit and pushes the tag. The tag-triggered workflow creates
the GitHub Release (notes / README badge). First-time listing on
registry.terraform.io
is a one-time Publish in the HashiCorp UI (Worklytics/worklytics-export/azurerm); later tags are
picked up by the Registry webhook.
| Workflow | What it covers |
|---|---|
terraform_lint.yaml |
terraform fmt -check |
terraform_validate.yaml |
terraform init / validate on examples/basic, plus terraform test unit tests |
terraform_integration.yaml |
Apply in a CI Azure subscription, then read/write a blob as the stand-in Worklytics GCP identity |
terraform_security.yaml |
Trivy IaC scan |
Unit tests live in tests/ and use Terraform's native test framework with mocked
azurerm / azuread providers (no cloud credentials).
Integration tests authenticate to Azure (GitHub → Entra OIDC) to apply this module, and to
GCP (GitHub → WIF) to impersonate the stand-in Worklytics tenant SA. The test then exchanges a
Google ID token, logs in with az --federated-token, and uploads/downloads a blob. Required GitHub
secrets (public repo) or variables (private repo):
| Name | Purpose |
|---|---|
GCP_WORKLOAD_IDENTITY_PROVIDER |
GitHub Actions WIF provider |
GCP_SERVICE_ACCOUNT |
CI agent SA (e.g. gh-actions-tf-azure-export@...) |
ENTRA_ID_CLIENT_ID |
Entra app for GitHub OIDC |
ENTRA_ID_TENANT_ID |
Entra tenant |
AZURE_SUBSCRIPTION_ID |
Subscription that contains the CI resource group |
AZURE_RESOURCE_GROUP_NAME |
Pre-created sandbox resource group (Owner scoped to this RG) |
The CI agent SA must be able to impersonate the stand-in tenant SA
(w8s-export-tf-ci-tenant@worklytics-ci.iam.gserviceaccount.com, shared with GCP export CI). The
Entra GitHub OIDC app must be able to create storage accounts, Entra applications, and role
assignments in the CI resource group (not subscription-wide). The resource group is provisioned
by worklytics-infra (src/org-github) and is delete-locked; workflows must not create or delete
it. Expected name: rg-w8s-tf-azure-export-ci.
(c) 2026 Worklytics, Co