收集 Nagios XI 日志

支持的平台:

本文档介绍了如何使用 Google Cloud Storage V2 将 Nagios XI 日志注入到 Google Security Operations。

Nagios XI 是一款全面的基础架构监控解决方案,可监控服务器、网络、应用和服务。它会跟踪主机和服务状态、效果指标,并通过其 REST API 针对 IT 基础设施问题生成提醒。

准备工作

确保您满足以下前提条件:

  • Google SecOps 实例
  • 启用了 Cloud Storage、Cloud Run、Pub/Sub 和 Cloud Scheduler API 的 GCP 项目
  • 创建和管理 GCS 存储桶的权限
  • 管理 GCS 存储桶的 IAM 政策的权限
  • 创建 Cloud Run 服务、Pub/Sub 主题和 Cloud Scheduler 作业的权限
  • 对 Nagios XI 的特权访问权限(具有用户管理权限)
  • Nagios XI 版本 5 或更高版本(支持 REST API)

配置 Nagios XI API 访问权限

如需让 Google SecOps 检索监控数据,您需要创建一个具有 API 访问权限和只读权限的用户账号。

创建拥有只读权限的用户并授予 API 访问权限

  1. 使用管理员权限登录 Nagios XI 网页界面。
  2. 前往管理 > 管理用户。
  3. 点击添加新用户。
  4. 提供以下配置详细信息:
    • 用户名:输入一个描述性名称(例如 chronicle-integration)
    • 密码:输入安全密码
    • 名称:输入 Chronicle Integration User
    • 电子邮件地址:请输入有效的电子邮件地址
  5. 在安全设置部分中,配置以下内容:
    • 授权级别:选择用户
    • 可查看所有主机和服务:选中此选项
    • 只读权限:选中此选项
    • API 访问权限:选中此选项
  6. 取消选中以下选项:
    • 强制用户在下次登录时更改密码
    • 通过电子邮件发送用户账号信息
    • 创建为监控联系人
  7. 确保勾选账号已启用。
  8. 点击添加用户。

检索 API 密钥

  1. 在管理用户页面中,点击相应用户账号 (chronicle-integration)。
  2. 在用户账号设置页面中,找到 API 密钥字段。
  3. 复制 API 密钥值。

验证权限

如需验证账号是否具有所需权限,请执行以下操作:

  1. 使用 chronicle-integration 用户账号登录 Nagios XI。
  2. 依次前往首页 > 主机状态。
  3. 如果您可以查看所有受监控的主机和服务,则表示您拥有所需的权限。
  4. 如果您看不到主机或服务,请与管理员联系,以获取可查看所有主机和服务权限。

测试 API 访问权限

  • 在继续进行集成之前,请测试您的凭据:

    # Replace with your actual values
    NAGIOS_HOST="https://your-nagios-server.example.com"
    API_KEY="your-api-key"
    
    # Test API access - query host status
    curl -k "${NAGIOS_HOST}/nagiosxi/api/v1/objects/hoststatus?apikey=${API_KEY}&pretty=1"
    

    成功的响应会返回一个包含主机状态信息的 JSON 对象。

创建 Google Cloud Storage 存储桶

  1. 前往 Google Cloud 控制台。
  2. 选择您的项目或创建一个新项目。
  3. 在导航菜单中,依次前往 Cloud Storage > 存储桶。
  4. 点击创建存储分区。
  5. 提供以下配置详细信息:

    设置 值
    为存储桶命名 输入一个全局唯一的名称(例如 nagios-xi-logs)
    位置类型 根据您的需求进行选择(区域级、双区域级、多区域级)
    位置 选择位置(例如 us-central1)
    存储类别 标准(建议用于频繁访问的日志)
    访问权限控制 均匀(推荐)
    保护工具 可选:启用对象版本控制或保留政策
  6. 点击创建。

为 Cloud Run 函数创建服务账号

  1. 在 GCP 控制台中,依次前往 IAM 和管理 > 服务账号。
  2. 点击创建服务账号。
  3. 提供以下配置详细信息:
    • 服务账号名称:输入 nagios-logs-collector-sa
    • 服务账号说明:输入 Service account for Cloud Run function to collect Nagios XI logs
  4. 点击创建并继续。
  5. 在向此服务账号授予对项目的访问权限部分,添加以下角色:
    1. 点击选择角色。
    2. 搜索并选择 Storage Object Admin。
    3. 点击 + 添加其他角色。
    4. 搜索并选择 Cloud Run Invoker。
    5. 点击 + 添加其他角色。
    6. 搜索并选择 Cloud Functions Invoker。
  6. 点击继续。
  7. 点击完成。

