Lab Task 4.3 - Scenario 1 (Better together)
Step 1: Catalyst Center
Create a new file named 03_deploy-template.yml in the dnac/playbooks directory.
1---
2- hosts: dnac_servers
3 gather_facts: false
4 connection: local
5 tasks:
6 - name: Get timestamp from the system
7 shell: "date +%Y-%m-%d%H-%M-%S"
8 register: tstamp
9
10 - debug: var=tstamp.stdout
11
12 - name: Set sentitive variables
13 ansible.builtin.set_fact:
14 api_token: "{{ lookup('env', 'api_token') }}"
15
16 - name: Get device details from NetBox
17 uri:
18 url: "http://198.18.134.22:9000/api/dcim/devices/?name={{ device_name }}"
19 method: GET
20 return_content: yes
21 headers:
22 accept: "application/json"
23 Authorization: "Token {{ api_token }}"
24 register: device
25
26 - debug: var=device
27
28 - name: Extract primary IP address without netmask
29 set_fact:
30 primary_ip: "{{ device.json.results[0].primary_ip4.address.split('/')[0] }}"
31
32 - name: Debug primary IP address
33 debug:
34 msg: "Primary IP address: {{ primary_ip }}"
35
36 - name: Get intended state from NetBox based on device ID
37 uri:
38 url: "http://198.18.134.22:9000/api/dcim/devices/{{ device.json.results.0['id'] }}/render-config/"
39 method: POST
40 return_content: yes
41 headers:
42 accept: "application/json"
43 Authorization: "Token {{ api_token }}"
44 register: intended_config
45
46 - debug: var=intended_config
47
48 - name: Extract and format the config content
49 ansible.builtin.set_fact:
50 formatted_config: "{{ intended_config.json.content | replace('\\n', '\n') }}"
51
52 - name: Debug formatted config
53 debug:
54 var: formatted_config
55
56 #
57 # Project Creation
58 #
59 - name: Create dayN-Templates project
60 cisco.dnac.configuration_template_project:
61 state: present
62 createTime: 0
63 description: string
64 name: Templates-by-Ansible
65 register: configuration_template_project_result
66
67 - name: Set Task ID
68 ansible.builtin.set_fact:
69 project_task_id: "{{ configuration_template_project_result.dnac_response.response.taskId }}"
70
71 - debug: var=project_task_id
72
73 - name: Pause
74 pause:
75 seconds: 5
76
77 - name: Get project task by id
78 cisco.dnac.task_info:
79 taskId: "{{ project_task_id }}"
80 register: result_project_task_id
81
82 - debug: var=result_project_task_id
83
84 - name: Set project ID
85 ansible.builtin.set_fact:
86 project_id: "{{ result_project_task_id.dnac_response.response.data }}"
87
88 - debug: var=project_id
89 #
90 # Template Info / Config Section
91 #
92 - name: Create an configuration_template_project
93 cisco.dnac.configuration_template_create:
94 name: "{{ device_name }}_{{ tstamp.stdout }}"
95 templateContent: '{{ formatted_config }}'
96 language: "VELOCITY"
97 projectName: "Onboarding Configuration"
98 deviceTypes:
99 - productFamily: "Routers"
100 projectId: "{{ project_id }}"
101 softwareType: "IOS-XE"
102 softwareVariant: "XE"
103 register: configuration_template_project_result
104
105 - debug: var=configuration_template_project_result
106
107 - name: Set Task ID
108 ansible.builtin.set_fact:
109 task_id: "{{ configuration_template_project_result.dnac_response.response.taskId }}"
110
111 - debug: var=task_id
112
113 - name: Pause
114 pause:
115 seconds: 30
116
117 - name: Get Task by id
118 cisco.dnac.task_info:
119 taskId: "{{ task_id }}"
120 register: result_task_id
121
122 - name: Set Template ID
123 ansible.builtin.set_fact:
124 template_id: "{{ (result_task_id.dnac_response.response.data | from_json).templateId }}"
125
126 - debug: var=template_id
127
128 - name: Create Versioning
129 cisco.dnac.configuration_template_version_create:
130 comments: "COMMITTED"
131 templateId: "{{ template_id }}"
132 register: template_version_result
133
134 #
135 # Get the device infos
136 #
137 - name: Get Network Device information
138 cisco.dnac.network_device_info:
139 managementIpAddress: [ "{{ primary_ip }}" ]
140 register: network_device_info_result
141
142 - debug: var=network_device_info_result
143
144 - name: Set Device ID
145 ansible.builtin.set_fact:
146 device_id: "{{ network_device_info_result.dnac_response.response[0].id }}"
147
148 - debug: var=device_id
149
150 #
151 # Deploy template to device
152 #
153 - name: Deploy dayN template on device
154 cisco.dnac.configuration_template_deploy:
155 forcePushTemplate: true
156 mainTemplateId: "{{ template_id }}"
157 targetInfo:
158 - id: "{{ device_id }}"
159 type: "MANAGED_DEVICE_UUID"
160 templateId: "{{ template_id }}"
161 register: template_deploy_result
162
163 - debug: var=template_deploy_result03_deploy-template.yml
Here is a breakdown of the playbook:
- Hosts and Execution Scope: Runs on
dnac_serverswith a local connection and no fact gathering. - Get Timestamp: Retrieves the current timestamp to use in template naming.
- Set API Token: Stores
api_tokenby retrieving it from environment variables. - Fetch Device Details: Queries NetBox for details of the
catalyst_switch. - Debug Device Data: Outputs the retrieved device details.
- Retrieve Intended Configuration: Requests NetBox to generate the intended configuration using the device ID.
- Debug Intended Configuration: Displays the retrieved intended state.
- Format Configuration: Replaces newline escape sequences in the intended configuration for proper formatting.
- Debug Formatted Configuration: Prints the formatted configuration.
Project Creation
- Create Project: Creates a Cisco DNA Center (DNAC) configuration template project named
Templates-by-Ansible. - Set Task ID: Extracts and stores the project creation task ID.
- Pause Execution: Waits 5 seconds for the project creation process to complete.
- Retrieve Project Task Status: Checks the status of the project creation task using the stored task ID.
- Set Project ID: Extracts and stores the project ID from the task result.
Template Creation
- Create Configuration Template: Defines a DNAC configuration template using
formatted_config, assigns it to theOnboarding Configurationproject, and associates it withIOS-XEdevices. - Set Task ID: Stores the task ID for tracking the template creation process.
- Pause Execution: Waits 30 seconds for template creation.
- Retrieve Task Status: Checks the status of the template creation task.
- Set Template ID: Extracts and stores the created template ID.
- Create Template Versioning: Commits the created template in DNAC.
Device Information Retrieval
- Get Device Information: Queries DNAC for details of the network device with IP
10.100.80.11. - Set Device ID: Extracts and stores the device ID from the DNAC response.
Template Deployment
- Deploy Configuration Template: Applies the created DNAC template to the device, forcing the push to the specified
device_id. - Debug Deployment Status: Outputs the result of the template deployment process.
Step 2: Nexus Dashboard
Create the file 02_deploy-template.yml in ndfc/playbooks:
02_deploy-template.yml
1---
2- hosts: ndfc_servers
3 gather_facts: false
4 tasks:
5 - name: Set sentitive variables
6 ansible.builtin.set_fact:
7 api_token: "{{ lookup('env', 'api_token') }}"
8
9 - name: Get device details from NetBox
10 uri:
11 url: "http://198.18.134.22:9000/api/dcim/devices/?name={{ nexus_switch }}"
12 method: GET
13 return_content: yes
14 headers:
15 accept: "application/json"
16 Authorization: "Token {{ api_token }}"
17 register: device
18
19 - debug: var=device
20
21 - name: Extract primary IP address without netmask
22 set_fact:
23 primary_ip: "{{ device.json.results[0].primary_ip4.address.split('/')[0] }}"
24
25 - name: Debug primary IP address
26 debug:
27 msg: "Primary IP address: {{ primary_ip }}"
28
29 - name: Get intended state from NetBox based on device ID
30 uri:
31 url: "http://198.18.134.22:9000/api/dcim/devices/{{ device.json.results.0['id'] }}/render-config/"
32 method: POST
33 return_content: yes
34 headers:
35 accept: "application/json"
36 Authorization: "Token {{ api_token }}"
37 register: intended_config
38
39 - debug: var=intended_config
40
41 - name: Extract and format the config content
42 ansible.builtin.set_fact:
43 formatted_config: "{{ intended_config.json.content | replace('\\n', '\n') }}"
44
45 - name: Debug formatted config
46 debug:
47 var: formatted_config
48
49 - name: Create policy including required variables
50 cisco.dcnm.dcnm_policy:
51 fabric: "{{ fabric_name }}"
52 state: merged
53 deploy: true
54 config:
55 - name: switch_freeform
56 create_additional_policy: true
57 priority: 400
58 description: ANSIBLE_DEPLOYMENT
59 policy_vars:
60 CONF: "{{ formatted_config }}"
61 - switch:
62 - ip: "{{ primary_ip }}"Here is a breakdown of what each part does:
- Hosts and Execution Scope: Runs on
ndfc_serverswithout gathering system facts. - Set API Token: Stores
api_tokenby retrieving it from environment variables. - Fetch Device Details from NetBox: Queries the NetBox API to get device details based on the
nexus_switchvariable. - Debug Device Data: Prints the retrieved device details for verification.
- Retrieve Intended Configuration: Requests NetBox to generate the intended device configuration using the device ID.
- Debug Intended Configuration: Displays the retrieved intended state for validation.
- Format Configuration: Processes the intended configuration by replacing escaped newlines with actual newlines.
- Debug Formatted Configuration: Outputs the formatted configuration for troubleshooting.
- Create and Deploy Policy: Uses the
cisco.dcnm.dcnm_policymodule to apply the formatted configuration to the target switch (10.100.80.10) within the specifiedfabric_name. The policy is merged and deployed automatically.
Step 3: SCC deployment
Create the file 01_configure-ftd.yml in scc/playbooks:
01_configure-ftd.yml
1- name: Retrieve Device and update physical interfaces configuration, add static routes, and deploy configuration
2 hosts: localhost
3 gather_facts: no
4 vars:
5 fmc_url: "https://cisco-cbeye--sc6nui.app.eu.cdo.cisco.com"
6 domain_uuid: "e276abec-e0f2-11e3-8169-6d9ed49b625f"
7 api_token: "{{ lookup('env', 'api_token') }}"
8 tasks:
9 - name: Get list of devices from FMC
10 uri:
11 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/devices/devicerecords?offset=0&limit=50"
12 method: GET
13 headers:
14 Authorization: "Bearer {{ api_token }}"
15 Accept: "application/json"
16 return_content: yes
17 validate_certs: no
18 register: fmc_devices
19
20 - name: Extract device id using jq
21 shell: |
22 echo '{{ fmc_devices.content }}' | jq -r '.items | select(.name=="{{ target_pod }}") | .id'
23 register: device_id_output
24 changed_when: false
25
26 - name: Debug device id
27 debug:
28 msg: "Device id for {{ target_pod }} is {{ device_id_output.stdout }}"
29
30 - name: Get physical interfaces for the device (container UUID)
31
32 uri:
33 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/devices/devicerecords/{{ device_id_output.stdout }}/physicalinterfaces"
34 method: GET
35 headers:
36 Authorization: "Bearer {{ api_token }}"
37 Accept: "application/json"
38 return_content: yes
39 validate_certs: no
40 register: phys_int_resp
41
42 - name: Extract GigabitEthernet0/0 id using jq
43 shell: |
44 echo '{{ phys_int_resp.content }}' | jq -r '.items | select(.name=="GigabitEthernet0/0") | .id'
45 register: gig0_0_id
46 changed_when: false
47
48 - name: Extract GigabitEthernet0/1 id using jq
49 shell: |
50 echo '{{ phys_int_resp.content }}' | jq -r '.items | select(.name=="GigabitEthernet0/1") | .id'
51 register: gig0_1_id
52 changed_when: false
53
54 - name: Debug physical interface ids
55 debug:
56 msg:
57 - "GigabitEthernet0/0 id: {{ gig0_0_id.stdout }}"
58 - "GigabitEthernet0/1 id: {{ gig0_1_id.stdout }}"
59
60 - name: Build interface list for update
61 set_fact:
62 interfaces_to_update:
63 - name: "GigabitEthernet0/0"
64 ifname: "Datacenter"
65 id: "{{ gig0_0_id.stdout }}"
66 ip_address: "172.16.1.2"
67 ip_netmask: "30"
68 - name: "GigabitEthernet0/1"
69 ifname: "Branch"
70 id: "{{ gig0_1_id.stdout }}"
71 ip_address: "172.16.0.2"
72 ip_netmask: "30"
73
74 - name: Update configuration of each physical interface via PUT
75 uri:
76 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/devices/devicerecords/{{ device_id_output.stdout }}/physicalinterfaces/{{ item.id }}"
77 method: PUT
78 headers:
79 Authorization: "Bearer {{ api_token }}"
80 Accept: "application/json"
81 Content-Type: "application/json"
82 body: "{{ {
83 'type': 'PhysicalInterface',
84 'nveOnly': false,
85 'hardware': { 'speed': 'AUTO', 'duplex': 'AUTO' },
86 'mode': 'NONE',
87 'id': item.id,
88 'MTU': 1500,
89 'enabled': true,
90 'name': item.name,
91 'ifname': item.ifname,
92 'managementOnly': false,
93 'ipv4': { 'static': { 'address': item.ip_address, 'netmask': item.ip_netmask } }
94 } | to_json }}"
95 status_code: 200
96 return_content: yes
97 validate_certs: no
98 loop: "{{ interfaces_to_update }}"
99 register: interface_update_results
100
101 - name: Show update results for all interfaces
102 debug:
103 var: interface_update_results
104
105 - name: Add static route for interface "Branch"
106 uri:
107 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/devices/devicerecords/{{ device_id_output.stdout }}/routing/ipv4staticroutes"
108 method: POST
109 headers:
110 Authorization: "Bearer {{ api_token }}"
111 Accept: "application/json"
112 Content-Type: "application/json"
113 body: |
114 {
115 "interfaceName": "Branch",
116 "selectedNetworks": [
117 {
118 "type": "Network",
119 "overridable": false,
120 "id": "06E6F43C-465D-0ed3-0000-004294985130",
121 "name": "Branch"
122 }
123 ],
124 "gateway": {
125 "object": {
126 "type": "Host",
127 "overridable": false,
128 "id": "06E6F43C-465D-0ed3-0000-004294985170",
129 "name": "branch-catalyst-host"
130 }
131 },
132 "metricValue": 1,
133 "type": "IPv4StaticRoute",
134 "isTunneled": false
135 }
136 status_code: 201
137 return_content: yes
138 validate_certs: no
139 register: static_route_branch
140 ignore_errors: true
141
142 - name: Show static route creation result for Branch
143 debug:
144 msg: "Static route for Branch created: {{ static_route_branch.content }}"
145
146 - name: Add static route for interface "Datacenter"
147 uri:
148 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/devices/devicerecords/{{ device_id_output.stdout }}/routing/ipv4staticroutes"
149 method: POST
150 headers:
151 Authorization: "Bearer {{ api_token }}"
152 Accept: "application/json"
153 Content-Type: "application/json"
154 body: |
155 {
156 "interfaceName": "Datacenter",
157 "selectedNetworks": [
158 {
159 "type": "Network",
160 "overridable": false,
161 "id": "06E6F43C-465D-0ed3-0000-004294985039",
162 "name": "Datacenter"
163 }
164 ],
165 "gateway": {
166 "object": {
167 "type": "Host",
168 "overridable": false,
169 "id": "06E6F43C-465D-0ed3-0000-004294985085",
170 "name": "datacenter-nexus-host"
171 }
172 },
173 "metricValue": 1,
174 "type": "IPv4StaticRoute",
175 "isTunneled": false,
176 "id": "06E6F43C-465D-0ed3-0000-004294985195"
177 }
178 status_code: 201
179 return_content: yes
180 validate_certs: no
181 register: static_route_datacenter
182 ignore_errors: true
183
184 - name: Show static route creation result for Datacenter
185 debug:
186 msg: "Static route for Datacenter created: {{ static_route_datacenter.content }}"
187
188 - name: Get current UTC timestamp in epoch (milliseconds)
189
190 shell: "date -u +%s%3N"
191 register: epoch_timestamp_ms
192 changed_when: false
193
194 - name: Store UTC epoch timestamp as a variable
195 set_fact:
196 epoch_timestamp_ms: "{{ epoch_timestamp_ms.stdout | int }}"
197
198 - name: Sleep for 20 seconds before deployment
199 pause:
200 seconds: 20
201
202 - name: Deploy configuration
203 uri:
204 url: "{{ fmc_url }}/api/fmc_config/v1/domain/{{ domain_uuid }}/deployment/deploymentrequests"
205 method: POST
206 headers:
207 Authorization: "Bearer {{ api_token }}"
208 Accept: "application/json"
209 Content-Type: "application/json"
210 body: "{{ {
211 'type': 'DeploymentRequest',
212 'version': epoch_timestamp_ms,
213 'forceDeploy': true,
214 'ignoreWarning': true,
215 'deviceList': [ device_id_output.stdout ],
216 'deploymentNote': 'GITLAB_PUSH'
217 } | to_json }}"
218 status_code: 202
219 return_content: yes
220 validate_certs: no
221 register: deployment_result
222
223 - name: Show deployment result
224 debug:
225 msg: "Deployment result: {{ deployment_result.content }}"This Ansible playbook interacts with the Cloud-Delivered Firepower Management Center (cdFMC) to configure a security device by retrieving its details, updating physical interfaces, adding static routes, and deploying the configuration.
Retrieve Device Information:
- Queries FMC to get a list of managed devices.
- Extracts the device ID of the target device (pod01).
- Retrieve and Configure Physical Interfaces:
- Fetches the physical interfaces of the target device.
- Extracts interface IDs for GigabitEthernet0/0 and GigabitEthernet0/1.
- Updates the interfaces with new IP addresses, netmasks, and settings.
Add Static Routes:
- Adds a static route for the Branch interface to reach a specific network via a predefined gateway.
- Adds a static route for the Datacenter interface to ensure traffic is properly routed.
Deploy Configuration:
- Sends a deployment request to FMC to apply all the changes to the target device.
Debugging and Validation:
- Prints retrieved device details, interface updates, static route creation responses, and the final deployment result.
Step 4: Lets put everything together
As usual 😉, update the pipeline file:
.gitlab-ci.yml
ATTENTION! If you are assigned to group 2, please make sure that you name your firewall target_pod=pod01-sgn This needs to be replaced in the pipeline file as an extra variable when you call the playbook.
1stages:
2 - deploy_config_dnac
3 - deploy_config_ndfc
4 - deploy_config_scc
5
6deploy_config_dnac:
7 stage: deploy_config_dnac
8 tags:
9 - docker-runner
10 image: cbeye592/ltrato-2600:dnac
11 id_tokens:
12 VAULT_ID_TOKEN:
13 aud: https://198.18.133.99:8200
14 secrets:
15 DNAC_HOST:
16 vault: DNAC/DNAC_HOST@pod01
17 file: false
18 token: $VAULT_ID_TOKEN
19 DNAC_VERIFY:
20 vault: DNAC/DNAC_VERIFY@pod01
21 file: false
22 token: $VAULT_ID_TOKEN
23 DNAC_USERNAME:
24 vault: DNAC/DNAC_USERNAME@pod01
25 file: false
26 token: $VAULT_ID_TOKEN
27 DNAC_PASSWORD:
28 vault: DNAC/DNAC_PASSWORD@pod01
29 file: false
30 token: $VAULT_ID_TOKEN
31 api_token:
32 vault: NETBOX/api_token@pod01
33 file: false
34 token: $VAULT_ID_TOKEN
35 before_script:
36 - source /root/ansible/bin/activate
37 - chmod -R 700 dnac
38 - cd dnac
39 script:
40 - ansible-playbook -i hosts playbooks/03_deploy-template.yml --extra-vars "device_name=POD01-CAT8KV-01"
41
42deploy_config_ndfc:
43 stage: deploy_config_ndfc
44 tags:
45 - docker-runner
46 image: cbeye592/ltrato-2600:ndfc
47 id_tokens:
48 VAULT_ID_TOKEN:
49 aud: https://198.18.133.99:8200
50 secrets:
51 ansible_user:
52 vault: NDFC/ansible_user@pod01
53 file: false
54 token: $VAULT_ID_TOKEN
55 ansible_password:
56 vault: NDFC/ansible_password@pod01
57 file: false
58 token: $VAULT_ID_TOKEN
59 DEVICE_USER:
60 vault: NDFC/DEVICE_USER@pod01
61 file: false
62 token: $VAULT_ID_TOKEN
63 DEVICE_PASSWORD:
64 vault: NDFC/DEVICE_PASSWORD@pod01
65 file: false
66 token: $VAULT_ID_TOKEN
67 api_token:
68 vault: NETBOX/api_token@pod01
69 file: false
70 token: $VAULT_ID_TOKEN
71 before_script:
72 - source /root/ansible/bin/activate
73 - chmod -R 700 ndfc
74 - cd ndfc
75 - echo "" >> hosts
76 - echo "ansible_user=$ansible_user" >> hosts
77 - echo "ansible_password=$ansible_password" >> hosts
78 script:
79 - "sed -i 's/\"username\": \"\",/\"username\": \"'${DEVICE_USER}'\",/g' data/01_add-devices.yaml"
80 - "sed -i 's/\"password\": \"\",/\"password\": \"'${DEVICE_PASSWORD}'\",/g' data/01_add-devices.yaml"
81 - ansible-playbook playbooks/02_deploy-template.yml --extra-vars "nexus_switch=POD01-N9KV-01" --extra-vars "fabric_name=pod01"
82
83configure_ftd_devices:
84 stage: deploy_config_scc
85 tags:
86 - docker-runner
87 image: cbeye592/ltrato-2600:scc
88 id_tokens:
89 VAULT_ID_TOKEN:
90 aud: https://198.18.133.99:8200
91 secrets:
92 api_token:
93 vault: SCC/api_token@pod01
94 file: false
95 token: $VAULT_ID_TOKEN
96 cdo_host:
97 vault: SCC/cdo_host@pod01
98 file: false
99 token: $VAULT_ID_TOKEN
100 before_script:
101 - source /root/ansible/bin/activate
102 - chmod -R 700 ndfc
103 - cd scc
104 script:
105 - ansible-playbook playbooks/01_configure-ftd.yml --extra-vars "target_pod=pod01-apjc"Validation
Catalyst Center
Check the created templates in Catalyst Center:
NDFC
Validate the create templates:
SCC
Check the configuration in SCC:
Until the config is completely pushed to the devices, it can take up to 5-10 minutes.
CML validation
Login to CML, open the console of datacenter-client-01, and run the following commands:
ping 10.0.0.10
traceroute 10.0.0.10