Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6f148d4
nested compliance in progress
Apr 9, 2025
ee63995
update comments
Apr 14, 2025
cd8ae6e
update section config eact match argument
Apr 14, 2025
7c16b4c
update nested compliance
Apr 17, 2025
75d564c
add hier config compliance option
Apr 21, 2025
20e44f9
update hier config compliance
Apr 22, 2025
dd122b4
update hier config compliance
Apr 23, 2025
618c995
add config type check, rename os variable
Apr 23, 2025
ac2f5d8
Merge branch 'develop' into dcates-nested-compliance
May 16, 2025
3dd3a3e
remove dunder based nested compliance
May 20, 2025
d742b6c
Merge branch 'develop' into dcates-nested-compliance
May 20, 2025
1e8b8dc
add tests and docs
May 20, 2025
4f284b4
Merge branch 'develop' into dcates-nested-compliance
Jul 18, 2025
14f2404
update nested compliance for hier config v3
Jul 18, 2025
91bcb9c
Add hier config nested compliance rule feature
Aug 8, 2025
d7572ae
ruff
Aug 8, 2025
f746e20
add changelog fragment
Aug 8, 2025
937a370
remove hier config v2 syntax option
Aug 14, 2025
b2e277a
update docs
Aug 14, 2025
abf40ac
Merge branch 'develop' into dcates-nested-compliance
jeffkala Oct 1, 2025
3fd57c2
Cookie updated by NetworkToCode Cookie Drift Manager Tool (#1048)
nautobot-bot Dec 3, 2025
54ff5f7
release v2.6.0 (#1049)
gsnider2195 Dec 5, 2025
b72b1d1
Improve logic in get_repo_types_for_job (#1052)
mzbroch Dec 16, 2025
97f0d92
Merge pull request #1056 from nautobot/mzb/ltm-2.4/1051
jeffkala Dec 16, 2025
02e1938
Release v2.6.1
jeffkala Dec 16, 2025
b2ce3db
Release v2.6.1
jeffkala Dec 16, 2025
acc4bdb
Merge pull request #1057 from nautobot/release-2.6.1
jeffkala Dec 16, 2025
bc8ac04
Backported UI fixes from develop to ltm-2.4 (#1053)
yanshcodilime Dec 23, 2025
d7ea490
Fixed Hier Config Remediation Options
joewesch Jan 7, 2026
7f32af1
Updated django-debug-toolbar version (#1069)
yanshcodilime Jan 12, 2026
9ff284d
Changed compliance rule match_config to preserve leading spaces
joewesch Jan 27, 2026
188a658
Merge pull request #1067 from nautobot/u/joewesch-fix-remediation-opt…
joewesch Jan 28, 2026
4505142
Merge pull request #1075 from nautobot/u/joewesch-match-config-leadin…
joewesch Jan 28, 2026
81c56b2
Release v2.6.2
Defiantearth Jan 28, 2026
d78d2a0
Merge pull request #1076 from nautobot/release-2.6.2
itdependsnetworks Jan 29, 2026
b85ce5f
Fixed isnull filter getting added to extra filters (#1096)
joewesch Apr 8, 2026
2d3f2b6
Release v2.6.3
joewesch Apr 8, 2026
1b7e8ce
Merge pull request #1099 from nautobot/release-2.6.3
joewesch Apr 8, 2026
d1765c2
Fixed a race condition when pushing to remote git repos (#1108)
joewesch Apr 30, 2026
c476d58
prepare release v2.6.4
joewesch May 1, 2026
48b7f88
Merge pull request #1111 from nautobot/release/2.6.4
joewesch May 1, 2026
6e6fd44
Merge remote-tracking branch 'upstream/ltm-2.4' into dcates-fix-merge…
May 29, 2026
b346091
Merge branch 'dcates-fix-merge-confilcts' into dcates-nested-compliance
May 29, 2026
e751e48
remove duplicate function
May 29, 2026
ee370f4
Merge remote-tracking branch 'upstream/develop' into dcates-fix-merge…
May 29, 2026
46f9d9e
update docs and config compliance
May 29, 2026
6fbbaa9
Merge branch 'dcates-fix-merge-confilcts' into dcates-nested-compliance
May 29, 2026
5fa4179
update documentation
May 29, 2026
bd47817
add pydantic dependency
May 29, 2026
9674db6
update readme and docs
May 29, 2026
75bc133
update tests
May 29, 2026
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This App is installed in the Nautobot Community Sandbox found over at [demo.naut

Full web-based HTML documentation for this app can be found over on the [Nautobot Docs](https://docs.nautobot.com/projects/golden-config/en/latest/) website:

- [User Guide](https://docs.nautobot.com/projects/golden-config/en/latest/user/app_overview/) - Overview, Using the App, Getting Started, Navigating compliance (cli, json, custom), backup, app usage, intended state creation.
- [User Guide](https://docs.nautobot.com/projects/golden-config/en/latest/user/app_overview/) - Overview, Using the App, Getting Started, Navigating compliance (cli, json, xml, custom, filtered), backup, app usage, intended state creation.
- [Administrator Guide](https://docs.nautobot.com/projects/golden-config/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the App.
- [Developer Guide](https://docs.nautobot.com/projects/golden-config/en/latest/dev/contributing/) - Extending the App, Code Reference, Contribution Guide.
- [Release Notes / Changelog](https://docs.nautobot.com/projects/golden-config/en/latest/admin/release_notes/)
Expand Down
1 change: 1 addition & 0 deletions changes/979.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added hier config compatability to compliance rules for matching nested config using hier config tags
3 changes: 3 additions & 0 deletions docs/user/app_feature_compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ For JSON based configs, the match is based on JSON's structure top level key nam
!!! note
"Config to Match" is mandatory for CLI configurations. If config to match is not defined for JSON, the complete JSON configuration will be compared. If the config to match is defined, comparison will take place only for defined keys.

!!! tip "Advanced CLI Compliance: Hierarchical Configuration"
For more sophisticated CLI compliance checking, you can use **Filtered Configuration Compliance** which leverages the `hier_config`. Start the "Config to Match" field with `# hier_config` followed by YAML rule blocks using the unified `match_rules` syntax. See [Filtered Configuration Compliance](./app_feature_compliancefiltered.md) for detailed documentation and examples.

!!! note
If the data is accidentally "corrupted" with a bad tested match, simply delete the devices an re-run the compliance process.

Expand Down
3 changes: 3 additions & 0 deletions docs/user/app_feature_compliancecli.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
!!! note
This document provides instructions for `CLI` configuration type based compliance. The other option is `JSON` based [structured data compliance](./app_feature_compliancejson.md).

!!! tip "Advanced CLI Compliance"
For more sophisticated CLI compliance checking with advanced filtering capabilities, see [Filtered Configuration Compliance](./app_feature_compliancefiltered.md) which provides advanced configuration matching using the `hier_config` library.

## Configuration Compliance Parsing Engine

Configuration compliance is different than a simple UNIX diff. While the UI provides both, the compliance metrics are not influenced by the UNIX diff
Expand Down
208 changes: 208 additions & 0 deletions docs/user/app_feature_compliancefiltered.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Filtered Configuration Compliance

## Overview

Filtered Configuration Compliance is a powerful extension to the standard CLI-based compliance checking that leverages the `hier_config` library to provide advanced configuration parsing and comparison capabilities. This feature allows you to define complex matching rules to focus compliance checks on specific configuration sections or patterns.

Unlike standard CLI compliance which matches configuration sections based on simple line-starting patterns, Filtered Configuration Compliance identifies and compares configuration elements based on their hierarchical relationships and specific attributes, which allows for filtering based on nested config lines.

## When to Use Filtered Configuration Compliance

This feature is particularly useful for:

- Configurations that are benign or non-consequential to overall device operation.
- When you are building out your compliance journey, and not prepared to include all configurations based on simple line matching.

!!! warning
Should not be used to provide full line matches or matches that are better served as data, as an example you should not do this `- startswith: description USER PORT` but should do this `- startswith: description`.

## Requirements

### Platform Support

Filtered configuration compliance requires:

1. **CLI Configuration Type**: Only [`CLI`](./app_feature_compliancecli.md) configuration types are supported for hierarchical compliance
2. **Platform Compatibility**: Your device platform must be supported by the `hier_config` library
3. **Network Driver Mapping**: The platform must have a valid `hier_config` mapping in its `network_driver_mappings`

## Configuring Filtered Compliance Rules

### Rule Identification

To create a filtered compliance rule, start the **Config to Match** field with the comment:

`# hier_config`

This comment must be the first line. The content that follows should be YAML consisting of one or more rule blocks. Each rule block uses the `match_rules` key containing an ordered list of predicates (e.g. `startswith`, `contains`, `re_search`, `endswith`).

### Syntax

Example with two rule blocks:

```yaml
# hier_config
- match_rules:
- startswith: router bgp
- startswith: neighbor
- match_rules:
- startswith: interface
- contains: GigabitEthernet
```

### Example Configuration Rules

#### Example 1: BGP Neighbor Configuration

To check compliance for all BGP neighbor configurations:

```yaml
# hier_config
- match_rules:
- startswith: router bgp
- startswith: neighbor
```

This rule will match configurations like:
```
router bgp 65001
neighbor 10.1.1.1
remote-as 65002
description PEER_ROUTER_A
neighbor 10.1.1.2
remote-as 65003
description PEER_ROUTER_B
```

#### Example 2: Interface MTU Settings

To check compliance for MTU settings on all interfaces:

```yaml
# hier_config
- match_rules:
- startswith: interface
- contains: mtu
```

This rule will match configurations like:
```
interface GigabitEthernet0/1
mtu 9000
interface GigabitEthernet0/2
mtu 1500
```

#### Example 3: SNMP Configuration

To check compliance for SNMP server configurations:

```yaml
# hier_config
- match_rules:
- startswith: snmp-server
```

This rule will match configurations like:
```
snmp-server community public RO
snmp-server community private RW
snmp-server host 192.168.1.100 version 2c public
```

#### Example 4: Access Control Lists

To check compliance for specific access control list entries:

```yaml
# hier_config
- match_rules:
- startswith: ip access-list
- contains: SECURITY
```

This rule will match configurations like:
```
ip access-list extended SECURITY_IN
permit tcp any host 192.168.1.100 eq 443
deny ip any any log
ip access-list extended SECURITY_OUT
permit ip 192.168.1.0 0.0.0.255 any
deny ip any any log
```

## Fallback Behavior for Empty Intended Configuration

In some cases, the intended configuration may not contain any elements that match the compliance rules, resulting in an empty intended configuration text. To handle this scenario, the system includes a fallback mechanism:

**Interface Fallback**: When the intended configuration text is empty, the system automatically looks for top-level interface configurations in the running configuration and uses them as the intended configuration baseline.

This fallback behavior:

1. **Triggers** when `intended_text` is empty after tag-based filtering
2. **Filters** for lines that start with "interface"
3. **Uses** these interface declarations as the intended configuration for comparison

**Example Scenario**:

- Your hierarchical rule targets specific VLAN configurations
- The intended configuration template doesn't include those VLANs
- The running configuration has interface declarations
- The system uses the interface lines from running config as the baseline

This ensures that remediation will not remove interfaces when intended configuration is empty for an interface.

Comment on lines +134 to +154

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's talk about this before pursuing further. My initial thought is this complicates things too much and going to be hard to meet everyone's POLA.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still feel similar, I don't fully follow the solution (examples would help), and it seems like more complication then required in a day 1 solution.

## Troubleshooting

### Common Issues

#### Platform Not Supported
**Error**: `Unsupported platform for hier_config v3: <platform>`

**Solution**: Ensure your device platform has a valid `hier_config` mapping in its `network_driver_mappings`. Check the platform configuration in Nautobot.

#### Invalid YAML Syntax
**Error**: `Invalid YAML in match_config: <error details>`

**Solution**: Validate your YAML syntax in the **Config to Match** field. Ensure proper indentation and structure.

#### Wrong Configuration Type
**Error**: `Hier config compliance rules are only supported for CLI config types`

**Solution**: Change the **Config Type** to `CLI` for hierarchical compliance rules.

### Debug Tips

1. **Check Platform Support**: Verify your platform's `network_driver_mappings` include `hier_config`
2. **Review hier_config Documentation**: Consult the [hier_config documentation](https://hier-config.readthedocs.io/en/latest/tags/) for advanced tagging examples
3. **Start Simple**: Begin with basic matching rules and gradually add complexity
4. **Empty Intended Configuration**: If you notice interface lines appearing in compliance results when not expected, check if your intended configuration contains the elements targeted by your hierarchical rules. The system automatically falls back to using running config interface declarations when intended configuration is empty

## Best Practices

### Rule Design

1. **Specific Matching**: Create focused rules that target specific configuration elements
2. **Logical Grouping**: Group related configuration elements in single rules when appropriate
3. **Clear Naming**: Use descriptive feature names that clearly indicate what the rule validates
4. **Complete Intended Configurations**: Ensure your intended configuration templates include all elements targeted by filtered compliance rules to avoid unexpected fallback behavior where interface lines from running config are used as intended baseline

## Advanced Usage

### Combining Multiple Rules

You can create multiple hierarchical compliance rules for the same platform to check different aspects of the configuration independently.

### Integration with Configuration Management

Filtered configuration compliance works seamlessly with existing configuration management workflows:

- Backup processes remain unchanged
- Intended configuration generation follows the same patterns
- Compliance reporting provides the same interface with enhanced precision

## Related Documentation

- [Standard CLI Compliance](./app_feature_compliancecli.md)
- [Configuration Compliance Overview](./app_feature_compliance.md)
- [hier_config Documentation](https://hier-config.readthedocs.io/)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ nav:
- Navigate Compliance JSON: "user/app_feature_compliancejson.md"
- Navigate Compliance XML: "user/app_feature_compliancexml.md"
- Navigate Compliance Custom: "user/app_feature_compliancecustom.md"
- Navigate Compliance Filtered: "user/app_feature_compliancefiltered.md"
- Navigate Intended: "user/app_feature_intended.md"
- Navigate SoT Agg: "user/app_feature_sotagg.md"
- Navigate Configuration Post-Processing: "user/app_feature_config_postprocessing.md"
Expand Down
107 changes: 105 additions & 2 deletions nautobot_golden_config/nornir_plays/config_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

# pylint: disable=relative-beyond-top-level
import difflib
import hashlib
import json
import logging
import os
from collections import defaultdict
from datetime import datetime

import hier_config
import yaml
from django.utils.timezone import make_aware
from hier_config.utils import HCONFIG_PLATFORM_V2_TO_V3_MAPPING
from lxml import etree
from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
Expand All @@ -16,6 +21,7 @@
from nornir.core.plugins.inventory import InventoryPluginRegister
from nornir.core.task import Result, Task
from nornir_nautobot.exceptions import NornirNautobotException
from pydantic import TypeAdapter
Comment thread
itdependsnetworks marked this conversation as resolved.

from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice
from nautobot_golden_config.exceptions import ComplianceFailure
Expand Down Expand Up @@ -171,8 +177,13 @@
intended_cfg = _open_file_config(intended_file)

for rule in rules[obj.platform.network_driver]:
_actual = get_config_element(rule, backup_cfg, obj, logger)
_intended = get_config_element(rule, intended_cfg, obj, logger)
# Check for hier config compliance rule
section_type = rule["section"][0]
if "# hier_config" in section_type:
_actual, _intended = process_nested_compliance_rule_hier_config(rule, backup_cfg, intended_cfg, obj, logger)
else:
_actual = get_config_element(rule, backup_cfg, obj, logger)
_intended = get_config_element(rule, intended_cfg, obj, logger)

Check warning on line 186 in nautobot_golden_config/nornir_plays/config_compliance.py

View workflow job for this annotation

GitHub Actions / unittest_report (3.13, postgresql, stable)

Missing coverage

Missing coverage on lines 181-186

# using update_or_create() method to conveniently update actual obj or create new one.
ConfigCompliance.objects.update_or_create(
Expand All @@ -194,6 +205,98 @@
return Result(host=task.host)


def process_nested_compliance_rule_hier_config(rule, backup_cfg, intended_cfg, obj, logger): # pylint: disable=too-many-locals
"""
Processes nested compliance rules using hierarchical configuration comparison.

This function compares backup (running) and intended configurations using hierarchical config parsing.
It applies tag-based filtering according to match criteria defined in the compliance rule's match_config.

Args:
rule (dict): Contains compliance rule details, including match configuration.
backup_cfg (str): The backup (running) configuration as a string.
intended_cfg (str): The intended configuration as a string.
obj (object): Object containing platform and device information.
logger (logging.Logger): Logger instance for logging errors and debug information.

Returns:
tuple[str, str]: Filtered running and intended configuration text for compliance comparison.

Notes:
The match config must have # hier_config as a comment at the top of the YAML block.

The match_config field in the rule should define hierarchical config tags and matching criteria in YAML format.
See: https://hier-config.readthedocs.io/en/latest/tags/
"""
# Check if the compliance rule is of type CLI
if rule["obj"].config_type != ComplianceRuleConfigTypeChoice.TYPE_CLI:
error_msg = "Hier config compliance rules are only supported for CLI config types."
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

# Convert v2 platform to v3 platform
v2_platform = obj.platform.network_driver_mappings.get("hier_config")
platform = HCONFIG_PLATFORM_V2_TO_V3_MAPPING.get(v2_platform)

if not platform:
error_msg = f"Unsupported platform for hier_config v3: {v2_platform}"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

# Create config objects using v3 API
running_config = hier_config.get_hconfig(platform_or_driver=platform, config_raw=backup_cfg)
generated_config = hier_config.get_hconfig(platform_or_driver=platform, config_raw=intended_cfg)

v3_tags = tuple()
tag_names = set()

# Create hier config v3 tags
try:
match_config = yaml.safe_load(rule["obj"].match_config)
for tag_rule in match_config:
# Create a unique tag name for each match rule
tag_name = hashlib.sha1(json.dumps(tag_rule, sort_keys=True).encode()).hexdigest() # noqa: S324
tag_rule["apply_tags"] = frozenset([tag_name])
tag_names.add(tag_name)
v3_tags = TypeAdapter(tuple[hier_config.models.TagRule, ...]).validate_python(match_config)
except yaml.YAMLError as e:
error_msg = f"Invalid YAML in match_config: {str(e)}"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg) from e

# Apply tags to the running and generated configs
for tag_rule in v3_tags:
for child in running_config.get_children_deep(tag_rule.match_rules):
child.tags_add(tag_rule.apply_tags)

for tag_rule in v3_tags:
for child in generated_config.get_children_deep(tag_rule.match_rules):
child.tags_add(tag_rule.apply_tags)
Comment on lines +268 to +274

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be applied the same on running and generated? When does get_children_deep get applied to a running but not generated config?


# Filter configs by the applied tags
running_config_generator = running_config.all_children_sorted_by_tags(tag_names, set())
running_text = "\n".join(c.cisco_style_text() for c in running_config_generator)

intended_config_generator = generated_config.all_children_sorted_by_tags(tag_names, set())
intended_text = "\n".join(c.cisco_style_text() for c in intended_config_generator)

# If the intended text is None, inject the top level parent lines from running config into the intended config.
# This will prevent the removal of top-level interfaces or other sections that are not matched by the tags
# For example, if the intent is to remove 'flow exporter' from the config when remediating
# Instead of this:
# no interface GigabitEthernet0/0
# we will have this:
# no flow exporter 192.0.0.1
if not intended_text:
running_config_top_level_lines = []
for child in running_config.all_children_sorted_by_tags(tag_names, set()):
if child.real_indent_level == 0 and child.cisco_style_text().startswith("interface"):
running_config_top_level_lines.append(child.cisco_style_text())
intended_text = "\n".join(running_config_top_level_lines)

return running_text, intended_text


def config_compliance(job): # pylint: disable=unused-argument
"""
Nornir play to generate configurations.
Expand Down
Loading
Loading