授予对 GCS 存储桶的 IAM 权限

  1. 前往 Cloud Storage > 存储桶。
  2. 点击您的存储桶名称 (nagios-xi-logs)。
  3. 前往权限标签页。
  4. 点击授予访问权限。
  5. 提供以下配置详细信息:
    • 添加主账号:输入服务账号电子邮件地址 (nagios-logs-collector-sa@PROJECT_ID.iam.gserviceaccount.com)
    • 分配角色:选择 Storage Object Admin
  6. 点击保存。

创建 Pub/Sub 主题

  1. 在 GCP 控制台中,前往 Pub/Sub > 主题。
  2. 点击创建主题。
  3. 提供以下配置详细信息:
    • 主题 ID:输入 nagios-logs-trigger
    • 将其他设置保留为默认值
  4. 点击创建。

创建 Cloud Run 函数以收集日志

Cloud Run 函数将由来自 Cloud Scheduler 的 Pub/Sub 消息触发,以从 Nagios XI REST API 提取日志并将其写入 GCS。

  1. 在 GCP 控制台中,前往 Cloud Run。
  2. 点击创建服务。
  3. 选择函数(使用内嵌编辑器创建函数)。
  4. 在配置部分中,提供以下配置详细信息:

    设置 值
    Service 名称 nagios-logs-collector
    区域 选择与您的 GCS 存储桶匹配的区域(例如 us-central1)
    运行时 选择 Python 3.12 或更高版本
  5. 在触发器(可选)部分中:

    1. 点击 + 添加触发器。
    2. 选择 Cloud Pub/Sub。
    3. 在选择 Cloud Pub/Sub 主题中,选择 nagios-logs-trigger。
    4. 点击保存。
  6. 在身份验证部分中:

    1. 选择需要进行身份验证。
    2. 检查 Identity and Access Management (IAM)。
  7. 向下滚动并展开容器、网络、安全性。

  8. 前往安全性标签页:

    • 服务账号:选择 nagios-logs-collector-sa
  9. 前往容器标签页:

    1. 点击变量和密钥。
    2. 为每个环境变量点击 + 添加变量:
    变量名称 示例值 说明
    GCS_BUCKET nagios-xi-logs GCS 存储桶名称
    GCS_PREFIX nagios-xi 日志文件的前缀
    STATE_KEY nagios-xi/state.json 状态文件路径
    NAGIOS_BASE_URL https://your-nagios-server.example.com Nagios XI 基础网址
    NAGIOS_API_KEY your-api-key Nagios XI API 密钥
    MAX_RECORDS 1000 每次运行时每个端点的记录数上限
    PAGE_SIZE 200 每个 API 页面的记录数
    LOOKBACK_HOURS 24 初始回溯期
  10. 在变量和密钥部分中,向下滚动到请求:

    • 请求超时:输入 600 秒(10 分钟)
  11. 前往设置标签页:

    • 在资源部分中:
      • 内存:选择 512 MiB 或更高
      • CPU:选择 1
  12. 在修订版本伸缩部分中:

    • 实例数下限:输入 0
    • 实例数上限:输入 100
  13. 点击创建。

  14. 等待服务创建完成(1-2 分钟)。

  15. 创建服务后,内嵌代码编辑器会自动打开。

