跳转至

Cedar 策略规范与进阶指南 (Cedar Policies)

Griptape Nodes 的权限策略模版底层基于 Cedar 驱动,这是由 Amazon 开源的表达力极强的细粒度授权策略语言。在企业管理看板的 策略编辑器 (Permission Editor) 中,默认的可视化构建器(Permission Builder)会自动将你在界面上的勾选项编译为 Cedar 代码。

当你需要编写 Raw Cedar(原生 Cedar 策略) 模版,或需要对可视化构建器自动生成的 Cedar 代码进行安全审计时,请参考本文档。

最佳实践:仅当可视化构建器的权限能力目录无法满足你的复杂管控诉求(例如多条件组合、特定项目链路限定等)时,才建议手写 Raw Cedar 策略。


策略决策输入参数 (Policy Inputs)

Cedar 引擎在做出每一次放行或阻断判定时,依赖四个固定的输入上下文元组:(principal, action, resource, context)。Griptape Nodes 将引擎运行时的每一个鉴权检查点以及许可证数据精准映射到这四个 Cedar 输入项中:

组成部分 承载数据内容 核心应用场景
principal 固定占位符:User::"<anonymous>"。 无。在规则中请保持无约束(通配)。
action 鉴权检查点标识,例如 Action::"LoadLibrary"。 指定当前策略规则管辖的底层操作。
resource 检查点针对的具体资源实体(已解析的算子库、节点类型、项目、模型或编解码器)。 精准匹配特定资产,或匹配该资产的属性特征。
context 活跃项目、执行引擎、算子库以及席位许可证的运行时状态事实。 将规则范围限定至某个具体项目,或某种席位类型。

策略合并与仲裁原则 (Combining Decisions)

当将多个策略模版组合生效时,Cedar 严格遵循两条仲裁铁律:

  1. 必须至少存在一条匹配的 permit 语句显式放行该操作;
  2. 只要存在一条匹配的 forbid 语句,将绝对压倒覆盖所有 permit 语句(哪怕该放行许可来自其他模版)。

可视化策略构建器会编译出两种经典的 Cedar 模式:

  • Exploration (全局放行 / 白名单例外):以通配放行语句 permit(principal, action, resource); 起手,随后追加若干特定的 forbid 语句定义黑名单例外;
  • Production (全局封锁 / 严格白名单):省去通配 permit 语句,仅针对明确经过安全审计的操作逐一添加 permit 放行规则。

默认拒绝与硬性阻断机制 (Default Deny & Hard Blocks)

Cedar 默认对一切未显式放行的操作实施拒绝(Default Deny)。因此,如果一个策略集内仅包含 forbid 语句,那么它实际上会拒绝所有操作,而不仅仅是 forbid 匹配到的那些操作!若要构建黑名单策略,必须将 forbid 规则与一条全局放行规则配对书写:

permit(principal, action, resource);

forbid(principal, action, resource)
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };

省略 permit 并不是绝对的硬性阻断,因为挂载给该席位的其他模版中的 permit 仍可能将其放行。如果你希望某个操作无论在何种情况下都绝对禁止被放行,请显式使用 forbid。Cedar 会将绑定到某个许可证密钥上的所有模版合并为一个统一的策略集执行最终求值,因此 permit 与 forbid 可以分别存在于不同的模版中。


鉴权检查点目录 (Checkpoints)

以下各类 Action 标识了 Griptape Nodes 执行引擎进行安全把关的核心检查点。当规则触发拒绝时,各检查点的拦截表现如下:

