code: def get_keys(data, search_key, max_matches=-1):
      """Search for a key anywhere in the nested data structure, returning all associated values.
  
      Example:
          If data = {
              'path': 'ABC',
              'details': {
                  'parent': {
                      'path': 'DEF'
                  }
              },
              'events': [
                  {
                      'path': 'GHI'
                  }
              ]
          }
          then get_keys(data, 'path') will return ['ABC', 'DEF', 'GHI'] (in any order)
  
      Args:
          data (dict or list): Record data to search
          search_key (str): Values associated with this key are returned
          max_matches (int): If > 0, only the first n matches are returned (performance optimization).
              WARNING: Dictionary traversal order is essentially random. Only rely on this shortcut
              if you are certain that there won't be more than n matches for the given key.
  
      Returns:
          (list) All values (or at most max_matches values) associated with the given key.
              The values in the result can be of any type. In the example above,
              get_keys(data, 'details') returns a list with a single element of type dict.
      """
      # NOTE: This function has been optimized for performance.
      # If you change this function, use timeit to ensure there are no performance regressions.
  
      # Recursion is generally inefficient due to stack shuffling for each function call/return.
      # Instead, we use a deque (double-ended queue) in a loop: deques have ~O(1) pop/append
      containers = deque()  # A queue of dicts and lists to examine
      containers.append(data)
      results = []
      while containers:
          obj = containers.popleft()
  
          if isinstance(obj, dict):
              if search_key in obj:
                  results.append(obj[search_key])
                  if 0 < max_matches == len(results):
                      # We found n matches - return early
                      return results
  
              # Enqueue all nested dicts and lists for further searching
              for key, val in obj.items():
                  # The data may contain normalized keys if data normalization feature is in use.
                  # We need to exclude normalization information from the data, otherwise this
                  # helper may fetch info from normalization if there are keyname conflict.
                  # For example, Key name 'userName' is both existed as a normalized key defined
                  # in conf/normalized_types.json and cloudtrail record schemas.
                  if key == Normalizer.NORMALIZATION_KEY:
                      continue
                  if val and isinstance(val, _CONTAINER_TYPES):
                      containers.append(val)
  
          else:
              # Obj is a list - enqueue all nested dicts and lists for further searching
              for val in obj:
                  if val and isinstance(val, _CONTAINER_TYPES):
                      containers.append(val)
      return results
  
file_path: streamalert/streamalert/shared/utils.py
imports: from collections import deque
  from netaddr import IPAddress, IPNetwork
  from netaddr.core import AddrFormatError
  from streamalert.shared.logger import get_logger
  from streamalert.shared.normalize import Normalizer
label_desc: Use image node_id 4 for calling a global function with example usage: get_keys(data, search_key, max_matches) and returns: results, results