Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d59662
replace capirca with aerleon
sdoiron0330 Apr 5, 2024
00c8bf5
initial pass at aerleon refactor
sdoiron0330 Apr 8, 2024
8290c6d
add change fragments
sdoiron0330 Apr 12, 2024
2502df1
bump aerleon version to latest release
loulecrivain Jun 4, 2025
cb3dcf1
update documentation
loulecrivain Jun 5, 2025
2f2dad1
rename cap_ variables to aerleon
loulecrivain Jun 12, 2025
92c7cf9
update to latest aerleon version
loulecrivain Jun 11, 2025
697a435
add hints for newly supported generators
loulecrivain Jun 11, 2025
5d67328
added configuration checks on startup
johannwagner Jul 11, 2025
67d3c69
add migration documentation for aerleon
loulecrivain Jul 31, 2025
1be8439
also add check for pre-migration custom driver helpers in config
loulecrivain Aug 1, 2025
27e14c2
update supported python versions and lock
loulecrivain Aug 18, 2025
6832032
fix migrations to be compatible with nautobot 2.0.0
loulecrivain Sep 8, 2025
8b36e15
alter policy model to add m2m field to virtualization.virtualmachine
loulecrivain Jun 27, 2025
b28457a
add assigned_virtual_machines field to PolicyTable
loulecrivain Jun 27, 2025
047e5a1
change forms to take policy attachement to VMs into account
loulecrivain Jun 30, 2025
c426c74
also show policies on virtual machines (same as devices)
loulecrivain Jun 30, 2025
5478acd
add virtual machine m2m field to nat policy
loulecrivain Jul 1, 2025
263fe99
added fields to forms and filters for virtualmachines
johannwagner Jul 7, 2025
9b3dfc6
add VMs to aerleon policy generation & views
loulecrivain Jul 2, 2025
d05ba18
Added Changelog fragment for Issue 296
johannwagner Jul 9, 2025
079f86a
adding API for VM trough model
johannwagner Jul 9, 2025
08e9c05
fix migrations
johannwagner Jul 9, 2025
2dba940
adding Tests for VM-based code paths
johannwagner Jul 9, 2025
05db470
fix q filter for AerleonPolicy
johannwagner Jul 9, 2025
87c72b3
fix more linting
johannwagner Jul 9, 2025
98f5a77
add changelog fragment
loulecrivain Jul 9, 2025
83b88de
squash + fix migrations
loulecrivain Jul 9, 2025
6821517
add missing type hints for drf spectacular
loulecrivain Jul 10, 2025
ba01af9
add changelog fragment for #315
loulecrivain Jul 10, 2025
38181d2
fix: Regenerate existing AerleonPolicies
johannwagner Aug 22, 2025
e529707
fix: Added missing changelog fragment
johannwagner Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/236.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Updated all references of `capirca` to `aerleon`.
2 changes: 2 additions & 0 deletions changes/236.dependencies
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Removed capirca dependency.
Added aerleon dependency.
1 change: 1 addition & 0 deletions changes/296.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug where RunCapircaJob crashes if a DynamicGroup exists.
1 change: 1 addition & 0 deletions changes/314.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow for firewall policies to also be set onto Virtual Machines.
1 change: 1 addition & 0 deletions changes/315.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix q search not working for CapircaPolicy models.
1 change: 1 addition & 0 deletions changes/321.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix policy generation job by refreshing of AerleonPolicies if they already exists.
2 changes: 1 addition & 1 deletion development/nautobot_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
# Each key in the dictionary is the name of an installed App and its value is a dictionary of settings.
# PLUGINS_CONFIG = {
# "nautobot_firewall_models": {
# "capirca_os_map": {
# "aerleon_os_map": {
# "cisco_ios": "cisco",
# "arista_eos": "arista",
# }
Expand Down
6 changes: 3 additions & 3 deletions docs/admin/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ PLUGINS_CONFIG = {
"nautobot_firewall_models": {
"default_status": "Active",
"allowed_status": ["Active"], # default shown, `[]` allows all
"capirca_remark_pass": True,
"capirca_os_map": {
"aerleon_remark_pass": True,
"aerleon_os_map": {
"cisco_ios": "cisco",
"arista_eos": "arista",
},
# "custom_capirca": "my.custom.func", # provides ability to overide capirca logic
# "custom_aerleon": "my.custom.func", # provides ability to overide aerleon logic
}
}
```
Expand Down
13 changes: 13 additions & 0 deletions docs/admin/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@ As part of the upgrade for Nautobot 2.0 it is recommended to perform a stepped u
## Upgrade Guide

When a new release comes out it may be necessary to run a migration of the database to account for any changes in the data models used by this app. Execute the command `nautobot-server post-upgrade` within the runtime environment of your Nautobot installation after updating the `nautobot-firewall-models` package via `pip`.

## Migrating from Capirca to Aerleon

Previous versions of nautobot-firewall-app used Capirca as their backend for firewall configuration generation.
Please make sure to follow the steps below while migrating from a version of this app which used Capirca
as a backend (latest is v2.2.2) to the newest version with Aerleon:

* Reinstall dependencies.
* Migrate your `nautobot_config.py`. Configuration keys with `capirca_` prefix have been renamed to `aerleon_`.
* Run database migrations as usual.

There are no breaking changes between Capirca and Aerleon generators. Normally you should not have to change any
firewall rules and/or policies.
44 changes: 22 additions & 22 deletions docs/user/capirca.md → docs/user/aerleon.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Capirca Integration
# Aerleon Integration

The firewall model plugin provides the ability to integrate with Capirca for configuration generation. The authors have applied a very light opinion onto the translation from the firewall models to generate valid policy (`.pol`), network (`.net`), and service (`.svc`) files that are consumed by Capirca.
The firewall model plugin provides the ability to integrate with Aerleon for configuration generation. The authors have applied a very light opinion onto the translation from the firewall models to generate valid policy (`.pol`), network (`.net`), and service (`.svc`) files that are consumed by Aerleon.

FW Model | Capirca
FW Model | Aerleon
--------------------------- | -------
Name (as applicable) | Header - Filter Name
Zone (as applicable) | Header - to-zone/from-zone
Expand All @@ -18,46 +18,46 @@ Service - tcp/udp | *.svc
Service Group | *.svc

!!! note
If this terminology is not familiar, please review the documentation at [Capirca](https://github.com/google/capirca).
If this terminology is not familiar, please review the documentation at [Aerleon](https://github.com/aerleon/aerleon).

## Special Considerations

* Capirca does not allow special characters in a majority of the named objects, as such named objects are modified to the ouput used when processed via a modified (to allow for capital letters) [Django slugify](https://docs.djangoproject.com/en/4.0/ref/utils/#django.utils.text.slugify), this includes:
* Aerleon does not allow special characters in a majority of the named objects, as such named objects are modified to the ouput used when processed via a modified (to allow for capital letters) [Django slugify](https://docs.djangoproject.com/en/4.0/ref/utils/#django.utils.text.slugify), this includes:
* Policy name, policy rule name, address, address group, zone, service, service group
* e.g. Policy called "Allow to Internet" will be called "Allow-to-Internet"
* Note: This **will not change** barring a major update from Capirca
* FQDN and IP Range are not supported by Capirca and will fail if attempting to use those features
* The zone is only used where Capirca supports it, at the time of this writing is only Palo Alto and Juniper SRX
* Note: This **will not change** barring a major update from Aerleon
* FQDN and IP Range are not supported by Aerleon and will fail if attempting to use those features
* The zone is only used where Aerleon supports it, at the time of this writing is only Palo Alto and Juniper SRX
* Zone based firewalls have headers on every rule
* Both Juniper SRX and Palo Alto support using the named zone "all" to represent all zones, but in all cases a zone must be set
* The "Filter Name" is a concatenation of the Policies applied to a given firewall
* Not all firewalls get a filter name, such as zone or direction based firewalls, which require a `chd_` custom field (more details below)
* An object (policy, policy rule, src-addr, dst-addr, etc.) is put into and out of use based on whether or not the status is `active` or as defined in your plugin configuration
* Anything other than active or defined in plugin setting `allowed_status` is ignored
* Removing the last active object in an source-address, destination-address, or service will fail the process to avoid your policy failing open
* The Platform `network_driver` must match the Capirca generator name
* You can optionally provide a mapping in the settings `capirca_os_map` to map from the current platform name, to the Capirca generator name
* The action of "remark" on a rule is not conidered, you can set the setting `capirca_remark_pass=False` if you want it to fail by default rather than silently skipping
* The Platform `network_driver` must match the Aerleon generator name
* You can optionally provide a mapping in the settings `aerleon_os_map` to map from the current platform name, to the Aerleon generator name
* The action of "remark" on a rule is not conidered, you can set the setting `aerleon_remark_pass=False` if you want it to fail by default rather than silently skipping

In addition to the above, you can add to any header or term by creating specific custom fields on the `PolicyRule` data model. They must start with:

* `chd_` - Capirca Header Data - will be applied to the `header` for any given rule.
* `ctd_` - Capirca Term Data - will be applied to the `term` for any given rule.
* `chd_` - Aerleon Header Data - will be applied to the `header` for any given rule (`chd_` name comes from previous versions of this app using Capirca, kept for compatibility).
* `ctd_` - Aerleon Term Data - will be applied to the `term` for any given rule (same comment than before).

The process is to create a custom field, such as `ctd_pan-application`, this will be applied to the PolicyRule as you describe. This can become problematic if you share the model for multiple firewall OSs. This can be conditionally applied via a custom field to the `Platform` model. This custom field **must** be named `capirca_allow` and be of type JSON and be a single list. For each OS defined by the platform, you can allow that custom field to populate. This allows you to use the same model, and not let the custom fields for one OS conflict with another OS.
The process is to create a custom field, such as `ctd_pan-application`, this will be applied to the PolicyRule as you describe. This can become problematic if you share the model for multiple firewall OSs. This can be conditionally applied via a custom field to the `Platform` model. This custom field **must** be named `aerleon_allow` and be of type JSON and be a single list. For each OS defined by the platform, you can allow that custom field to populate. This allows you to use the same model, and not let the custom fields for one OS conflict with another OS.

```python
capirca_allow = ['ctd_pan-application', 'ctd_expiration']
aerleon_allow = ['ctd_pan-application', 'ctd_expiration']
```

> Note: This is pseudo-code and is technically the custom_field called `capirca_allow` that has the data `["ctd_pan-application", "ctd_expiration"]` in this example.
> Note: This is pseudo-code and is technically the custom_field called `aerleon_allow` that has the data `["ctd_pan-application", "ctd_expiration"]` in this example.

As previously mentioned, there is only a small opinion that is applied from the translation between the model and Capirca. That being said, Capirca has an opinion on how rules and objects are deployed, and within this project there is no consideration for how that may not align with anyone's intention on how Capirca should work. All such considerations should be referred to the Capirca project. There is no intention to modify the output that Capirca creates **in any situation** within this plugin.
As previously mentioned, there is only a small opinion that is applied from the translation between the model and Aerleon. That being said, Aerleon has an opinion on how rules and objects are deployed, and within this project there is no consideration for how that may not align with anyone's intention on how Aerleon should work. All such considerations should be referred to the Aerleon project. There is no intention to modify the output that Aerleon creates **in any situation** within this plugin.

That being said, in an effort to provide flexibility, you can override the translation process. However, you would be responsible for that implementation. You can provide within your setting, a dotted path [import_string](https://docs.djangoproject.com/en/4.0/ref/utils/#django.utils.module_loading.import_string) to your own function. This is provided in the `custom_capirca` setting within your Plugin Configurations. The signature takes a `Device` object instance and must return a tuple of `(pol, svc, net, cfg)`, none of which are required to have data.
That being said, in an effort to provide flexibility, you can override the translation process. However, you would be responsible for that implementation. You can provide within your setting, a dotted path [import_string](https://docs.djangoproject.com/en/4.0/ref/utils/#django.utils.module_loading.import_string) to your own function. This is provided in the `custom_aerleon` setting within your Plugin Configurations. The signature takes a `Device` object instance and must return a tuple of `(pol, svc, net, cfg)`, none of which are required to have data.

```python
self.pol, self.svc, self.net, self.cfg = import_string(PLUGIN_CFG["custom_capirca"])(self.device)
self.pol, self.svc, self.net, self.cfg = import_string(PLUGIN_CFG["custom_aerleon"])(self.device)
```

## Summary
Expand All @@ -66,14 +66,14 @@ To summarize, what this integration provides and does not provide.

### Provides

* Integrations with Capirca
* Integrations with Aerleon
* The ability to manage per platform Headers and Terms
* A Job that generated the configurations at the time you want
* The ability to override the opinionated Capirca solution
* The ability to override the opinionated Aerleon solution

### Does not Provide

* An opinionated configuration management solution that matches anything other than Capirca-provided configurations
* An opinionated configuration management solution that matches anything other than Aerleon-provided configurations
* The ability to push configurations directly and natively from Nautobot
* The immediate updating from data in a `Policy` or `PolicyRule` that gets reflected in the configuration, instead when the job is ran
* Any post processing of configuration or pre-validation of data (such as checking if object name starts with an integer)
2 changes: 1 addition & 1 deletion docs/user/app_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ An app for [Nautobot](https://github.com/nautobot/nautobot) that is meant to mod
- Layer 4 firewall policies
- Extended access control lists
- NAT policies
- Generation of firewall configurations, via Capirca
- Generation of firewall configurations, via Aerleon

Future development will include the ability to onboard an existing access list from a device and the ability to generate device configuration.

Expand Down
2 changes: 1 addition & 1 deletion docs/user/app_use_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Although policies can be created without any rules it is recommended to create t

For examples of REST and GraphQL API usage see the [External Interactions](external_interactions.md) page.

Should you wish to integrate Nautobot data with firewall configuration policies, please read the [Capirca Integration](capirca.md) page.
Should you wish to integrate Nautobot data with firewall configuration policies, please read the [Aerleon Integration](aerleon.md) page.

## Screenshots

Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ nav:
- App Overview: "user/app_overview.md"
- Getting Started: "user/app_getting_started.md"
- Using the App: "user/app_use_cases.md"
- Capirca Integration: "user/capirca.md"
- Aerleon Integration: "user/aerleon.md"
- Frequently Asked Questions: "user/faq.md"
- External Interactions: "user/external_interactions.md"
- Data Models:
Expand Down
23 changes: 21 additions & 2 deletions nautobot_firewall_models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""App declaration for nautobot_firewall_models."""

import logging

# Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added
from importlib import metadata

from nautobot.apps import NautobotAppConfig
from nautobot.core.signals import nautobot_database_ready

__version__ = metadata.version(__name__)
LOGGER = logging.getLogger(__name__)


class NautobotFirewallModelsConfig(NautobotAppConfig):
Expand All @@ -22,8 +25,8 @@ class NautobotFirewallModelsConfig(NautobotAppConfig):
min_version = "2.0.0"
max_version = "2.9999"
default_settings = {
"capirca_remark_pass": True,
"capirca_os_map": {},
"aerleon_remark_pass": True,
"aerleon_os_map": {},
"allowed_status": ["Active"],
"default_status": "Active",
"protect_on_delete": True,
Expand All @@ -37,6 +40,22 @@ def ready(self):
nautobot_database_ready.connect(nautobot_firewall_models.signals.create_configured_statuses_signal, sender=self)
nautobot_database_ready.connect(nautobot_firewall_models.signals.associate_statuses_signal, sender=self)

from nautobot_firewall_models.constants import PLUGIN_CFG # pylint: disable=import-outside-toplevel

# Note: If there is more than a single configuration key wrong, we want to print the log messages for all of
# them and fail later.
capirca_config_found = False
for key in PLUGIN_CFG.keys():
if key in ("capirca", "custom_capirca") or key.startswith("capirca"):
LOGGER.error(
"%s is invalid: nautobot-firewall-models moved from capirca to aerleon, please adapt your configuration.",
key,
)
capirca_config_found = True

if capirca_config_found:
raise RuntimeError("capirca plugin configuration detected, failing..")

super().ready()


Expand Down
26 changes: 23 additions & 3 deletions nautobot_firewall_models/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ class Meta:
fields = "__all__"


class CapircaPolicySerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""CapircaPolicy Serializer."""
class AerleonPolicySerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""AerleonPolicy Serializer."""

class Meta:
"""Meta attributes."""

model = models.CapircaPolicy
model = models.AerleonPolicy
fields = "__all__"


Expand All @@ -216,6 +216,16 @@ class Meta:
fields = "__all__"


class PolicyVirtualMachineM2MSerializer(ValidatedModelSerializer):
"""PolicyVirtualMachineM2M Serializer."""

class Meta:
"""Meta attributes."""

model = models.PolicyVirtualMachineM2M
fields = "__all__"


class PolicyDynamicGroupM2MSerializer(ValidatedModelSerializer):
"""PolicyDynamicGroupM2M Serializer."""

Expand All @@ -236,6 +246,16 @@ class Meta:
fields = "__all__"


class NATPolicyVirtualMachineM2MSerializer(ValidatedModelSerializer):
"""NATPolicyDeviceM2M Serializer."""

class Meta:
"""Meta attributes."""

model = models.NATPolicyVirtualMachineM2M
fields = "__all__"


class NATPolicyDynamicGroupM2MSerializer(ValidatedModelSerializer):
"""NATPolicyDynamicGroupM2M Serializer."""

Expand Down
4 changes: 3 additions & 1 deletion nautobot_firewall_models/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
router.register("address-object-group", views.AddressObjectGroupViewSet)
router.register("application-object", views.ApplicationObjectViewSet)
router.register("application-object-group", views.ApplicationObjectGroupViewSet)
router.register("capirca-policy", views.CapircaPolicyViewSet)
router.register("aerleon-policy", views.AerleonPolicyViewSet)
router.register("fqdn", views.FQDNViewSet)
router.register("ip-range", views.IPRangeViewSet)
router.register("nat-policy-rule", views.NATPolicyRuleViewSet)
router.register("nat-policy", views.NATPolicyViewSet)
router.register("nat-policy-device-association", views.NATPolicyDeviceM2MViewSet)
router.register("nat-policy-vm-association", views.NATPolicyVirtualMachineM2MViewSet)
router.register("nat-policy-dynamic-group-association", views.NATPolicyDynamicGroupM2MViewSet)
router.register("policy-rule", views.PolicyRuleViewSet)
router.register("policy", views.PolicyViewSet)
router.register("policy-device-association", views.PolicyDeviceM2MViewSet)
router.register("policy-vm-association", views.PolicyVirtualMachineM2MViewSet)
router.register("policy-dynamic-group-association", views.PolicyDynamicGroupM2MViewSet)
router.register("service-object", views.ServiceObjectViewSet)
router.register("service-object-group", views.ServiceObjectGroupViewSet)
Expand Down
26 changes: 21 additions & 5 deletions nautobot_firewall_models/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ class NATPolicyViewSet(NautobotModelViewSet):
filterset_class = filters.NATPolicyFilterSet


class CapircaPolicyViewSet(ModelViewSet):
"""CapircaPolicy viewset."""
class AerleonPolicyViewSet(ModelViewSet):
"""AerleonPolicy viewset."""

queryset = models.CapircaPolicy.objects.all().prefetch_related("tags")
serializer_class = serializers.CapircaPolicySerializer
filterset_class = filters.CapircaPolicyFilterSet
queryset = models.AerleonPolicy.objects.all().prefetch_related("tags")
serializer_class = serializers.AerleonPolicySerializer
filterset_class = filters.AerleonPolicyFilterSet


###########################
Expand All @@ -147,6 +147,14 @@ class PolicyDeviceM2MViewSet(ModelViewSet):
filterset_class = filters.PolicyDeviceM2MFilterSet


class PolicyVirtualMachineM2MViewSet(ModelViewSet):
"""PolicyVirtualMachineM2M viewset."""

queryset = models.PolicyVirtualMachineM2M.objects.all()
serializer_class = serializers.PolicyVirtualMachineM2MSerializer
filterset_class = filters.PolicyVirtualMachineM2MFilterSet


class PolicyDynamicGroupM2MViewSet(ModelViewSet):
"""PolicyDynamicGroupM2M viewset."""

Expand All @@ -163,6 +171,14 @@ class NATPolicyDeviceM2MViewSet(ModelViewSet):
filterset_class = filters.NATPolicyDeviceM2MFilterSet


class NATPolicyVirtualMachineM2MViewSet(ModelViewSet):
"""NATPolicyVirtualMachineM2MViewSet viewset."""

queryset = models.NATPolicyVirtualMachineM2M.objects.all()
serializer_class = serializers.NATPolicyVirtualMachineM2MSerializer
filterset_class = filters.NATPolicyVirtualMachineM2MFilterSet


class NATPolicyDynamicGroupM2MViewSet(ModelViewSet):
"""NATPolicyDynamicGroupM2M viewset."""

Expand Down
Loading