Skip to main content

I have several questions around the NQE api endpoint for running a queries. This endpoint has parameters for limit and offset for the number of records to skip and how many records to request.

(example from the documentation)

{
  "query": "foreach d in network.devices select { Name: d.name }",
  "queryId": "FQ_ac651cb2901b067fe7dbfb511613ab44776d8029",
  "commitId": "84f84b0c0a0a1805ddff0ca5451c2c55c58605e5",
  "queryOptions": {
    "offset": 20,
    "limit": 100,
    "sortBy": {
      "columnName": "Name",
      "order": "ASC"
    },
    "columnFilters": b
      {
        "columnName": "Name",
        "value": "MyDeviceName"
      }
    ]
  },
  "parameters": {
    "mtuThreshold": 123,
    "ntpServers": "
      "10.22.2.3",
      "192.33.4.1"
    ]
  }
}

 

I have a few questions, and would appreciate any guidance the community can provide

  1. how do i know the total number of records that is available ? do i just ask for the next number of records and if none are returned then i have the full set ?
  2. if i run the api for a 2nd or 3rd time, with the offset amended, will it re-run the query from scratch or are the results held somewhere in a cache.
  3. is there any way to increase the amount of time allowed before a response can occur. e.g. via the API or even in the UI. I’ve noticed in the UI, for queries that do logic against ACLs, etc then it can time out. Note I’m using python (requests library)
  4. Would it better if we are using the API to filter outside of the NQE query for efficiency reasons. I know there are options in the API endpoint to perform some filtering.

My use case is about finding firewall ACL/rules that match IP Addresses, using a parameterized query

// represents all networks
allNetworks = ipSubnet("0.0.0.0/0");

// which vendors are relevant
includeVendors = uVendor.CHECKPOINT];

// convert list of strings to ip addresses
queryIPAddresses(addresses:List<String>) =
foreach address in addresses
select ipAddress(address);

// returns true for each address that matches the subnet
subnetMatchIP(subnet, addresses) =
foreach ip in addresses
where ip in subnet
select true;

getICMPCodes(icmptype, icmpcode) =
min(foreach x in s0]
let code = if icmpcode.start == 0 && icmptype.end == 255
then ""
else toString(icmpcode.start)
let type = toString(icmptype.start)
let response = if code == ""
then "ICMP-" + type
else "ICMP-" + type + "-" + code
select response);

protocolLookup(protocol) =
min(foreach x in 0]
let protocolString = if protocol.start == 6
then "TCP"
else if protocol.start == 17
then "UDP"
else if protocol.start == 1
then "ICMP"
else "OTHER"
select protocolString);

@query
getCSVRules(fromHosts: List<String>, allowAllNetworks: Bool)=
foreach x in C0]
let ipaddresses = queryIPAddresses(fromHosts)
foreach device in network.devices
where device.platform.vendor in includeVendors
foreach aclEntry in device.aclEntries
foreach src in aclEntry.headerMatches.ipv4Src
// default match src addresses, ignore all networks, unless overridden
where src != allNetworks || allowAllNetworks
foreach dst in aclEntry.headerMatches.ipv4Dst
// default match dst addresses, ignore all networks, unless overridden
where dst != allNetworks || allowAllNetworks
// make sure a match against src or dst or a rule
let matches = subnetMatchIP(dst, ipaddresses) + subnetMatchIP(src, ipaddresses)
// more than one match means the rule is valid
where length(matches) >= 1
foreach protocol in aclEntry.headerMatches.ipProtocol
foreach port in aclEntry.headerMatches.tpDst
let protocolString = protocolLookup(protocol)
let portString = if protocolString == "ICMP"
then getICMPCodes(min(aclEntry.headerMatches.icmpType), min(aclEntry.headerMatches.icmpCode))
else if port.start == port.end
then protocolString + "-" + toString(port.start)
else protocolString + "-" + toString(port.start) + "-" +
toString(port.end)
select distinct {
deviceName: device.name,
name: aclEntry.name,
sources: src,
destinations: dst,
ports: portString,
action: aclEntry.action
};