检查点 Action 触发时机 对应 Resource 触发拒绝时的表现
Action::"LoadLibrary" 算子库越过元数据阶段并准备装载时。 Library 该库被打标为不可用,其错误图标上会悬浮展示拒绝原因。
Action::"InstantiateNode" 在工作流中实例化/创建节点时。 NodeType 画布上生成一个 Error Proxy 错误代理节点代替真实节点。被拒绝的节点类型也会提前在算子库面板中灰显列出。
Action::"LoadProject" 读取项目模板时。 Project 项目模板加载失败。
Action::"ActivateProject" 将某项目切换激活为当前活动项目时。 Project 项目切换失败,工作区保持停留在原项目。
Action::"OfferModel" 前端构建大语言模型下拉选择器列表时。 Model 该模型直接从前端下拉菜单中被过滤剔除,不可见。
Action::"InvokeModel" 算子节点发起大模型推理调用时。 Model 模型请求在调度前置点被拦截阻断,调用失败。
Action::"ReadVideoCodec" 即将读取视频数据,或构建编码器选项列表时。 VideoCodec 视频读取被拒,或该解码器从列表中剔除。
Action::"WriteVideoCodec" 即将写入/导出视频,或构建编码器选项列表时。 VideoCodec 视频写入被拒,或该编码器从列表中剔除。

资源实体属性参考 (Resource Attributes)

除非表格中特别注明“始终存在”,否则各项资源属性均为可选 (Optional)。在读取可选属性之前,必须先使用 has 进行防护性存在判断。详见后文的 对可选属性进行防御性检查。

Library (算子库资源)

属性字段 数据类型 出现条件与说明
id string 始终存在。算子库的唯一标识名称。
lifecycle_stage string 当该库在元数据中声明了生命周期阶段时存在(如 STABLE、LABS)。

NodeType (算子节点资源)

属性字段 数据类型 出现条件与说明
id string 始终存在。算子节点的类名/类型标识。
executes_arbitrary_code bool 始终存在。当节点的算子库声明了包含任意 Python 代码执行风险时为 true。
lifecycle_stage string 当节点声明了阶段,或从其所属库继承了生命周期阶段时存在。
model_ids set 当该节点声明了将使用到的模型列表时存在。
provider_ids set 当该节点声明了模型或服务商依赖时存在。
model_families set 当节点声明的模型在所属算子库的模型目录中成功解析到模型家族时存在。

Project (项目工程资源)

属性字段 数据类型 出现条件与说明
id string 始终存在。项目的唯一 ID(在编辑器中创建的项目通常为 GUID 字符串)。
name string 项目模板已充分加载并解析出名称后存在。

提示:编写人类可读规则时可使用 name;若要求 100% 绝对精确匹配,请使用 id。在 permit 语句中强烈推荐使用 id。因为项目名称必须在模板完全加载后才可用,若在加载前评估未命中放行条件,会导致项目加载直接被拒。

Model (大语言模型资源)

属性字段 数据类型 出现条件与说明
id string 始终存在。模型目录中声明的稳定模型 Key。
provider_id string 该 Key 在模型目录中成功解析到了具体的服务商 ID。
model_families set 解析后的模型声明了所属模型家族(包含该单项家族字符串的集合)。

若需按服务商批量匹配而非单模型过滤,请参考下方的 实体层级结构。

VideoCodec (视频编解码器资源)

属性字段 数据类型 出现条件与说明
id string 始终存在。探测识别出的编码格式名称,例如 h264、hevc、prores。
container_format string 已知容器封装格式时存在,例如 mp4、mov。

实体层级结构 (Entity Hierarchy)

ModelProvider (服务商父级层级)

每一个 Model 资源在逻辑上从属于提供它的服务商父节点,因此你可以使用 Cedar 原生的 in 操作符批量管控某个服务商旗下的所有模型,而无需穷举列出具体模型名称。

例如:

forbid(principal, action, resource)
unless { resource in ModelProvider::"anthropic" };

服务商 ID 取自算子库 model_catalog 中声明的供应商键名(如 anthropic 或 ollama)。


上下文运行时事实 (Context Facts)

务必对上下文数据进行 has 防御

所有 context 事实在底层均为可选的。Griptape Nodes 仅注入当前能够解析出的上下文事实,其余字段将被省略。因此在读取任何 context 字段之前,必须使用 has 关键字先进行校验!

标准防护写法:先判断记录是否存在,再读取属性值:

when {
  context has loaded_libraries &&
  context.loaded_libraries.names.contains("My Library")
}
上下文字段 数据类型 详细说明
active_project.id string 当前活动项目的全局唯一 ID,用作执行引擎注册表键。
active_project.name string 活动项目的显示名称(在模板解析完毕后可用)。必须配合自身的 has 防护。
engine.id string 当前活动引擎的唯一标识。
loaded_libraries.names set 截至当前时刻已成功加载的所有算子库名称集合。
license_id string 触发当前操作的许可证密钥 ID。
org_id string 许可证归属的企业组织 ID。
license_type string 席位类型:"headless"(无头自动化)或 "interactive"(图形交互)。

项目管辖范围 (Project Scope)

在可视化构建器中,若将策略模版指定为 Project-scoped (项目受限) 模式,构建器会在每一条 Cedar 语句中自动注入以下过滤子句:

when { context has active_project && context.active_project.id == "<project id>" }

Griptape Nodes 会针对活动项目继承链上的每一个项目节点依次执行策略求值。这一机制带来了两个关键架构特质:

  1. 必须涵盖祖先项目 (Include ancestor projects):当一个子项目继承自父项目时,权限判定会沿着继承链层层求值:先评估当前活动项目,随后评估每一个祖先项目。链条上的每一次求值都必须通过。因此,针对父项目的 forbid 规则会自动连带阻断其所有子项目;同理,若配置项目白名单,必须将链条中涉及的所有祖先项目 ID 一并加入放行规则。

    例如:假设 Shot 42 (镜头42) 继承自 Studio Defaults (工作室全局默认) 项目。若要在 Shot 42 中放行算子库加载,必须同时为两个项目 ID 声明 permit:

    permit(principal, action == Action::"LoadLibrary", resource)
    when {
      context has active_project &&
      context.active_project.id == "<Shot 42 id>"
    };
    
    permit(principal, action == Action::"LoadLibrary", resource)
    when {
      context has active_project &&
      context.active_project.id == "<Studio Defaults id>"
    };
    

    若只写了第一个 permit,Shot 42 本身校验通过,但在沿链校验 Studio Defaults 时会因缺少 permit 而被拒。同时,任何匹配 Studio Defaults 的 forbid 也会立即在 Shot 42 中生效阻断。

  2. 考虑默认项目环境 (Default Project):除非显式指定切换,否则引擎默认引导启动至默认系统项目,其固定 ID 为 <system-defaults>。你无需手动为该 ID 编写 permit 放行,但显式的 forbid 规则在此状态下依然生效。


拒绝拦截注解规范 (Denial Annotations)

Cedar 原生引擎仅会返回拒绝了操作的规则标识,并不会向终端用户解释其具体缺失了什么权限或应当如何解决。Griptape Nodes 引入了三个专有注解(Annotations)来丰富报错上下文。这些注解为 Griptape 的上层约定规范,Cedar 底层引擎在评估策略逻辑时会自动忽略它们:

注解标识 核心作用与显示位置
@id("<slug>") 为该规则指定一个稳定的语义标识。在触发拒绝时直接展示,替代难读的规则位置编号。
@capability("<name>") 标识用户当前所缺失的能力项标识,例如 arbitrary-code-execution。
@advice("<text>") 具体的指导建议文案。这是终端用户在报错弹窗或悬浮提示中直接看到的指引文本。
@id("nodes/no-arbitrary-code")
@capability("arbitrary-code-execution")
@advice("当前许可证不允许执行包含任意代码的节点。请联系工作室管理员申请授权。")
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has executes_arbitrary_code && resource.executes_arbitrary_code };

在编写 forbid 规则时,强烈建议全部附带这些注解。若缺失 @advice,用户在被拦截时只能看到冰冷的规则 ID,而无法获得任何解决引导。

