安全与合规

配好了安全服务不等于安全,得有人看告警并处置

GuardDuty 开了但没人看,等于没开。我们提供托管安全运营,含分级响应与月度态势报告。

安全事件分级与响应

等级示例响应时限处置方式
P1 严重凭证泄露、勒索软件、数据外泄15 分钟立即隔离、吊销凭证、启动应急流程
P2 高特权账号异常登录、安全组开放高危端口1 小时确认合法性、必要时阻断并通知
P3 中高危漏洞、加密配置缺失、异常 API 调用1 个工作日评估影响、安排修复窗口
P4 低配置偏离基线、低危漏洞1 周纳入例行整改批次

托管安全运营包含什么

  • GuardDuty、Security Hub、Inspector、Macie 的统一接入与调优(降噪很关键)
  • 7×24 告警值守,P1/P2 事件电话触达
  • 漏洞按资产重要性排优先级,跟踪修复闭环
  • 月度安全态势报告:新增风险、修复进度、趋势对比
  • 季度权限审阅:清理僵尸账号、过宽权限、闲置 Access Key
  • 年度红蓝对抗演练与应急流程验证

凭证泄露自动响应

python credential_compromise_response.py
"""GuardDuty 检测到凭证异常使用时的自动响应。

处置顺序很重要:
    1. 先给凭证附加拒绝一切的策略(立即止血,比删除快且可回溯)
    2. 快照当前会话信息用于取证
    3. 通知安全团队与凭证所属人
    4. 不自动删除任何东西——取证需要现场
"""

from __future__ import annotations

import json
import os
from datetime import datetime, timezone

import boto3

iam = boto3.client("iam")
sts = boto3.client("sts")
sns = boto3.client("sns")
ddb = boto3.resource("dynamodb")

QUARANTINE_POLICY_ARN = os.environ["QUARANTINE_POLICY_ARN"]
ALERT_TOPIC_ARN = os.environ["ALERT_TOPIC_ARN"]
FORENSICS_TABLE = os.environ["FORENSICS_TABLE"]

HIGH_SEVERITY = 7.0


def quarantine_user(user_name: str) -> dict:
    """附加拒绝策略并禁用所有 Access Key。"""
    actions = []

    iam.attach_user_policy(UserName=user_name, PolicyArn=QUARANTINE_POLICY_ARN)
    actions.append("attached-quarantine-policy")

    keys = iam.list_access_keys(UserName=user_name)["AccessKeyMetadata"]
    for key in keys:
        if key["Status"] == "Active":
            iam.update_access_key(
                UserName=user_name,
                AccessKeyId=key["AccessKeyId"],
                Status="Inactive",
            )
            actions.append(f"deactivated-key-{key['AccessKeyId'][-4:]}")

    return {"user": user_name, "actions": actions}


def quarantine_role(role_name: str) -> dict:
    """角色无法禁用,改为收紧信任策略并附加拒绝策略。"""
    iam.attach_role_policy(RoleName=role_name, PolicyArn=QUARANTINE_POLICY_ARN)
    return {"role": role_name, "actions": ["attached-quarantine-policy"]}


def record_forensics(finding: dict, response: dict) -> None:
    ddb.Table(FORENSICS_TABLE).put_item(Item={
        "finding_id": finding["id"],
        "detected_at": finding.get("updatedAt","),
        "responded_at": datetime.now(timezone.utc).isoformat(),
        "severity": str(finding.get("severity",")),
        "type": finding.get("type","),
        "account": finding.get("accountId","),
        "region": finding.get("region","),
        "raw_finding": json.dumps(finding, default=str)[:380000],
        "response": json.dumps(response),
    })


def lambda_handler(event, context):  # noqa: ARG001
    finding = event["detail"]
    severity = float(finding.get("severity", 0))
    ftype = finding.get("type",")

    if severity < HIGH_SEVERITY or "Credential" not in ftype:
        return {"action": "logged-only", "severity": severity}

    resource = finding.get("resource", {})
    access_key_detail = resource.get("accessKeyDetails", {})
    principal_type = access_key_detail.get("userType",")
    principal_name = access_key_detail.get("userName",")

    response: dict = {"principal": principal_name, "type": principal_type}
    try:
        if principal_type == "IAMUser":
            response.update(quarantine_user(principal_name))
        elif principal_type == "AssumedRole":
            role = principal_name.split("/")[0] if "/" in principal_name else principal_name
            response.update(quarantine_role(role))
        else:
            response["actions"] = ["manual-review-required"]
    except iam.exceptions.NoSuchEntityException:
        response["actions"] = ["principal-not-found"]

    record_forensics(finding, response)

    sns.publish(
        TopicArn=ALERT_TOPIC_ARN,
        Subject=f"[P1] 凭证异常已自动隔离:{principal_name}",
        Message=json.dumps({
            "finding_type": ftype,
            "severity": severity,
            "account": finding.get("accountId"),
            "region": finding.get("region"),
            "principal": principal_name,
            "actions_taken": response.get("actions", []),
            "next_steps": [
                "确认该凭证的合法用途与最近使用记录",
                "检查该凭证在事发期间的全部 API 调用",
                "确认无横向移动后,轮换凭证并解除隔离",
            ],
        }, ensure_ascii=False, indent=2),
    )

    return response

隔离而不是删除,是为了保留取证现场。删掉用户以后,CloudTrail 里的调用记录就很难关联分析了。

下一步

把云的复杂度交给我们,你只管做业务

留下需求,沐杉云的解决方案架构师会在一个工作日内联系你,提供免费的现状评估、迁移方案草案与 TCO 测算表。