Skip to content

Commit

Permalink
Named variables support (#37)
Browse files Browse the repository at this point in the history
this replaces integer variables by strings, allowing more readable
definitions. (ie: $0 become $username).
  • Loading branch information
daeMOn63 committed Dec 4, 2020
1 parent 8056c49 commit 6af1c88
Show file tree
Hide file tree
Showing 21 changed files with 164 additions and 177 deletions.
62 changes: 31 additions & 31 deletions biscuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ func TestBiscuit(t *testing.T) {
block2.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "caveat", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Symbol("read")}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable(0), Symbol("read")}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable("0"), Symbol("read")}},
},
},
},
Expand Down Expand Up @@ -109,27 +109,27 @@ func TestBiscuitRules(t *testing.T) {
builder := NewBuilder(root)

builder.AddAuthorityRule(Rule{
Head: Predicate{Name: "right", IDs: []Atom{Variable(1), Symbol("read")}},
Head: Predicate{Name: "right", IDs: []Atom{Variable("1"), Symbol("read")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
})
builder.AddAuthorityRule(Rule{
Head: Predicate{Name: "right", IDs: []Atom{Variable(1), Symbol("write")}},
Head: Predicate{Name: "right", IDs: []Atom{Variable("1"), Symbol("write")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
})
builder.AddAuthorityCaveat(Caveat{Queries: []Rule{
{
Head: Predicate{Name: "allowed_users", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "allowed_users", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
Constraints: []Constraint{{
Name: Variable(0),
Name: Variable("0"),
Checker: SymbolInChecker{
Set: map[Symbol]struct{}{Symbol("alice"): {}, Symbol("bob"): {}},
Not: false,
Expand All @@ -150,22 +150,22 @@ func TestBiscuitRules(t *testing.T) {
block.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat1", IDs: []Atom{Variable(0), Variable(1)}},
Head: Predicate{Name: "caveat1", IDs: []Atom{Variable("0"), Variable("1")}},
Body: []Predicate{
{Name: "right", IDs: []Atom{Symbol("authority"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "right", IDs: []Atom{Symbol("authority"), Variable("0"), Variable("1")}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "operation", IDs: []Atom{Symbol("ambient"), Variable("1")}},
},
},
},
})
block.AddCaveat(Caveat{
Queries: []Rule{
{
Head: Predicate{Name: "caveat2", IDs: []Atom{Variable(0)}},
Head: Predicate{Name: "caveat2", IDs: []Atom{Variable("0")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(0)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable(0)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("0")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable("0")}},
},
},
},
Expand Down Expand Up @@ -234,17 +234,17 @@ func TestGenerateWorld(t *testing.T) {
authorityFact2 := Fact{Predicate: Predicate{Name: "fact2", IDs: []Atom{Symbol("authority"), String("file2")}}}

authorityRule1 := Rule{
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable(1), Symbol("read")}},
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable("1"), Symbol("read")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
}
authorityRule2 := Rule{
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable(1), Symbol("write")}},
Head: Predicate{Name: "right", IDs: []Atom{Symbol("authority"), Variable("1"), Symbol("write")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable(0), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Variable("0"), Variable("1")}},
},
}

Expand All @@ -269,10 +269,10 @@ func TestGenerateWorld(t *testing.T) {

blockBuild := b.CreateBlock()
blockRule := Rule{
Head: Predicate{Name: "blockRule", IDs: []Atom{Variable(1)}},
Head: Predicate{Name: "blockRule", IDs: []Atom{Variable("1")}},
Body: []Predicate{
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable(1)}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable(1)}},
{Name: "resource", IDs: []Atom{Symbol("ambient"), Variable("1")}},
{Name: "owner", IDs: []Atom{Symbol("ambient"), Symbol("alice"), Variable("1")}},
},
}
blockBuild.AddRule(blockRule)
Expand Down Expand Up @@ -318,7 +318,7 @@ func TestGenerateWorldErrors(t *testing.T) {
Desc: "invalid ambient authority fact",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
Facts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable("0")}}},
},
},
{
Expand All @@ -332,14 +332,14 @@ func TestGenerateWorldErrors(t *testing.T) {
Desc: "invalid block fact authority",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
BlockFacts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("authority"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("authority"), Variable("0")}}},
},
},
{
Desc: "invalid block fact ambient",
Symbols: &datalog.SymbolTable{"authority", "ambient"},
BlockFacts: []Fact{
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable(0)}}},
{Predicate: Predicate{Name: "test", IDs: []Atom{Symbol("ambient"), Variable("0")}}},
},
},
{
Expand Down
75 changes: 32 additions & 43 deletions experiments/pop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@ import (
// Signature flow overview:
//
// ---- server generates a token to be signed ----
// $data = staticCtx | randomChallenge[0:16]
//
// server adds:
// then server adds:
// - facts:
// - should_sign(#authority, dataID, alg, pubkey)
// - data(#authority, dataID, staticCtx | challenge[16])
// - should_sign(#authority, $dataID, $alg, $pubkey)
// - data(#authority, $dataID, $data)
// - caveat:
// - *valid(0?)<- should_sign(#authority, $0, $1, $2), valid_signature(#ambient, $0, $1, $2)
// - *valid($dataID)<- should_sign(#authority, $dataID, $alg, $pubkey, valid_signature(#ambient, $dataID, $alg, $pubkey)
//
// ---- server sends the token to the client ----
//
// client queries for:
// - *to_sign(dataID, $0, $1) <- should_sign(#authority, $0, $1, pubkey), data(#authority, $0, $2)
// with:
// $0: dataID
// $1: alg
// $2: data
//
// - *to_sign(dataID, $alg, $pubkey) <- should_sign(#authority, $dataID, $alg, $pubkey), data(#authority, $dataID, $data)

// foreach to_sign facts:
// - verify data starts with staticCtx
// - let tokenHash = Sha256(authorityBlock | all blocks | all keys)
Expand All @@ -46,18 +43,10 @@ import (
// ---- client sends the token to the server ----
//
// server queries for:
// - *to_validate($0, $1, $2, $3, $4, $5, $6) <-
// should_sign(#authority, $0, $1, $2),
// data(#authority, $0, $3),
// signature($0, $2, $4, $5, $6)
// with:
// $0: dataID
// $1: alg
// $2: pubkey
// $3: data
// $4: signature
// $5: signerNonce
// $6: signerTimestamp
// - *to_validate($dataID, $alg, $pubkey, $data, $signature, $signerNonce, $signerTimestamp) <-
// should_sign(#authority, $dataID, $alg, $pubkey),
// data(#authority, $dataID, $data),
// signature($0, $pubkey, $signature, $signerNonce, $signerTimestamp)
//
// foreach to_validate facts:
// - let tokenHash = Sha256(authorityBlock | all blocks expect the last one | all keys except the last one)
Expand Down Expand Up @@ -128,13 +117,13 @@ func getServerToken(t *testing.T, pubkey ed25519.PublicKey) ([]byte, sig.PublicK

// This caveat requires every "should_sign" fact to have a matching "valid_signature" fact,
// that can only provided by the verifier (due to the ambient tag)
// *valid(0?)<- should_sign(#authority, $0, $1, $2), valid_signature(#ambient, $0, $1, $2)
// *valid($dataID)<- should_sign(#authority, $dataID, $alg, $pubkey), valid_signature(#ambient, $dataID, $alg, $pubkey)
builder.AddAuthorityCaveat(biscuit.Caveat{Queries: []biscuit.Rule{
{
Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable(0)}},
Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable("dataID")}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
{Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
},
},
}})
Expand All @@ -158,13 +147,13 @@ func clientSign(t *testing.T, rootPubkey sig.PublicKey, pubkey ed25519.PublicKey

t.Logf("clientSign world:\n%s", verifier.PrintWorld())

// This query returns to_sign(dataID, alg, data) facts which require a signature with a private key matching pubkey.
// This query returns to_sign($dataID, $alg, $data) facts which require a signature with a private key matching pubkey.
// in this example: [to_sign(0, "ed25519", "hex:7369676e2074686973")]
toSign, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{Name: "to_sign", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
Head: biscuit.Predicate{Name: "to_sign", IDs: []biscuit.Atom{biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Bytes(pubkey)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(2)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Bytes(pubkey)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("pubkey")}},
},
})
require.NoError(t, err)
Expand All @@ -185,9 +174,9 @@ func clientSign(t *testing.T, rootPubkey sig.PublicKey, pubkey ed25519.PublicKey

// We have a "to_sign" fact, so we check if the token doesn't already hold a signature:
alreadySigned, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable(0)}},
Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable("dataID")}},
Body: []biscuit.Predicate{
{Name: "signature", IDs: []biscuit.Atom{dataID, biscuit.Bytes(pubkey), biscuit.Variable(0)}},
{Name: "signature", IDs: []biscuit.Atom{dataID, biscuit.Bytes(pubkey), biscuit.Variable("dataID")}},
},
})
require.NoError(t, err)
Expand Down Expand Up @@ -248,23 +237,23 @@ func verifySignature(t *testing.T, rootPubKey sig.PublicKey, b []byte) {

t.Logf("verifySignature world before:\n%s", verifier.PrintWorld())

// Generate "to_validate(dataID, alg, pubkey, data, signerNonce, signerTimestamp, signature)" facts from existing signatures
// Generate "to_validate($dataID, $alg, $pubkey, $data, $signerNonce, $signerTimestamp, $signature)" facts from existing signatures
toValidate, err := verifier.Query(biscuit.Rule{
Head: biscuit.Predicate{
Name: "to_validate",
IDs: []biscuit.Atom{
biscuit.Variable(0), // dataID
biscuit.Variable(1), // alg
biscuit.Variable(2), // pubkey
biscuit.Variable(3), // data
biscuit.Variable(4), // signature
biscuit.Variable(5), // signerNonce
biscuit.Variable(6), // signerTimestamp
biscuit.Variable("dataID"),
biscuit.Variable("alg"),
biscuit.Variable("pubkey"),
biscuit.Variable("data"),
biscuit.Variable("signature"),
biscuit.Variable("signerNonce"),
biscuit.Variable("signerTimestamp"),
}},
Body: []biscuit.Predicate{
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(3)}},
{Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6)}},
{Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("alg"), biscuit.Variable("pubkey")}},
{Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable("dataID"), biscuit.Variable("data")}},
{Name: "signature", IDs: []biscuit.Atom{biscuit.Variable("dataID"), biscuit.Variable("pubkey"), biscuit.Variable("signature"), biscuit.Variable("signerNonce"), biscuit.Variable("signerTimestamp")}},
},
})
require.NoError(t, err)
Expand Down
48 changes: 24 additions & 24 deletions parser/GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This document describes the currently supported Datalog grammar.
Represents a Datalog type, can be one of: symbol, variable, integer, string, date, bytes, or set.

