收集 Akamai SIEM 连接器日志

解析器版本:2.0

支持的平台:

本指南介绍了如何将 Akamai SIEM 连接器日志注入到 Google Security Operations。Akamai SIEM 集成通过 SIEM 集成 API 以 JSON 格式近乎实时地提供来自 Akamai 平台的安全事件。这些事件包括来自 Web 应用防火墙 (WAF)、Bot Manager 和其他 Akamai 安全产品的数据。由于 Akamai SIEM Integration API 基于拉取,因此计划函数会定期提取事件并将其写入对象存储,然后 Google SecOps 会提取这些事件。您可以使用以下两种受支持的方法之一来配置提取:

  • 方法 1:Cloud Storage。Cloud Run 函数会定期从 Akamai SIEM Integration API 中拉取事件,并将其写入 Cloud Storage 存储桶,供 Google SecOps 提取。
  • 方法 2:Amazon S3。AWS Lambda 函数会定期从 Akamai SIEM 集成 API 拉取事件,并将其写入 Amazon S3 存储桶,供 Google SecOps 提取。

准备工作

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

两种方法共有的步骤

  • Google SecOps 实例。
  • 通过管理 SIEM 用户角色对 Akamai Control Center 拥有特权访问权限。

方法 1 的其他前提条件 (Cloud Storage)

  • 启用了 Cloud Storage、Cloud Run、Pub/Sub 和 Cloud Scheduler API 的 Google Cloud 项目。
  • 创建和管理 Cloud Storage 存储分区、服务账号、Cloud Run 函数、Pub/Sub 主题和 Cloud Scheduler 作业的权限。

方法 2 的其他前提条件 (Amazon S3)

  • AWS(S3、Identity and Access Management、Lambda、EventBridge)的特权访问权限。

在 Akamai Control Center 中启用 SIEM 集成

  1. 登录 Akamai 控制中心
  2. 前往云安全部分下的安全配置
  3. 打开您要收集 SIEM 数据的安全配置(以及相应版本)。
  4. 点击高级设置,然后展开 SIEM 集成的数据收集
  5. 前往云安全部分下的安全配置
  6. 打开您要收集 SIEM 数据的安全配置(以及相应版本)。
  7. 点击高级设置,然后展开 SIEM 集成的数据收集
  8. 点击开启以启用 SIEM。
  9. 选择要导出哪些安全政策的数据:
    • 所有安全政策:针对违反安全配置中任何或所有安全政策的事件发送 SIEM 数据。
    • 特定安全政策:从下拉列表中选择一项或多项特定安全政策。
  10. 可选:如果您使用账号保护器,并希望包含未加密的用户名,请勾选包含用户名复选框。

  11. 可选:如果您想在 SIEM 事件中接收 JA4 指纹信息,请选中包含 JA4 客户端 TLS 指纹复选框。

  12. 可选:如果您想排除属于特定保护类型和操作的事件,请点击添加例外情况。选择您不希望 SIEM 收集的保护措施和相关操作。

  13. 可选:如果您想在 SIEM 事件中接收 JA4 指纹信息,请选中包含 JA4 客户端 TLS 指纹复选框。

  14. 可选:如果您想排除属于特定保护类型和操作的事件,请点击添加例外情况。选择您不希望 SIEM 收集的保护措施和相关操作。

  15. 点击保存

  16. 复制并保存“SIEM 集成”部分中的安全配置 ID (configId)。您需要此值来进行 Lambda 配置。

  17. 复制并保存“SIEM 集成”部分中的安全配置 ID (configId)。您需要此值来进行 Lambda 配置。

