更新时间:2026-11-04 GMT+08:00
分享

使用Identity出站认证安全管理敏感信息

场景描述

在AgentArts智能体运行时中,Agent运行时通常需要访问外部系统(如LLM API、第三方SaaS服务、云资源等),这些外部系统需要API Key、Token、密码等敏感凭证。当前开发者最常用的方式是将这些凭证通过环境变量注入到运行时实例中。

环境变量方式存在以下安全风险:

风险点

说明

危害等级

明文暴露

环境变量在运行时内以明文形式存在,任何能读取/proc/self/environ的进程均可获取全部凭证

高

版本控制泄漏

环境变量配置(如Dockerfile、docker-compose.yml、部署脚本)可能被提交到代码仓库,导致凭证随代码泄漏

严重

运行时可枚举

环境变量可通过env、printenv等命令一次性枚举,攻击者获取运行时 Shell 后可批量窃取所有凭证

高

无细粒度控制

环境变量对所有运行时内进程可见,无法实现“仅特定Agent逻辑可访问特定凭证”的最小权限控制

中

无审计追踪

凭证的读取、使用过程无操作日志,发生泄漏后无法追溯访问链路

高

轮换困难

凭证轮换需要重新部署运行时或修改环境变量配置,无法热更新,导致服务中断

中

AgentArts Identity服务提供出站认证(Outbound Authentication)能力,允许Agent在运行时通过SDK动态获取凭证,而无需将凭证硬编码在环境变量中。

对比维度

环境变量

Identity 出站认证

安全性

明文存储,运行时可枚举,无加密保护

凭证加密存储在Identity服务中,运行时按需获取,不落盘

可管理性

散落在各运行时配置中,难以统一管理

在Identity服务中集中管理凭证提供者(Credential Provider),支持命名空间隔离

审计能力

无操作日志,无法追溯凭证访问

通过CTS(Cloud Trace Service)审计 Identity 操作,凭证获取可追溯

轮换能力

需重新部署运行时,服务中断

在Identity服务中更新凭证提供者,Agent运行时自动获取新凭证,无需重启

细粒度控制

所有进程可见,无法限制访问范围

通过Outbound Identity绑定,仅授权Agent可获取对应凭证

多租户隔离

环境变量在运行时间无法隔离

通过出站身份(Outbound Identity)实现租户级凭证隔离

核心概念

出站认证(Outbound Authentication)

出站认证是指智能体运行时在访问外部系统时所使用的身份认证机制。与入站认证(Inbound Authentication,验证调用方身份)不同,出站认证解决的是“Agent 如何安全地证明自己的身份并获取访问外部资源的凭证”。

AgentArts Identity 服务通过以下组件实现出站认证:

概念

说明

Outbound Identity(出站身份)

代表运行时中智能体的逻辑身份。每个智能体运行时可关联一个或多个出站身份,作为凭证获取的主体标识。创建后返回URN(Uniform Resource Name)用于唯一标识。

Credential Provider(凭证提供者)

存储和管理外部系统凭证的实体。支持多种认证类型(API Key、OAuth2、STS),凭证信息在Identity服务中加密存储,不暴露明文。

Outbound Access Token(出站访问令牌)

智能体运行时用于向Identity服务请求凭证的短期令牌。可通过用户Token 或用户ID创建,具有时效性。

Credential Retrieval(凭证获取)

智能体通过SDK在运行时动态获取凭证的过程。支持显式调用和装饰器自动注入两种模式。

凭证生命周期

支持的认证类型

认证类型

适用场景

凭证提供者创建方法

API Key

访问以API Key认证的外部服务(如 OpenAI、第三方 API)

create_api_key_credential_provider()

OAuth 2.0

访问需要OAuth2授权的服务(如 GitHub、Google、Microsoft)

create_oauth2_credential_provider()

STS Token

访问华为云资源(通过 IAM 委托获取临时 AK/SK/SecurityToken)

create_sts_credential_provider()

OAuth2凭证提供者支持的Vendor:

Vendor

对应平台

GithubOauth2

GitHub

GoogleOauth2

Google

MicrosoftOauth2

Microsoft

OAuth2认证流程支持两种模式:

模式

说明

适用场景

