Skip to main content

NQE - Finding and Visualizing Config Junk in Large-Scale Networks

  • March 31, 2026
  • 0 replies
  • 6 views

The Problem with “It Doesn’t Hurt Anything”

Over time, stale configuration creates real friction:

  • Configs become harder to read and reason about
  • Engineers hesitate to make changes because “something might depend on it”
  • Troubleshooting takes longer because it’s unclear what actually matters
  • The network slowly accumulates configuration debt

None of this usually comes from bad configuration. It comes from old configuration that was never cleaned up.

The Core Objective

The primary objective of this approach is not to modify or remove configuration automatically, but to make potential problem areas visible.

  • Identify configuration elements that are likely unused
  • Extract them in a consistent, scalable way
  • Present the results in a form that engineers can easily understand

Once those configs are visible, humans can make informed decisions instead of guessing.

NQE for Cisco ASA

This NQE identifies objects that are not used by any ACL entries.
  This is just one example of how we can quickly identify “config junk.”
  Any comments or advice would be greatly appreciated.

Step-by-Step Process

  • Extract Objects: Parses configuration to find all object and object-group definitions
  • Find ACL References: Searches access-list entries for direct object/object-group usage
  • Track Group Hierarchies: Maps parent-child relationships between object-groups via group-object statements
  • Resolve Transitive Usage: Iteratively expands usage through 10 levels of nested object-group references to find all indirectly used objects
  • Identify Violations: Compares all defined objects against the complete set of used objects
  • Report Results: Flags unused objects as violations and shows which access-lists reference each object
// Retrieve object definitions
objectPattern = `object {objectType:string} {objectName:string}`;

getObjects(device) =
foreach match in patternMatches(device.files.config, objectPattern)
select {
type: match.data.objectType,
name: match.data.objectName,
kind: "object"
};

// Retrieve object-group definitions
objectGroupPattern = `object-group {type:string} {objectGroupName:string}`;

getObjectGroups(device) =
foreach match in patternMatches(device.files.config, objectGroupPattern)
select {
type: match.data.type,
name: match.data.objectGroupName,
kind: "object-group"
};

// Retrieve both objects and object-groups together
getAllObjects(device) =
getObjects(device) + getObjectGroups(device);

// Retrieve objects or object-groups referenced via group-object within object-groups
getGroupObjectReferences(device) =
foreach match in patternMatches(device.files.config, objectGroupPattern)
let parentName = match.data.objectGroupName
foreach childLine in match.line.children
let groupObjectMatch = patternMatch(childLine.text, `group-object {referencedName:string}`)
where isPresent(groupObjectMatch)
select {
parentGroup: parentName,
childObject: groupObjectMatch.referencedName
};

// Retrieve objects and object-groups directly referenced from access-lists
getAclReferences(device) =
foreach line in device.files.config
let aclMatch = patternMatch(line.text, `access-list {aclName:string} {rest:(string*)}`)
where isPresent(aclMatch)
// Look for references starting with "object " or "object-group "
let objectMatches = regexMatches(line.text, re`object (?<name:String>\S+)`)
let objectGroupMatches = regexMatches(line.text, re`object-group (?<name:String>\S+)`)
foreach match in objectMatches + objectGroupMatches
select {
referencedName: match.data.name,
aclName: aclMatch.aclName
};

// Retrieve the list of objects/object-groups directly referenced by ACLs
getDirectlyReferencedObjects(device) =
foreach ref in getAclReferences(device)
select distinct ref.referencedName;