为 SIEM 集成创建 Akamai API 凭据

  1. 登录 Akamai 控制中心
  2. 依次前往账号管理 > 身份和访问权限 > 用户和 API 客户端
  3. 依次前往账号管理 > 身份和访问权限 > 用户和 API 客户端
  4. 点击创建 API 客户端
  5. 点击高级以创建高级 API 客户端。
  6. 点击高级以创建高级 API 客户端。
  7. 提供以下配置详细信息:
    • 名称:输入一个描述性名称(例如 Google SecOps SIEM Poller)。
    • API 服务:选择 SIEM,并将访问权限级别设置为 READ-WRITE
    • 名称:输入一个描述性名称(例如 SecOps SIEM Poller)。
    • API 服务:选择 SIEM,并将访问权限级别设置为 READ-WRITE
  8. 点击创建 API 客户端
  9. 点击下载可将凭据保存为 .edgerc 文件,点击复制可复制凭据。请妥善保存以下值:
    • 客户端令牌
    • 客户端密钥 (Client Secret)
    • Access Token
    • 主机(例如 akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net

方法 1:Cloud Storage

使用此方法将 Akamai SIEM 连接器日志收集到 Cloud Storage 存储桶中。一个 Cloud Run 函数,由 Cloud Scheduler 通过 Pub/Sub 触发,定期从 Akamai SIEM 集成 API 中拉取事件,并将其写入存储桶,供 Google SecOps 提取。

创建 Cloud Storage 存储桶

  1. 前往 Google Cloud 控制台
  2. 选择您的项目或创建新项目。
  3. 前往 Cloud Storage > 存储分区
  4. 点击创建存储分区
  5. 提供以下配置详细信息:
    • 指定存储桶的名称:输入一个全局唯一的名称(例如 akamai-siem-logs)。
    • 位置类型:根据您的需求进行选择(区域、双区域或多区域)。
    • 位置:选择位置(例如 us-central1)。
    • 存储类别:选择标准
    • 访问权限控制:选择统一
  6. 点击创建

为 Cloud Run 函数创建服务账号

  1. 前往 IAM 和管理 > 服务账号
  2. 点击创建服务账号
  3. 提供以下配置详细信息:
    • 服务账号名称:输入 akamai-siem-collector-sa
    • 服务账号说明:输入 Service account for Cloud Run function to collect Akamai SIEM Connector logs
  4. 点击创建并继续
  5. 授予以下角色:
    • Storage Object Admin:将事件写入存储桶并管理状态文件。
    • Cloud Run Invoker:允许 Pub/Sub 调用该函数。
  6. 点击继续,然后点击完成

向服务账号授予对相应存储桶的访问权限

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

创建 Pub/Sub 主题

  1. 依次前往 Pub/Sub > 主题
  2. 点击创建主题
  3. 主题 ID 字段中,输入 akamai-siem-trigger
  4. 点击创建

创建 Cloud Run 函数以收集日志

Cloud Run 函数由来自 Cloud Scheduler 的 Pub/Sub 消息触发。它使用 Akamai EdgeGrid 凭据对请求进行签名,从 SIEM 集成 API 中提取事件,并将这些事件写入存储桶。

  1. 前往 Cloud Run
  2. 点击创建服务
  3. 选择函数以使用内嵌代码编辑器。
  4. 提供以下配置详细信息:
    • 服务名称:输入 akamai-siem-collector
    • 区域:选择与您的存储桶匹配的区域(例如 us-central1)。
    • 运行时:选择 Python 3.12 或更高版本。
  5. 触发器部分中:
    1. 点击添加触发器
    2. 选择 Pub/Sub
    3. 选择 Pub/Sub 主题部分,选择 akamai-siem-trigger
    4. 点击保存
  6. 身份验证部分中,选择需要进行身份验证
  7. 展开容器、网络、安全性,然后在安全性标签页上将服务账号设置为 akamai-siem-collector-sa
  8. 容器 > 变量和密钥标签页中,添加以下环境变量:

    变量名称 示例值
    GCS_BUCKET akamai-siem-logs
    GCS_PREFIX akamai-siem/
    STATE_KEY akamai-siem/state.json
    AKAMAI_HOST akab-xxxxxxxx.luna.akamaiapis.net
    AKAMAI_CLIENT_TOKEN akab-client-token-xxxx
    AKAMAI_CLIENT_SECRET your-client-secret
    AKAMAI_ACCESS_TOKEN akab-access-token-xxxx
    AKAMAI_CONFIG_ID 12345
    LIMIT 10000
    WINDOW_SECONDS 3600
    HTTP_TIMEOUT 110
    MAX_PAGES 20
  9. 请求超时设置为 600 秒,并将内存设置为 512 MiB 或更高。

  10. 点击创建。服务创建完成后,系统会打开内嵌代码编辑器。

添加函数代码

  1. 函数入口点字段中,输入 main
  2. 在内嵌代码编辑器中,将 main.py 的内容替换为以下代码:

    import functions_framework
    from google.cloud import storage
    import json
    import os
    import time
    import uuid
    import requests
    from akamai.edgegrid import EdgeGridAuth
    
    storage_client = storage.Client()
    
    GCS_BUCKET = os.environ.get('GCS_BUCKET')
    GCS_PREFIX = os.environ.get('GCS_PREFIX', 'akamai-siem/')
    STATE_KEY = os.environ.get('STATE_KEY', 'akamai-siem/state.json')
    AKAMAI_HOST = os.environ.get('AKAMAI_HOST')
    CLIENT_TOKEN = os.environ.get('AKAMAI_CLIENT_TOKEN')
    CLIENT_SECRET = os.environ.get('AKAMAI_CLIENT_SECRET')
    ACCESS_TOKEN = os.environ.get('AKAMAI_ACCESS_TOKEN')
    CONFIG_ID = os.environ.get('AKAMAI_CONFIG_ID')
    LIMIT = int(os.environ.get('LIMIT', '10000'))
    WINDOW_SEC = int(os.environ.get('WINDOW_SECONDS', '3600'))
    HTTP_TIMEOUT = int(os.environ.get('HTTP_TIMEOUT', '110'))
    MAX_PAGES = int(os.environ.get('MAX_PAGES', '20'))
    
    def _session():
        """Create a requests session that signs requests with Akamai EdgeGrid."""
        session = requests.Session()
        session.auth = EdgeGridAuth(
            client_token=CLIENT_TOKEN,
            client_secret=CLIENT_SECRET,
            access_token=ACCESS_TOKEN,
        )
        return session
    
    def _load_state(bucket):
        """Load the saved offset from GCS."""
        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, state):
        """Persist the offset to GCS for the next run."""
        try:
            bucket.blob(STATE_KEY).upload_from_string(
                json.dumps(state, separators=(',', ':')),
                content_type='application/json',
            )
        except Exception as e:
            print(f'Warning: could not save state: {e}')
    
    def _fetch(session, offset, from_ts):
        """Fetch one batch of events. Use offset when available, otherwise a time window.
    
        The Akamai SIEM API does not allow combining the offset parameter with from/to.
        """
        url = f'https://{AKAMAI_HOST}/siem/v1/configs/{CONFIG_ID}'
        params = {'limit': LIMIT}
        if offset:
            params['offset'] = offset
        else:
            params['from'] = int(from_ts)
        resp = session.get(
            url, params=params, timeout=HTTP_TIMEOUT,
            headers={'Accept': 'application/json'},
        )
        resp.raise_for_status()
        return resp.text
    
    def _parse_ndjson(text):
        """Split the NDJSON response into event lines and the trailing context object.
    
        Each line is a JSON security event; the last line is a context object that
        contains the offset to use for the next request.
        """
        lines = [ln for ln in text.splitlines() if ln.strip()]
        if not lines:
            return [], {}
        try:
            context = json.loads(lines[-1])
        except ValueError:
            return lines, {}
        if isinstance(context, dict) and 'offset' in context:
            return lines[:-1], context
        return lines, {}
    
    def _write_events(bucket, event_lines, page_num):
        """Write a batch of raw JSON events to GCS, one event per line."""
        ts_path = time.strftime('%Y/%m/%d', time.gmtime())
        uniq = f'{int(time.time() * 1e6)}_{uuid.uuid4().hex[:8]}'
        key = f'{GCS_PREFIX}{ts_path}/akamai_siem_p{page_num:03d}_{uniq}.json'
        bucket.blob(key).upload_from_string(
            '\n'.join(event_lines),
            content_type='application/x-ndjson',
        )
        return key
    
    @functions_framework.cloud_event
    def main(cloud_event):
        """Triggered by Pub/Sub to pull Akamai SIEM events and write them to GCS."""
        if not all([GCS_BUCKET, AKAMAI_HOST, CLIENT_TOKEN, CLIENT_SECRET,
                    ACCESS_TOKEN, CONFIG_ID]):
            print('Error: missing required environment variables')
            return
    
        bucket = storage_client.bucket(GCS_BUCKET)
        state = _load_state(bucket)
        offset = state.get('offset')
        from_ts = float(state.get('last_to_ts') or (time.time() - WINDOW_SEC))
    
        session = _session()
        pages = 0
        total = 0
        try:
            while pages < MAX_PAGES:
                text = _fetch(session, offset, from_ts)
                events, context = _parse_ndjson(text)
                if events:
                    _write_events(bucket, events, pages + 1)
                    total += len(events)
                new_offset = context.get('offset')
                if new_offset:
                    offset = new_offset
                pages += 1
                # Stop when this window is drained (fewer than LIMIT events returned).
                if not new_offset or len(events) < LIMIT:
                    break
    
            state['offset'] = offset
            state['last_to_ts'] = time.time()
            state['last_successful_run'] = time.time()
            _save_state(bucket, state)
            print(f'Retrieved {total} events across {pages} pages')
        except Exception as e:
            print(f'Error collecting Akamai SIEM logs: {e}')
            raise
    
  3. 创建一个包含以下内容的 requirements.txt 文件:

    functions-framework==3.*
    google-cloud-storage==2.*
    requests>=2.31.0
    edgegrid-python>=1.3.1
    
  4. 点击部署,然后等待部署完成。