USER_FEDERATION

用户联合认证,代表终端用户身份获取 Token

需要用户授权访问其个人资源的场景

M2M

机器间认证(Machine-to-Machine),服务身份直接获取 Token

服务间无用户参与的自动调用场景

前提条件

在开始操作前,请确保已完成以下准备工作:

  1. 已开通AgentArts服务并完成服务授权:请在“授权管理”页面进行云服务开通及授权,详细信息请参考授权管理。
  2. 已创建AgentArts智能体运行时,详细请参考通过控制台部署。
  3. 运行时部署时已配置委托(可使用系统默认的 DefaultAgentArtsRuntimeAgency 或手动创建)。
  4. 已开通Identity服务。

操作步骤

步骤一:创建出站身份

出站身份是凭证获取的主体,智能体运行时通过出站身份标识自身,并向Identity服务请求凭证。

  1. 登录AgentArts智能体平台。
  2. 在左侧导航栏选择“治理与安全 > 身份认证”,进入“Agent Identity”页面。
  3. 选择“出站身份”,单击 “创建出站身份”。
  4. 输入出站身份信息。

    • 身份名称:出站身份的名称,用于标识出站身份。
    • 出站身份支持的认证类型,支持OAuth 客户端、API Key、IAM 委托三种类型。
      • OAuth 客户端:OAuth 客户端是在身份提供商处注册的应用程序,使用OAuth 2.0协议代表用户请求访问用户的数据,具体请参考表1。
      • API Key:API Key 是用于身份验证和授权访问资源的唯一标识符。
      • IAM 委托:使用您登录管理控制台时使用的 IAM 委托身份。
      表1 OAuth 客户端参数说明

      提供商

      提供商配置

      参数说明

      自定义提供商

      提供商类型配置

      • Discovery URL:通过标准化的 URL 自动获取提供商的签发者、授权端点、令牌端点等全部元数据信息。推荐优先使用此方式,配置简单且能自动适配提供商端点变更。
      • 自定义配置:手动填写提供商的元数据字段(签发者、授权端点、令牌端点、响应类型等)。适用于提供商不支持 Discovery URL 或需要覆盖特定端点地址的场景。

      客户端 ID

      身份提供商或授权服务器分配给客户端应用程序的唯一标识符,用于在OAuth 2.0流程中标识客户端身份。

      客户端密钥

      分配给客户端应用程序的唯一密钥值,与客户端ID配对使用,用于用户在发出请求时针对客户端进行身份验证。

      Discovery URL

      提供商类型配置为 Discovery URL 时显示,提供一个 Discovery URL,供客户端用户发现身份提供商,Identity 服务通过该 URL 自动获取签发者(issuer)、授权端点(authorization_endpoint)、令牌端点(token_endpoint)等配置信息。

      签发者

      提供商类型配置为自定义配置时显示,授权服务器的基本URL,用于识别令牌签发者。

      授权端点

      提供商类型配置为自定义配置时显示,授权服务器上用于接收用户授权请求的 URL。

      令牌端点

      提供商类型配置为自定义配置时显示,客户端使用授权交换访问令牌的 URL。

      响应类型(可选)

      提供商类型配置为自定义配置时显示,授权服务器支持的OAuth 2.0响应类型,例如代码或令牌。可添加多个响应类型,也可逐个删除。

      预置的提供商

      选择提供商

      • Microsoft:对接Microsoft Entra ID(原 Azure AD),适用于访问 Microsoft 365、Azure等微软云服务资源。选择此项后需额外填写租户 ID,用于定位具体的企业租户。
      • Github:对接 GitHub OAuth App,适用于访问GitHub API(如读取仓库信息、用户资料等)。仅需填写客户端ID和客户端密钥。
      • Google:对接Google Identity Platform,适用于访问Google API(如Gmail、Google Drive等)。仅需填写客户端ID和客户端密钥。

      客户端 ID

      身份提供商或授权服务器分配给客户端应用程序的唯一标识符。

      客户端密钥

      分配给客户端应用程序的唯一密钥值,用户在发出请求时针对客户端进行身份验证。

      租户 ID

      提供商选择为“Microsoft”时显示,是分配给Microsoft Entra租户的唯一标识符。

  5. 单击“创建”,系统将创建出站身份并返回。

