From 7653aeb21bb94ecf74c0f69d4f0bc0064578587e Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 12:33:07 +0100 Subject: [PATCH 1/6] first draft of the snapshot blogpost --- content/blog/snapshots.md | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 content/blog/snapshots.md diff --git a/content/blog/snapshots.md b/content/blog/snapshots.md new file mode 100644 index 0000000..fb69412 --- /dev/null +++ b/content/blog/snapshots.md @@ -0,0 +1,76 @@ ++++ +title = "Biscuit snapshots" +description = "A key feature for auditing & debugging" +date = 2023-11-20T00:09:00+02:00 +draft = false +template = "blog/page.html" + +[taxonomies] +authors = ["clementd"] + +[extra] +lead = "What are biscuit snapshots, how do they work and how can they be useful" ++++ + +One of the defining features in biscuit is the common language for authorization policies. Along with the cryptographic constructs used in tokens, it is what allows offline attuenation. + +A common language for policies means that we can have a standardized serialization format, which in turn means it can be embedded in a token, which gives us offline attenuation. Neat! + +It turns out that being able to serialize authorization policies gives us another benefit: it is possible to take a snapshot of an authorization process and save it for later. That's what we'll talk about today. + +## Authorizer snapshots + +In `biscuit-rust` and most biscuit libraries, the authorization process is carried out through an `Authorizer` value. + +An `Authorizer` is created from a biscuit token, along with facts, rules, checks, and policies added by the authorizing party. + +Once all this has been provided, the `Authorizer` runs datalog evaluation (it repeatedly generates new datalog facts from rules unless no new facts can be generated). Once this is done, checks and policies are evaluated and are used to compute the authorization result (all checks have to pass, and the first policy to match must be an `allow` policy). The `Authorizer` makes sure these two steps are carried out in a timely fashion by aborting after a specified timeout, if too many facts are generated, or after a specific amount of iterations. This is crucial to make sure authorization does not become a DoS target. + +The good news is that an `Authorizer` only contains serializable data, and as such can be stored. + +```rust +let mut authorizer = authorizer!( + r#"time({now}); + resource("/file1.txt"); + operation("read"); + check if user($user); + allow if right("/file1.txt", read); + "#, + now = SystemTime::now(), +); +authorizer.add_token(biscuit); +let result = authorizer.authorize(); +println!("{}", authorizer.snapshot().to_base64_snapshot()) +``` + +This will give you something like: + +``` + CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA +``` + +Once you have that, you can inspect it with the CLI: + +``` +echo "CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA" | biscuit inspect-snapshot - +``` + +Or directly with the [web-based snapshot inspector](todo): + + + +Here you can see the whole authorization context, as well as interesting metadata such as the time taken by the authorization process (17μs, not too bad) and the number of iterations needed by fact generation (here, 0 as there are no rules). + +## Snapshots use cases + +### Auditing & debugging + +Being able to inspect the full authorization context after the fact feels a bit like a superpower. You can confidently say why a request was granted or denied after the fact. This can save you hours of work when trying to debug a gnarly authorization issues, instead of trying to modify your access policies until something works. + +I have found snapshots to be immensely valuable when working on complex authorization logic. Instead of using a debugger or putting `println!()` calls everywhere, I just printed an authorizer snapshot and inspected it interactively with `biscuit-cli`. For instance, `biscuit-cli` lets me run queries on snapshots, which helped me easily detect typos or test predicates. + +A similar use-case is auditing access. For highly sensitive operations, you might want to keep track of who is accessing resources, and why they are allowed to. Snapshots are a perfect use-case for that. + +### Resumable execution + +Snapshots allow separating the authorization process in several steps: first you create an authorizer from a biscuit token, and then pass the authorizer around (serialized through a snapshot), to finally resume authorization somewhere else. While doing it in one step is better in most cases, some software stacks can be overly restrictive and force a separate authentication step (verify biscuit signatures) before authorization (evaluate datalog policies). From 0648b9611e0b96b44a08d8de2e6703a6b2d18a6c Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 14:36:06 +0100 Subject: [PATCH 2/6] snapshots post: add links --- content/blog/snapshots.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/content/blog/snapshots.md b/content/blog/snapshots.md index fb69412..ac21f05 100644 --- a/content/blog/snapshots.md +++ b/content/blog/snapshots.md @@ -20,13 +20,13 @@ It turns out that being able to serialize authorization policies gives us anothe ## Authorizer snapshots -In `biscuit-rust` and most biscuit libraries, the authorization process is carried out through an `Authorizer` value. +In [`biscuit-rust`][biscuit-rust] and most biscuit libraries, the authorization process is carried out through an [`Authorizer`][authorizer] value. -An `Authorizer` is created from a biscuit token, along with facts, rules, checks, and policies added by the authorizing party. +An [`Authorizer`][authorizer] is created from a biscuit token, along with facts, rules, checks, and policies added by the authorizing party. -Once all this has been provided, the `Authorizer` runs datalog evaluation (it repeatedly generates new datalog facts from rules unless no new facts can be generated). Once this is done, checks and policies are evaluated and are used to compute the authorization result (all checks have to pass, and the first policy to match must be an `allow` policy). The `Authorizer` makes sure these two steps are carried out in a timely fashion by aborting after a specified timeout, if too many facts are generated, or after a specific amount of iterations. This is crucial to make sure authorization does not become a DoS target. +Once all this has been provided, the [`Authorizer`][authorizer] runs datalog evaluation (it repeatedly generates new datalog facts from rules unless no new facts can be generated). Once this is done, checks and policies are evaluated and are used to compute the authorization result (all checks have to pass, and the first policy to match must be an `allow` policy). The [`Authorizer`][authorizer] makes sure these two steps are carried out in a timely fashion by aborting after a specified timeout, if too many facts are generated, or after a specific amount of iterations. This is crucial to make sure authorization does not become a DoS target. -The good news is that an `Authorizer` only contains serializable data, and as such can be stored. +The good news is that an [`Authorizer`][authorizer] only contains serializable data, and as such can be stored. ```rust let mut authorizer = authorizer!( @@ -55,7 +55,7 @@ Once you have that, you can inspect it with the CLI: echo "CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA" | biscuit inspect-snapshot - ``` -Or directly with the [web-based snapshot inspector](todo): +Or directly with the [web-based snapshot inspector](/docs/tooling/snapshot-inspector/): @@ -74,3 +74,15 @@ A similar use-case is auditing access. For highly sensitive operations, you migh ### Resumable execution Snapshots allow separating the authorization process in several steps: first you create an authorizer from a biscuit token, and then pass the authorizer around (serialized through a snapshot), to finally resume authorization somewhere else. While doing it in one step is better in most cases, some software stacks can be overly restrictive and force a separate authentication step (verify biscuit signatures) before authorization (evaluate datalog policies). + +## Tooling support + +Saving and loading snapshots is available in [`biscuit-rust`][biscuit-rust] and [`biscuit-python`][biscuit-python]. + +[`biscuit-cli`][biscuit-cli] also lets you save a snapshot (`biscuit inspect --dump-snapshot-to`) and inspect a snapshot (`biscuit inspect-snapshot`), with optional authorizer code and queries. [`biscuit-web-components`][biscuit-web-components] provides a `` component, which allows inspecting and querying snapshot. + +[biscuit-rust]: https://crates.io/crates/biscuit-auth +[biscuit-cli]: https://github.com/biscuit-auth/biscuit-cli +[biscuit-web-components]: https://doc.biscuitsec.org/usage/web-components +[biscuit-python]: https://pypi.org/project/biscuit-python/ +[authorizer]: https://docs.rs/biscuit-auth/4.0.0/biscuit_auth/struct.Authorizer.html From 56f7549d18b61aeab8f779d6d9e1a409ddae85b9 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 14:36:17 +0100 Subject: [PATCH 3/6] snapshots post: add output for biscuit inspect-snapshot --- content/blog/snapshots.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/content/blog/snapshots.md b/content/blog/snapshots.md index ac21f05..b01fb1a 100644 --- a/content/blog/snapshots.md +++ b/content/blog/snapshots.md @@ -52,7 +52,29 @@ This will give you something like: Once you have that, you can inspect it with the CLI: ``` -echo "CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA" | biscuit inspect-snapshot - +$ echo "CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA" \ + | biscuit inspect-snapshot - + +// Facts: +// origin: 0 +right("/file1.txt", "read"); +user("1234"); +// origin: authorizer +operation("read"); +resource("/file1.txt"); +time(2023-11-17T11:17:04Z); + +// Checks: +// origin: authorizer +check if user($user); +// origin: 0 +check if time($time), $time < 2023-12-01T00:00:00Z; + +// Policies: +allow if right("/file1.txt", "read"); + +⏱️ Execution time: 17μs (0 iterations) +🙈 Datalog check skipped 🛡️ ``` Or directly with the [web-based snapshot inspector](/docs/tooling/snapshot-inspector/): From ba94f0a6f85ee0bea103dac255867d68d6d46f0b Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 15:01:38 +0100 Subject: [PATCH 4/6] cli: document snapshots --- docs/src/usage/command-line.md | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/src/usage/command-line.md b/docs/src/usage/command-line.md index 7f44cd9..a828f5d 100644 --- a/docs/src/usage/command-line.md +++ b/docs/src/usage/command-line.md @@ -106,6 +106,31 @@ $ biscuit inspect --raw-input biscuit-file.bc \ > Matched allow policy: allow if right("file1") ``` +## Generate a snapshot + +Biscuit inspect can store the authorization context to a file, which can be inspected later. The file will contain both the token contents, and the authorizer contents. + +``` +$ biscuit inspect --raw-input biscuit-file.bc \ + --public-key-file public-key-file \ + --authorize-with 'allow if right("file1");' \ + --include-time \ + --dump-snapshot-to snapshot-file +> Open biscuit +> Authority block: +> == Datalog == +> right("file1"); +> +> == Revocation id == +> a1675990f0b23015019a49b6b003c14fcfd2be134c9899b8146f4f702f8089486ca20766e188cd3388eb8ef653327a78e2dc0f6e42d31be8d97b1c5a8488eb0e +> +> ========== +> +> ✅ Public key check succeeded 🔑 +> ✅ Authorizer check succeeded 🛡️ +> Matched allow policy: allow if right("file1") +``` + ## Attenuate a token ``` @@ -124,3 +149,32 @@ $ biscuit attenuate --raw-input biscuit-file.bc --add-ttl "1 day" --block "" # this will prevent a biscuit from being attenuated further $ biscuit seal --raw-input biscuit-file.bc ``` + +## Inspect a snapshot + +`inspect-snapshot` displays the contents of a snapshot (facts, rules, checks, policies), as well as how much time has been spent evaluating datalog. + +The authorization process can be resumed with `--authorize-interactive`, `--authorize-with`, or `--authorize-with-file`. + +The authorizer can be queried with `--query` or `--query-all` + +``` +$ biscuit inspect-snapshot snapshot-file \ + --authorize-with "" \ + --query 'data($file) <- right($file)' +// Facts: +// origin: 0 +right("file1"); +// origin: authorizer +time(2023-11-17T13:59:04Z); + +// Policies: +allow if right("file1"); + +⏱️ Execution time: 13μs (0 iterations) +✅ Authorizer check succeeded 🛡️ +Matched allow policy: allow if right("file1") + +🔎 Running query: data($file) <- right($file) +data("file1") +``` From dc7577c754f0a74a34f1f2ade81e1c1dee053f5f Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 15:05:40 +0100 Subject: [PATCH 5/6] Document `bc-snapshot-printer` --- docs/src/usage/web-components.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/src/usage/web-components.md b/docs/src/usage/web-components.md index e0d53ff..9657228 100644 --- a/docs/src/usage/web-components.md +++ b/docs/src/usage/web-components.md @@ -192,6 +192,18 @@ check if time($time), $time < 2023-05-04T00:00:00Z; +### Snapshot printer + +This component allows you to inspect the contents of a snapshot, optionally adding extra authorization code or queries. + +```html + + +``` + + + + ### Datalog playground The datalog playground allows you to type in and evaluate datalog code without From 32eaf201265614f83d77dde262afda72d47b4211 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 17 Nov 2023 15:10:39 +0100 Subject: [PATCH 6/6] Document snapshots in biscuit-rust --- docs/src/usage/rust.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/src/usage/rust.md b/docs/src/usage/rust.md index 4fc607b..b75312e 100644 --- a/docs/src/usage/rust.md +++ b/docs/src/usage/rust.md @@ -75,12 +75,27 @@ fn authorize(token: &Biscuit) -> Result<(), error::Token> { // link the token to the authorizer authorizer.add_token(token)?; - authorizer.authorize()?; + let result = authorizer.authorize(); + // store the authorization context + println!("{}", authorizer.to_base64_snapshot()?); + + let _ = result?; Ok(()) } ``` +## Restore an authorizer from a snasphot + +```rust +use biscuit_auth::Authorizer; + +fn display(snapshot: &str) { + let authorizer = Authorizer::from_base64_snapshot(snapshot).unwrap(); + println!("{authorizer}"); +} +``` + ## Attenuate a token ```rust