创建 Cloud Scheduler 作业

  1. 前往 Cloud Scheduler
  2. 点击创建作业
  3. 提供以下配置详细信息:
    • 名称:输入 akamai-siem-collector-5min
    • 区域:选择与 Cloud Run 函数相同的区域。
    • 频率:输入 */5 * * * *(每 5 分钟一次)。
    • 时区:选择 UTC
    • 目标类型:选择 Pub/Sub
    • 主题:选择 akamai-siem-trigger
    • 消息正文:输入 {}
  4. 点击创建
  5. 如需验证设置,请点击作业中的强制运行,然后检查 Cloud Run 日志和存储桶,确认事件已写入。

在 Google SecOps 中配置 Feed 以从 Cloud Storage 注入数据

  1. 依次前往 SIEM 设置 > Feed
  2. 点击添加新 Feed
  3. 在下一页上,点击配置单个 Feed
  4. Feed 名称字段中,输入 Feed 的名称(例如 Akamai SIEM Connector GCS)。
  5. 选择 Cloud Storage V2 作为来源类型
  6. 选择 Akamai SIEM 连接器作为日志类型
  7. 点击Chronicle 服务账号字段旁边的获取服务账号,然后复制服务账号电子邮件地址。
  8. 点击下一步
  9. 为以下输入参数指定值:
    • 存储分区 URI:输入 gs://akamai-siem-logs/akamai-siem/。此网址必须以尾随正斜杠 (/) 结尾。
    • 来源删除选项:根据您的偏好选择删除选项。
    • 文件存在时间上限:包含在过去指定天数内修改的文件。默认值为 180 天。
  10. 点击下一步
  11. 最终确定界面中查看新的 Feed 配置,然后点击提交