通过 SDK 创建(可选):

配置环境变量:

HUAWEICLOUD_SDK_AK        = HPUAYJR9HAM3TFYYCxxx
HUAWEICLOUD_SDK_SK        = xxxxxxxxxxxxxxxxxxxxxxx
HUAWEICLOUD_SDK_PROJECT_ID = 019fa87fb74272a5a2490bf0bxxxxxx

AK及SK获取请参考如何获取华为云AK/SK。

PROJECT_ID获取方式如下:

  1. 鼠标移动到右上角的用户名处,在下拉列表中单击“我的凭证”。
  2. 在“API凭证”页面的项目列表中查看项目ID。

    图1 查看项目ID

执行如下代码创建:

from agentarts.sdk.service.identity.identity_client import IdentityClient

# 初始化 Identity 客户端,指定区域
client = IdentityClient(region="cn-southwest-2")

# 创建出站身份
workload = client.create_workload_identity(
    name="my-agent-outbound",
)
print(f"Workload URN: {workload.urn}")

# 创建 API Key 凭证提供者
api_key_provider = client.create_api_key_credential_provider(
    name="openai-api-key",
    api_key="sk-xxxxxxxxxxxxxxxxxxxx"  # 实际使用时从安全来源获取,勿硬编码
)
print(f"API Key Provider URN: {api_key_provider.urn}")

# 创建 OAuth2 凭证提供者(以 GitHub 为例)
oauth2_provider = client.create_oauth2_credential_provider(
    name="github-oauth",
    vendor="GithubOauth2",
    client_id="your-client-id",
    client_secret="your-client-secret"  # 实际使用时从安全来源获取
)
print(f"OAuth2 Provider URN: {oauth2_provider.urn}")

# 创建 STS 凭证提供者
sts_provider = client.create_sts_credential_provider(
    name="huaweicloud-iam",
    agency_urn="agency-urn:your-agency",
    tags=[{"key": "env", "value": "prod"}]
)
print(f"STS Provider URN: {sts_provider.urn}")

步骤二:在SDK中获取敏感信息

完成控制台配置后,在智能体代码中使用Identity SDK获取凭证。SDK 提供两种获取模式:显式调用和装饰器自动注入。

方式一:显式调用

适用于需要精细控制凭证获取时机的场景。

from agentarts.sdk.service.identity import IdentityClient

# 初始化 Identity 客户端
client = IdentityClient(region="cn-southwest-2")

# 步骤 a:获取出站访问令牌(Outbound Access Token)
# 方式 1:使用用户 Token 创建
workload_token = client.create_workload_access_token(
    workload_name="my-agent-outbound",
    user_token="user-oauth-token"  # 从运行时上下文获取
)

# 方式 2:使用用户 ID 创建
# workload_token = client.create_workload_access_token_for_user_id(
#     workload_name="my-agent-outbound",
#     user_id="user-123"
# )

# 步骤 b:根据凭证提供者类型获取对应凭证

# 获取 API Key
api_key = client.get_resource_api_key(
    provider_name="openai-api-key",
    workload_access_token=workload_token
)

# 获取 OAuth2 Access Token(支持指定 scopes)
# oauth2_token = client.get_resource_oauth2_token(
#     provider_name="github-oauth",
#     scopes=["user:email", "read:user"],
#     workload_access_token=workload_token
# )

# 获取 STS Token(临时 AK/SK/SecurityToken)
# sts_token = client.get_resource_sts_token(
#     provider_name="huaweicloud-iam",
#     workload_access_token=workload_token,
#     agency_session_name="agent-session"
# )
# print(f"Access Key: {sts_token.access_key_id}")
# print(f"Secret Key: {sts_token.secret_access_key}")
# print(f"Security Token: {sts_token.security_token}")

方式二:装饰器自动注入(推荐)

使用装饰器模式时,SDK会自动获取出站访问令牌并注入凭证到函数参数中,开发者无需手动管理令牌生命周期。推荐使用此方式,可减少代码量并自动处理凭证刷新。

from typing import Optional
from agentarts.sdk import require_access_token, require_api_key, require_sts_token
from agentarts.sdk.identity.types import StsCredentials