- symbol is prefixed with a `#` sign followed by text, e.g. `#read`
- variable is prefixed with a `$` sign followed by an unsigned 32bit base-10 integer, e.g. `$0`
- variable is prefixed with a `$` sign followed by a string or an unsigned 32bit base-10 integer, e.g. `$0` or `$variable1`
- integer is any base-10 int64
- string is any utf8 character sequence, between double quotes, e.g. `"/path/to/file.txt"`
- date is RFC3339 encoded, e.g. `2006-01-02T15:04:05Z07:00`
Expand All @@ -24,43 +24,43 @@ Constraints allows performing checks on a variable, below is the list of availab

### Integer:

- Equal: `$0 == 1`
- Greater than: `$0 > 1`
- Greater than or equal: `$0 >= 1`
- Less than: `$0 < 1`
- Less than or equal: `$0 <= 1`
- In: `$0 in [1, 2, 3]`
- Not in: `$0 not in [1, 2, 3]`
- Equal: `$i == 1`
- Greater than: `$i > 1`
- Greater than or equal: `$i >= 1`
- Less than: `$i < 1`
- Less than or equal: `$i <= 1`
- In: `$i in [1, 2, 3]`
- Not in: `$i not in [1, 2, 3]`

### String

- Equal: `$0 == "abc"`
- Starts with: `prefix($0, "abc")`
- Ends with: `suffix($0, "abc")`
- Regular expression: `match($0, "^abc\s+def$") `
- In: `$0 in ["abc", "def"]`
- Not in: `$0 not in ["abc", "def"]`
- Equal: `$s == "abc"`
- Starts with: `prefix($s, "abc")`
- Ends with: `suffix($s, "abc")`
- Regular expression: `match($s, "^abc\s+def$") `
- In: `$s in ["abc", "def"]`
- Not in: `$s not in ["abc", "def"]`

### Date

- Before: `$0 < "2006-01-02T15:04:05Z07:00"`
- After: `$0 > "2006-01-02T15:04:05Z07:00"`
- Before: `$date < "2006-01-02T15:04:05Z07:00"`
- After: `$date > "2006-01-02T15:04:05Z07:00"`

### Symbols

- In:`$0 in [#a, #b, #c]`
- Not in:`$0 not in [#a, #b, #c]`
- In:`$sym in [#a, #b, #c]`
- Not in:`$sym not in [#a, #b, #c]`

### Bytes

- Equal: `$0 == "hex:3df97fb5"`
- In: `$0 in ["hex:3df97fb5", "hex:4a8feed1"]`
- Not in: `$0 not in ["hex:3df97fb5", "hex:4a8feed1"]`
- Equal: `$b == "hex:3df97fb5"`
- In: `$b in ["hex:3df97fb5", "hex:4a8feed1"]`
- Not in: `$b not in ["hex:3df97fb5", "hex:4a8feed1"]`

### Set

- Any: `$0 in [#read, #write]`
- None: `$0 not in [#read, #write]`
- Any: `$set in [#read, #write]`
- None: `$set not in [#read, #write]`

## Fact

Expand All @@ -73,7 +73,7 @@ The head is a single predicate, the body is a list of predicates, and followed b

It has the format: `*Head <- Body @ Constraints`

e.g. `*right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1) @ $0 == "username", prefix($1, "/home/username")`
e.g. `*right(#authority, $file, #read) <- resource(#ambient, $file), owner(#ambient, $user, $file) @ $user == "username", prefix($file, "/home/username")`

# Caveat

Expand Down
6 changes: 3 additions & 3 deletions parser/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Caveat struct {

type Atom struct {
Symbol *string `"#" @Ident`
Variable *uint32 `| "$" @Int`
Variable *string `| "$" @(Int|Ident)`
Bytes *HexString `| @@`
String *string `| @String`
Integer *int64 `| @Int`
Expand All @@ -44,7 +44,7 @@ type Constraint struct {
}

type VariableConstraint struct {
Variable *uint32 `"$" @Int`
Variable *string `"$" @(Int|Ident)`
Date *DateComparison `((@@`
Bytes *BytesComparison `| @@`
String *StringComparison `| @@`
Expand All @@ -54,7 +54,7 @@ type VariableConstraint struct {

type FunctionConstraint struct {
Function *string `@( "prefix" | "suffix" | "match" ) "("`
Variable *uint32 `"$" @Int ","`
Variable *string `"$" @(Int|Ident) ","`
Argument *string `@String ")"`
}

Expand Down
Loading

0 comments on commit 6af1c88

Please sign in to comment.