添加函数代码

  1. 在入口点字段中输入 main。
  2. 在内嵌代码编辑器中,创建两个文件:

    • main.py:

      import functions_framework
      from google.cloud import storage
      import json
      import os
      import urllib3
      from datetime import datetime, timezone, timedelta
      import time
      
      http = urllib3.PoolManager(
        timeout=urllib3.Timeout(connect=5.0, read=30.0),
        retries=False,
      )
      
      storage_client = storage.Client()
      
      GCS_BUCKET = os.environ.get('GCS_BUCKET')
      GCS_PREFIX = os.environ.get('GCS_PREFIX', 'nagios-xi')
      STATE_KEY = os.environ.get('STATE_KEY', 'nagios-xi/state.json')
      NAGIOS_BASE_URL = os.environ.get('NAGIOS_BASE_URL', '').rstrip('/')
      NAGIOS_API_KEY = os.environ.get('NAGIOS_API_KEY')
      MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '1000'))
      PAGE_SIZE = int(os.environ.get('PAGE_SIZE', '200'))
      LOOKBACK_HOURS = int(os.environ.get('LOOKBACK_HOURS', '24'))
      
      ENDPOINTS = [
        'hoststatus',
        'servicestatus',
        'statehistory',
        'logentries',
      ]
      
      @functions_framework.cloud_event
      def main(cloud_event):
        if not all([GCS_BUCKET, NAGIOS_BASE_URL, NAGIOS_API_KEY]):
          print('Error: Missing required environment variables')
          return
      
        try:
          bucket = storage_client.bucket(GCS_BUCKET)
          state = load_state(bucket)
          now = datetime.now(timezone.utc)
      
          if isinstance(state, dict) and state.get('last_event_time'):
            try:
              last_val = state['last_event_time']
              if last_val.endswith('Z'):
                last_val = last_val[:-1] + '+00:00'
              last_time = datetime.fromisoformat(last_val)
              last_time = last_time - timedelta(minutes=2)
            except Exception as e:
              print(f"Warning: Could not parse last_event_time: {e}")
              last_time = now - timedelta(hours=LOOKBACK_HOURS)
          else:
            last_time = now - timedelta(hours=LOOKBACK_HOURS)
      
          print(f"Fetching logs from {last_time.isoformat()} to {now.isoformat()}")
      
          start_ts = int(last_time.timestamp())
          end_ts = int(now.timestamp())
      
          all_records = []
          newest_time = None
      
          for endpoint in ENDPOINTS:
            records, endpoint_newest = fetch_nagios_endpoint(
              endpoint, start_ts, end_ts
            )
            for record in records:
              record['_nagios_endpoint'] = endpoint
            all_records.extend(records)
      
            if endpoint_newest:
              if newest_time is None or endpoint_newest > newest_time:
                newest_time = endpoint_newest
      
          if not all_records:
            print("No new records found.")
            save_state(bucket, now.isoformat())
            return
      
          timestamp = now.strftime('%Y%m%d_%H%M%S')
          object_key = f"{GCS_PREFIX}/nagios_logs_{timestamp}.ndjson"
          blob = bucket.blob(object_key)
      
          ndjson = '\n'.join(
            [json.dumps(r, ensure_ascii=False, default=str) for r in all_records]
          ) + '\n'
          blob.upload_from_string(ndjson, content_type='application/x-ndjson')
      
          print(f"Wrote {len(all_records)} records to gs://{GCS_BUCKET}/{object_key}")
      
          if newest_time:
            save_state(bucket, newest_time)
          else:
            save_state(bucket, now.isoformat())
      
          print(f"Successfully processed {len(all_records)} records")
      
        except Exception as e:
          print(f'Error processing logs: {str(e)}')
          raise
      
      def fetch_nagios_endpoint(endpoint, start_ts, end_ts):
        base_url = f"{NAGIOS_BASE_URL}/nagiosxi/api/v1/objects/{endpoint}"
      
        records = []
        newest_time = None
        start_index = 0
        page_num = 0
        backoff = 1.0
      
        while True:
          page_num += 1
      
          if len(records) >= MAX_RECORDS:
            print(f"{endpoint}: Reached max_records limit ({MAX_RECORDS})")
            break
      
          remaining = min(PAGE_SIZE, MAX_RECORDS - len(records))
          params = [
            f"apikey={NAGIOS_API_KEY}",
            f"starttime={start_ts}",
            f"endtime={end_ts}",
            f"records={remaining}",
            f"start={start_index}",
          ]
          url = f"{base_url}?{'&'.join(params)}"
      
          try:
            response = http.request('GET', url)
      
            if response.status == 429:
              retry_after = int(response.headers.get('Retry-After', str(int(backoff))))
              print(f"{endpoint}: Rate limited (429). Retrying after {retry_after}s...")
              time.sleep(retry_after)
              backoff = min(backoff * 2, 30.0)
              continue
      
            backoff = 1.0
      
            if response.status != 200:
              print(f"{endpoint}: HTTP Error {response.status}")
              response_text = response.data.decode('utf-8')
              print(f"Response body: {response_text}")
              break
      
            data = json.loads(response.data.decode('utf-8'))
      
            record_count = data.get('recordcount', 0)
            result_key = get_result_key(endpoint)
            page_results = data.get(result_key, [])
      
            if not page_results:
              print(f"{endpoint}: No more results at offset {start_index}")
              break
      
            print(f"{endpoint} page {page_num}: Retrieved {len(page_results)} events")
            records.extend(page_results)
      
            for event in page_results:
              try:
                event_time = event.get('state_time') or event.get('status_update_time') or event.get('entry_time')
                if event_time:
                  if newest_time is None or event_time > newest_time:
                    newest_time = event_time
              except Exception as e:
                print(f"Warning: Could not parse event time: {e}")
      
            if len(page_results) < remaining:
              print(f"{endpoint}: Reached last page (returned {len(page_results)} < {remaining})")
              break
      
            start_index += len(page_results)
      
          except Exception as e:
            print(f"{endpoint}: Error fetching logs: {e}")
            break
      
        print(f"{endpoint}: Retrieved {len(records)} total records from {page_num} pages")
        return records, newest_time
      
      def get_result_key(endpoint):
        key_map = {
          'hoststatus': 'hoststatus',
          'servicestatus': 'servicestatus',
          'statehistory': 'statehistory',
          'logentries': 'logentry',
        }
        return key_map.get(endpoint, endpoint)
      
      def load_state(bucket):
        try:
          blob = bucket.blob(STATE_KEY)
          if blob.exists():
            return json.loads(blob.download_as_text())
        except Exception as e:
          print(f"Warning: Could not load state: {e}")
        return {}
      
      def save_state(bucket, last_event_time_iso):
        try:
          state = {
            'last_event_time': last_event_time_iso,
            'last_run': datetime.now(timezone.utc).isoformat()
          }
          blob = bucket.blob(STATE_KEY)
          blob.upload_from_string(
            json.dumps(state, indent=2),
            content_type='application/json'
          )
          print(f"Saved state: last_event_time={last_event_time_iso}")
        except Exception as e:
          print(f"Warning: Could not save state: {e}")
      
    • requirements.txt:

      functions-framework==3.*
      google-cloud-storage==2.*
      urllib3>=2.0.0
      
  3. 点击部署以保存和部署函数。

  4. 等待部署完成(2-3 分钟)。

