#cedar-policy #cedar #policy #authorization #security #agent #data-provider

bin+lib cedar-local-agent

创建基于Cedar的异步授权程序的基础库

2 个稳定版本

2.0.0 2024年3月14日
1.1.0 2024年2月29日
1.0.0 2023年12月14日

#228数据库接口

21 每月下载次数
avp-local-agent中使用

Apache-2.0

175KB
3K SLoC

Cedar Local Agent

此crate是实验性的。

cedar-local-agent crate 为客户提供创建可以处理两种不同操作模式的异步授权程序的有用基础

  1. 在评估请求时,授权程序可以缓存所有应用策略和实体数据
  2. 在评估请求时,授权程序无法缓存所有应用策略和实体数据

cedar-local-agent crate 提供了一个 simple::Authorizer,它可以构建为选项(1)或(2)。该 simple::Authorizer 使用策略和实体提供者构建。这些提供者可以由客户实现。

cedar-local-agent 提供了实现选项(1)的提供者的示例实现。一个文件系统策略集提供者:file::PolicySetProvider,以及一个实体提供者:file::EntityProvider

有关Cedar语言/项目的更多信息,请访问cedarpolicy.com

用法

Cedar Local Agent 可以通过 cedar-local-agent crate 在您的应用程序中使用。

cedar-local-agent 添加到您的 Cargo.toml 文件中的依赖项。例如

[dependencies]
cedar-local-agent = "1.0"

快速入门

构建一个本地授权器,该授权器使用本地存储的策略集、实体存储和模式来评估授权决策。

策略数据:tests/data/sweets.cedar

实体数据:tests/data/sweets.entities.json

模式:tests/data/sweets.schema.cedar.json

构建策略集

let policy_set_provider = PolicySetProvider::new(
    policy_set_provider::ConfigBuilder::default()
        .policy_set_path("tests/data/sweets.cedar")
        .build()
        .unwrap(),
)
.unwrap();

构建实体提供者

let entity_provider = EntityProvider::new(
    entity_provider::ConfigBuilder::default()
        .entities_path("tests/data/sweets.entities.json")
        .schema_path("tests/data/sweets.schema.cedar.json")
        .build()
        .unwrap(),
)
.unwrap();

构建授权器

let authorizer: Authorizer<PolicySetProvider, EntityProvider> = Authorizer::new(
    AuthorizerConfigBuilder::default()
        .entity_provider(Arc::new(entity_provider))
        .policy_set_provider(Arc::new(policy_set_provider))
        .build()
        .unwrap(),
);

评估决策

assert_eq!(
    authorizer
        .is_authorized(&Request::new(
            Some(format!("User::\"Cedar\"").parse().unwrap()),
            Some(format!("Action::\"read\"").parse().unwrap()),
            Some(format!("Box::\"3\"").parse().unwrap()),
            Context::empty(),
        ), &Entities::empty())
        .await
        .unwrap()
        .decision(),
    Decision::Deny
)

simple::Authorizer is_authorized API语义

simple::Authorizer is_authorized API接受API内的Cedar请求Cedar实体

pub async fn is_authorized(
    &self,
    request: &Request,
    entities: &Entities,
) -> Result<Response, AuthorizerError> { ... }

对于将相同的实体标识符(EID)作为输入传递给实体提供者并返回的场景,输入被认为是最后一个值。此API优先考虑“最后一个值获胜”的语义。此行为将根据RFC-0020进行更改。

更新file::PolicySetProviderfile::EntityProvider数据

file::PolicySetProviderfile::EntityProvider在初始化时收集数据并将其缓存到内存中。在授权决策过程中不会从磁盘读取数据。

在授权器初始化后,策略和实体数据可以在磁盘上修改。为此,提供了刷新策略和实体数据的功能,以适应授权决策。为此,需要至少两个额外的线程,共计三个线程。

  1. 主线程处理is_authorized调用
  2. 信号器线程通知接收器所需的更新
  3. 接收器线程监听更新