# API Key 自动注入(同步函数)
@require_api_key(provider_name="openai-api-key")
def call_llm(api_key: Optional[str] = None):
    """api_key 参数由 SDK 自动注入,无需手动传入"""
    import openai
    client = openai.OpenAI(api_key=api_key)
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "Hello!"}]
    )
    return response.choices[0].message.content

# OAuth2 Access Token 自动注入 - USER_FEDERATION 模式(异步函数)
@require_access_token(
    provider_name="github-oauth",
    scopes=["user:email", "read:user"],
    auth_flow="USER_FEDERATION"
)
async def fetch_github_user_data(access_token: Optional[str] = None):
    """access_token 参数由 SDK 自动注入"""
    import httpx
    async with httpx.AsyncClient() as http_client:
        resp = await http_client.get(
            "https://api.github.com/user",
            headers={"Authorization": f"Bearer {access_token}"}
        )
        return resp.json()

# OAuth2 Access Token 自动注入 - M2M 模式(异步函数)
@require_access_token(
    provider_name="internal-service-oauth",
    auth_flow="M2M"
)
async def call_internal_service(access_token: Optional[str] = None):
    """M2M 模式,服务间直接认证"""
    import httpx
    async with httpx.AsyncClient() as http_client:
        resp = await http_client.post(
            "https://internal.example.com/api",
            headers={"Authorization": f"Bearer {access_token}"}
        )
        return resp.json()

# STS Token 自动注入(异步函数)
@require_sts_token(
    provider_name="huaweicloud-iam",
    agency_session_name="agent-session"
)
async def access_huawei_resource(sts_credentials: Optional[StsCredentials] = None):
    """sts_credentials 参数由 SDK 自动注入"""
    print(f"AK: {sts_credentials.access_key_id}")
    print(f"SK: {sts_credentials.secret_access_key}")
    print(f"SecurityToken: {sts_credentials.security_token}")
    # 使用 STS 凭证访问华为云资源
    # ...

步骤三:在Agent代码中安全使用凭证

以下是一个完整的Agent示例,展示在智能体运行时中使用Identity出站认证访问外部LLM服务的完整流程。

"""
AgentArts 智能体示例:使用 Identity 出站认证安全获取 API Key 并调用 LLM
"""
from typing import Optional
from agentarts.sdk import require_api_key
from agentarts.sdk.runtime import AgentArtsRuntimeApp

# 使用装饰器自动注入 API Key
# provider_name 对应步骤1中创建的身份名称
@require_api_key(provider_name="openai-api-key")
def generate_response(user_input: str, api_key: Optional[str] = None) -> str:
    """
    使用 Identity 注入的 API Key 调用 LLM 生成回复。

    Args:
        user_input: 用户输入文本
        api_key: 由 SDK 装饰器自动注入,无需手动传入

    Returns:
        LLM 生成的回复文本
    """
    import openai

    # 使用 Identity 注入的 API Key,而非环境变量
    client = openai.OpenAI(api_key=api_key)

    try:
        response = client.chat.completions.create(
            model="gpt-4",
            messages=[
                {"role": "system", "content": "你是一个有帮助的助手。"},
                {"role": "user", "content": user_input}
            ],
            temperature=0.7
        )
        return response.choices[0].message.content
    except Exception as e:
        # 凭证获取或 API 调用失败时的降级处理
        # 注意:错误日志中不要打印 api_key 内容
        print(f"LLM 调用失败: {type(e).__name__}")
        return "抱歉,服务暂时不可用,请稍后重试。"

# 使用 AgentArts SDK 封装为标准运行时应用
app = AgentArtsRuntimeApp()

@app.invocation
def handle_invocation(request):
    """处理智能体调用请求"""
    user_input = request.get("input", "")
    response = generate_response(user_input)
    return {"output": response}

if __name__ == "__main__":
    app.run()  # 默认监听 8080 端口

代码示例

Python SDK完整示例:多凭证类型场景

以下示例展示在一个Agent中同时使用API Key、OAuth2和STS三种凭证类型:

