Skip to content

First Steps

This guide walks you through configuring Cisco ISE using Network as Code — starting from a single YAML file and a one-line Terraform module, then progressively introducing ISE’s identity, network access, and TrustSec constructs that let you manage ISE’s full configuration as data.

Prerequisites:

  1. The ISE REST APIs must be enabled before Terraform can communicate with your ISE instance. Log in to the ISE admin portal and:

    • Navigate to Administration -> Settings -> API Settings.
    • Under API Service Settings, enable ERS (Read/Write) and Open API (Read/Write).
    • Click Save.
  2. The ISE provider authenticates using environment variables. Set the following in your terminal:

    Terminal window
    export ISE_URL=https://10.1.1.100
    export ISE_USERNAME=admin
    export ISE_PASSWORD=password
  3. In a new project directory, create a file named ise.nac.yaml with the following content. Replace the ip with the management address of a device you want ISE to authenticate:

    ise:
    network_resources:
    network_devices:
    - name: Router1
    ips:
    - ip: 10.1.2.3
    radius:
    shared_secret: Cisco123

    This file describes a single network device — a RADIUS client that ISE will authenticate. The structure mirrors the sections you would otherwise click through in the ISE admin portal.

  4. In the same directory, create a file named main.tf:

    module "ise" {
    source = "netascode/nac-ise/ise"
    version = "0.3.0"
    yaml_files = ["ise.nac.yaml"]
    }

    This is the only Terraform configuration required. The nac-ise module reads the YAML file and maps its contents to the underlying ISE provider resources automatically.

  5. Run the following command to download the provider and module:

    Terminal window
    terraform init
  6. Preview and apply your changes:

    Terminal window
    terraform apply

    Review the plan and type yes to confirm. Terraform pushes the configuration to ISE via its REST APIs.

Congratulations! You have created your first ISE policy object using Network as Code. The sections below show how to expand this foundation into ISE’s other configuration areas — network device groups, identity management, network access policy, and TrustSec.

All of the sections below build on the same ise.nac.yaml file — there is only ever one file, and every top-level key (network_resources, identity_management, network_access, trust_sec) lives side by side under the single root ise: key. Each code block below is complete and paste-able as shown:

  • When a block updates a section you already have (like network_resources below), it shows that section in full — replace what you had with it.
  • When a block adds a new section, a # ... comment marks where your existing sections go; only the new section needs to be added to your file, alongside what is already there.

A “Putting It All Together” snippet at the end shows the complete file after all the sections have been added.

Network device groups let you organize devices by role, location, or device type, so policy can reference the group instead of each device individually. This is useful for anything that applies to a class of device (e.g., all devices at a given site sharing the same authorization policy).

Every device belongs to exactly one group in each of ISE’s three built-in top-level categories — Device Type, Location, and IPSEC Device — defaulting to the catch-all All Device Types, All Locations, and Is IPSEC Device groups when not set explicitly. Because Terraform manages network_device_groups as a complete list, you must declare a value for every category the device belongs to; otherwise each apply will keep trying to remove the categories ISE fills in by default, and never converge.

Update the network_resources block in ise.nac.yaml — replacing what you added in step 3 — to add the group definition and reference it (along with the two default categories) from the device, using the full path#name for the nested group:

ise:
network_resources:
network_device_groups:
- name: Routers
path: "All Device Types"
network_devices:
- name: Router1
ips:
- ip: 10.1.2.3
network_device_groups:
- "All Device Types#Routers"
- "All Locations"
- "Is IPSEC Device"
radius:
shared_secret: Cisco123

Router1 now belongs to the Routers group, nested under the built-in All Device Types root. When a device references a nested group, use the same path#name value used to define the group (here, All Device Types#Routers) rather than just the group’s bare name — the bare name only works when referencing a built-in root group directly (e.g. All Device Types on its own). A device can belong to multiple groups, and groups can be nested further by chaining additional #-delimited segments in path.

Identity management covers the users and endpoints ISE authenticates. The simplest resource here is an internal user — an account defined directly in ISE’s local identity store.

identity_management is a new top-level section. Add it to ise.nac.yaml alongside the network_resources block from the previous step:

ise:
# ... network_resources from the previous step ...
identity_management:
internal_users:
- name: jdoe
password: Cisco123
first_name: Jane Doe
change_password: false

This creates a local user that can authenticate against policies referencing the Internal Users identity source, without needing an external Active Directory or LDAP store.

Network access policy determines what a session is allowed to do once it authenticates, and is built from a few connected pieces: policy elements (dACLs, authorization profiles) that define what to grant, and policy sets with authorization rules that define when to grant it.

network_access is another new top-level section. Start by adding it to ise.nac.yaml, alongside network_resources and identity_management, with a downloadable ACL (dACL) that defines the traffic permitted for a matching session:

