Name

sn_agent.AgentAllowListGenerator

Description

Code to generate an allow-list for Agent Client Collector Users should execute this script and take the contents to use for the allow-list file specified in the acc.yml file for each agent.

Script

var AgentAllowListGenerator = Class.create();

/* Function algorithm:
* - starting at the check definition, ordered by the command, iterate through each record
* - the first part of the check command is considered the entry's exec(uting command)
* - the remainder of the check command is considered part of the entry's arg list
* - iterate through the checks parameters and secure parameters, if any param is active=false,
*   set entry's skip_arguments=true
* - iterate through the check instances of this check definition
* - from this check command, add to the entry's arg list
* - iterate through the check instance parameters and secure parameters as before
* - continue to the next check instance until no more
* - continue to the next check definition
* - if the exec is the same as the previous, continue to populate the entry
* - if the exec is a new exec, validate the entry, save it to the allowList array 
*   and start a new entry
* - continue scanning the check definition, then its parameters, 
*   then its check instances, then the instance parameters as before until there are no more
*/
AgentAllowListGenerator.generate = function() {
  // contains template for API used by ITSM Playbook
  var ITSM_PLAYBOOK_CK_NAME = 'utils.playbook_osquery';
  var allowList = [];

  var entry = AgentAllowListGenerator.initEntry();
  var fullCommand;
  var exec;
  var lastExec = '';
  var args;
  var sp;

  // iterate through the check def ordered by command
  var check = new GlideRecord('sn_agent_check_def');
  check.orderBy('command');
  check.query();
  while (check.next()) {
      // parse out the executing command from the arguments
      fullCommand = check.getValue('command');

      var result = AgentAllowListGenerator.parseCheckCommand(fullCommand);
      exec = result[0];
      args = result[1];

      var checkSysId = check.getValue('sys_id');
      var checkName = check.getValue('name');

      // is the executing command match the current entry's command
      if (lastExec == exec) {
          // if so, update it
          AgentAllowListGenerator.populateEntry(checkSysId, entry, args, true);
          AgentAllowListGenerator.populateEntryFromCheckInstances(checkSysId, entry);
      } else {
          // else, push the last entry into allowList
          if (lastExec != '') {
              AgentAllowListGenerator.validateEntry(entry);
              allowList.push(entry);
          }

          // re-initialize the entry and populate the new data
          entry = AgentAllowListGenerator.initEntry(exec);

          // if osqueryi, handle ITSM Playbook queries first
          if (exec == 'osqueryi' && ITSM_PLAYBOOK_CK_NAME.equals(checkName)) {
              AgentAllowListGenerator.handleITSMPlaybook(entry, args);
          } else {
              AgentAllowListGenerator.populateEntry(checkSysId, entry, args, true);
              AgentAllowListGenerator.populateEntryFromCheckInstances(checkSysId, entry);
          }

          // flag this as the current entry
          lastExec = exec;
      }
  }

  // push the last entry
  if (entry.exec != "") {
      AgentAllowListGenerator.validateEntry(entry);
      allowList.push(entry);
  }

  // create the JSON
  var json = new global.JSON();
  json.prettify();
  return json.encode(allowList);
};

// function to initialize the allowList entry
AgentAllowListGenerator.initEntry = function(execCommand) {
  if (!execCommand)
      execCommand = '';

  entry = {
      'exec': execCommand,
      'args': [],
      'skip_arguments': false
  };

  return entry;
};

// parse check command string to executing command and argument string
AgentAllowListGenerator.parseCheckCommand = function(command) {
  var exec = '';
  var args = '';

  if (!command)
      return [exec, args];

  command = command.replaceAll("\r\n", "\n");
  sp = command.indexOf(' ');

  if (sp > 0) {
      exec = command.substring(0, sp);
      args = command.substring(sp + 1, command.length);
  } else {
      exec = command;
      args = '';
  }

  return [exec, args];
};

// function to populate the allowList entry based on the check arguments
AgentAllowListGenerator.populateEntry = function(checkSysId, entry, args, isDef) {
  if (args != '') {
      if (args.indexOf('.labels.') > 0)
          entry.skip_arguments = true;
      else
          entry.args.push(args); // only push args that don't contain labels
  }

  AgentAllowListGenerator.populateEntryFromParameters(checkSysId, entry, isDef);
};