Channels 用于在信号发送线程和接收线程之间进行通信。提供了两个创建信号发送线程的函数。两个函数都返回一个信号发送线程和一个 tokio::broadcast::receiver 作为输出。

  1. clock_ticker_task 会定期根据时钟持续时间唤醒并发出信号
  2. file_inspector_task 会定期唤醒并使用抗碰撞哈希函数(SHA256)检查文件差异,并在修改时发出通知

注意:在选择触发策略刷新的信号发送器的刷新率时需要小心。我们已设置了一个 RefreshRate 枚举,它提供了至少15秒的几种刷新率选项。这足够快以适应大多数应用程序,但又足够慢,以至于不太可能触发大多数策略源的各种节流或性能影响。

接收器需要传递到一个新的独立线程中以监听和响应事件。 update_provider_data_task 处理以 Event 的形式接收这些信号。消息一次处理一条。接收器线程会阻塞,直到成功或失败地更新了提供者的数据。

示例:每15秒更新策略集提供者数据的用法

let (clock_ticker_signal_thread, receiver) = clock_ticker_task(RefreshRate::FifteenSeconds);

let policy_set_provider = Arc::new(PolicySetProvider::new(
    policy_set_provider::ConfigBuilder::default()
        .policy_set_path("tests/data/sweets.cedar")
        .build()
        .unwrap(),
)
.unwrap());

let update_provider_thread = update_provider_data_task(policy_set_provider.clone(), receiver);

注意:这些后台线程必须在应用程序的生命周期内保持作用域。如果在后台线程中更新时出现问题,它将产生一个 error!() 消息,但不会导致应用程序崩溃。

这意味着如果由于任何原因,代理无法更新其策略集(由于错误),代理将继续以过时的策略集运行,并记录一个错误。由于代理将记录 error!(),因此可以配置基于日志的警报,以便快速捕获这些失败,但这超出了本README的范围。

跟踪

此crate发出 tracing 跟踪数据,并可集成到标准跟踪实现中。

授权日志记录

授权日志设计用于提供检测和响应功能。示例功能可以在 Mitre D3fend 矩阵 下找到,例如 用户行为分析

授权者提供的事件作为 tracing events 发出。授权事件包含在跟踪 spans 中。授权事件默认使用 开放网络安全格式 格式化。授权事件可以可选地过滤、格式化并直接路由到授权日志。请参见示例

// Dependencies must be included in the `Cargo.toml` file of the application
// tracing, tracing-appender, tracing-subscriber

let authorization_roller = tracing_appender::rolling::minutely("logs", "authorization.log");
let (authorization_non_blocking, _guard) = tracing_appender::non_blocking(authorization_roller);
let authorization_log_layer = tracing_subscriber::fmt::layer()
    .event_format(AuthorizerFormatter(AuthorizerTarget::Simple))
    .with_writer(authorization_non_blocking);
tracing_subscriber::registry()
    .with(authorization_log_layer)
    .try_init()
    .expect("Logging Failed to Start, Exiting.");

要过滤授权事件日志,向授权者提供包含要记录字段的 FieldSet 的日志配置。默认情况下,如果没有显式配置,则不会记录任何字段。

授权请求内记录所有内容的示例用法。注意:记录所有内容是不安全的;请参阅安全日志配置

let log_config =
    log::ConfigBuilder::default()
        .field_set(log::FieldSetBuilder::default()
            .principal(true)
            .action(true)
            .resource(true)
            .context(true)
            .entities(log::FieldLevel::All)
            .build()
            .unwrap())
    .build()
    .unwrap();

let authorizer: Authorizer<PolicySetProvider, EntityProvider> = Authorizer::new(
    AuthorizerConfigBuilder::default()
        .entity_provider(...)
        .policy_set_provider(...)
        .log_config(log_config)
        .build()
        .unwrap(),
);

注意:Authorizerlog_config 中的 FieldSet::entities 指的是Cedar实体。还有一个名为 OCSF 的字段 entity,它指的是发送请求的主实体。

这意味着当 Authorizerlog_config 中的 FieldSet::entities 设置为 FieldLevel::None 时,OCSF 实体仍然会被记录。这不是错误,而是预期行为。

有关如何设置授权日志的更多示例,请参阅我们的使用示例

安全日志配置

使用设置任何与Cedar相关的字段(主体、操作、资源、上下文和实体)为 truelog::FieldSet 配置将导致该字段被记录。这些字段可能包含敏感信息,应谨慎公开。此外,Cedar语言在Request 中没有当前的字段大小限制。带有详尽日志记录的大请求可能导致更多的磁盘I/O,这可能会对应用程序的性能产生负面影响。

对于安全的配置,创建一个默认的 log::FieldSet,使用 log::FieldSetBuilder::default().build().unwrap()。此选项将像这样擦除用户输入

"实体":{"数据":{"父级":[]},"名称":"敏感信息","类型":"敏感信息"}

注意

Cedar目前不支持从Request结构中提取Context,因为它私有,因此使用request.to_string()提取。这并不理想,因为它记录了整个请求(主体、操作、资源、上下文)而不是仅记录上下文。

特别是这带来了两个怪癖

  • 如果 FieldSet 将主体和上下文设置为 true,则生成的日志将包含主体两次。
  • 如果 FieldSet 将主体设置为 false 且上下文设置为 true,则生成的日志仍将包含主体,因为它包含在提取上下文所需的 request.to_string() 调用中。

上述内容不仅针对主体,也针对操作和资源。已创建一个cedar GitHub问题,以在cedar Request结构中添加上下文的getter来解决此问题:https://github.com/cedar-policy/cedar/issues/363

示例应用程序

本项目基于一个虚构的应用程序,该程序允许用户管理糖果盒。存在两个实体

  1. 盒(资源)
  2. 用户(主体)

存在两个用户实体

  1. 埃里克
  2. 迈克

存在十个资源,Box。每个盒子都有一个实体标识符(id),其数值介于1-10之间。每个Box有一个属性owner。每个Box的所有者定义在tests/data/sweets.entities.json文件中,此文件代表该应用程序的完整信息数据库。

Users可以对每个Box执行的操作在架构文件中定义:tests/data/sweets.schema.cedar.json。总的来说,每个User可以对Box资源执行以下操作之一:readupdatedelete

策略是一个声明,它指出哪些Users被明确允许或被明确禁止对资源Box执行操作。以下是一个示例策略

@id("owner-policy")
permit(principal, action, resource)
when { principal == resource.owner };

有关这些策略的完整详细信息,请参阅tests/data/sweets.cedar。此文件代表该应用程序的所有策略。

给定schemaentitiespolicy_set,应用程序可以使用上述提供的Authorizer。以下是一个示例请求和预期结果

 assert_eq!(
     authorizer
         .is_authorized(&Request::new(
             Some(format!("User::\"Mike\"").parse().unwrap()),
             Some(format!("Action::\"read\"").parse().unwrap()),
             Some(format!("Box::\"3\"").parse().unwrap()),
             Context::empty(),
         ), &Entities::empty())
         .await
         .unwrap()
         .decision(),
     Decision::Deny
 );
 assert_eq!(
     authorizer
         .is_authorized(&Request::new(
             Some(format!("User::\"Mike\"").parse().unwrap()),
             Some(format!("Action::\"read\"").parse().unwrap()),
             Some(format!("Box::\"2\"").parse().unwrap()),
             Context::empty(),
         ), &Entities::empty())
         .await
         .unwrap()
         .decision(),
     Decision::Allow
 );

请参阅以下示例应用程序,该应用程序位于集成测试中:tests/lib.rs

一般安全注意事项

以下是在使用cedar-local-agent启用存储在本地文件系统上的Cedar策略进行本地评估时需要考虑的一些安全问题的概述。

可信计算环境

cedar-local-agent仅是一个库,客户可以将其包装在HTTP服务器中,并在主机群上部署。因此,用户需要采取任何必要的预防措施来确保满足除cedar-local-agent能够强制执行之外的所有安全要求。这包括

  1. 磁盘上策略存储的文件系统权限应限制为最小权限,请参阅限制对本地数据文件的访问
  2. 磁盘上OCSF日志位置的文件系统权限遵循最小权限,请参阅OCSF日志目录权限
  3. 配置安全可靠的 cedar-local-agent,请参阅快速入门更新 file::PolicySetProviderfile::EntityProvider 数据以获取配置最佳实践。
  4. 验证读取文件的大小和向 is_authorized 发起的请求,以防止潜在的拒绝服务。

限制对本地数据文件的访问

此 crate 提供的本地授权者只需对本地存储的策略集、实体存储和模式文件进行 读取 访问。

对本地数据文件(策略、实体和模式)的写入访问应仅限于真正需要修改这些文件的用户,例如,添加新实体和删除旧策略。

在没有对本地数据文件进行限制的情况下,恶意操作系统(OS)用户可以添加或删除策略,修改实体属性,进行难以识别的细微更改,甚至更改策略以拒绝所有操作。为了说明这种可能性,考虑以下来自[示例应用程序](## Example application)的 cedar 文件中的 cedar 策略。

@id("mike-edit-box-1")
permit (
    principal == User::"Mike",
    action == Action::"update",
    resource == Box::"1"
);

@id("eric-view-box-9")
permit (
    principal == User::"Eric",
    action == Action::"read",
    resource == Box::"9"
);

在这个例子中,主体“Mike”被允许在资源箱“1”上执行“更新”,而主体“Eric”被允许在资源箱“9”上执行“读取”。现在,考虑一个恶意 OS 用户将以下语句添加到相同的策略文件中。

@id("ill-intentioned-policy")
forbid(principal, action, resource);

在下一次策略文件刷新周期中,file::PolicySetProvider 将刷新策略文件内容到内存中,并且本地授权者将拒绝任何主体的任何操作。

如何避免这种情况发生?

为了防止这种安全问题,必须限制对数据文件的读取访问,更重要的是,限制对这些文件的写入访问。只有需要写入更改策略或实体的用户或组才应被允许这样做(例如,从内部应用程序获取策略的另一个代理)。

以下是如何避免这种问题的示例,假设您使用 cedar-local-agent crate 构建 local-agent,具有以下文件夹结构。

authz-agent/
  |- authz_daemon (executable)

authz-local-data/
  |- policies.cedar
  |- entities.json
  |- schema.json

现在假设有一个名为“authz-daemon”的 OS 用户从用户组“authz-ops”中执行“authz_daemon”。您还有一个来自同一用户组“authz-ops”的用户“authz-ops-admin”,他将能够更新数据文件。

然后,使用以下命令将“authz-ops-admin”设为 authz-local-data 文件夹的所有者

$ chown -R authz-ops-admin:authz-ops authz-local-data

然后,将“authz-daemon”用户设为 authz-agent 文件夹的所有者

$ chown -R authz-daemon:authz-ops authz-agent

最后,使 authz-local-data 可由所有人读取,但只有所有者可写入

$ chmod u=rwx,go=r authz-local-data

OCSF 日志目录权限

此 crate 提供的本地授权者需要对其写入 OCFS 日志的目录进行 读取写入 访问。

假设我们有以下目录结构

authz-agent/
  |- authz_daemon (executable)

ocsf-log-dir/
  |- authorization.log.2023-11-15-21-02
  ...

现在假设有一个名为 authz-daemon 的 OS 用户执行 authz_daemon,它应该位于名为“log-reader”的组中。

然后,将 authz-daemon 用户设为 ocsf-log-dir 文件夹的所有者

$ chown -R authz-daemon:log-reader ocsf-log-dir

现在,我们将使 ocsf-log-dir 可由所有者读取和写入,但不能由其他人写入。我们允许 log-reader 组中的任何人读取文件夹的内容,但不能写入。

$ chmod u=wrx,g=rx,o= ocsf-log-dir

注意:我们需要允许 执行 权限才能访问目录中的文件。

任何需要访问日志的代理,例如 AWS Cloudwatch Agent,都应该以日志读取组中的用户身份运行,以便它们能够获得适当的访问权限(有关如何配置 Cloudwatch Agent 以特定用户身份运行的说明,请参阅文档)。

获取帮助

许可证

本项目受 Apache-2.0 许可证许可。

依赖项

~19–31MB
~467K SLoC