ise:
# ... network_resources and identity_management from previous steps ...
network_access:
policy_elements:
downloadable_acls:
- name: DACL1
dacl_content: |
permit ip any host 192.168.1.1
deny ip any any

Next, reference the dACL from an authorization profile — the result that gets applied once a rule matches. Add authorization_profiles alongside downloadable_acls, under the same policy_elements key:

ise:
# ... network_resources and identity_management from previous steps ...
network_access:
policy_elements:
downloadable_acls:
- name: DACL1
dacl_content: |
permit ip any host 192.168.1.1
deny ip any any
authorization_profiles:
- name: Employee_Access
access_type: ACCESS_ACCEPT
dacl_name: DACL1

Finally, add a policy set with an authorization rule that matches on a condition and applies the profile. A policy set always needs its own top-level condition to scope which sessions it evaluates — here, wired sessions — in addition to the rule’s own condition, which narrows further to a specific identity group. Add policy_sets as a sibling of policy_elements, under the same network_access key:

ise:
# ... network_resources and identity_management from previous steps ...
network_access:
policy_elements:
downloadable_acls:
- name: DACL1
dacl_content: |
permit ip any host 192.168.1.1
deny ip any any
authorization_profiles:
- name: Employee_Access
access_type: ACCESS_ACCEPT
dacl_name: DACL1
policy_sets:
- name: Employee_Policy_Set
service_name: Default Network Access
condition:
type: ConditionAttributes
dictionary_name: Radius
attribute_name: NAS-Port-Type
operator: equals
attribute_value: Ethernet
authorization_rules:
- name: Employees
state: enabled
condition:
type: ConditionAttributes
dictionary_name: IdentityGroup
attribute_name: Name
operator: equals
attribute_value: "User Identity Groups:Employees"
profiles:
- Employee_Access

Together, these four pieces form a complete policy: for wired sessions (matching the policy set’s condition) where the identity group condition also matches, the rule grants Employee_Access, which pushes DACL1 to restrict what that session can reach. A condition — at either the policy set or rule level — can be a single attribute match (as above), a ConditionAndBlock/ConditionOrBlock combining several, or a ConditionReference to a reusable condition defined under policy_elements.conditions.

TrustSec lets you segment the network using Security Group Tags (SGTs) instead of IP-based ACLs. Sessions and devices are tagged with an SGT, and a policy matrix decides — for each pair of source and destination SGTs — which Security Group ACL (SGACL) is applied to the traffic between them.

trust_sec is the last new top-level section. Add it to ise.nac.yaml, alongside network_resources, identity_management, and network_access, with a Security Group and a matrix entry that uses it:

ise:
# ... network_resources, identity_management, and network_access from previous steps ...
trust_sec:
security_groups:
- name: Sgt97
value: 97
matrix_entries:
- source_sgt: Sgt97
destination_sgt: Sgt97
sgacl_name: Permit IP

This assigns Sgt97 the tag value 97, then adds an entry to the built-in Production matrix permitting traffic between endpoints tagged Sgt97 and each other, using the default Permit IP SGACL. matrix_entries is a shorthand for the Production matrix and works on every ISE version; if you need additional named matrices (e.g. PreProd), use the matrices list instead — see the Matrices data model for details.

After working through the sections above, your ise.nac.yaml should look like this in full:

ise:
network_resources:
network_device_groups:
- name: Routers
path: "All Device Types"
network_devices:
- name: Router1
ips:
- ip: 10.1.2.3
network_device_groups:
- "All Device Types#Routers"
- "All Locations"
- "Is IPSEC Device"
radius:
shared_secret: Cisco123
identity_management:
internal_users:
- name: jdoe
password: Cisco123
first_name: Jane Doe
change_password: false
network_access:
policy_elements:
downloadable_acls:
- name: DACL1
dacl_content: |
permit ip any host 192.168.1.1
deny ip any any
authorization_profiles:
- name: Employee_Access
access_type: ACCESS_ACCEPT
dacl_name: DACL1
policy_sets:
- name: Employee_Policy_Set
service_name: Default Network Access
condition:
type: ConditionAttributes
dictionary_name: Radius
attribute_name: NAS-Port-Type
operator: equals
attribute_value: Ethernet
authorization_rules:
- name: Employees
state: enabled
condition:
type: ConditionAttributes
dictionary_name: IdentityGroup
attribute_name: Name
operator: equals
attribute_value: "User Identity Groups:Employees"
profiles:
- Employee_Access
trust_sec:
security_groups:
- name: Sgt97
value: 97
matrix_entries:
- source_sgt: Sgt97
destination_sgt: Sgt97
sgacl_name: Permit IP

Run terraform apply once more to apply everything in one pass.


You now have the full building blocks of a Network as Code ISE deployment. Explore the ISE Data Models section for the complete list of configurable attributes, and the simple or comprehensive example repositories for reference implementations.