Skip to content

Commit

Permalink
Merge pull request #125 from biscuit-auth/snapshot-post
Browse files Browse the repository at this point in the history
snapshot blogpost & documentation
  • Loading branch information
divarvel authored Nov 20, 2023
2 parents c17d123 + 32eaf20 commit bf8411a
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
110 changes: 110 additions & 0 deletions content/blog/snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
+++
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`][biscuit-rust] and most biscuit libraries, the authorization process is carried out through an [`Authorizer`][authorizer] value.

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`][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`][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 -
// 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/):

<bc-snapshot-printer snapshot="CgkI6AcQZBjAhD0Q2YkBGvMBCAQSCi9maWxlMS50eHQSBDEyMzQiRBADGgkKBwgKEgMYgQgaDQoLCAQSAxiACBICGAAqJgokCgIIGxIGCAUSAggFGhYKBAoCCAUKCAoGIIDEpKsGCgQaAggAKjUQAxoJCgcIAhIDGIAIGggKBggDEgIYABoMCgoIBRIGILCX3aoGKg4KDAoCCBsSBggKEgIICjIVChEKAggbEgsIBBIDGIAIEgIYABAAOicKAgoAEggKBggDEgIYABIJCgcIAhIDGIAIEgwKCggFEgYgsJfdqgY6HgoCEAASDQoLCAQSAxiACBICGAASCQoHCAoSAxiBCEAA"></bc-snapshot-printer>

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).

## 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 `<bc-snapshot-printer>` 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
54 changes: 54 additions & 0 deletions docs/src/usage/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand All @@ -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")
```
17 changes: 16 additions & 1 deletion docs/src/usage/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/src/usage/web-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ check if time($time), $time < 2023-05-04T00:00:00Z;
</code></pre>
</bc-token-generator>

### Snapshot printer

This component allows you to inspect the contents of a snapshot, optionally adding extra authorization code or queries.

```html
<bc-snapshot-printer snapshot="CgkI6AcQZBjAhD0Q72YaZAgEEgVmaWxlMSINEAMaCQoHCAQSAxiACCoQEAMaDAoKCAUSBiCo492qBjIRCg0KAggbEgcIBBIDGIAIEAA6EgoCCgASDAoKCAUSBiCo492qBjoPCgIQABIJCgcIBBIDGIAIQAA=" showAuthorizer="true" showQuery="true">
</bc-snapshot-printer>
```

<bc-snapshot-printer snapshot="CgkI6AcQZBjAhD0Q72YaZAgEEgVmaWxlMSINEAMaCQoHCAQSAxiACCoQEAMaDAoKCAUSBiCo492qBjIRCg0KAggbEgcIBBIDGIAIEAA6EgoCCgASDAoKCAUSBiCo492qBjoPCgIQABIJCgcIBBIDGIAIQAA=" showAuthorizer="true" showQuery="true">
</bc-snapshot-printer>

### Datalog playground

The datalog playground allows you to type in and evaluate datalog code without
Expand Down

0 comments on commit bf8411a

Please sign in to comment.