so i know the NQE above is a little on the complex side, as i wanted to make the output nice for a basic demo. I’ll happily remove this if i’m doing this inside python. The main challenge I would think is the number of iterations generated as I turn each access rule into it’s atomic components. This equates to a number of lines directly related to the multiplication of sources * destinations * protocol * ports, which is a huge number of permutations when consider a large estate.

Hence asking for some guidance here, as the functionality above is could be really useful.

For example, requestor wants to replace a server, that is in security controls. They may not know where though. However we can identify all the existing places where such a server (or sets of servers) are configured using this query. (Please note I think you can remove the filtering for CHECK POINT, etc and cover all security type controls for network devices).

Another optimisation i thought about if using the API endpoint to grab the data would to feed the script the device or devices to check acl’s against, allowing us to reduce the amount of data being queried in one NQE query.

I should mention, i wanted to include matches such as

1.1.1.1/32 matches rules that have 1.1.1.0/24 in them.


Hi @AndyL ,

On your first question: Yes, there is nothing in the response that indicates the total number of rows. You should script it as you suggested. Once you make a request that has fewer results than you asked for based on your limit (perhaps no rows at all) then you can terminate, since you have all the rows.

On your second question: if you make subsequent API requests for the same query, with the same parameter values, the results will be provided based on the already-computed result set. The first time you run the query on given parameter values, it will run the query and store the results. Subsequent requests will use the stored results.

On your 3rd question. Yes, there is an “NQE job timeout (minutes)” setting which you can adjust. It is set to 20 minutes by default, which should be sufficient for most purposes. In fact, if you are getting close to that, you may be getting timeouts client side or due to network devices terminating connections. Usually, when queries run that long, we should adjust the query to be more efficient or investigate whether the system can improve its execution strategy for such queries.

On your 4th question. Usually including the filters in the API request is best because the backend can avoid sending unnecessary data, and may be able to optimize data loading. If you find that filtering makes the response time slower, please let us know so we can look into this.

Thanks for providing the query. Yes, I think you might get some slowdown due to “expanding” all the attributes of the ACLs. Can you keep them rolled up? If you can that may help speed things up and also make the result more consumable. For example you could do something like this, where we collect the srcs, dsts, ports into variables (using let statements) and show them like that in each row. To do that, we have to adjust your where clause a bit. Essentially, we need to see if any subnet in the src or dst sets contains any of the input hosts.

 

// returns true if any address matches the subnet
subnetMatchIP(subnet, addresses) =
max(foreach ip in addresses
select ip in subnet);

foreach device in network.devices
where device.platform.vendor in includeVendors
foreach aclEntry in device.aclEntries
let srcs = aclEntry.headerMatches.ipv4Src
// default match src addresses, ignore all networks, unless overridden
let dsts = aclEntry.headerMatches.ipv4Dst
// default match dst addresses, ignore all networks, unless overridden
// make sure a match against src or dst or a rule
where length(dsts) > 0 &&
max(foreach dst in dsts
select subnetMatchIP(dst, fromHosts)) ||
length(srcs) > 0 &&
max(foreach src in srcs
select subnetMatchIP(src, fromHosts))
foreach protocol in aclEntry.headerMatches.ipProtocol
let protocolString = protocolLookup(protocol)
let ports = (foreach port in aclEntry.headerMatches.tpDst
select if protocolString == "ICMP"
then getICMPCodes(min(aclEntry.headerMatches.icmpType), min(aclEntry.headerMatches.icmpCode))
else if port.start == port.end
then protocolString + "-" + toString(port.start)
else protocolString + "-" + toString(port.start) + "-" +
toString(port.end))
select distinct {
deviceName: device.name,
name: aclEntry.name,
sources: srcs,
destinations: dsts,
ports: ports,
action: toString(aclEntry.action)
}

 


Reply