收集 Oracle Cloud Infrastructure 审核日志
本文档介绍了如何使用 Amazon S3 将 Oracle Cloud Infrastructure 审核日志注入到 Google Security Operations。
准备工作
确保您满足以下前提条件:
- Google SecOps 实例。
- 具有创建和管理以下各项权限的 Oracle Cloud Infrastructure 账号:
- Service Connector Hub
- Oracle Functions
- 保险柜和密钥
- 动态群组和 IAM 政策
- 日志记录
- 具有以下权限的 AWS 账号:创建和管理:
- S3 存储分区
- IAM 用户和政策
创建 Amazon S3 存储桶
- 登录 AWS Management Console。
- 依次前往 S3 > 创建存储桶。
- 提供以下配置详细信息:
- 存储分区名称:输入一个唯一名称(例如
oci-audit-logs-bucket)。 - AWS 区域:选择一个区域(例如
us-east-1)。 - 保留其他选项的默认设置。
- 存储分区名称:输入一个唯一名称(例如
- 点击创建存储分区。
- 保存存储桶名称和区域以备后用。
在 AWS 中为 OCI Functions 创建 IAM 用户
- 登录 AWS Management Console。
- 依次前往 IAM > 用户 > 添加用户。
- 提供以下配置详细信息:
- 用户名:输入用户名(例如
oci-functions-s3-user)。 - 访问权限类型:选择访问密钥 - 以程序化方式访问。
- 用户名:输入用户名(例如
- 点击 Next: Permissions。
- 点击 Attach existing policies directly。
- 搜索并选择 AmazonS3FullAccess 政策。
- 点击 Next: Tags。
- 点击下一步:检查。
- 点击创建用户。
- 重要提示:在成功页面上,复制并保存以下凭据:
- 访问密钥 ID
- Secret 访问密钥
在 OCI Vault 中存储 AWS 凭据
为了安全地存储 AWS 凭据,您必须使用 Oracle Cloud Infrastructure Vault,而不是在函数代码中对其进行硬编码。
创建保险箱和主加密密钥
- 登录 Oracle Cloud 控制台。
- 前往身份和安全 > 保险库。
- 如果您没有保险库,请点击创建保险库。
- 提供以下配置详细信息:
- 在隔离区中创建:选择您的隔离区。
- 名称:输入一个名称(例如
oci-functions-vault)。
- 点击创建保险库。
- 创建保险柜后,点击保险柜名称即可将其打开。
- 在主加密密钥下,点击创建密钥。
- 提供以下配置详细信息:
- 保护模式:软件
- 名称:输入一个名称(例如
oci-functions-key)。 - 密钥形状:算法:AES
- 密钥形状:长度:256 位
- 点击创建密钥。
为 AWS 凭据创建 Secret
- 在保管库中,点击 Secret 下的创建 Secret。
- 为 AWS 访问密钥提供以下配置详细信息:
- 在隔离区中创建:选择您的隔离区。
- 名称:
aws-access-key - 说明:S3 的 AWS 访问密钥
- 加密密钥:选择您创建的主加密密钥。
- 密钥类型内容:纯文本
- Secret Contents:粘贴您的 AWS 访问密钥 ID。
- 点击创建 Secret。
- 复制并保存此密钥的 OCID(看起来像
ocid1.vaultsecret.oc1...)。 - 再次点击创建 Secret 以创建第二个 Secret。
- 为 AWS 密钥提供以下配置详细信息:
- 在隔离区中创建:选择您的隔离区。
- 名称:
aws-secret-key - 说明:S3 的 AWS Secret 密钥
- 加密密钥:选择相同的主加密密钥。
- Secret Type Contents:纯文本
- Secret 内容:粘贴您的 AWS 私有访问密钥。
- 点击创建 Secret。
- 复制并保存此密钥的 OCID。
为 OCI Functions 创建动态群组
- 登录 Oracle Cloud 控制台。
- 依次前往身份和安全 > 身份 > 动态群组。
- 点击创建动态群组。
提供以下配置详细信息:
- 名称:
oci-functions-dynamic-group - 说明:用于 OCI 函数访问保险柜密钥的动态群组
匹配规则:输入以下规则(将
<your_compartment_ocid>替换为您的区间 OCID):ALL {resource.type = 'fnfunc', resource.compartment.id = '<your_compartment_ocid>'}
- 名称:
点击创建。
创建用于保险柜访问权限的 IAM 政策
- 登录 Oracle Cloud 控制台。
- 依次前往身份和安全 > 身份 > 政策。
- 选择要在其中创建政策的区间。
- 点击创建政策。
提供以下配置详细信息:
- 名称:
oci-functions-vault-access-policy - 说明:允许 OCI Functions 从 Vault 读取 Secret
- 政策生成器:切换显示手动编辑器。
政策语句:输入以下内容(将
<compartment_name>替换为您的区间名称):allow dynamic-group oci-functions-dynamic-group to manage secret-family in compartment <compartment_name>
- 名称:
点击创建。
创建 OCI 函数应用
- 登录 Oracle Cloud 控制台。
- 依次前往开发者服务 > 应用(位于“功能”下方)。
- 点击创建应用。
- 提供以下配置详细信息:
- 名称:输入一个名称(例如
oci-logs-to-s3-app)。 - VCN:选择您所在隔离区中的 VCN。
- 子网:选择一个或多个子网。
- 名称:输入一个名称(例如
- 点击创建。
创建和部署 OCI 函数
设置 Cloud Shell(推荐)
- 在 Oracle Cloud 控制台中,点击右上角的 Cloud Shell 图标。
- 等待 Cloud Shell 完成初始化。
创建函数
在 Cloud Shell 中,为您的函数创建一个新目录:
mkdir pushlogs cd pushlogs初始化新的 Python 函数:
fn init --runtime python这会创建三个文件:
func.py、func.yaml和requirements.txt。
更新了 func.py
将
func.py的内容替换为以下代码:import io import json import logging import boto3 import oci import base64 import os from fdk import response def handler(ctx, data: io.BytesIO = None): """ OCI Function to push audit logs from OCI Logging to AWS S3 """ try: # Parse incoming log data from Service Connector funDataStr = data.read().decode('utf-8') funData = json.loads(funDataStr) logging.getLogger().info(f"Received {len(funData)} log entries") # Replace these with your actual OCI Vault secret OCIDs secret_key_id = "ocid1.vaultsecret.oc1..<your_secret_key_ocid>" access_key_id = "ocid1.vaultsecret.oc1..<your_access_key_ocid>" # Replace with your S3 bucket name s3_bucket_name = "oci-audit-logs-bucket" # Use Resource Principals for OCI authentication signer = oci.auth.signers.get_resource_principals_signer() secret_client = oci.secrets.SecretsClient({}, signer=signer) def read_secret_value(secret_client, secret_id): """Retrieve and decode secret value from OCI Vault""" response = secret_client.get_secret_bundle(secret_id) base64_secret_content = response.data.secret_bundle_content.content base64_secret_bytes = base64_secret_content.encode('ascii') base64_message_bytes = base64.b64decode(base64_secret_bytes) secret_content = base64_message_bytes.decode('ascii') return secret_content # Retrieve AWS credentials from OCI Vault awsaccesskey = read_secret_value(secret_client, access_key_id) awssecretkey = read_secret_value(secret_client, secret_key_id) # Initialize boto3 session with AWS credentials session = boto3.Session( aws_access_key_id=awsaccesskey, aws_secret_access_key=awssecretkey ) s3 = session.resource('s3') # Process each log entry for i in range(0, len(funData)): # Use timestamp as filename filename = funData[i].get('time', f'log_{i}') # Remove special characters from filename filename = filename.replace(':', '-').replace('.', '-') logging.getLogger().info(f"Processing log entry: {filename}") # Write log entry to temporary file temp_file = f'/tmp/{filename}.json' with open(temp_file, 'w', encoding='utf-8') as f: json.dump(funData[i], f, ensure_ascii=False, indent=4) # Upload to S3 s3_key = f'{filename}.json' s3.meta.client.upload_file( Filename=temp_file, Bucket=s3_bucket_name, Key=s3_key ) logging.getLogger().info(f"Uploaded {s3_key} to S3 bucket {s3_bucket_name}") # Clean up temporary file os.remove(temp_file) return response.Response( ctx, response_data=json.dumps({ "status": "success", "processed_logs": len(funData) }), headers={"Content-Type": "application/json"} ) except Exception as e: logging.getLogger().error(f"Error processing logs: {str(e)}") return response.Response( ctx, response_data=json.dumps({ "status": "error", "message": str(e) }), headers={"Content-Type": "application/json"}, status_code=500 )- 将
secret_key_id替换为 AWS 密钥的实际保险库密钥 OCID - 将
access_key_id替换为 AWS 访问密钥的实际保险库密钥 OCID - 将
s3_bucket_name替换为您的实际 S3 存储桶名称
- 将
更新 func.yaml
将 func.yaml 的内容替换为:
schema_version: 20180708
name: pushlogs
version: 0.0.1
runtime: python
build_image: fnproject/python:3.9-dev
run_image: fnproject/python:3.9
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
更新 requirements.txt
将
requirements.txt的内容替换为:fdk>=0.1.56 boto3 oci
部署该函数
将 Fn 上下文设置为使用您的应用:
fn use context <region-context> fn update context oracle.compartment-id <compartment-ocid>部署函数的方法如下:
fn -v deploy --app oci-logs-to-s3-app等待部署完成。您应该会看到输出,指示函数已成功部署。
验证函数是否已创建:
fn list functions oci-logs-to-s3-app
创建服务连接器以将 OCI 审核日志发送到函数
- 登录 Oracle Cloud 控制台。
- 依次前往分析和 AI > 信息传递 > Service Connector Hub。
- 选择要在其中创建服务连接器的区间。
- 点击创建服务连接器。
配置服务连接器详细信息
- 提供以下配置详细信息:
服务连接器信息:
* 连接器名称:输入一个描述性名称(例如 audit-logs-to-s3-connector)。
* 说明:可选说明(例如“将 OCI 审核日志转发到 AWS S3”)。
* 资源隔离区:选择隔离区。
配置来源
- 在配置来源下:
- 来源:选择日志记录。
- 区间:选择包含审核日志的区间。
- 日志组:选择
_Audit(这是审核日志的默认日志组)。 - 日志:点击 + 另一条日志。
- 选择相应区间对应的审核日志(例如
_Audit_Include_Subcompartment)。
配置目标
- 在配置目标下:
- 目标:选择函数。
- 隔离区:选择包含函数应用的隔离区。
- 函数应用:选择
oci-logs-to-s3-app(您之前创建的应用)。 - 函数:选择
pushlogs(您部署的函数)。
配置政策
在配置政策下:
- 查看显示的必需 IAM 政策语句。
- 点击创建以自动创建所需的政策。
点击创建以创建服务连接器。
等待服务连接器创建并激活。状态应更改为有效。
验证日志是否正在推送到 AWS S3
- 登录 Oracle Cloud 控制台。
- 执行一些会生成审核日志的操作(例如,创建或修改资源)。
- 等待 2-5 分钟,让系统处理日志。
- 登录 AWS Management Console。
- 前往 S3 > 存储分区。
- 点击您的存储桶(例如
oci-audit-logs-bucket)。 - 验证 JSON 日志文件是否显示在存储桶中。
为 Google SecOps 配置 AWS S3 存储桶和 IAM
为 Chronicle 创建 IAM 用户
- 登录 AWS Management Console。
- 依次前往 IAM > 用户 > 添加用户。
- 提供以下配置详细信息:
- 用户名:输入
chronicle-s3-reader。 - 访问权限类型:选择访问密钥 - 以程序化方式访问。
- 用户名:输入
- 点击 Next: Permissions。
- 点击 Attach existing policies directly。
- 搜索并选择 AmazonS3ReadOnlyAccess 政策。
- 点击 Next: Tags。
- 点击下一步:检查。
- 点击创建用户。
- 点击下载 CSV 文件以保存访问密钥 ID 和私有访问密钥。
- 点击关闭。
可选:创建自定义 IAM 政策以实现最小权限访问
如果您只想限制对特定存储桶的访问权限,请执行以下操作:
- 依次前往 IAM > 政策 > 创建政策。
- 点击 JSON 标签页。
输入以下政策:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::oci-audit-logs-bucket", "arn:aws:s3:::oci-audit-logs-bucket/*" ] } ] }- 将
oci-audit-logs-bucket替换为您的存储分区名称。
- 将
点击 Next: Tags。
点击下一步:检查。
提供以下配置详细信息:
- 名称:
chronicle-s3-read-policy - 说明:对 OCI 审核日志存储桶的只读访问权限
- 名称:
点击创建政策。
返回到 IAM > 用户,然后选择
chronicle-s3-reader用户。依次点击添加权限 > 直接附加政策。
搜索并选择
chronicle-s3-read-policy。移除之前添加的 AmazonS3ReadOnlyAccess 政策。
点击添加权限。
在 Google SecOps 中配置 Feed 以注入 Oracle Cloud 审核日志
- 依次前往 SIEM 设置> Feed。
- 点击添加新 Feed。
- 在下一页上,点击配置单个 Feed。
- 在Feed 名称字段中,输入 Feed 的名称(例如
Oracle Cloud Audit Logs)。 - 选择 Amazon S3 V2 作为来源类型。
- 选择 Oracle Cloud Infrastructure 作为日志类型。
- 点击下一步。
- 为以下输入参数指定值:
- S3 URI:输入 S3 存储桶 URI(例如
s3://oci-audit-logs-bucket/)。 - 来源删除选项:根据您的偏好选择删除选项:
- 从不:建议用于测试和初始设置。
- 删除已转移的文件:成功提取文件后将其删除(用于生产环境,以控制存储费用)。
- 文件存在时间上限:包含在过去指定天数内修改的文件。默认值为 180 天。
- 访问密钥 ID:输入您创建的 Chronicle IAM 用户的访问密钥 ID。
- 私有访问密钥:输入您创建的 Chronicle IAM 用户的私有访问密钥。
- 资产命名空间:资产命名空间。
- 注入标签:要应用于此 Feed 中事件的标签。
- S3 URI:输入 S3 存储桶 URI(例如
- 点击下一步。
- 在最终确定界面中查看新的 Feed 配置,然后点击提交。
UDM 映射表
| 日志字段 | UDM 映射 | 逻辑 |
|---|---|---|
data.request.headers.authorization.0 |
event.idm.read_only_udm.additional.fields |
从 data.request.headers.authorization.0 中获取的值,并以键值对的形式添加,其中键为“Request Headers Authorization”。 |
data.compartmentId |
event.idm.read_only_udm.additional.fields |
从 data.compartmentId 中提取的值,并以键值对的形式添加,其中键为“compartmentId”。 |
data.compartmentName |
event.idm.read_only_udm.additional.fields |
从 data.compartmentName 中获取的值,并以键值对的形式添加,其中键为“compartmentName”。 |
data.response.headers.Content-Length.0 |
event.idm.read_only_udm.additional.fields |
从 data.response.headers.Content-Length.0 中获取的值,并以键值对的形式添加,其中键为“Response Headers Content-Length”。 |
data.response.headers.Content-Type.0 |
event.idm.read_only_udm.additional.fields |
从 data.response.headers.Content-Type.0 中获取的值,并添加为键值对,其中键为“Response Headers Content-Type”。 |
data.eventGroupingId |
event.idm.read_only_udm.additional.fields |
从 data.eventGroupingId 中提取的值,并添加为键值对,其中键为“eventGroupingId”。 |
oracle.tenantid,data.identity.tenantId |
event.idm.read_only_udm.additional.fields |
如果存在,则从 oracle.tenantid 中获取值,否则从 data.identity.tenantId 中获取值。它以键值对的形式添加,其中键为“tenantId”。 |
data.message |
event.idm.read_only_udm.metadata.description |
从 data.message 中获取的值。 |
time |
event.idm.read_only_udm.metadata.event_timestamp |
从 time 中提取的值,并解析为 ISO8601 时间戳。 |
event.idm.read_only_udm.metadata.event_type |
默认设置为 GENERIC_EVENT。如果存在正文(IP 或主机名)和目标 IP,则设置为 NETWORK_CONNECTION。如果仅存在正文,则设置为 STATUS_UPDATE。 |
|
time |
event.idm.read_only_udm.metadata.ingested_timestamp |
如果 oracle.ingestedtime 不为空,则从 time 字段中获取值并将其解析为 ISO8601 时间戳。 |
oracle.tenantid |
event.idm.read_only_udm.metadata.product_deployment_id |
从 oracle.tenantid 中获取的值。 |
type |
event.idm.read_only_udm.metadata.product_event_type |
从 type 中获取的值。 |
oracle.logid |
event.idm.read_only_udm.metadata.product_log_id |
从 oracle.logid 中获取的值。 |
specversion |
event.idm.read_only_udm.metadata.product_version |
从 specversion 中获取的值。 |
data.request.action |
event.idm.read_only_udm.network.http.method |
从 data.request.action 中获取的值。 |
data.identity.userAgent |
event.idm.read_only_udm.network.http.parsed_user_agent |
从 data.identity.userAgent 中提取并解析的值。 |
data.response.status |
event.idm.read_only_udm.network.http.response_code |
从 data.response.status 中获取的值并转换为整数。 |
data.protocol |
event.idm.read_only_udm.network.ip_protocol |
data.protocol 中的数值会转换为其字符串表示形式(例如,6 变为“TCP”,17 变为“UDP”)。 |
data.bytesOut |
event.idm.read_only_udm.network.sent_bytes |
从 data.bytesOut 中获取的值,并转换为无符号整数。 |
data.packets |
event.idm.read_only_udm.network.sent_packets |
从 data.packets 中获取的值并转换为整数。 |
data.identity.consoleSessionId |
event.idm.read_only_udm.network.session_id |
从 data.identity.consoleSessionId 中获取的值。 |
id |
event.idm.read_only_udm.principal.asset.product_object_id |
从 id 中获取的值。 |
source |
event.idm.read_only_udm.principal.hostname |
从 source 中获取的值。 |
data.sourceAddress,data.identity.ipAddress |
event.idm.read_only_udm.principal.ip |
data.sourceAddress 和 data.identity.ipAddress 中的值会合并到此字段中。 |
data.sourcePort |
event.idm.read_only_udm.principal.port |
从 data.sourcePort 中获取的值并转换为整数。 |
data.request.headers.X-Forwarded-For.0 |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 data.request.headers.X-Forwarded-For.0 中获取值,并以键值对的形式添加,其中键为“x forward”。 |
oracle.compartmentid |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 oracle.compartmentid 中获取的值,并添加为键值对,其中键为“compartmentid”。 |
oracle.loggroupid |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 oracle.loggroupid 中提取的值,并以键值对的形式添加,其中键为“loggroupid”。 |
oracle.vniccompartmentocid |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 oracle.vniccompartmentocid 中获取的值,并添加为键值对,其中键为“vniccompartmentocid”。 |
oracle.vnicocid |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 oracle.vnicocid 中提取的值,并添加为键值对,其中键为“vnicocid”。 |
oracle.vnicsubnetocid |
event.idm.read_only_udm.principal.resource.attribute.labels |
从 oracle.vnicsubnetocid 中获取的值,并以键值对的形式添加,其中键为“vnicsubnetocid”。 |
data.flowid |
event.idm.read_only_udm.principal.resource.product_object_id |
从 data.flowid 中获取的值。 |
data.identity.credentials |
event.idm.read_only_udm.principal.user.attribute.labels |
从 data.identity.credentials 中获取值,并以键值对的形式添加,其中键为“credentials”。 |
data.identity.principalName |
event.idm.read_only_udm.principal.user.user_display_name |
从 data.identity.principalName 中获取的值。 |
data.identity.principalId |
event.idm.read_only_udm.principal.user.userid |
从 data.identity.principalId 中获取的值。 |
data.action |
event.idm.read_only_udm.security_result.action |
默认设置为 UNKNOWN_ACTION。如果 data.action 为“REJECT”,则此参数设置为 BLOCK。如果 data.action 为“ACCEPT”,则此属性设置为 ALLOW。 |
data.endTime |
event.idm.read_only_udm.security_result.detection_fields |
从 data.endTime 中提取的值,并添加为键值对,其中键为“endTime”。 |
data.startTime |
event.idm.read_only_udm.security_result.detection_fields |
从 data.startTime 中提取的值,并添加为键为“startTime”的键值对。 |
data.status |
event.idm.read_only_udm.security_result.detection_fields |
从 data.status 中获取的值,并添加为键值对,其中键为“status”。 |
data.version |
event.idm.read_only_udm.security_result.detection_fields |
从 data.version 中提取的值,并以键值对的形式添加,其中键为“version”。 |
data.destinationAddress |
event.idm.read_only_udm.target.ip |
从 data.destinationAddress 中获取的值。 |
data.destinationPort |
event.idm.read_only_udm.target.port |
从 data.destinationPort 中获取的值并转换为整数。 |
data.request.path |
event.idm.read_only_udm.target.url |
从 data.request.path 中获取的值。 |
event.idm.read_only_udm.metadata.product_name |
设置为“ORACLE CLOUD AUDIT”。 | |
event.idm.read_only_udm.metadata.vendor_name |
设置为“ORACLE”。 |
需要更多帮助?获得社区成员和 Google SecOps 专业人士的解答。