See all posts

Wiz as code: Accelerating Wiz with Terraform - part I

As organizations scale their cloud infrastructure, securing assets efficiently becomes a critical challenge.

6 minMar 27, 2025
Bendik Schartum Thorbjørnsen
Bendik Schartum ThorbjørnsenSecurity Engineer
Bendik Schartum Thorbjørnsen

Introduction

Wiz provides robust visibility into cloud security posture. You can read more on this in our article Elevating Cloud Security with O3 Cyber and Wiz. When implementing Wiz, we have found that manually configuring Wiz across multi-cloud environments can be time-consuming and error-prone. In this article, we share how we are accelerating deployment with our internal framework for Wiz as Code, where we use Terraform to automate Wiz deployments. This enforces security policies and configuration controls consistently.

In this article, we explore how Terraform can be used to deploy and manage Wiz configurations.

Why Use Terraform for Wiz?

Wiz has full support for Terraform through its own provider, enabling organizations to automate tasks such as:

  • Setting up Wiz Connectors for AWS, Azure, GCP, and Kubernetes
  • Defining Project structures based on company data
  • Managing Access Controls and Role-Based permissions efficiently
  • Integrating Wiz Alerts and Findings into SIEMs and Incident Response workflows
  • Configuring Wiz Controls and Configuration Rules

Deploying Wiz with Terraform

To begin, ensure to have

  • API access to Wiz, including an API key for authentication.
  • Cloud provider credentials (AWS, Azure, GCP) configured for Terraform.

Wiz uses Service accounts to access its GraphQL API and interact with the service.

1. Securely store credentials

Configure your CI/CD by securely setting your Wiz API Client ID and Secret. For integrations with your Cloud provider, setup a secure way of accessing your resources.

Example: Github Actions
Store the Wiz API key in Github secrets and use
OpenID Connect (OIDC) to securely connect to Azure environment. Remember to carefully setup your CI/CD pipeline and YAML-file to prevent privilege escalations attacks, read more here.

2. Defining Wiz Provider in Terraform

Set up the Wiz provider in Terraform to establish connection. Here's a basic configuration:

terraform {
  required_providers {
    wiz = {
      version = "1.22.xxxx"
      source = "tf.app.wiz.io/wizsec/wiz"
    }
  }
}

3. Define and setup a organization structure

Organizations can automate the Project structure creation in Wiz by importing data from authoritative sources. Determining a “Source of Truth” is the first key step.
Here's a few examples from various data sources we've worked with:

  • Azure Active Directory (Entra ID): Use the Graph API to fetch information such as the organizational structure, departments, and teams to automatically create corresponding Wiz Projects and Sub-Projects.
  • Configuration Management Database (CMDB): Extract application relationships, service ownership, and business context to define project hierarchies and access controls in Wiz.
  • HR Systems: Leverage employee data to automate role assignments and access permissions based on job functions and responsibilities.
  • Landing Zones: For companies using automated processes such as the Cloud Adoption Framework (CAF) module in Terraform to define landing zones, these standardized environments can also serve as a foundation for Wiz project structures—inheriting users and and configurations.

4. How projects are defined

The following code demonstrates how to create a project and configure Resource Scopes. Through tagging subscriptions, resource groups and resources in Azure and GitHub repository topics, we can automatically assign resources to Wiz projects. This approach ensures efficient organization of team resources while minimizing manual configuration.

data "wiz_cloud_organizations" "github_orgs_lookup" {
    cloud_provider = ["GitHub"]
    search         = ["ORG IT"]
}

data "wiz_cloud_organizations" "external_id_lookup" {
    search = ["$MANAGEMENT_ID"]
}

data "wiz_cloud_organizations" "Team_A_devops" {
    cloud_provider = ["AzureDevOps"]
    search         = ["Team_A"]
}

resource "wiz_project" "Team_A" {
  name              = "Team_A"
  description       = ""
  is_folder         = false
  parent_project_id = wiz_project.parent.id
  risk_profile {
    is_actively_developed = "YES"
    has_authentication    = "YES"
    has_exposed_api       = "YES"
    is_internet_facing    = "YES"
    is_customer_facing    = "YES"
    stores_data           = "YES"
    business_impact       = "HBI"
    is_regulated          = "YES"
  }
  
  #Assigning resoruces to the project

  # Subscriptions by team tag
  cloud_account_tags_links {
    cloud_account_tags {
      key = "team"
      value = "Team_A"
    }
  }

  # Resources/resrouce groups by team tag
  dynamic "cloud_organization_links" {
    for_each = data.wiz_cloud_organizations.external_id_lookup.cloud_organizations
    content {
      cloud_organization = cloud_organization_links.value.id
      resource_group_tags {
         key = "team"
         value = "Team_A"
      }
      resource_tags {
        key = "team"
        value = "Team_A"
      }
    }
  }

  # Github repositories by topics
  dynamic "version_control_organization_links" {
    for_each = data.wiz_cloud_organizations.github_orgs_lookup.cloud_organizations
    content {
        cloud_organization = version_control_organization_links.value.id
        topics             = ["Team_A"]
    }
  }
  
  # Azure DevOps organiations
  dynamic "version_control_organization_links" {
    for_each = data.wiz_cloud_organizations.Team_A_devops.cloud_organizations
    content {
        cloud_organization = version_control_organization_links.value.id
    }
  }
}