"""
AgentArts 智能体示例:同时使用多种凭证类型
- API Key:调用 LLM 服务
- OAuth2:访问 GitHub API
- STS Token:访问华为云 OBS
"""
from typing import Optional
from agentarts.sdk import require_api_key, require_access_token, require_sts_token
from agentarts.sdk.identity.types import StsCredentials
from agentarts.sdk.runtime import AgentArtsRuntimeApp

app = AgentArtsRuntimeApp()


# ========== API Key 场景:调用 LLM ==========
@require_api_key(provider_name="openai-api-key")
def call_llm(prompt: str, api_key: Optional[str] = None) -> str:
    """使用 API Key 调用 LLM"""
    import openai
    client = openai.OpenAI(api_key=api_key)
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content


# ========== OAuth2 场景:访问 GitHub ==========
@require_access_token(
    provider_name="github-oauth",
    scopes=["user:email", "read:user"],
    auth_flow="USER_FEDERATION"
)
async def get_github_user_info(access_token: Optional[str] = None) -> dict:
    """使用 OAuth2 Token 访问 GitHub 用户信息"""
    import httpx
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.github.com/user",
            headers={"Authorization": f"Bearer {access_token}"}
        )
        return resp.json()


# ========== STS Token 场景:访问华为云 OBS ==========
@require_sts_token(
    provider_name="huaweicloud-iam",
    agency_session_name="obs-access-session"
)
async def upload_to_obs(
    file_data: bytes,
    bucket: str,
    object_key: str,
    sts_credentials: Optional[StsCredentials] = None
) -> str:
    """使用 STS Token 上传文件到 OBS"""
    from obs import ObsClient

    obs_client = ObsClient(
        access_key_id=sts_credentials.access_key_id,
        secret_access_key=sts_credentials.secret_access_key,
        security_token=sts_credentials.security_token,
        server="obs.cn-southwest-2.myhuaweicloud.com"
    )

    resp = obs_client.putContent(bucket, object_key, file_data)
    if resp.status < 300:
        return f"obs://{bucket}/{object_key}"
    else:
        raise Exception(f"OBS 上传失败: {resp.errorCode}")


# ========== 主入口 ==========
@app.invocation
async def handle_invocation(request):
    """处理智能体调用请求"""
    task_type = request.get("task", "")

    if task_type == "chat":
        result = call_llm(request.get("input", ""))
        return {"output": result}

    elif task_type == "github":
        user_info = await get_github_user_info()
        return {"output": f"GitHub 用户: {user_info.get('login', 'unknown')}"}

    elif task_type == "upload":
        file_data = request.get("file_data", "").encode()
        url = await upload_to_obs(
            file_data=file_data,
            bucket="my-bucket",
            object_key=f"uploads/{request.get('filename', 'file.txt')}"
        )
        return {"output": f"文件已上传: {url}"}

    return {"output": "未知任务类型"}


if __name__ == "__main__":
    app.run()

凭证过期与刷新处理

Identity SDK的装饰器模式会自动处理短期凭证的获取和刷新。对于需要手动管理凭证生命周期的场景,参考以下模式:

"""
凭证过期处理与自动重试模式
"""
from typing import Optional
import time
from functools import wraps
from agentarts.sdk.service.identity import IdentityClient

# 初始化 Identity 客户端
identity_client = IdentityClient(region="cn-southwest-2")

# 缓存凭证(仅缓存非敏感的标识信息,不缓存凭证本身)
_credential_cache = {}
_credential_expiry = {}


def get_fresh_api_key(provider_name: str, workload_token: str, max_retries: int = 3) -> str:
    """
    获取 API Key,支持自动重试。

    Args:
        provider_name: 凭证提供者名称
        workload_token: 出站访问令牌
        max_retries: 最大重试次数

    Returns:
        API Key 字符串

    Raises:
        RuntimeError: 重试次数耗尽后仍无法获取凭证
    """
    last_error = None

    for attempt in range(max_retries):
        try:
            api_key = identity_client.get_resource_api_key(
                provider_name=provider_name,
                workload_access_token=workload_token
            )
            return api_key
        except Exception as e:
            last_error = e
            error_type = type(e).__name__
            # 不打印凭证相关信息,仅打印错误类型
            print(f"凭证获取失败 (尝试 {attempt + 1}/{max_retries}): {error_type}")
            # 指数退避等待
            time.sleep(2 ** attempt)

    raise RuntimeError(f"凭证获取失败,已重试 {max_retries} 次。最后错误: {type(last_error).__name__}")