// function to populate the allowList entry based on check instances
AgentAllowListGenerator.populateEntryFromCheckInstances = function(checkSysId, entry) {
  var fullCommand;
  var exec;
  var lastExec = '';
  var args;
  var sp;

  var inst = new GlideRecord('sn_agent_check');
  inst.addQuery('check_def', checkSysId);
  inst.query();
  while (inst.next()) {
      // parse out the executing command from the arguments
      fullCommand = inst.getValue('command');
      var result = AgentAllowListGenerator.parseCheckCommand(fullCommand);
      exec = result[0];
      args = result[1];

      var checkInstSysId = inst.getValue('sys_id');

      if (exec != entry.exec) {
          gs.info('skipping check instance ' + checkInstSysId + ' since command does not match check def');
          continue;
      }

      AgentAllowListGenerator.populateEntry(checkInstSysId, entry, args, false);
  }
};

// function to set skip_arguments based on parameters
AgentAllowListGenerator.populateEntryFromParameters = function(checkSysId, entry, isDef) {
  var TABLE_PARAM;
  var TABLE_SECURE_PARM;
  var FIELD_CHECK_REF;

  if (isDef) {
      // for check definitions
      TABLE_PARAM = 'sn_agent_check_param_def';
      TABLE_SECURE_PARAM = 'sn_agent_check_secure_param_def';
      FIELD_CHECK_REF = 'check_def';
  } else {
      // for check instances
      TABLE_PARAM = 'sn_agent_check_param';
      TABLE_SECURE_PARAM = 'sn_agent_check_secure_param';
      FIELD_CHECK_REF = 'check';
  }

  // can't do anything if there is no check to look up parameters for
  if (gs.nil(checkSysId))
      return;

  // if this is already true, just return
  if (entry.skip_arguments == true)
      return;

  // if there are any inactive check parameters, we should set skip_arguments = true
  // so that the whitelist will still accomodate it when the check parameter is changed to active
  var params = new GlideRecord(TABLE_PARAM);
  params.addQuery(FIELD_CHECK_REF, checkSysId);
  params.addQuery('active', 'false');
  params.query();

  if (params.getRowCount() > 0) {
      entry.skip_arguments = true;
      return;
  }

  // repeat for secure parameters (for now)
  params = new GlideRecord(TABLE_SECURE_PARAM);
  params.addQuery(FIELD_CHECK_REF, checkSysId);
  params.query();

  if (params.getRowCount() > 0) {
      entry.skip_arguments = true;
      return;
  }
};

AgentAllowListGenerator.handleITSMPlaybook = function(entry, args) {
  // get any other arguments in the check commands
  var strippedArgs = args.replace('"{{.labels.params_query}}"', '');

  // find flags with a number value (--logger_min_status 1)
  var re = /--(?:[a-z_]+)\s(?:\d+)/g;
  var result = strippedArgs.match(re);
  if (result) {
      result.forEach(function(item) {
          entry.args.push(item);
          strippedArgs = strippedArgs.replace(item, '');
      });
  }

  var idvArgs = strippedArgs.split(' ');
  for (var i = 0; i < idvArgs.length; i++) {
      var arg = idvArgs[i].trim();
      if (arg)
          entry.args.push(arg);
  }

  // table with the actual query statements
  var queries = new GlideRecord('sn_pb_content_query_definition');
  if (!queries.isValid())
      return;

  queries.query();
  while (queries.next()) {
      // since the label is double-quoted, these values need to be as well
      entry.args.push('"' + queries.getValue('command') + '"');
  }
};

// function to make sure the entry meets all requirements before adding it to the allowList
AgentAllowListGenerator.validateEntry = function(entry) {
  // args cannot be empty
  if (entry.args.length == 0)
      entry.args.push('');

  // if skip_arguments set, clear the args list
  if (entry.skip_arguments == true)
      entry.args = [''];

  // only store the unique arguments
  if (entry.args.length > 1)
      entry.args = new global.ArrayUtil().unique(entry.args);
};

AgentAllowListGenerator.prototype = {
  initialize: function() {},

  type: 'AgentAllowListGenerator'
};

Sys ID

fa1d5cf689989010f877f8e7a467caaa

Offical Documentation

Official Docs: