Using Biscuits

Biscuits are the self-describing, decentralized authorization for Space and Time.

Most databases have a central authority (a Database Administrator, or DBA) who enforces authorization based on a set of centralized rules. In a decentralized environment, this must work a little different.

Space and Time employs biscuits, a self-describing cryptographic authorization key that tells the network (a) who has access (b) to what resources (c) with what capabilities (d) for how long, and (e) with what security checks on the requesting user. They are created from a ED25519 private key that matches the public key built into any on-network resource.

Let's Get Started!

To be able to validate your script, we'd ask that you generate a new file called Using_Biscuits.sh that you can commit to your branch of the SXT-Community/SXTAccreditation repo at the end of the activity.



How do Biscuits Work?

Biscuits are created locally (client) using the Private Key of the database object (table or view), which is then verified on the network (server) with the object's Public key.

The process of creating a biscuit looks like:

  1. Create an ED25519 public/private keypair

  2. Create the desired object, with the Public key from #1 added to the WITH statement (required)

  3. Submit the final CREATE... statement to the Space and Time network to be created, again including the Public key.

  4. With the same Private key from #1, create a biscuit with the appropriate constraints and authorizations.

  5. The user submits a query to the network, and includes a biscuit.

  6. The table or view verifies the biscuit instructions, filters and capabilities using it's Public key, which must match the corresponding Private key from step #4


📘

The ED25519 keypair created for tables and views are encoded in HEX, whereas the ED25519 keypair created for Users are encoded as BASE64. This is done to reinforce the fact that table keypairs and user keypairs are different. Also, HEX is SQL-Safe (needed to embed in CREATE statement), whereas Base64 is not.


Defining a Biscuit's Capabilities and Checks

To create a biscuit, you need a "datalog" - text that defines the authorization granted and to whom, in terms of filter "checks" and "capabilities." This datalog text is then signed by the private key that matches the table's public key.

Datalog entries come in two flavors: Capabilities and Checks (filters).

Capabilities

Capabilities are always structured as:sxt:capability("<operation>", "<resource>");

Operations are: dql_select, dml_insert, dml_update, dml_delete, ddl_create, ddl_drop

Resources are objects in the Space and Time network, most commonly Tables and Views. It is also possible to use the wildcard character * to represent ALL capabilities and/or resources (as long as the resource's public key matches).

A few examples:

sxt:capability("dql_select", "mySchema.myTable");  
sxt:capability("dml_insert", "mySchema.myTable");  
sxt:capability("dml_update", "mySchema.myTable");  
sxt:capability("dml_delete", "mySchema.myTable");
sxt:capability("*", "mySchema.myTable");
sxt:capability("dql_select", "*");
sxt:capability("*", "*");

"Wildcard" biscuits are fine for development, but should rarely (if ever) be used in production.

Checks

Checks are structured like IF statements, and allow standard operations like equality, inequality, greater than, less than, etc. Currently you can write checks on:

  • Timestamp
  • UserID logged in
  • Subscription associated with the UserID

A few examples:

check if sxt:user("Alice");   
check if sxt:subscription("abc123_example_subscription");
check if time($time), $time <= 2024-07-01T12:00:00Z; 

Filtering by your organization's subscription is a common way to protect your data using biscuits - even if the biscuit is leaked, biscuit checks cannot be changed - a subscription check only allowing User_IDs belonging to your subscription makes the biscuit useless to everyone else. Check out this page on finding your subscription.

Each line is evaluated alone, meaning they behave like AND operations. If you would like an OR operation (i.e., UserABC OR Subscription123) add them together on one line:

check if sxt:user("Alice") or sxt:subscription("abc123_example_subscription");  
check if time($time), $time <= 2024-07-01T13:00:00Z or $time >= 2024-07-01T12:00:00Z;

📘

Time format must be ISO8601 compliant, as seen above.

A full combined biscuit might look something like:

sxt:capability("dql_select", "mySchema.myTable");  
sxt:capability("dml_insert", "mySchema.myTable");  
check if sxt:user("Alice") or sxt:subscription("abc123_example_subscription");  
check if time($time), $time <= 2025-07-01T12:00:00Z; 

Once you have the datalog (text above) and table's private key, you can put them together into a biscuit!

The SXT CLI does much of this work for you, and is a far easier approach. To see biscuit creation in action, check out this recipe!


Create Biscuit with SXTCLI

The easiest way to create new biscuits is to use the SXT CLI. You can create a biscuit for a resource (table or view) before or after creating the resource - it doesn't matter. You only need the private key and one capability to create a biscuit.

Confirm prerequisites: SXTCLI 0.0.6 or higher is installed and working, and you have loaded your table's public and private keys into environment variables RESOURCE_PUBLIC_KEY and RESOURCE_PRIVATE_KEY.

To test:

# display character length of private key, and full public key:
Demo~$ echo -n $RESOURCE_PRIVATE_KEY | wc -m
      64

Demo~$ echo $RESOURCE_PUBLIC_KEY
5CA5D53A2F58227676063C8187CEB171962B86909CF59205CBDCEEE98E2BDB69


# display version of sxtcli, and help menu:
Demo~$ sxtcli version
Version: 0.0.6


Demo~$ sxtcli help
Usage: <main class> [COMMAND]
Commands:
  help          Display help information about the specified command.
  authenticate  Perform platform authentication commands.
  biscuit       Perform biscuit commands.
  discover      Perform resource discovery commands.
  sql           Perform SQL commands.
  sql-support   Perform supporting SQL commands.
  subscription  Perform subscription commands.
  activity      Perform activity retrieval commands.
  version       Displays the version information

Build the biscuit: for the example biscuit below, we'll assume:

  • the table: mySchema.myTable
  • granting: select and insert permissions only
  • restricted only to: UserID Alice in the SubscriptionID abc123456789def
  • expiring end of year, 2024

This set of criteria would look like:

sxtcli biscuit generate table \
  --privateKey=$RESOURCE_PRIVATE_KEY \
  --resources="mySchema.myTable" \
  --operations=SELECT,INSERT \
  --users="Alice" \
  --subscriptions="abc123456789def" \
  --expires="2024-12-31T23:59:59Z"

The above SXTCLI command will create the biscuit, and print the full list of capabilities and checks, as well as the biscuit key at the bottom. Given the above sample input, the expected output would be:

Biscuit content:
 Facts:
  sxt:capability("dml_insert","myschema.mytable");
  sxt:capability("dql_select","myschema.mytable");
 Checks:
  check if sxt:user("Alice");
  check if sxt:subscription("abc123456789def");
  check if time($time), $time <= "2024-12-31T23:59:59Z";
Biscuit:
EtwCCvEBCg5zeHQ...

To confirm that the biscuit key contains the correct information, you can visit BiscuitSec.org and paste the above Biscuit key into the "Encoded token" textbox on the main page, and it will display the contents:

📘

It is possible to create one biscuit that is good for many tables, but requires all tables be created with the same public key, so they can all be verify by the biscuit created with the same private key.


Create Biscuits for SXTTemp.Test_

Let's use the CLI to create a few biscuits for the table we created in the previous activity!
Make sure you've assigned your RESOURCE_PRIVATE_KEY and USERID to environment variables.

SELECT biscuit, which we can share with people who can read our table, but force key-rotation thru expiration:

# SELECT Biscuit:
sxtcli biscuit generate table \
  --privateKey=$RESOURCE_PRIVATE_KEY \
  --resources="SXTTemp.Test_$USERID" \
  --expires="2024-12-31T23:59:59Z" \
  --operations="SELECT" 

APPEND biscuit, for applications that can append new data, but can't read:

# APPEND Biscuit:
sxtcli biscuit generate table \
  --privateKey=$RESOURCE_PRIVATE_KEY \
  --resources="SXTTemp.Test_$USERID" \
  --operations="INSERT" 

ETL biscuit, for automated loads and maintenance (but cannot drop or create), but make sure the user is part of your subscription, so only authorized users can access:

# LOADER Biscuit:
sxtcli biscuit generate table \
  --privateKey=$RESOURCE_PRIVATE_KEY \
  --resources="SXTTemp.Test_$USERID" \
  --subscriptions="8cb60b4b-1f67-423c-aad1-e5df478a0c05" \
  --operations="SELECT,INSERT,UPDATE,DELETE,MERGE" 

Find a friend and swap SELECT biscuits, to see if you can access each-other's data!



Log Your Biscuits!

For validation and demonstration, you can use someone else's biscuit to INSERT your biscuits into a permissioned table, even though you can't SELECT from the table.

First, below is an INSERT biscuit that will allow you to insert into a table called SXTLABS.LEARN_Biscuits. Because many people will be inserting their biscuits for validation, this biscuit does NOT include SELECT access, only INSERT:

# real biscuit:
INSERT_ONLY_BISCUIT="ErEBCkcKDnN4dDpjYXBhYmlsaXR5CgpkbWxfaW5zZXJ0ChZzeHRsYWJzLmxlYXJuX2Jpc2N1aXRzGAMiDwoNCIAIEgMYgQgSAxiCCBIkCAASIF8E9QEj16bKEKijhDNt1V41Ug8liEJWmmEjoOPe8jMBGkCUS7Ew4O8RVXWoPi5ez24UJRTkqiiQj2o7mjsHpVAOnm9jMI6XTwgkfuDSFofk255aakfEgXPEkK-uPeYOxYwPIiIKIOmv7a2O-9LCdRowzTSCWVdlT6wwawS91jliwf1nN1Je"

Using this biscuit and the SXTCLI, go collect the three biscuits you made in the previous section, and log them into the SXTLABS.LEARN_Biscuits table (replace $SELECT_BISCUIT, $APPEND_BISCUIT, and $LOADER_BISCUIT with your 3 biscuits):

INSERT_SQL=$(cat << EOM

  INSERT INTO SXTLABS.LEARN_Biscuits (TABLENAME, USER_ID, Insert_Timestamp, BISCUIT_NAME, BISCUIT)
  VALUES
     ('SXTTemp.Test_$USERID', '$USERID', CURRENT_TIMESTAMP, 'SELECT', '$SELECT_BISCUIT' )
   , ('SXTTemp.Test_$USERID', '$USERID', CURRENT_TIMESTAMP, 'APPEND', '$APPEND_BISCUIT' )
   , ('SXTTemp.Test_$USERID', '$USERID', CURRENT_TIMESTAMP, 'LOADER', '$LOADER_BISCUIT' )

EOM
)

sxtcli sql \
  --url=$API_URL \
  --accessToken=$ACCESS_TOKEN \
  --sqlText="$INSERT_SQL" \
  --biscuits=$INSERT_ONLY_BISCUIT

Just to demonstrate, you can try to select from this table using the supplied biscuit, but you will see an error message as the biscuit does NOT authorize you to SELECT:

sxtcli sql \
  --url=$API_URL \
  --accessToken=$ACCESS_TOKEN \
  --sqlText="select * from SXTLABS.LEARN_Biscuits" \
  --biscuits=$INSERT_ONLY_BISCUIT
sql failed
[403] Forbidden
Details: Biscuit authorization failed: (dql_select,sxtlabs.learn_biscuits) failed on biscuit(s) 0



Get Credit:

You'll get credit for this activity if you:

How should I commit my branch changes?

When you're complete with the activity, simply commit your changes to your branch of work, then push to the SXT-Community/SXTAccreditation repo. Many IDEs have automated processes to manage this for you, but to do this manually by opening a temrinal / WSL window, and typing:

  • Line 1: Change to your work folder in your SXT-Community/SXTAccreditation directory
  • Line 2: Make sure your branch is active
  • Line 3: Add all changed files to your local stage
  • Line 4: Commit your changes
  • Line 5: Push your commited changes to github!
cd ./your/path/SXTAccreditation/<your_folder>
git checkout <your_branch>
git add .  
git commit -m "added my new working file"
git push

If you get an error on line 5 saying, The current branch stephens_work has no upstream branch, this is because Github doesn't know about your branch yet. Simply run the command the git tool recommends - it will look something like:
git push --set-upstream origin <your_branch>

When you're done for the day, head over to SXT-Community/SXTAccreditation/Pulls page on Github, and (if you're logged in) you'll see a message like:

Click on the button above to create a Pull Request, give us some feedback in the description, and click "Create Pull Request". Done!