def with_api_key_retry(provider_name: str, max_retries: int = 3):
    """
    装饰器:为需要 API Key 的函数添加自动重试逻辑。

    当凭证获取失败时自动重试,而非直接失败。
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    # 每次尝试都重新获取凭证,不使用缓存
                    workload_token = identity_client.create_workload_access_token(
                        workload_name="my-agent-outbound",
                        user_token=kwargs.get("user_token", "")
                    )
                    api_key = get_fresh_api_key(
                        provider_name=provider_name,
                        workload_token=workload_token,
                        max_retries=1  # 内部不再重试,由外层控制
                    )
                    kwargs["api_key"] = api_key
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt < max_retries - 1:
                        print(f"调用失败 (尝试 {attempt + 1}/{max_retries}): {type(e).__name__}")
                        time.sleep(2 ** attempt)
                    else:
                        raise
        return wrapper
    return decorator


# 使用示例
@with_api_key_retry(provider_name="openai-api-key", max_retries=3)
def call_llm_with_retry(user_input: str, api_key: Optional[str] = None) -> str:
    """带自动重试的 LLM 调用"""
    import openai
    client = openai.OpenAI(api_key=api_key)
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": user_input}]
    )
    return response.choices[0].message.content

从环境变量迁移到Identity出站认证

以下示例展示如何将现有环境变量方式逐步迁移到 Identity 出站认证,支持过渡期双模式运行:

"""
迁移辅助器:从环境变量模式平滑迁移到 Identity 出站认证
支持双模式运行,便于灰度迁移
"""
import os
from typing import Optional
from agentarts.sdk import require_api_key


# 迁移开关:通过环境变量控制是否启用 Identity 出站认证
USE_IDENTITY_AUTH = os.getenv("AGENTARTS_USE_IDENTITY_AUTH", "false").lower() == "true"


def get_api_key(provider_name: str, env_var_name: str) -> Optional[str]:
    """
    获取 API Key,支持双模式:
    - Identity 出站认证模式(推荐)
    - 环境变量模式(迁移过渡期使用)

    Args:
        provider_name: Identity 凭证提供者名称
        env_var_name: 环境变量名称(迁移过渡期使用)

    Returns:
        API Key 字符串
    """
    if USE_IDENTITY_AUTH:
        # Identity 模式:通过 SDK 获取
        # 使用装饰器需要包装在函数中
        @require_api_key(provider_name=provider_name)
        def _get_key(api_key: Optional[str] = None) -> str:
            return api_key
        return _get_key()
    else:
        # 环境变量模式:迁移过渡期使用
        # 添加告警日志提醒迁移
        import logging
        logging.warning(
            f"正在使用环境变量 {env_var_name} 获取凭证,"
            "建议迁移到 Identity 出站认证。"
            "设置 AGENTARTS_USE_IDENTITY_AUTH=true 启用 Identity 模式。"
        )
        return os.getenv(env_var_name)


# 使用示例
def call_external_api(user_input: str) -> str:
    """调用外部 API,自动适配 Identity 或环境变量模式"""
    api_key = get_api_key(
        provider_name="openai-api-key",
        env_var_name="OPENAI_API_KEY"
    )

    if not api_key:
        raise RuntimeError("无法获取 API Key,请检查 Identity 配置或环境变量")

    import openai
    client = openai.OpenAI(api_key=api_key)
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": user_input}]
    )
    return response.choices[0].message.content

凭证安全使用规范

凭证最小权限原则

原则

实施方法

一个凭证提供者仅服务一个外部系统

为每个外部系统创建独立的凭证提供者,而非用一个通用凭证访问多个系统

OAuth2 Scope最小化

仅请求必要的OAuth2 Scope,如仅需读取用户信息则不请求write权限

STS委托权限最小化

IAM委托仅授予Agent实际需要的资源操作权限,避免使用 * 通配权限

出站身份与运行时一一对应

每个智能体运行时关联独立的出站身份,避免多运行时共享同一身份

凭证轮换策略

"""
凭证轮换管理示例
建议通过 CI/CD 管道定期更新凭证提供者
"""
from agentarts.sdk.service.identity import IdentityClient

client = IdentityClient(region="cn-southwest-2")

def rotate_api_key(provider_name: str, new_api_key: str):
    """
    轮换 API Key 凭证提供者。

    在 Identity 服务中更新凭证后,Agent 运行时下次获取凭证时
    将自动获取新值,无需重启运行时。

    建议轮换周期:
    - API Key:每 90 天
    - OAuth2 Client Secret:每 180 天
    - STS Token:自动过期,无需手动轮换
    """
    # 删除旧凭证提供者
    # 注意:SDK 暂不提供直接更新接口,需删除后重建
    # client.delete_credential_provider(name=provider_name)

    # 创建新的凭证提供者
    provider = client.create_api_key_credential_provider(
        name=provider_name,
        api_key=new_api_key
    )
    print(f"凭证轮换完成: {provider_name}")

轮换凭证时,旧凭证提供者删除前需确保没有正在进行的请求使用旧凭证。建议在业务低峰期执行轮换操作。

日志脱敏

在Agent代码中,严禁将凭证内容输出到日志。以下规则必须严格遵守:

规则

正确做法

错误做法

禁止打印凭证值

print(f"API Key 获取成功")

print(f"API Key: {api_key}")

禁止将凭证写入文件

凭证仅在内存中使用,函数结束后由GC回收

with open("config.txt", "w") as f: f.write(api_key)

禁止在异常信息中包含凭证

raise RuntimeError("凭证获取失败")

raise RuntimeError(f"凭证获取失败: {api_key}")

禁止在HTTP请求日志中记录认证头

配置HTTP客户端过滤 Authorization 头

使用默认日志配置导致Token出现在请求日志中

"""
日志脱敏示例
"""
import logging

logger = logging.getLogger(__name__)

# 配置日志过滤器,过滤敏感信息
class CredentialRedactionFilter(logging.Filter):
    """过滤日志中的敏感信息"""

    SENSITIVE_PATTERNS = [
        "api_key", "api-key", "apikey",
        "access_token", "access-token", "accesstoken",
        "secret", "password", "credential",
        "authorization", "bearer"
    ]

    def filter(self, record):
        message = str(record.getMessage())
        for pattern in self.SENSITIVE_PATTERNS:
            if pattern in message.lower():
                # 标记为敏感,但保留错误类型
                record.msg = f"[REDACTED: 含敏感字段 {pattern}]"
                return True
        return True

# 应用过滤器
logger.addFilter(CredentialRedactionFilter())


@require_api_key(provider_name="openai-api-key")
def safe_call(api_key: Optional[str] = None):
    """正确做法:日志中不包含凭证"""
    logger.info("开始调用 LLM API")  # 正确
    # logger.info(f"使用 API Key: {api_key}")  # 错误!禁止这样做

    try:
        # 调用逻辑...
        pass
    except Exception as e:
        logger.error(f"调用失败: {type(e).__name__}")  # 正确:仅记录错误类型
        # logger.error(f"调用失败: {e}")  # 有风险:异常信息可能包含凭证

错误处理与降级策略

当凭证获取失败时,Agent应具备合理的降级策略,而非直接崩溃:

"""
凭证获取失败的降级策略
"""
from typing import Optional
from agentarts.sdk import require_api_key
import logging

logger = logging.getLogger(__name__)


class FallbackResponse:
    """降级响应"""
    RATE_LIMITED = "服务繁忙,请稍后重试"
    AUTH_FAILED = "服务暂时不可用,请稍后重试"
    UNKNOWN_ERROR = "服务异常,请稍后重试"


def call_with_fallback(provider_name: str, fallback_message: str = FallbackResponse.UNKNOWN_ERROR):
    """
    装饰器:为函数添加凭证获取失败降级逻辑

    Args:
        provider_name: Identity 凭证提供者名称
        fallback_message: 降级时返回的消息
    """
    def decorator(func):
        @require_api_key(provider_name=provider_name)
        def wrapper(*args, api_key: Optional[str] = None, **kwargs):
            if api_key is None:
                # 凭证获取失败,返回降级消息
                logger.warning(f"凭证获取失败,触发降级: {func.__name__}")
                return fallback_message

            try:
                return func(*args, api_key=api_key, **kwargs)
            except Exception as e:
                error_type = type(e).__name__
                logger.error(f"函数执行失败: {func.__name__}, 错误类型: {error_type}")

                # 根据错误类型返回不同降级消息
                if "RateLimit" in error_type:
                    return FallbackResponse.RATE_LIMITED
                elif "Auth" in error_type:
                    return FallbackResponse.AUTH_FAILED
                else:
                    return fallback_message
        return wrapper
    return decorator


# 使用示例
@call_with_fallback(
    provider_name="openai-api-key",
    fallback_message="AI 助手暂时无法响应,请稍后重试。"
)
def generate_response(user_input: str, api_key: Optional[str] = None) -> str:
    """生成回复,带降级保护"""
    import openai
    client = openai.OpenAI(api_key=api_key)
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": user_input}]
    )
    return response.choices[0].message.content

常见问题

Q1:凭证获取失败如何排查?

排查步骤:

  1. 检查出站身份是否已创建:在Identity控制台确认出站身份存在且状态正常。
  2. 检查凭证提供者是否已创建:在Identity控制台确认对应名称的凭证提供者存在且状态正常。
  3. 检查运行时委托配置:在运行时详情页确认已配置IAM委托,且委托信任主体类型为“云服务”、云服务为service.WorkloadSandboxMetadata。
  4. 检查IAM权限:确认委托已授权kms:cmk:createDataKey、kms:cmk:decryptDataKey、csms:secret:create、csms:secret:delete权限。
  5. 检查区域一致性:确认Identity SDK客户端初始化的region与运行时部署区域一致。
  6. 查看运行时日志:在运行时详情页的“日志”页签查看LTS日志,搜索Identity相关错误信息。

Q2:凭证过期如何自动刷新?

Identity SDK 的装饰器模式(@require_api_key、@require_access_token、@require_sts_token)会自动管理凭证生命周期:

  • 短期凭证(如 OAuth2 Token、STS Token):装饰器在每次函数调用时自动获取最新凭证,无需手动刷新。
  • 长期凭证(如 API Key):装饰器每次调用时从 Identity 服务获取,凭证提供者更新后自动生效。

如需手动管理凭证刷新,参考凭证过期与刷新处理中的代码示例。

Q3:多租户场景下的凭证如何隔离?

AgentArts Identity通过以下机制实现多租户凭证隔离:

隔离层

机制

说明

租户级隔离

每个租户的Identity服务独立

租户A的凭证提供者对租户B不可见

出站身份级隔离

每个运行时关联独立的出站身份

运行时A仅能获取其关联身份下的凭证

凭证提供者命名空间

凭证提供者名称在租户内唯一

不同运行时使用不同名称的凭证提供者

多租户最佳实践:

  • 为不同租户的Agent运行时创建独立的出站身份。
  • 凭证提供者命名使用租户前缀,如tenant-a-openai-key、tenant-b-openai-key。
  • 不同租户的运行时配置不同的IAM委托,实现权限级隔离。

Q4:如何从环境变量方案迁移到Identity出站认证?

迁移步骤:

步骤

操作

说明

1

在Identity服务中创建出站身份

将所有环境变量中的凭证导入Identity服务

3

在Agent代码中使用双模式获取凭证

参考从环境变量迁移到Identity出站认证的代码示例

4

设置环境变量AGENTARTS_USE_IDENTITY_AUTH=true启用 Identity 模式

灰度验证Identity模式功能正常

5

验证Agent功能正常后,清除运行时环境变量中的凭证

完成迁移,移除环境变量中的敏感信息

6

更新CI/CD管道

确保部署流程不再注入凭证环境变量

注意:迁移完成后,务必清除运行时环境变量中的所有敏感凭证,并检查代码仓库历史提交中是否有凭证泄漏(可使用git log -p | grep -i "api_key\|secret\|token" 进行检查)。

Q5:Identity SDK是否支持Java语言?

当前AgentArts SDK以Python为主。Identity SDK的核心类IdentityClient位于 agentarts.sdk.service.identity包中。

相关文档