NQE - Cisco / Arista - Check for No Local Username

  • 17 January 2024
  • 1 reply
  • 69 views

Userlevel 3

How does one access a Cisco or Arista device if TACACS+ or RADIUS is unavailable?  A local username is required.  What happens when your security clean up work goes a bit too far and removes all local usernames?  This quick NQE configuration check verifies that there is at least one local username.  (See the other NQE examples posted in this Community for configuration pattern matching).  

/**
* @intent Find all Cisco and Arista devices where there is no local username configured.
* @description Local usernames are used when TACACS+ or RADIUS is not accessible. Find all Cisco and Arista devices where ** no ** "username" is configured.
*/

pattern = ```
username {user:string}
```;

foreach device in network.devices
where device.platform.vendor == Vendor.CISCO ||
device.platform.vendor == Vendor.ARISTA ||
device.platform.vendor == Vendor.HP
let platform = device.platform
where isPresent(platform.model)
select {
violation: length(blockMatches(device.files.config, pattern)) < 1,
name: device.name,
vendor: platform.vendor,
model: platform.model,
os: platform.os,
osVer: platform.osVersion
}

The following NQE, attributed to another Forward Networks engineer, allows one to list non-compliant usernames.  Search for username = admin.  If the username does not exist, indicate a violation and list the local username configured.  
 

/**
* @intent Search Cisco Configuration for usernames that are not well known
* @description To ensure consistent configuration, search the Cisco devices for usernames that are not well known, or outlyers. Report as Violations.
*/

Expected_Usernames = ["admin"];

// Check if the username exists
check_username(Device_Name) =
foreach device in network.devices
where device.name == Device_Name
let configured_usernames = (foreach line in device.files.config
let configured_usernames = patternMatch(line.text, `username {username:string}`)
where isPresent(configured_usernames) && isPresent(configured_usernames.username)
select configured_usernames.username)

let missing_users = configured_usernames - Expected_Usernames
where length(missing_users) >0
select { Device_Name: Device_Name, User_List: missing_users, Info: "UNKNOWN_USER" };


// main
foreach device in network.devices
where device.platform.vendor == Vendor.CISCO || device.platform.vendor == Vendor.ARISTA
let non_compliant_devices = check_username(device.name)
foreach item in non_compliant_devices
select {violation: true, Device_Name: item.Device_Name, User_List: item.User_List, Info: item.Info}

 

Since we are on the topic of TACACS+ and checking configuration compliance, here is one more easy one.  Let’s check to see if TACACS+ is properly configured on Cisco or Arista devices.  Edit the IP addresses as appropriate for your environment.  A violation is true when the tacacServer string does not match the IP addresses in the list.  This NQE could be used for NQE Verification.  Meaning that this NQE is executed when the Snapshot is evaluated.  Flag the NQE as “Add to Verify” to execute during Snapshot processing. 

// Note that this NQE finds all lines.  Some systems use multiple lines to configure the TACACS+ server values. The result can be two or more lines of output.
// Documentation on Block Patterns:
// https://fwd.app/docs/nqe/guides/block-patterns/
// TACACS Configuration Example:
// https://fwd.app/docs/nqe/guides/data-extraction/
// ----------------------------------
tacacsPattern =
```
tacacs server {server:string}
address ipv4 {serverIP:string}
```;

//
IOSTACACS =
foreach device in network.devices
/// Uncomment the next two lines to match only CISCO or ARISTA vendors.
// where device.platform.vendor == Vendor.CISCO ||
// device.platform.vendor == Vendor.ARISTA
foreach line in device.files.config
let match = patternMatch(line.text, `tacacs-server host {serverIP:string}`)
where isPresent(match)
let platform = device.platform
select {
deviceName: device.name,
tacacsServer: match.serverIP,
vendor: platform.vendor,
model: platform.model,
os: platform.os,
osVersion: platform.osVersion,
managementIps: platform.managementIps
};

// ----------------------------------
IOSXETACACS =
foreach device in network.devices
/// Uncomment the next two lines to match only CISCO or ARISTA vendors.
// where device.platform.vendor == Vendor.CISCO ||
// device.platform.vendor == Vendor.ARISTA
let matchData = blockMatches(device.files.config, tacacsPattern)
foreach line in matchData
let match = line.data
where isPresent(match)
let platform = device.platform
select {
deviceName: device.name,
tacacsServer: match.serverIP,
vendor: platform.vendor,
model: platform.model,
os: platform.os,
osVersion: platform.osVersion,
managementIps: platform.managementIps
};

allTACACS = IOSTACACS + IOSXETACACS;

foreach x in allTACACS
select {
violation: x.tacacsServer not in ["10.5.5.10", "10.5.10.10"],
deviceName: x.deviceName,
tacacsServer: x.tacacsServer,
vendor: x.vendor,
model: x.model,
os: x.os,
osVersion: x.osVersion,
managementIps: x.managementIps
}

 


1 reply

Userlevel 3

One of the things you might consider is to leverage the set evaluation instead of using `||` and you can add matches to the output to see what `username` entries exist.
 

pattern = ```
username {user:string}
```;

foreach device in network.devices
let vendor = device.platform.vendor
where vendor in [Vendor.CISCO, Vendor.ARISTA, Vendor.HP]
let platform = device.platform
where isPresent(platform.model)
let matches = blockMatches(device.files.config, pattern)
select {
violation: length(matches) == 0 ,
name: device.name,
vendor: platform.vendor,
model: platform.model,
os: platform.os,
osVer: platform.osVersion,
matches: foreach match in matches select match.data
}

 

Reply