@capability 的取值虽然是自由字符串,但为了与系统内置提示保持高度统一,推荐优先复用可视化构建器的规范命名:

  • library —— 算子库基础权限
  • library-lifecycle —— 算子库生命周期
  • node-lifecycle —— 算子节点生命周期
  • arbitrary-code-execution —— 任意代码执行拦截
  • project —— 项目访问控制
  • model —— 具体模型调用
  • model-provider —— 模型服务商
  • model-family —— 模型家族
  • video-codec —— 视频编解码器

经典实战 Raw Cedar 策略范例

以下每个示例均为可以直接用于生产环境的完整 Raw Cedar 策略模版。

范例 1:拦截包含任意 Python 代码执行的节点

该示例包含全局放行语句,构成一套完整的黑名单阻断策略:

permit(principal, action, resource);

@id("nodes/no-arbitrary-code")
@capability("arbitrary-code-execution")
@advice("当前许可证已被禁用执行任意代码的算子节点。")
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has executes_arbitrary_code && resource.executes_arbitrary_code };

范例 2:拦截不稳定/实验阶段的算子库与节点

建议将库与节点的拦截语句分开声明,以便在触发拦截时精准指出用户缺失的对应能力项:

permit(principal, action, resource);

@id("lifecycle/no-experimental-libraries")
@capability("library-lifecycle")
@advice("当前许可证已阻断实验性算子库。请向工作室管理员咨询已受审批准入的库名单。")
forbid(principal, action == Action::"LoadLibrary", resource)
when {
  resource has lifecycle_stage &&
  (resource.lifecycle_stage == "LABS" || resource.lifecycle_stage == "ALPHA")
};

@id("lifecycle/no-experimental-nodes")
@capability("node-lifecycle")
@advice("当前许可证已阻断实验性节点。请使用 STABLE 或 BETA 正式版替代。")
forbid(principal, action == Action::"InstantiateNode", resource)
when {
  resource has lifecycle_stage &&
  (resource.lifecycle_stage == "LABS" || resource.lifecycle_stage == "ALPHA")
};

范例 3:仅允许调用指定的两家大模型服务商

通过 unless 子句反转匹配条件,除声明的供应商外全面阻断。由于 in 操作符在无法找到匹配祖先时直接返回 false 而非报错,因此无需使用 has 进行额外包裹。

限制模型时请同时声明 OfferModel 与 InvokeModel 检查点。OfferModel 负责从界面下拉列表中优雅移除,InvokeModel 负责阻断已绑定旧配置的执行流:

permit(principal, action, resource);

@id("models/approved-providers")
@capability("model-provider")
@advice("当前许可证仅被批准使用 Anthropic 与 OpenAI 旗下的大模型。")
forbid(principal, action in [Action::"OfferModel", Action::"InvokeModel"], resource)
unless { resource in ModelProvider::"anthropic" || resource in ModelProvider::"openai" };

范例 4:阻断整个模型家族(如淘汰旧版本)

因为 model_families 是集合类型,使用 Cedar 的 contains 函数进行匹配:

permit(principal, action, resource);

@id("models/no-claude-3")
@capability("model-family")
@advice("Claude 3 家族模型已在当前生产环境中停用下线,请升级切换至 Claude 4。")
forbid(principal, action in [Action::"OfferModel", Action::"InvokeModel"], resource)
when { resource has model_families && resource.model_families.contains("Claude 3") };

范例 5:仅允许打开和激活指定的白名单项目

加载(Load)与激活(Activate)在引擎中属于两个独立把关的检查点,因此规则中需同时声明二者:

permit(principal, action, resource);

@id("projects/approved")
@capability("project")
@advice("该项目未包含在您的席位授权许可范围内,请联系管理员申请访问权限。")
forbid(principal, action in [Action::"LoadProject", Action::"ActivateProject"], resource)
unless {
  resource has id &&
  (resource.id == "8f2c1a04-9d3e-4b7a-9f10-2c5d6e8a1b33" || resource.id == "c07b5e91-4a2d-4f88-bd63-1e9f7a205c48")
};