向 Google SecOps 服务账号授予对相应存储桶的访问权限

  1. 前往 Cloud Storage > 存储分区
  2. 点击您的存储桶名称。
  3. 前往权限标签页。
  4. 点击授予访问权限
  5. 提供以下配置详细信息:
    • 添加主账号:粘贴从 Feed 设置中复制的 Google SecOps 服务账号电子邮件地址。
    • 分配角色:选择 Storage Object Viewer(如果您选择了删除选项,则选择 Storage Object Admin)。
  6. 点击保存

方法 2:Amazon S3

使用此方法可使用 AWS Lambda 和 EventBridge 将 Akamai SIEM 连接器日志收集到 Amazon S3 存储桶中。

为 Google SecOps 配置 AWS S3 存储桶和 IAM

  1. 按照以下用户指南创建 Amazon S3 存储桶创建存储桶
  2. 保存存储桶名称区域以供日后参考(例如 akamai-siem-logs)。
  3. 按照以下用户指南创建用户创建 IAM 用户
  4. 选择创建的用户
  5. 选择安全凭据标签页。
  6. 访问密钥部分中,点击创建访问密钥
  7. 选择第三方服务作为使用情形
  8. 点击下一步
  9. 可选:添加说明标记。
  10. 点击创建访问密钥
  11. 点击下载 .csv 文件以保存访问密钥密钥,供日后参考。
  12. 点击完成
  13. 选择权限标签页。
  14. 权限政策部分中,点击添加权限
  15. 选择添加权限
  16. 选择直接附加政策
  17. 搜索 AmazonS3FullAccess 政策。
  18. 选择相应政策。
  19. 点击下一步
  20. 点击添加权限

