39个版本 (14个稳定版)

5.0.0 2024年7月31日
4.1.1 2024年2月14日
4.0.0 2023年8月29日
3.2.0 2023年6月10日
0.2.0 2019年11月27日

#9认证

Download history 98/week @ 2024-05-04 159/week @ 2024-05-11 1297/week @ 2024-05-18 18729/week @ 2024-05-25 25423/week @ 2024-06-01 11832/week @ 2024-06-08 9411/week @ 2024-06-15 3461/week @ 2024-06-22 2428/week @ 2024-06-29 22785/week @ 2024-07-06 31296/week @ 2024-07-13 32366/week @ 2024-07-20 34411/week @ 2024-07-27 23853/week @ 2024-08-03 47933/week @ 2024-08-10 41101/week @ 2024-08-17

每月 149,445次下载
8 crates 中使用

Apache-2.0

570KB
14K SLoC

饼干认证和授权令牌

饼干是专为微服务架构设计的授权令牌,具有以下特性

  • 去中心化验证:任何节点只需使用公共信息即可验证令牌;
  • 离线委托:持有人可以通过衰减其权限来从另一个令牌创建一个新有效令牌,而无需与任何人通信;
  • 基于能力:微服务的授权应与请求相关的权限相关联,而不是依赖于验证者可能不理解的身份;
  • 灵活的权限管理:令牌使用逻辑语言来指定衰减并添加对环境数据的限制;
  • 足够小,可以适用于任何地方(cookie等)。

非目标

  • 这并不是一种新的认证协议。饼干令牌可以作为由其他系统(如OAuth)提供的不可见令牌使用。
  • 撤销:虽然令牌带有过期日期,但撤销需要外部状态管理。

用法

在本例中,我们将看到如何创建令牌、添加一些检查、序列化和反序列化令牌、添加更多检查,并在请求的上下文中验证这些检查。

extern crate biscuit_auth as biscuit;

use biscuit::{KeyPair, Biscuit, error};

fn main() -> Result<(), error::Token> {
  // let's generate the root key pair. The root public key will be necessary
  // to verify the token
  let root = KeyPair::new();
  let public_key = root.public();

  // creating a first token
  let token1 = {
    // the first block of the token is the authority block. It contains global
    // information like which operation types are available
    let biscuit = biscuit!(r#"
          right("/a/file1.txt", "read");
          right("/a/file1.txt", "write");
          right("/a/file2.txt", "read");
          right("/b/file3.txt", "write");
    "#).build(&root)?; // the first block is signed

    println!("biscuit (authority): {}", biscuit);

    biscuit.to_vec()?
  };

  // this token is only 258 bytes, holding the authority data and the signature
  assert_eq!(token1.len(), 258);

  // now let's add some restrictions to this token
  // we want to limit access to `/a/file1.txt` and to read operations
  let token2 = {
    // the token is deserialized, the signature is verified
    let deser = Biscuit::from(&token1, root.public())?;

    // biscuits can be attenuated by appending checks
    let biscuit = deser.append(block!(r#"
      // checks are implemented as logic rules. If the rule produces something,
      // the check is successful
      // here we verify the presence of a `resource` fact with a path set to "/a/file1.txt"
      // and a read operation
      check if resource("/a/file1.txt"), operation("read");
    "#));
    println!("biscuit (authority): {}", biscuit);

    biscuit.to_vec()?
  };

  // this new token fits in 400 bytes
  assert_eq!(token2.len(), 400);

  /************** VERIFICATION ****************/

  // let's deserialize the token:
  let biscuit2 = Biscuit::from(&token2, public_key)?;

  // let's define 3 verifiers (corresponding to 3 different requests):
  // - one for /a/file1.txt and a read operation
  // - one for /a/file1.txt and a write operation
  // - one for /a/file2.txt and a read operation

  let v1 = authorizer!(r#"
     resource("/a/file1.txt");
     operation("read");
     
     // a verifier can come with allow/deny policies. While checks are all tested
     // and must all succeeed, allow/deny policies are tried one by one in order,
     // and we stop verification on the first that matches
     //
     // here we will check that the token has the corresponding right
     allow if right("/a/file1.txt", "read");
     // explicit catch-all deny. here it is not necessary: if no policy
     // matches, a default deny applies
     deny if true;
  "#);

  let mut v2 = authorizer!(r#"
     resource("/a/file1.txt");
     operation("write");
     allow if right("/a/file1.txt", "write");
  "#);
  
  let mut v3 = authorizer!(r#"
     resource("/a/file2.txt");
     operation("read");
     allow if right("/a/file2.txt", "read");
  "#);

  // the token restricts to read operations:
  assert!(biscuit.authorize(&v1).is_ok());
  // the second verifier requested a read operation
  assert!(biscuit.authorize(&v2).is_err());
  // the third verifier requests /a/file2.txt
  assert!(biscuit.authorize(&v3).is_err());

  Ok(())
}

概念

饼干令牌由一系列定义数据和必须与请求一起验证的检查的块组成。任何失败的检查都会使整个令牌无效。

如果您持有有效的令牌,您可以将新块添加到令牌中,以进一步限制它,例如限制对特定资源的访问或添加短的有效期。这将生成一个新的、有效的令牌。这可以在不询问原始令牌创建者的离线状态下完成。

另一方面,如果修改或删除了块,令牌将失败加密签名验证。

密码学

饼干令牌从饼干和JSON Web Tokens中汲取灵感,复制了两者中有用的功能

  • 类似于饼干的离线委托
  • 基于公钥密码学,类似于JWT,因此任何持有根公钥的应用程序都可以验证令牌(而饼干基于根共享机密)

用于授权策略的逻辑语言:Datalog

我们依赖于Datalog的一个修改版本,它可以以紧凑的形式表示复杂的行为,并在数据上添加灵活的约束。

以下是用该语言实现的一些检查示例

  • 当请求的资源是 "file.txt" 且操作是 "读取" 时有效
  • 如果当前时间早于2030年1月1日00:00:00 UTC
  • 源IP地址在集合[1.2.3.4, 5.6.7.8]中
  • 资源与前缀"/home/biscuit/data/"匹配
  • 但它也可以组合成更复杂的模式,例如:如果用户有读取权限或用户是组织的成员,并且组织有读取权限,或者有读取权限的其他用户已将权限委派给用户,则右侧有读取权限。

与Datalog类似,该语言围绕事实和规则构建,但有一些细微的修改

  • 块可以提供事实,但它们对其他块不可见。它们包含使用当前块中的事实或来自权威和环境的上下文的事实所编写的规则。如果一个块中的所有规则都成功,则该块被验证。

检查规则需要存在一个或多个事实,并且可以对这些事实有附加的表达式。可以创建如下这样的规则

  • 检查资源("file1")
  • 检查是否路径($path) & 拥有者("user1", $path) // $path代表一个变量。我们寻找一组路径,其中在哪里路径解析为相同的值
  • 检查时间($t),$t < 2019-02-05T23:00:00Z // 过期日期
  • 检查应用($app),操作($op),用户($user),权利("app", $app, $op),拥有者($user, $app),信用($user, $amount),$amount > 0 // 验证用户拥有应用程序,应用程序有权执行操作,存在关于操作的信用信息,并且信用大于0

符号和符号表

为了减少令牌的大小,该格式包含一个包含字符串的符号表。任何字符串都作为索引序列化为该表。

它们可以用于事实或规则的格式化输出。例如,在一个包含["resource", "operation", "read", "rule1", "file.txt"]的表中,我们可以有一个如下规则:#4 <- #0(#5) & #1(#2)将打印为rule1 <- resource("file.txt"), operation("read")

biscuit实现附带默认符号表,以避免在每个令牌中传输频繁值。

C绑定

该项目可以使用cargo-c生成C绑定。

编译它

cargo cinstall --prefix=/usr --destdir=./build

使用以下命令运行C集成测试

cargo ctest

许可证

根据Apache License,版本2.0授权(LICENSE-APACHEhttp://www.apache.org/licenses/LICENSE-2.0

贡献

除非你明确声明,否则根据Apache-2.0许可证定义的,任何有意提交给您的工作以包含的贡献,应按上述方式授权,不附加任何额外条款或条件。

依赖

~8–20MB
~304K SLoC