范例 6:限制视频编码写入格式

本规则仅针对视频导出写入检查点生效:

permit(principal, action, resource);

@id("video/no-prores-writes")
@capability("video-codec")
@advice("当前许可证未授权导出 ProRes 格式。请选择渲染为 H.264。")
forbid(principal, action == Action::"WriteVideoCodec", resource)
when { resource has id && resource.id == "prores" };

范例 7:将安全规则精准限定在指定项目中生效

Cedar 会通过逻辑 AND 自动组合多个 when 子句,因此你可以将项目范围限定与具体的资源判定逻辑分开书写,条理更清晰:

permit(principal, action, resource);

@id("show-a/no-experimental-nodes")
@capability("node-lifecycle")
@advice("Show A 项目生产封板环境强制锁定为稳定版节点。")
forbid(principal, action == Action::"InstantiateNode", resource)
when {
  context has active_project &&
  context.active_project has name &&
  context.active_project.name == "Show A"
}
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };

范例 8:限制 Headless 静默席位的模型调用权限

针对自动化渲染农场或 CI/CD 机器人的席位进行限制:

permit(principal, action, resource);

@id("headless/no-model-invocation")
@capability("model")
@advice("无头自动化席位在此套餐策略下不可直接发起云端大模型调用。")
forbid(principal, action == Action::"InvokeModel", resource)
when { context has license_type && context.license_type == "headless" };

易错陷阱与排坑要点 (Gotchas)

1. 务必对可选属性执行 has 防护检查

尝试读取一个不存在的可选属性会导致 Cedar 引擎抛出求值异常。Cedar 会将产生异常的条件视为“未满足 (unsatisfied)”,而 Griptape Nodes 引擎的安全机制则会对任何无法完全干净求值的操作直接执行拒绝 (Deny)。因此,未经保护的裸属性读取会导致 forbid 规则意外漏掉缺少该属性的合法资源,进而导致后续判定失控。

// 错误写法:会导致所有未显式声明 lifecycle_stage 的普通节点全部被连带阻断
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource.lifecycle_stage == "LABS" };

// 正确写法:先校验属性存在,再判断属性值
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };

由于 && 是短路求值的,务必将 has 校验放置在最左侧。

对于多层嵌套属性,每一层都需要单独保护。context has active_project 并不代表 active_project 一定拥有 name:

when {
  context has active_project &&
  context.active_project has name &&
  context.active_project.name == "Show A"
}

2. 严防属性与 Action 名称拼写手误

Griptape 节点引擎在加载 Cedar 策略时,不会针对业务架构 Schema 强校验字段拼写。诸如 resource.lifecycle_stge(少写 a)或 Action::"LoadLibrry"(少写 a)等拼写错误能够被 Cedar 语法解析器正常解析通过,但在实际运行时永远无法匹配中任何真实事件,导致策略形同虚设或静默失效。

3. Production 严格白名单模式下切勿遗漏必要检查点

如果一个 Production 模板仅放行了 permit(..., action == Action::"LoadLibrary", ...),那么库的加载确实可以通过,但 Cedar 的 Default Deny 机制会直接将后续的 InstantiateNode、OfferModel 等一切其他操作全部封死!

在手写纯白名单策略时,要么挂载另一个包含宽泛放行的模版,要么在一份策略中将整个工作流运转所需的全部检查点完整声明:

// 宽泛放行当前引擎管辖的所有检查点基线
permit(principal, action in [
  Action::"LoadLibrary",
  Action::"InstantiateNode",
  Action::"LoadProject",
  Action::"ActivateProject",
  Action::"OfferModel",
  Action::"InvokeModel",
  Action::"ReadVideoCodec",
  Action::"WriteVideoCodec"
], resource);

延伸阅读