Cisco Secure Access is Cisco's cloud-delivered Security Service Edge (SSE) solution. It provides secure access to private applications, SaaS apps, and the internet—regardless of where users or resources are located.
From the start, Secure Access offered APIs for automation. This opened the door for scripting deployments, integrating with orchestration tools, and building custom workflows. But for teams already using Infrastructure as Code, there was a gap: no native Terraform support. That changed in January 2026 with the release of the Cisco Secure Access Terraform Provider.
Now teams that use Terraform to manage their infrastructure can use the same workflows to manage at least some of their Secure Access deployments alongside it.
In this blog post, we'll cover how to setup your Terraform environment and then walk through an example of securing branch office access to a private application.
Prerequisites
To generate API credentials in Secure Access, navigate to Admin > Management > API Keys. Click Add and select your Key Scope.
Step 1 - Define your credentials
There are several ways to handle sensitive credentials in Terraform: environment variables, secrets managers like HashiCorp Vault, or CI/CD pipeline secrets.
For simplicity, we'll use a `terraform.tfvars` file. A '.tfvars' file is where you assign values to your Terraform variables. It keeps your actual secrets and environment-specific values separate from your configuration logic.
csa_key_id = "your-key-id"csa_key_secret = "your-key-secret"
Because a '.tfvars' file contains secrets, we must be sure to keep it out of version control! This means adding the .tfvars file to gitignore.
Step 2 - Declare Input Variables
Variables define the inputs your configuration accepts—their names, types, and descriptions. They let Terraform know what inputs to expect, mark sensitive values so they won't appear in logs, and give you helpful error messages if something is missing. In this snippet, we define variables for the API key id and key secret.
variable "csa_key_id" { type = string description = "Cisco Secure Access API Key ID" sensitive = true}variable "csa_key_secret" { type = string description = "Cisco Secure Access API Key Secret" sensitive = true}
Step 3 - Configure the Terraform provider
A provider is a Terraform plugin that knows how to communicate with a specific API. Providers exist for cloud platforms (AWS, Azure, GCP), SaaS products, and infrastructure services like Cisco Secure Access. The provider handles authentication and translates your Terraform configuration into API calls. In this snipper, you can see that we reference the variables defined in the previous step.
terraform { required_version = ">= 1.1" required_providers { ciscosecureaccess = { source = "CiscoDevNet/ciscosecureaccess" version = "~> 1.0" } }}provider "ciscosecureaccess" { key_id = var.csa_key_id key_secret = var.csa_key_secret}
Step 4 - Define the Network Tunnel
A network tunnel group in Cisco Secure Access represents a site-to-site VPN connection—typically from a branch office firewall or router to the Secure Access cloud. It defines the type of tunnel (e.g., Meraki MX, FTD, Azure, etc), the region where the tunnel originates, what network ranges sit behind it, and the authentication details needed to establish the connection.
Note that Terraform currently only supports static routes.
resource "ciscosecureaccess_network_tunnel_group" "branch_office" { name = "Seattle Branch Office" region = "us-west-2" device_type = "other" identifier_prefix = "seattle-branch" preshared_key = "YourSecurePreSharedKey123" network_cidrs = ["10.50.0.0/24"]
}
# Output the tunnel configuration details for firewall setupoutput "tunnel_hubs" { value = ciscosecureaccess_network_tunnel_group.branch_office.hubs description = "Tunnel hub details for configuring the branch peer"}
We output tunnel hub details (e.g., authentication ID, data center IP) that we will use to configure the branch peer.
Step 5 - Define the Private Resource
A private resource represents an internal application or service you want to protect with Secure Access. You specify its network address, which ports and protocols it uses, and how users or networks can reach it (via client-based ZTNA, network tunnels, or both).
resource "ciscosecureaccess_private_resource" "internal_app" { name = "Inventory Management System" description = "Internal inventory app - managed by Terraform" access_types = ["network"] # Network-based access via tunnel addresses = [{ addresses = ["10.100.50.10/32"] traffic_selector = [ { ports = "443", protocol = "http/https" } ] }]}
Step 6 - Define the Access Policy
An access policy is the rule that ties everything together. It defines who or what (the source) can reach which resources (the destination), and whether to allow or block that access. This is where you enforce Zero Trust principles. This policy grants access to the private resource only from the branch office tunnel—no other source can reach it.
resource "ciscosecureaccess_access_policy" "branch_to_app" { name = "Seattle Branch to Inventory App" description = "Allow Seattle branch office access to inventory system" action = "allow" enabled = true log_level = "LOG_ALL" source_ids = [ciscosecureaccess_network_tunnel_group.branch_office.id] private_resource_ids = [ciscosecureaccess_private_resource.internal_app.id]}
terraform init terraform plan terraform apply
That's it. You've created a network tunnel for your branch office, defined a private application, and locked down access so only traffic from that tunnel can reach the app — all in a few lines of code!
Note the tunnel status shows disconnected because the tunnel must be configured on the peer side.
Terraform provides a quick and easy way to build and destroy your CSA infrastructure.
What else can you manage with Terraform? In addition to the resources highlighted in this blog post - as of this write up - you can provision Destination Lists and Resource Connectors using Terraform. Check out the official documentation here.
Please reach out with any questions or comments about using IaC to configure and secure your resources.