To further streamline the Project creation, you can create reusable Terraform modules that help maintain a well-structured organization.
Example: Automated modules and data stored as locals

locals {
	# Export data from source of truth if avaiable or manually populate
	projects {
		SecurityOperations = {
      name               = "Project"
      description        = ""
      project_owners     = []
      security_champions = []
      parent_project_id  = module.SubFolder.folder_id
      cloud_accounts = [
        "Name of your subscription"
      ]
	  }
  }
}

module "SubFolder" {
  source             = "./modules/wiz-folder"
  name               = "Root Group"
  description        = "Level 1 Folder"
  parent_project_id  = ""
}

module "wiz_projects_org" {
  source   = "./modules/wiz-project"
  for_each = local.projects

  name               = lookup(each.value, "name", null)
  description        = lookup(each.value, "description", null)
  project_owners     = lookup(each.value, "project_owners", [])
  security_champions = lookup(each.value, "security_champions", [])
  risk_profile       = lookup(each.value, "risk_profile", null)
  parent_project_id  = lookup(each.value, "parent_project_id", null)

  cloud_account_links = lookup(each.value, "cloud_accounts", [])
  cloud_organization_links = lookup(each.value, "cloud_organizations", [])
  kubernetes_cluster_links = lookup(each.value, "kubernetes_clusters", [])
  container_registry_links = lookup(each.value, "container_registries", [])
  repository_links = lookup(each.value, "repositories", [])
}

This approach ensures that Wiz project structures remain synchronized with your organization's actual structure and reduces manual configuration effort. Regular synchronization jobs can keep the Wiz environment up-to-date as your organization evolves.

****The modules can be forked and customized to fit customer needs. For example, in this case, we combine the tagging approach with the project module.

locals {
  projects = {
    plattform = {
      name                = "Platform"
      description         = "This is the folder for platform"
      project_owners      = []
      security_champions  = []
      parent_project_id   = local.project_map["Platform"]
      risk_profile  = {
      }
      team_tag = "platform"
      repository_devops_links = ["devops-platform"]
    }
  }
}

module "wiz_projects_org" {
  source   = "./modules/wiz-project"
  for_each = local.projects

  name               = try(each.value.name, null)
  description        = try(each.value.description, null)
  project_owners     = try(each.value.project_owners, [])
  security_champions = try(each.value.security_champions, [])
  risk_profile       = try(each.value.risk_profile, null)
  parent_project_id  = try(each.value.parent_project_id, null)

  team_tag                  = try(each.value.team_tags, [])
  repository_devops_links   = try(each.value.cloud_accounts, [])
}

5. Single Sign-on (SSO) and group mapping

Wiz can configure SAML identity providers to enable Single Sign-On (SSO). Using the resource type wiz_saml_group_mapping we can export the groups associated to each team from our source of truth.

One of the key features of projects in Wiz is the democratization of security and ****reducing noise for the end user. In the example below we show how we based on Entra ID groups can assign reader/admin roles per project. This reduces the future administrative burden by using Entra ID groups that already are managed per team.

resource "wiz_saml_idp" "saml_sso_azure" {
  name                         = "Org SSO - Entra ID"
  issuer_url                   = "<https://sts.windows.net/>"
  login_url                    = "<https://login.microsoftonline.com/>"
  logout_url                   = "<https://login.microsoftonline.com/>"
  use_provider_managed_roles   = true
  allow_manual_role_override   = true
  merge_groups_mapping_by_role = false
  certificate                  = <<EOT
-----BEGIN CERTIFICATE-----
[CERTIFICATE]
-----END CERTIFICATE-----
EOT
}

# Adding all group mappings
resource "wiz_saml_group_mapping" "role_map" {
 saml_idp_id = wiz_saml_idp.saml_sso_azure.id
 
 #Global Scope
 group_mapping {
    provider_group_id = "00000000-0000-0000-0000-000000000000" 
    role = "GLOBAL_ADMIN"
    description = "Entra ID group - Global admin"
  }
  
  #Project Scope
  group_mapping {
    provider_group_id = "00000000-0000-0000-0000-000000000000" 
    role = "PROJECT_ADMIN"
    description = "Entra ID group - Project_Admin_teamA"
    projects = [
      local.project_map["Team_A"]
    ]
	}
}

Important to note:

During deployment of the Wiz SSO enterprise application we set the Attributes and claims for determining the groups. In the App Registration we determine what value should match the provider_group_id-attribute

Summary

Adopting Wiz as Code aims to support an automated and scalable approach to configuring Wiz consistently and governing cloud environments.

Security teams looking to deploy Wiz as Code should start by defining their organization structure and creating projects based on their source of truth. Next, they should configure SSO and role mappings to ensure proper access control. In part 2, we will look into how to configure integration and automation rules. As security requirements evolve, leveraging Infrastructure as Code with Terraform helps maintain a robust and scalable security posture that aligns with business needs.