// Retrieve recursively referenced objects/object-groups (up to 10 levels)
getAllUsedObjects(directlyReferenced, groupReferences) =
foreach x in [0]
// Traverse references hierarchically
let used0 = directlyReferenced
let used1 = used0 + (foreach ref in groupReferences
where ref.parentGroup in used0
select distinct ref.childObject)
let used2 = used1 + (foreach ref in groupReferences
where ref.parentGroup in used1
select distinct ref.childObject)
let used3 = used2 + (foreach ref in groupReferences
where ref.parentGroup in used2
select distinct ref.childObject)
let used4 = used3 + (foreach ref in groupReferences
where ref.parentGroup in used3
select distinct ref.childObject)
let used5 = used4 + (foreach ref in groupReferences
where ref.parentGroup in used4
select distinct ref.childObject)
let used6 = used5 + (foreach ref in groupReferences
where ref.parentGroup in used5
select distinct ref.childObject)
let used7 = used6 + (foreach ref in groupReferences
where ref.parentGroup in used6
select distinct ref.childObject)
let used8 = used7 + (foreach ref in groupReferences
where ref.parentGroup in used7
select distinct ref.childObject)
let used9 = used8 + (foreach ref in groupReferences
where ref.parentGroup in used8
select distinct ref.childObject)
let used10 = used9 + (foreach ref in groupReferences
where ref.parentGroup in used9
select distinct ref.childObject)
foreach name in used10
select distinct name;

// Retrieve the list of ACLs referencing an object/object-group
// (including indirect references, up to 10 levels)
getReferencingAcls(objectName, aclReferences, groupReferences) =
foreach x in [0]
// ACLs that directly reference this object/object-group
let directAcls = (foreach ref in aclReferences
where ref.referencedName == objectName
select ref.aclName)
// Parent object-groups that reference this object/object-group
let parentGroups0 = (foreach ref in groupReferences
where ref.childObject == objectName
select ref.parentGroup)
// Recursively trace parent object-groups (up to 10 levels)
let parentGroups1 = parentGroups0 + (foreach ref in groupReferences
where ref.childObject in parentGroups0
select ref.parentGroup)
let parentGroups2 = parentGroups1 + (foreach ref in groupReferences
where ref.childObject in parentGroups1
select ref.parentGroup)
let parentGroups3 = parentGroups2 + (foreach ref in groupReferences
where ref.childObject in parentGroups2
select ref.parentGroup)
let parentGroups4 = parentGroups3 + (foreach ref in groupReferences
where ref.childObject in parentGroups3
select ref.parentGroup)
let parentGroups5 = parentGroups4 + (foreach ref in groupReferences
where ref.childObject in parentGroups4
select ref.parentGroup)
let parentGroups6 = parentGroups5 + (foreach ref in groupReferences
where ref.childObject in parentGroups5
select ref.parentGroup)
let parentGroups7 = parentGroups6 + (foreach ref in groupReferences
where ref.childObject in parentGroups6
select ref.parentGroup)
let parentGroups8 = parentGroups7 + (foreach ref in groupReferences
where ref.childObject in parentGroups7
select ref.parentGroup)
let parentGroups9 = parentGroups8 + (foreach ref in groupReferences
where ref.childObject in parentGroups8
select ref.parentGroup)
let parentGroups10 = parentGroups9 + (foreach ref in groupReferences
where ref.childObject in parentGroups9
select ref.parentGroup)
// ACLs that reference parent object-groups
let indirectAcls = (foreach ref in aclReferences
where ref.referencedName in parentGroups10
select ref.aclName)
// Merge all ACLs and remove duplicates
foreach aclName in directAcls + indirectAcls
select distinct aclName;

foreach device in network.devices
where device.platform.os == OS.ASA
// Retrieve required data once per device
let allObjects = getAllObjects(device)
let aclReferences = getAclReferences(device)
let directlyReferenced = getDirectlyReferencedObjects(device)
let groupReferences = getGroupObjectReferences(device)
let usedObjects = getAllUsedObjects(directlyReferenced, groupReferences)
// Check usage for each object/object-group
foreach obj in allObjects
let referencingAccessLists = getReferencingAcls(obj.name, aclReferences, groupReferences)
select {
violation: obj.name not in usedObjects,
Device: device.name,
"Object Kind": obj.kind,
"Object Type": obj.type,
"Object Name": obj.name,
"Referencing Access Lists": referencingAccessLists,
"Reference Count": length(referencingAccessLists)
}

Result