创建 Cloud Scheduler 作业

  1. 在 GCP 控制台中,前往 Cloud Scheduler。
  2. 点击创建作业。
  3. 提供以下配置详细信息:

    设置 值
    名称 nagios-logs-collector-hourly
    区域 选择与 Cloud Run 函数相同的区域
    频率 0 * * * *(每小时整点)
    时区 选择时区(建议选择世界协调时间 [UTC])
    目标类型 Pub/Sub
    主题 选择nagios-logs-trigger
    消息正文 {}(空 JSON 对象)
  4. 点击创建。

时间表频率选项

根据日志量和延迟时间要求选择频次:

频率 Cron 表达式 使用场景
每隔 5 分钟 */5 * * * * 大容量、低延迟
每隔 15 分钟 */15 * * * * 搜索量中等
每小时 0 * * * * 标准(推荐)
每 6 小时 0 */6 * * * 低容量、批处理
每天 0 0 * * * 历史数据收集

测试集成

  1. 在 Cloud Scheduler 控制台中,找到您的作业 (nagios-logs-collector-hourly)。
  2. 点击强制运行以手动触发作业。
  3. 等待几秒钟。
  4. 前往 Cloud Run > 服务。
  5. 点击 nagios-logs-collector。
  6. 点击日志标签页。
  7. 验证函数是否已成功执行。查找:

    Fetching logs from YYYY-MM-DDTHH:MM:SS+00:00 to YYYY-MM-DDTHH:MM:SS+00:00
    hoststatus page 1: Retrieved X events
    servicestatus page 1: Retrieved X events
    statehistory page 1: Retrieved X events
    logentries page 1: Retrieved X events
    Wrote X records to gs://nagios-xi-logs/nagios-xi/nagios_logs_YYYYMMDD_HHMMSS.ndjson
    Successfully processed X records
    
  8. 前往 Cloud Storage > 存储桶。

  9. 点击 nagios-xi-logs。

  10. 转到 nagios-xi/ 文件夹。

  11. 验证是否已创建带有当前时间戳的新 .ndjson 文件。

如果您在日志中看到错误,请执行以下操作:

  • HTTP 401:验证 NAGIOS_API_KEY 环境变量是否正确,以及用户是否已启用 API 访问权限
  • HTTP 403:验证用户账号是否具有可查看所有主机和服务权限
  • HTTP 429:速率限制 - 函数将自动重试并退避
  • 缺少环境变量:验证是否已在 Cloud Run 函数配置中设置所有必需的变量

