This update is where I have managed to get all the pieces together to form a complete Infrastructure as Code pipeline for the self-service firewall rules on the Cisco ASA. The Go firewall project incorporates the ASA API Terraform project and a GitLab pipeline.
The flow of the application in the current prototype is as follows;
- User goes to the firewall portal web app
- A new firewall rule is created
- The firewall rule is stored inside the Postgres DB
- Inside the ASA Terraform repo a branch named
FirewallRules
is switched to or created from themain
branch - The firewall rule in Terraform format is written to the
main.tf
file. - The
main.tf
file is then committed and pushed to the ASA Terraform repo in GitLab - The GitLab pipeline is skipped from running automatically, allowing for numerous firewall rules to be created
- The GitLab pipeline can be manually run for the branch
FirewallRules
- Running the pipeline collects all the new Terraform firewall rules and will perform the operations; fmt, validate, build automatically.
- The administrator of the repo can review the build and ensure that the config is acceptable and can create a Merge Request to commit the changes from the
FirewallRules
branch to themain
branch. This is a manual operation in the pipeline namedmerge_main
. - When the new Merge Request is made, this needs to be manually merged, giving another opportunity to compare the configuration changes.
- Once merged, the pipeline is run again (fmt, validate, build) and then a manual button to deploy the new configuration to the ASA firewall.
- The process can be repeated.
The Process Illustrated
1-2. User Creates New Rule in Portal
The firewall portal is running locally on my machine at http://127.0.0.1:8010/
3. Firewall Rules Stored in Postgres DB
Database running locally contains all the firewall rules
3-6. Git Commands to Create New Branch and Push
Inside the ASA Terraform repo a branch named FirewallRules
is switched to or created from the main
branch
0 1 2 3 4 |
stefankelly@Stefans-MacBook-Pro asaterraform % git branch * FirewallRules main |
0 1 2 3 4 5 6 7 8 9 |
resource "ciscoasa_access_in_rules" "_10_10_10_144_192_168_2_5_8000" { interface = "OUTSIDE" rule { source = "10.10.10.144" destination = "192.168.2.5" destination_service = "tcp/8000" } } |
7. The GitLab Pipeline is Skipped
The commit message contains [ci skip]
, which GitLab uses to skip the automatic pipeline run.
0 1 2 3 4 5 6 7 |
c1859a4 (HEAD -> FirewallRules, origin/FirewallRules) Firewall Rule Update [ci skip] 7f57981 Firewall Rule Update [ci skip] 68b1a3c Firewall Rule Update [ci skip] 463a816 Firewall Rule Update [ci skip] aaac41d Firewall Rule Update [ci skip] ead19cd Firewall Rule Update [ci skip] |
8-10. Manually Run Pipeline for FirewallRules Branch & Create MR
The pipeline must be run for all of the new firewall rules. The pipeline is run against the branch FirewallRules
.
The Merge Request is created and can be reviewed and merged into main
11-12. Pipeline to Deploy New Rules
When the new firewall rule config has been merged into the main
branch, automatically a pipeline is started. The admin may review the build
step and then manually deploy the new rule config to the firewalls.
Terraform moves around some of the configuration, so it looks in the plan as if more is changing than really is. All that really counts is that there are 0 destroy and 1 add.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create ~ update in-place Terraform will perform the following actions: # ciscoasa_access_in_rules._10_10_10_144_172_16_1_5_23 will be updated in-place ~ resource "ciscoasa_access_in_rules" "_10_10_10_144_172_16_1_5_23" { id = "OUTSIDE" # (3 unchanged attributes hidden) ~ rule { ~ destination = "172.16.1.5/32" -> "172.16.1.5" id = "2607959980" ~ source = "10.10.10.144/32" -> "10.10.10.144" # (3 unchanged attributes hidden) } } # ciscoasa_access_in_rules._10_10_10_144_172_16_1_5_8000 will be updated in-place ~ resource "ciscoasa_access_in_rules" "_10_10_10_144_172_16_1_5_8000" { id = "OUTSIDE" # (3 unchanged attributes hidden) ~ rule { ~ destination = "172.16.1.5/32" -> "172.16.1.5" id = "3444149839" ~ source = "10.10.10.144/32" -> "10.10.10.144" # (3 unchanged attributes hidden) } } # ciscoasa_access_in_rules._10_10_10_144_172_16_1_5_8001 will be updated in-place ~ resource "ciscoasa_access_in_rules" "_10_10_10_144_172_16_1_5_8001" { id = "OUTSIDE" # (3 unchanged attributes hidden) ~ rule { ~ destination = "172.16.1.5/32" -> "172.16.1.5" id = "2743468692" ~ source = "10.10.10.144/32" -> "10.10.10.144" # (3 unchanged attributes hidden) } } # ciscoasa_access_in_rules._10_10_10_144_172_16_1_5_8002 will be updated in-place ~ resource "ciscoasa_access_in_rules" "_10_10_10_144_172_16_1_5_8002" { id = "OUTSIDE" # (3 unchanged attributes hidden) ~ rule { ~ destination = "172.16.1.5/32" -> "172.16.1.5" id = "1448648150" ~ source = "10.10.10.144/32" -> "10.10.10.144" # (3 unchanged attributes hidden) } } # ciscoasa_access_in_rules._10_10_10_144_192_168_1_5_8000 will be updated in-place ~ resource "ciscoasa_access_in_rules" "_10_10_10_144_192_168_1_5_8000" { id = "OUTSIDE" # (2 unchanged attributes hidden) ~ rule { ~ destination = "192.168.1.5/32" -> "192.168.1.5" id = "420772709" ~ source = "10.10.10.144/32" -> "10.10.10.144" # (3 unchanged attributes hidden) } } # ciscoasa_access_in_rules._10_10_10_144_192_168_2_5_8000 will be created + resource "ciscoasa_access_in_rules" "_10_10_10_144_192_168_2_5_8000" { + id = (known after apply) + interface = "OUTSIDE" + last_updated = (known after apply) + managed = false + rule { + active = true + destination = "192.168.2.5" + destination_service = "tcp/8000" + id = (known after apply) + permit = true + source = "10.10.10.144" } } # ciscoasa_access_in_rules.inside_dmz_ssh will be updated in-place ~ resource "ciscoasa_access_in_rules" "inside_dmz_ssh" { id = "INSIDE" # (3 unchanged attributes hidden) ~ rule { id = "3592811573" ~ source = "192.168.1.5/32" -> "192.168.1.5" # (4 unchanged attributes hidden) } } # ciscoasa_interface_physical.gig_outside will be updated in-place ~ resource "ciscoasa_interface_physical" "gig_outside" { id = "GigabitEthernet0_API_SLASH_0" name = "OUTSIDE" # (9 unchanged attributes hidden) - ip_address { - dhcp = [ - { - dhcp_broadcast = true - dhcp_client = [ - { - metric = 1 - primary_track_id = -1 - set_default_route = true - sla_tracking_settings = [] - tracking_enabled = false }, ] - dhcp_option_using_mac = false }, ] -> null - static = [] -> null } + ip_address { + dhcp = [ + { + dhcp_broadcast = true + dhcp_client = [ + { + metric = 1 + primary_track_id = 6 + set_default_route = true + sla_tracking_settings = (known after apply) + tracking_enabled = false }, ] + dhcp_option_using_mac = false }, ] + static = [] } # (1 unchanged block hidden) } # ciscoasa_network_service.tcp_443 will be updated in-place ~ resource "ciscoasa_network_service" "tcp_443" { id = "TCP_443" name = "TCP_443" ~ value = "tcp/https" -> "tcp/443" } # ciscoasa_network_service.tcp_80 will be updated in-place ~ resource "ciscoasa_network_service" "tcp_80" { id = "TCP_80" name = "TCP_80" ~ value = "tcp/http" -> "tcp/80" } Plan: 1 to add, 9 to change, 0 to destroy. |
ASA Rules present through the Firewall Portal App and some GitLab management.
0 1 2 3 4 5 6 7 8 9 |
ASA1# sh run access-list OUTSIDE_access_in access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 172.16.1.5 eq telnet access-list OUTSIDE_access_in extended permit object-group WEB_PORTS any4 object-group ALL_DMZ_SERVERS access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 172.16.1.5 eq 8001 access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 172.16.1.5 eq 8002 access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 172.16.1.5 eq 8000 access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 192.168.1.5 eq 8000 access-list OUTSIDE_access_in extended permit tcp host 10.10.10.144 host 192.168.2.5 eq 8000 |
Future Improvements
- The ASA Terraform repo must already be downloaded onto the same host as where the Go firewall portal is running from
- The host must have the SSH private key to interact with the ASA Terraform GitLab repo
- The web application must wait for the Git commands to run before it displays the firewall summary page to indicate that the firewall addition has been a success.
- There are a couple of unnecessary steps in the pipeline
Problems
- GitLab variables needed to be unprotected so they can be used on my
FirewallRules
unprotected branch - GitLab runner for the project needed to have the shared
- GNS3 Cisco ASA required 4096MB of RAM, up from 2048MB to avoid crashes