为 S3 上传配置 IAM 政策和角色

  1. 在 AWS 控制台中,依次前往 IAM > 政策 > 创建政策 > JSON 标签页
  2. 复制并粘贴以下政策。
  3. 政策 JSON(将 akamai-siem-logs 替换为您的存储桶名称):

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowPutObjects",
                "Effect": "Allow",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::akamai-siem-logs/*"
            },
            {
                "Sid": "AllowGetStateObject",
                "Effect": "Allow",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::akamai-siem-logs/akamai-siem/state.json"
            }
        ]
    }
    
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowPutObjects",
                "Effect": "Allow",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::akamai-siem-logs/*"
            },
            {
                "Sid": "AllowGetStateObject",
                "Effect": "Allow",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::akamai-siem-logs/akamai-siem/state.json"
            }
        ]
    }
    
  4. 点击下一步

  5. 输入政策名称 AkamaiSIEMtoS3Policy,然后点击创建政策

  6. 依次前往 IAM > 角色 > 创建角色

  7. 选择 AWS 服务

  8. 选择 Lambda 作为用例。

  9. 点击下一步

  10. 搜索并选择政策 AkamaiSIEMtoS3Policy

  11. 搜索并选择政策 AkamaiSIEMtoS3Policy

  12. 点击下一步

  13. 输入角色名称 AkamaiSIEMtoS3Role,然后点击创建角色

创建 Lambda 函数

  1. AWS 控制台中,依次前往 Lambda > 函数 > 创建函数
  2. 点击从头开始创作
  3. 提供以下配置详细信息:

    设置
    名称 AkamaiSIEMtoS3Function
    运行时 Python 3.13
    架构 x86_64
    执行角色 使用现有角色
    现有角色 AkamaiSIEMtoS3Role
    设置
    名称 AkamaiSIEMtoS3Function
    运行时 Python 3.13
    架构 x86_64
    执行角色 使用现有角色
    现有角色 AkamaiSIEMtoS3Role
  4. 点击创建函数

  5. 创建函数后,打开 Code 标签页,删除存根并粘贴以下代码:

    import json
    import boto3
    import os
    import urllib3
    import hmac
    import hashlib
    import base64
    from datetime import datetime
    from urllib.parse import urlparse, urljoin
    ```python
    import json
    import boto3
    import os
    import urllib3
    import hmac
    import hashlib
    import base64
    from datetime import datetime
    from urllib.parse import urlparse, urljoin
    
    # Configuration from environment variables
    S3_BUCKET = os.environ['S3_BUCKET']
    S3_PREFIX = os.environ.get('S3_PREFIX', 'akamai-siem/')
    STATE_KEY = os.environ.get('STATE_KEY', 'akamai-siem/state.json')
    # Configuration from environment variables
    S3_BUCKET = os.environ['S3_BUCKET']
    S3_PREFIX = os.environ.get('S3_PREFIX', 'akamai-siem/')
    STATE_KEY = os.environ.get('STATE_KEY', 'akamai-siem/state.json')
    
    AKAMAI_HOST = os.environ['AKAMAI_HOST']
    AKAMAI_CLIENT_TOKEN = os.environ['AKAMAI_CLIENT_TOKEN']
    AKAMAI_CLIENT_SECRET = os.environ['AKAMAI_CLIENT_SECRET']
    AKAMAI_ACCESS_TOKEN = os.environ['AKAMAI_ACCESS_TOKEN']
    AKAMAI_CONFIG_IDS = os.environ['AKAMAI_CONFIG_IDS'].split(',')
    AKAMAI_HOST = os.environ['AKAMAI_HOST']
    AKAMAI_CLIENT_TOKEN = os.environ['AKAMAI_CLIENT_TOKEN']
    AKAMAI_CLIENT_SECRET = os.environ['AKAMAI_CLIENT_SECRET']
    AKAMAI_ACCESS_TOKEN = os.environ['AKAMAI_ACCESS_TOKEN']
    AKAMAI_CONFIG_IDS = os.environ['AKAMAI_CONFIG_IDS'].split(',')
    
    LIMIT = int(os.environ.get('LIMIT', '10000'))
    LIMIT = int(os.environ.get('LIMIT', '10000'))
    
    s3_client = boto3.client('s3')
    http = urllib3.PoolManager()
    s3_client = boto3.client('s3')
    http = urllib3.PoolManager()
    
    def load_state():
        """Load offset state from S3"""
        try:
            response = s3_client.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(response['Body'].read().decode('utf-8'))
        except s3_client.exceptions.NoSuchKey:
            return {}
        except Exception as e:
            print(f"Error loading state: {e}")
            return {}
    def load_state():
        """Load offset state from S3"""
        try:
            response = s3_client.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(response['Body'].read().decode('utf-8'))
        except s3_client.exceptions.NoSuchKey:
            return {}
        except Exception as e:
            print(f"Error loading state: {e}")
            return {}
    
    def save_state(state):
        """Save offset state to S3"""
        try:
            s3_client.put_object(
                Bucket=S3_BUCKET,
                Key=STATE_KEY,
                Body=json.dumps(state, indent=2).encode('utf-8'),
                ContentType='application/json'
            )
        except Exception as e:
            print(f"Error saving state: {e}")
    def save_state(state):
        """Save offset state to S3"""
        try:
            s3_client.put_object(
                Bucket=S3_BUCKET,
                Key=STATE_KEY,
                Body=json.dumps(state, indent=2).encode('utf-8'),
                ContentType='application/json'
            )
        except Exception as e:
            print(f"Error saving state: {e}")
    
    def make_edgegrid_auth_header(url, method='GET'):
        """Create EdgeGrid authentication header"""
        timestamp = datetime.utcnow().strftime('%Y%m%dT%H:%M:%S+0000')
        nonce = base64.b64encode(os.urandom(16)).decode('utf-8')
    
        parsed_url = urlparse(url)
        relative_url = parsed_url.path
        if parsed_url.query:
            relative_url += '?' + parsed_url.query
    
        auth_header = f'EG1-HMAC-SHA256 ' \
                     f'client_token={AKAMAI_CLIENT_TOKEN};' \
                     f'access_token={AKAMAI_ACCESS_TOKEN};' \
                     f'timestamp={timestamp};' \
                     f'nonce={nonce};'
    
        data_to_sign = '\t'.join([
            method,
            parsed_url.scheme,
            parsed_url.netloc,
            relative_url,
            '',  # Request body for GET
            '',  # No additional headers
        ])
    
        signing_key = hmac.new(
            AKAMAI_CLIENT_SECRET.encode('utf-8'),
            timestamp.encode('utf-8'),
            hashlib.sha256
        ).digest()
    
        auth_signature = base64.b64encode(
            hmac.new(
                signing_key,
                (data_to_sign + auth_header).encode('utf-8'),
                hashlib.sha256
            ).digest()
        ).decode('utf-8')
    
        return auth_header + f'signature={auth_signature}'
    def make_edgegrid_auth_header(url, method='GET'):
        """Create EdgeGrid authentication header"""
        timestamp = datetime.utcnow().strftime('%Y%m%dT%H:%M:%S+0000')
        nonce = base64.b64encode(os.urandom(16)).decode('utf-8')
    
        parsed_url = urlparse(url)
        relative_url = parsed_url.path
        if parsed_url.query:
            relative_url += '?' + parsed_url.query
    
        auth_header = f'EG1-HMAC-SHA256 ' \
                     f'client_token={AKAMAI_CLIENT_TOKEN};' \
                     f'access_token={AKAMAI_ACCESS_TOKEN};' \
                     f'timestamp={timestamp};' \
                     f'nonce={nonce};'
    
        data_to_sign = '\t'.join([
            method,
            parsed_url.scheme,
            parsed_url.netloc,
            relative_url,
            '',  # Request body for GET
            '',  # No additional headers
        ])
    
        signing_key = hmac.new(
            AKAMAI_CLIENT_SECRET.encode('utf-8'),
            timestamp.encode('utf-8'),
            hashlib.sha256
        ).digest()
    
        auth_signature = base64.b64encode(
            hmac.new(
                signing_key,
                (data_to_sign + auth_header).encode('utf-8'),
                hashlib.sha256
            ).digest()
        ).decode('utf-8')
    
        return auth_header + f'signature={auth_signature}'
    
    def fetch_akamai_events(config_id, offset=None):
        """Fetch events from Akamai SIEM API"""
        base_url = f'https://{AKAMAI_HOST}'
        endpoint = f'/siem/v1/configs/{config_id}'
    
        params = f'limit={LIMIT}'
        if offset:
            params += f'&offset={offset}'
    
        url = f'{base_url}{endpoint}?{params}'
    
        try:
            headers = {
                'Authorization': make_edgegrid_auth_header(url)
            }
    
            response = http.request('GET', url, headers=headers, timeout=120)
    
            if response.status != 200:
                print(f"Error response {response.status}: {response.data.decode('utf-8')}")
                return [], offset
    
            # Parse multi-JSON response (newline-delimited JSON)
            lines = response.data.decode('utf-8').strip().split('\n')
            events = []
            new_offset = offset
    
            for line in lines:
                if not line.strip():
                    continue
                try:
                    obj = json.loads(line)
    
                    # Check if this is offset context (metadata object with offset)
                    if 'offset' in obj and ('total' in obj or 'responseContext' in obj):
                        new_offset = obj.get('offset')
                        continue
    
                    # This is an event
                    events.append(obj)
                except json.JSONDecodeError as e:
                    print(f"Warning: Failed to parse line: {e}")
                    continue
    
            return events, new_offset
    
        except Exception as e:
            print(f"Error fetching events for config {config_id}: {e}")
            return [], offset
    def fetch_akamai_events(config_id, offset=None):
        """Fetch events from Akamai SIEM API"""
        base_url = f'https://{AKAMAI_HOST}'
        endpoint = f'/siem/v1/configs/{config_id}'
    
        params = f'limit={LIMIT}'
        if offset:
            params += f'&offset={offset}'
    
        url = f'{base_url}{endpoint}?{params}'
    
        try:
            headers = {
                'Authorization': make_edgegrid_auth_header(url)
            }
    
            response = http.request('GET', url, headers=headers, timeout=120)
    
            if response.status != 200:
                print(f"Error response {response.status}: {response.data.decode('utf-8')}")
                return [], offset
    
            # Parse multi-JSON response (newline-delimited JSON)
            lines = response.data.decode('utf-8').strip().split('\n')
            events = []
            new_offset = offset
    
            for line in lines:
                if not line.strip():
                    continue
                try:
                    obj = json.loads(line)
    
                    # Check if this is offset context (metadata object with offset)
                    if 'offset' in obj and ('total' in obj or 'responseContext' in obj):
                        new_offset = obj.get('offset')
                        continue
    
                    # This is an event
                    events.append(obj)
                except json.JSONDecodeError as e:
                    print(f"Warning: Failed to parse line: {e}")
                    continue
    
            return events, new_offset
    
        except Exception as e:
            print(f"Error fetching events for config {config_id}: {e}")
            return [], offset
    
    def lambda_handler(event, context):
        """Lambda handler - fetches Akamai events and writes to S3"""
        print(f"Starting Akamai SIEM fetch at {datetime.utcnow().isoformat()}Z")
    
        state = load_state()
        total_events = 0
    
        for config_id in AKAMAI_CONFIG_IDS:
            config_id = config_id.strip()
            if not config_id:
                continue
    
            print(f"Fetching events for config: {config_id}")
    
            current_offset = state.get(config_id)
            events, new_offset = fetch_akamai_events(config_id, current_offset)
    
            if events:
                print(f"Fetched {len(events)} events for config {config_id}")
    
                # Write events to S3 as newline-delimited JSON
                timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
                s3_key = f'{S3_PREFIX}{config_id}/{timestamp}.json'
    
                payload = '\n'.join(json.dumps(event) for event in events)
    
                try:
                    s3_client.put_object(
                        Bucket=S3_BUCKET,
                        Key=s3_key,
                        Body=payload.encode('utf-8'),
                        ContentType='application/json'
                    )
                    print(f"Wrote {len(events)} events to s3://{S3_BUCKET}/{s3_key}")
    
                    # Update offset only after successful write
                    if new_offset:
                        state[config_id] = new_offset
                        total_events += len(events)
                except Exception as e:
                    print(f"Error writing to S3: {e}")
            else:
                print(f"No new events for config {config_id}")
    
        # Save updated state
        save_state(state)
    
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': f'Successfully processed {total_events} events',
                'configs_processed': len(AKAMAI_CONFIG_IDS)
            })
        }
    

    def lambda_handler(event, context): """Lambda 处理程序 - 获取 Akamai 事件并写入 S3""" print(f"Starting Akamai SIEM fetch at {datetime.utcnow().isoformat()}Z")

    state = load_state()
    total_events = 0
    
    for config_id in AKAMAI_CONFIG_IDS:
        config_id = config_id.strip()
        if not config_id:
            continue
    
        print(f"Fetching events for config: {config_id}")
    
        current_offset = state.get(config_id)
        events, new_offset = fetch_akamai_events(config_id, current_offset)
    
        if events:
            print(f"Fetched {len(events)} events for config {config_id}")
    
            # Write events to S3 as newline-delimited JSON
            timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
            s3_key = f'{S3_PREFIX}{config_id}/{timestamp}.json'
    
            payload = '\n'.join(json.dumps(event) for event in events)
    
            try:
                s3_client.put_object(
                    Bucket=S3_BUCKET,
                    Key=s3_key,
                    Body=payload.encode('utf-8'),
                    ContentType='application/json'
                )
                print(f"Wrote {len(events)} events to s3://{S3_BUCKET}/{s3_key}")
    
                # Update offset only after successful write
                if new_offset:
                    state[config_id] = new_offset
                    total_events += len(events)
            except Exception as e:
                print(f"Error writing to S3: {e}")
        else:
            print(f"No new events for config {config_id}")
    
    # Save updated state
    save_state(state)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': f'Successfully processed {total_events} events',
            'configs_processed': len(AKAMAI_CONFIG_IDS)
        })
    }
    
  6. 点击部署以保存代码。

  7. 依次前往配置 > 环境变量 > 修改

  8. 依次前往配置 > 环境变量 > 修改

  9. 针对以下各项点击添加环境变量

环境变量 环境变量

示例值
S3_BUCKET akamai-siem-logs
S3_PREFIX akamai-siem/
STATE_KEY akamai-siem/state.json
AKAMAI_HOST akab-xxxxxxxx.luna.akamaiapis.net
AKAMAI_CLIENT_TOKEN your-client-token
AKAMAI_CLIENT_SECRET your-client-secret
AKAMAI_ACCESS_TOKEN your-access-token
AKAMAI_CONFIG_IDS 12345,67890
LIMIT 10000
示例值
S3_BUCKET akamai-siem-logs
S3_PREFIX akamai-siem/
STATE_KEY akamai-siem/state.json
AKAMAI_HOST akab-xxxxxxxx.luna.akamaiapis.net
AKAMAI_CLIENT_TOKEN your-client-token
AKAMAI_CLIENT_SECRET your-client-secret
AKAMAI_ACCESS_TOKEN your-access-token
AKAMAI_CONFIG_IDS 12345,67890
LIMIT 10000
  1. 点击保存
  2. 选择配置标签页。
  3. 常规配置面板中,点击修改
  4. 超时更改为 5 分钟(300 秒),然后点击保存
  5. 选择配置标签页。
  6. 常规配置面板中,点击修改
  7. 超时更改为 5 分钟(300 秒),然后点击保存

创建 EventBridge 计划

  1. 依次前往 Amazon EventBridge > 调度器 > 创建调度
  2. 提供以下配置详细信息:
    • 时间安排名称:输入 AkamaiSIEMtoS3-5min
    • 安排模式:选择周期性安排
    • 安排类型:选择基于费率的安排
    • 速率表达式:输入 5,然后选择分钟
    • 时间安排名称:输入 AkamaiSIEMtoS3-5min
    • 安排模式:选择周期性安排
    • 安排类型:选择基于费率的安排
    • 速率表达式:输入 5,然后选择分钟
  3. 点击下一步
  4. 提供以下配置详细信息:
    • 目标:选择 AWS Lambda 调用
    • Lambda 函数:选择 AkamaiSIEMtoS3Function
    • 目标:选择 AWS Lambda 调用
    • Lambda 函数:选择 AkamaiSIEMtoS3Function
  5. 点击下一步
  6. 点击下一步(跳过可选设置)。
  7. 检查并点击创建时间表

在 Google SecOps 中配置 Feed 以注入 Akamai SIEM 连接器日志

  1. 依次前往 SIEM 设置 > Feed
  2. 点击添加新 Feed
  3. 在下一页上,点击配置单个 Feed
  4. Feed 名称输入一个唯一名称(例如 Akamai SIEM Connector)。
  5. 点击添加新 Feed
  6. 在下一页上,点击配置单个 Feed
  7. Feed 名称输入一个唯一名称(例如 Akamai SIEM Connector)。
  8. 选择 Amazon S3 V2 作为来源类型
  9. 选择 Akamai SIEM 连接器作为日志类型
  10. 点击下一步,然后点击提交
  11. 点击下一步,然后点击提交
  12. 为以下输入参数指定值:

    • S3 URIs3://akamai-siem-logs/akamai-siem/
    • 源删除选项:根据您的偏好选择删除选项。
    • 文件存在时间上限:包含在过去指定天数内修改的文件。默认值为 180 天。
    • 访问密钥 ID:有权访问 S3 存储桶的用户访问密钥。
    • 私有访问密钥:有权访问 S3 存储桶的用户私有密钥。
    • 资产命名空间资产命名空间
    • 注入标签:应用于此 Feed 中事件的标签。
    • S3 URIs3://akamai-siem-logs/akamai-siem/
    • 源删除选项:根据您的偏好选择删除选项。
    • 文件存在时间上限:包含在过去指定天数内修改的文件。默认值为 180 天。
    • 访问密钥 ID:有权访问 S3 存储桶的用户访问密钥。
    • 私有访问密钥:有权访问 S3 存储桶的用户私有密钥。
    • 资产命名空间资产命名空间
    • 注入标签:应用于此 Feed 中事件的标签。
  13. 点击下一步,然后点击提交

UDM 映射表

日志字段 UDM 映射 逻辑
accept_language additional.fields 已合并
additional_policy_key additional.fields 已合并
attackData_label additional.fields 已合并
attackData_ruleActions_label additional.fields 已合并
attackData_ruleData_label additional.fields 已合并
attackData_ruleSelectors_label additional.fields 已合并
attackdata_ruleMessages_label additional.fields 已合并
attackdata_ruleTags_label additional.fields 已合并
attackdata_ruleVersions_label additional.fields 已合并
botData_label additional.fields 已合并
continent_label additional.fields 已合并
dur_label additional.fields 已合并
etag_label additional.fields 已合并
format_label additional.fields 已合并
httpMessage_query_label additional.fields 已合并
httpMessage_response_headers_label additional.fields 已合并
identity_label additional.fields 已合并
identityv2_label additional.fields 已合并
mime_version_label additional.fields 已合并
regionCode_label additional.fields 已合并
req_id_label additional.fields 已合并
requestHeaders_label additional.fields 已合并
responseSegment_label additional.fields 已合并
ruleAction_list additional.fields 已合并
ruleData_list additional.fields 已合并
ruleMessages_list additional.fields 已合并
ruleSelectors_list additional.fields 已合并
ruleTags_list additional.fields 已合并
ruleVersions_list additional.fields 已合并
rules_list additional.fields 已合并
same_site additional.fields 已合并
telemetryType_label additional.fields 已合并
Date metadata.event_timestamp 解析为 EEE, dd MMM yyyy HH:mm:ss ZZZ
expires metadata.event_timestamp 解析为 EEE, dd-MMM-yyyy HH:mm:ss ZZZ
httpMessage.start metadata.event_timestamp 解析为 UNIX
event_type metadata.event_type 直接映射
attackData.configId metadata.product_log_id 直接映射
version metadata.product_version 直接映射
protocol network.application_protocol 直接映射
MS-ASProtocolVersion network.application_protocol_version 直接映射
httpMessage.method network.http.method 直接映射
httpMessage.status network.http.response_code 已重命名/已映射
User-Agent network.http.user_agent 直接映射
httpMessage.bytes network.sent_bytes 已重命名/已映射
ASP.NET_SessionId network.session_id 直接映射
httpMessage.requestId network.session_id 直接映射
httpMessage.tls network.tls.version 直接映射
attackData.clientIP principal.asset.ip 已合并
attackData.clientIP principal.ip 已合并
geo.city principal.location.city 直接映射
geo.country principal.location.country_or_region 直接映射
geo.asn principal.location.name 直接映射
additional_content_Security_Policy principal.resource.attribute.labels 已合并
DeviceType principal.resource.name 直接映射
DeviceId principal.resource.product_object_id 直接映射
cache_label security_result.about.labels 已合并
accept_encoding_label security_result.about.resource.attribute.labels 已合并
accept_label security_result.about.resource.attribute.labels 已合并
connection_label security_result.about.resource.attribute.labels 已合并
content_encoding security_result.about.resource.attribute.labels 已合并
content_length_label security_result.about.resource.attribute.labels 已合并
content_type_label security_result.about.resource.attribute.labels 已合并
req_content_length_label security_result.about.resource.attribute.labels 已合并
req_content_type_label security_result.about.resource.attribute.labels 已合并
desc security_result.description 直接映射
CP_IsMobile_label security_result.detection_fields 已合并
X-BEServer_label security_result.detection_fields 已合并
X-CalculatedBETarget_label security_result.detection_fields 已合并
X-Content-Type-Options_label security_result.detection_fields 已合并
X-DiagInfo_label security_result.detection_fields 已合并
X-FEServer_label security_result.detection_fields 已合并
X-MS-BackOffDuration_label security_result.detection_fields 已合并
apiId_label security_result.detection_fields 已合并
backend_label security_result.detection_fields 已合并
p3p_label security_result.detection_fields 已合并
type_label security_result.detection_fields 已合并
vary_label security_result.detection_fields 已合并
attackData.policyId security_result.summary 直接映射
Host target.asset.hostname 直接映射
httpMessage.host target.asset.hostname 直接映射
path target.file.full_path 直接映射
Host target.hostname 直接映射
httpMessage.host target.hostname 直接映射
httpMessage.port target.port 直接映射
Cmd target.process.command_line 直接映射
httpMessage.path target.url 直接映射
User target.user.userid 直接映射
不适用 metadata.event_type 常量:GENERIC_EVENT
不适用 metadata.product_name 常量:AKAMAI_SIEM_CONNECTOR
不适用 metadata.vendor_name 常量:AKAMAI_SIEM_CONNECTOR
不适用 principal.resource.resource_type 常量:DEVICE

更新日志

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

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