检索 Google SecOps 服务账号

  1. 前往 SIEM 设置 > Feed。
  2. 点击添加新 Feed。
  3. 点击配置单个 Feed。
  4. 在 Feed 名称字段中,输入 Feed 的名称(例如 Nagios XI Logs GCS)。
  5. 选择 Google Cloud Storage V2 作为来源类型。
  6. 选择 Nagios 作为日志类型。
  7. 点击获取服务账号。
  8. 系统会显示一个唯一的服务账号电子邮件地址。例如:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  9. 复制此电子邮件地址,以便在下一步中使用。

  10. 点击下一步。

  11. 为以下输入参数指定值:

    • 存储桶网址:输入带有前缀路径的 GCS 存储桶 URI:

      gs://nagios-xi-logs/nagios-xi/
      
    • 来源删除选项:根据您的偏好选择删除选项:

      • 永不:转移后永不删除任何文件(建议用于测试)。
      • 删除已转移的文件:在成功转移后删除文件。
      • 删除已转移的文件和空目录:在成功转移后删除文件和空目录。

    • 文件存在时间上限:包含过去指定天数内修改过的文件(默认值为 180 天)

    • 资源命名空间:资源命名空间

    • 注入标签:要应用于此 Feed 中事件的标签

  12. 点击下一步。

  13. 在最终确定界面中查看新的新闻信息流配置,然后点击提交。

向 Google SecOps 服务账号授予 IAM 权限

  1. 前往 Cloud Storage > 存储桶。
  2. 点击 nagios-xi-logs。
  3. 前往权限标签页。
  4. 点击授予访问权限。
  5. 提供以下配置详细信息:
    • 添加主账号:粘贴 Google SecOps 服务账号电子邮件地址
    • 分配角色:选择 Storage Object Viewer
  6. 点击保存。

UDM 映射表

日志字段 UDM 映射 逻辑
src_ip has_principal 如果成功将 src_ip 合并到 principal.ip 和 principal.asset.ip,则设置为“true”
src_ip principal.asset.ip 直接复制的值
src_ip principal.ip 直接复制的值
jobid jobid_label.key 设置为“Jobid”
jobid jobid_label.value 直接复制值
jobid_label principal.resource.attribute.labels 从 jobid_label 合并
pid principal.process.pid 直接复制的值
ent metadata.product_event_type 直接复制的值
说明 metadata.description 直接复制值
msg_ip has_target 如果 msg_ip 与 target.ip 和 target.asset.ip 成功合并,则设置为“true”
msg_ip target.asset.ip 直接复制值
msg_ip target.ip 直接复制值
端口 target.port 直接复制并转换为整数的值
第 1 列 principal.asset.hostname 如果 ent == "SERVICE ALERT",则取自 column1 的值;如果 ent == "SERVICE NOTIFICATION",则取自 column2 的值
第 1 列 principal.hostname 如果 ent == "SERVICE ALERT",则为 column1 中的值;如果 ent == "SERVICE NOTIFICATION",则为 column2 中的值
第 1 列 principal.user.user_display_name 如果 ent == "SERVICE NOTIFICATION",则直接复制值
第 2 列 security_result.summary 如果 ent == "SERVICE ALERT" 且 column2 不为空,则为 column3 中的值;如果 ent == "SERVICE NOTIFICATION",则为 column3 中的值
第 3 列 security_result.summary 如果 ent == "SERVICE ALERT" 且 column3 不为空,则为 column3 中的值;否则,如果 ent == "SERVICE NOTIFICATION",则为 column3 中的值
column4 security_result.severity 如果 column4 位于 ["LOW", "MEDIUM", "HIGH", "CRITICAL"] 中,且 ent 位于 ["SERVICE NOTIFICATION", "SERVICE ALERT"] 中,则直接复制值
column4 security_result.severity_details 如果 column4 不在 ["LOW", "MEDIUM", "HIGH", "CRITICAL"] 中,且 ent 在 ["SERVICE NOTIFICATION", "SERVICE ALERT"] 中,则直接复制值
has_principal metadata.event_type 如果 has_principal 和 has_target 为 true,则设置为“NETWORK_CONNECTION”;否则,如果 has_principal 为 true,则设置为“STATUS_UPDATE”;否则,如果 has_principal_user 为 true,则设置为“USER_UNCATEGORIZED”;否则,设置为“GENERIC_EVENT”
has_target metadata.event_type
has_principal_user metadata.event_type
security_result event.idm.read_only_udm.security_result 从 security_result 合并
元数据 event.idm.read_only_udm.metadata 已从“元数据”重命名
目标 event.idm.read_only_udm.target 已从目标重命名
主体 event.idm.read_only_udm.principal 已重命名为“principal”
网络 event.idm.read_only_udm.network 从网络重命名
metadata.product_name metadata.product_name 设置为“NAGIOS”
metadata.vendor_name metadata.vendor_name 设置为“NAGIOS”

更新日志

查看相应解析器的更新日志

需要更多帮助?获得社区成员和 Google SecOps 专业人士的解答。