Help us shape the future of Go! Take the 2026 Go Developer Survey

biscuit

package module
v2.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

biscuit-go

biscuit-go is an implementation of Biscuit in Go. It aims to be fully compatible with other existing implementations, so that tokens issued by, for example, the Rust version, could be validated by this library and vice versa.

Documentation and specifications

Usage

Create a biscuit
rng := rand.Reader
publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)

authority, err := parser.FromStringBlockWithParams(`
	right("/a/file1.txt", {read});
	right("/a/file1.txt", {write});
	right("/a/file2.txt", {read});
	right("/a/file3.txt", {write});
`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")})

if err != nil {
	panic(fmt.Errorf("failed to parse authority block: %v", err))
}

builder := biscuit.NewBuilder(privateRoot)
builder.AddBlock(authority)

b, err := builder.Build()
if err != nil {
	panic(fmt.Errorf("failed to build biscuit: %v", err))
}

token, err := b.Serialize()
if err != nil {
	panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

// token is now a []byte, ready to be shared
// The biscuit spec mandates the use of URL-safe base64 encoding for textual representation:
fmt.Println(base64.URLEncoding.EncodeToString(token))
Attenuate a biscuit
b, err = biscuit.Unmarshal(token)
if err != nil {
    panic(fmt.Errorf("failed to deserialize biscuit: %v", err))
}

// Attenuate the biscuit by appending a new block to it
blockBuilder := b.CreateBlock()
block, err := parser.FromStringBlockWithParams(`
		check if resource($file), operation($permission), [{read}].contains($permission);`,
	map[string]biscuit.Term{"read": biscuit.String("read")})
if err != nil {
	panic(fmt.Errorf("failed to parse block: %v", err))
}
blockBuilder.AddBlock(block)

attenuatedBiscuit, err := b.Append(rng, blockBuilder.Build())
if err != nil {
    panic(fmt.Errorf("failed to append: %v", err))
}
attenuatedToken, err := b.Serialize()
if err != nil {
    panic(fmt.Errorf("failed to serialize biscuit: %v", err))
}

// attenuatedToken is a []byte, representing an attenuated token
Verify a biscuit
b, err := biscuit.Unmarshal(token)
if err != nil {
    panic(fmt.Errorf("failed to deserialize token: %v", err))
}

authorizer, err := b.Authorizer(publicRoot)
if err != nil {
    panic(fmt.Errorf("failed to verify token and create authorizer: %v", err))
}

authorizerContents, err := parser.FromStringAuthorizerWithParams(`
	resource({res});
	operation({op});
	allow if right({res}, {op});
	`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("read")})
if err != nil {
	panic(fmt.Errorf("failed to parse authorizer: %v", err))
}
authorizer.AddAuthorizer(authorizerContents)

if err := authorizer.Authorize(); err != nil {
    fmt.Printf("failed authorizing token: %v\n", err)
} else {
    fmt.Println("success authorizing token")
}
Using biscuit-go grammar

biscuit-go provides a datalog parser, allowing to input datalog elements as plain strings, along with support for parameter substitution.

See GRAMMAR reference for the complete syntax.

The parsers supports parsing whole blocks (containing several facts, rules and checks), whole authorizers (containing several facts, rules, checks and policies), as well as individual facts, rules, checks and policies. Parsing and adding elements individually is especially useful when doing so from inside a loop.

The parser module provides convenient helpers for parsing a string into datalog elements (FromStringFact, FromStringRule, FromStringCheck, FromStringPolicy, FromStringBlock, FromStringAuthorizer, for static datalog snippets, and their counterparts allowing parameter substitution: FromStringFactWithParams, FromStringRuleWithParams, FromStringCheckWithParams, FromStringPolicyWithParams, FromStringBlockWithParams, FromStringAuthorizerWithParams).

Panic on parsing errors

In most cases, FromString* functions will let you handle errors. If you do not wish to handle errors and instead crash on errors (for instance in one-off scripts), it can be done by first creating a parser instance, and using the panic-y functions:

p := parser.New()
b := biscuit.NewBuilder(privateRoot)

b.AddBlock(p.Must().Block(`
	right("/a/file1.txt", {read});
	right("/a/file1.txt", {write});
	right("/a/file2.txt", {read});
	right("/a/file3.txt", {write});
`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")}))

b.AddFact(p.Must().Fact(`resource({res})`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt")}))
b.AddRule(p.Must().Rule(`
    can_read($file) 
        <- resource($file)
        $file.starts_with("/a/")
`, nil))

Do note that these helpers take two arguments: a datalog snippet and a parameters map. If the datalog snippet does not contain parameters, nil can be passed as the second argument.

Examples

License

Licensed under Apache License, Version 2.0.

Documentation

Index

Examples

Constants

View Source
const (
	PolicyKindAllow = iota
	PolicyKindDeny
)
View Source
const MaxSchemaVersion uint32 = 3
View Source
const MinSchemaVersion uint32 = 3

Variables

View Source
var (
	ErrMissingSymbols   = errors.New("biscuit: missing symbols")
	ErrPolicyDenied     = errors.New("biscuit: denied by policy")
	ErrNoMatchingPolicy = errors.New("biscuit: denied by no matching policies")
)
View Source
var (
	// ErrSymbolTableOverlap is returned when multiple blocks declare the same symbols
	ErrSymbolTableOverlap = errors.New("biscuit: symbol table overlap")
	// ErrInvalidAuthorityIndex occurs when an authority block index is not 0
	ErrInvalidAuthorityIndex = errors.New("biscuit: invalid authority index")
	// ErrInvalidAuthorityFact occurs when an authority fact is an ambient fact
	ErrInvalidAuthorityFact = errors.New("biscuit: invalid authority fact")
	// ErrInvalidBlockFact occurs when a block fact provides an authority or ambient fact
	ErrInvalidBlockFact = errors.New("biscuit: invalid block fact")
	// ErrInvalidBlockRule occurs when a block rule generate an authority or ambient fact
	ErrInvalidBlockRule = errors.New("biscuit: invalid block rule")
	// ErrEmptyKeys is returned when verifying a biscuit having no keys
	ErrEmptyKeys = errors.New("biscuit: empty keys")
	// ErrNoPublicKeyAvailable is returned when no public root key is available to verify the
	// signatures on a biscuit's blocks.
	ErrNoPublicKeyAvailable = errors.New("biscuit: no public key available")
	// ErrUnknownPublicKey is returned when verifying a biscuit with the wrong public key
	ErrUnknownPublicKey = errors.New("biscuit: unknown public key")

	ErrInvalidSignature = errors.New("biscuit: invalid signature")

	ErrInvalidSignatureSize = errors.New("biscuit: invalid signature size")

	ErrInvalidKeySize = errors.New("biscuit: invalid key size")

	ErrUnsupportedAlgorithm = errors.New("biscuit: unsupported signature algorithm")

	// Deprecated: use ErrUnsupportedAlgorithm.
	UnsupportedAlgorithm = ErrUnsupportedAlgorithm
)
View Source
var (
	ErrDuplicateFact     = errors.New("biscuit: fact already exists")
	ErrInvalidBlockIndex = errors.New("biscuit: invalid block index")
)
View Source
var (
	// DefaultAllowPolicy allows the biscuit to verify sucessfully as long as all its checks generate some facts.
	DefaultAllowPolicy = Policy{Kind: PolicyKindAllow, Queries: []Rule{{Head: Predicate{Name: "allow"}}}}
	// DefaultDenyPolicy makes the biscuit verification fail in all cases.
	DefaultDenyPolicy = Policy{Kind: PolicyKindDeny, Queries: []Rule{{Head: Predicate{Name: "deny"}}}}
)
View Source
var ErrFactNotFound = errors.New("biscuit: fact not found")

Functions

func WithRNG

func WithRNG(r io.Reader) compositionOption

WithRNG supplies a random number generator as a byte stream from which to read when generating key pairs with which to sign blocks within biscuits.

func WithRootKeyID

func WithRootKeyID(id uint32) compositionOption

WithRootKeyID specifies the identifier for the root key pair used to sign a biscuit's authority block, allowing a consuming party to later select the corresponding public key to validate that signature.

func WithSymbols

func WithSymbols(symbols *datalog.SymbolTable) builderOption

WithSymbols supplies a symbol table to use when composing biscuits.

Types

type Authorizer

type Authorizer interface {
	AddAuthorizer(a ParsedAuthorizer)
	AddBlock(b ParsedBlock)
	AddFact(fact Fact)
	AddRule(rule Rule)
	AddCheck(check Check)
	AddPolicy(policy Policy)
	Authorize() error
	Query(rule Rule) (FactSet, error)
	Biscuit() *Biscuit
	Reset()
	PrintWorld() string
	LoadPolicies([]byte) error
	SerializePolicies() ([]byte, error)
}

func NewVerifier

func NewVerifier(b *Biscuit, opts ...AuthorizerOption) (Authorizer, error)

type AuthorizerOption

type AuthorizerOption func(w *authorizer)

func WithWorldOptions

func WithWorldOptions(opts ...datalog.WorldOption) AuthorizerOption

type BinaryOp

type BinaryOp binaryOpType
const (
	BinaryUndefined BinaryOp = iota
	BinaryLessThan
	BinaryLessOrEqual
	BinaryGreaterThan
	BinaryGreaterOrEqual
	BinaryEqual
	BinaryContains
	BinaryPrefix
	BinarySuffix
	BinaryRegex
	BinaryAdd
	BinarySub
	BinaryMul
	BinaryDiv
	BinaryAnd
	BinaryOr
	BinaryIntersection
	BinaryUnion
)

func (BinaryOp) Type

func (BinaryOp) Type() OpType

type Biscuit

type Biscuit struct {
	// contains filtered or unexported fields
}

Biscuit represents a valid Biscuit token It contains multiple `Block` elements, the associated symbol table, and a serialized version of this data

Example
package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"

	"github.com/eclipse-biscuit/biscuit-go/v2"
	"github.com/eclipse-biscuit/biscuit-go/v2/parser"
)

func main() {
	rng := rand.Reader
	publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)

	authority, err := parser.FromStringBlockWithParams(`
		right("/a/file1.txt", {read});
		right("/a/file1.txt", {write});
		right("/a/file2.txt", {read});
		right("/a/file3.txt", {write});
	`, map[string]biscuit.Term{"read": biscuit.String("read"), "write": biscuit.String("write")})
	if err != nil {
		panic(fmt.Errorf("failed to parse authority block: %v", err))
	}

	builder := biscuit.NewBuilder(privateRoot)
	builder.AddBlock(authority)

	b, err := builder.Build()
	if err != nil {
		panic(fmt.Errorf("failed to build biscuit: %v", err))
	}

	token, err := b.Serialize()
	if err != nil {
		panic(fmt.Errorf("failed to serialize biscuit: %v", err))
	}

	fmt.Printf("Token1 length: %d\n", len(token))

	deser, err := biscuit.Unmarshal(token)
	if err != nil {
		panic(fmt.Errorf("failed to deserialize biscuit: %v", err))
	}

	blockBuilder := deser.CreateBlock()

	block, err := parser.FromStringBlockWithParams(`
			check if resource($file), operation($permission), [{read}].contains($permission);`,
		map[string]biscuit.Term{"read": biscuit.String("read")})

	if err != nil {
		panic(fmt.Errorf("failed to parse block: %v", err))
	}
	blockBuilder.AddBlock(block)

	b2, err := deser.Append(rng, blockBuilder.Build())
	if err != nil {
		panic(fmt.Errorf("failed to append: %v", err))
	}

	token2, err := b2.Serialize()
	if err != nil {
		panic(fmt.Errorf("failed to serialize biscuit: %v", err))
	}

	fmt.Printf("Token2 length: %d\n", len(token2))

	// Verify
	b2, err = biscuit.Unmarshal(token2)
	if err != nil {
		panic(fmt.Errorf("failed to deserialize token: %v", err))
	}

	v1, err := b2.Authorizer(publicRoot)
	if err != nil {
		panic(fmt.Errorf("failed to create verifier: %v", err))
	}

	authorizer, err := parser.FromStringAuthorizerWithParams(`
		resource({res});
		operation({op});
		allow if right({res}, {op});
		`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("read")})

	if err != nil {
		panic(fmt.Errorf("failed to parse authorizer: %v", err))
	}
	v1.AddAuthorizer(authorizer)

	if err := v1.Authorize(); err != nil {
		// fmt.Println(v1.PrintWorld())

		fmt.Println("forbidden to read /a/file1.txt")
	} else {
		//fmt.Println(v1.PrintWorld())

		fmt.Println("allowed to read /a/file1.txt")
	}

	v1, _ = b2.Authorizer(publicRoot)

	authorizer, err = parser.FromStringAuthorizerWithParams(`
		resource({res});
		operation({op});
		allow if right({res}, {op});
		`, map[string]biscuit.Term{"res": biscuit.String("/a/file1.txt"), "op": biscuit.String("write")})

	if err != nil {
		panic(fmt.Errorf("failed to parse authorizer: %v", err))
	}
	v1.AddAuthorizer(authorizer)

	if err := v1.Authorize(); err != nil {
		fmt.Println("forbidden to write /a/file1.txt")
	} else {
		fmt.Println("allowed to write /a/file1.txt")
	}

}
Output:
Token1 length: 251
Token2 length: 433
allowed to read /a/file1.txt
forbidden to write /a/file1.txt

func New

func New(rng io.Reader, root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, authority *Block) (*Biscuit, error)

func Unmarshal

func Unmarshal(serialized []byte) (*Biscuit, error)

func (*Biscuit) Append

func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error)

func (*Biscuit) Authorizer

func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error)

Authorizer checks the signature and creates an Authorizer. The Authorizer can then test the authorizaion policies and accept or refuse the request.

func (*Biscuit) AuthorizerFor

func (b *Biscuit) AuthorizerFor(keySource PublickKeyByIDProjection, opts ...AuthorizerOption) (Authorizer, error)

AuthorizerFor selects from the supplied source a root public key to use to verify the signatures on the biscuit's blocks, returning an error satisfying errors.Is(err, ErrNoPublicKeyAvailable) if no such public key is available. If the signatures are valid, it creates an Authorizer, which can then test the authorization policies and accept or refuse the request.

func (*Biscuit) BlockCount

func (b *Biscuit) BlockCount() int

func (*Biscuit) Checks

func (b *Biscuit) Checks() [][]datalog.Check

func (*Biscuit) Code

func (b *Biscuit) Code() []string

func (*Biscuit) CreateBlock

func (b *Biscuit) CreateBlock() BlockBuilder

func (*Biscuit) GetBlockID

func (b *Biscuit) GetBlockID(fact Fact) (int, error)

GetBlockID returns the first block index containing a fact starting from the authority block and then each block in the order they were added. ErrFactNotFound is returned when no block contains the fact.

func (*Biscuit) GetContext

func (b *Biscuit) GetContext() string

func (*Biscuit) RevocationIds

func (b *Biscuit) RevocationIds() [][]byte

func (*Biscuit) RootKeyID

func (b *Biscuit) RootKeyID() *uint32

func (*Biscuit) Seal

func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error)

func (*Biscuit) Serialize

func (b *Biscuit) Serialize() ([]byte, error)

func (*Biscuit) String

func (b *Biscuit) String() string

type Block

type Block struct {
	// contains filtered or unexported fields
}

func (*Block) Code

func (b *Block) Code(symbols *datalog.SymbolTable) string

func (*Block) String

func (b *Block) String(symbols *datalog.SymbolTable) string

type BlockBuilder

type BlockBuilder interface {
	AddBlock(block ParsedBlock) error
	AddFact(fact Fact) error
	AddRule(rule Rule) error
	AddCheck(check Check) error
	SetContext(string)
	Build() *Block
}

func NewBlockBuilder

func NewBlockBuilder(baseSymbols *datalog.SymbolTable) BlockBuilder

type Bool

type Bool bool

func (Bool) String

func (b Bool) String() string

func (Bool) Type

func (b Bool) Type() TermType

type Builder

type Builder interface {
	AddBlock(block ParsedBlock) error
	AddAuthorityFact(fact Fact) error
	AddAuthorityRule(rule Rule) error
	AddAuthorityCheck(check Check) error
	SetContext(string)
	Build() (*Biscuit, error)
}

func NewBuilder

func NewBuilder(root ed25519.PrivateKey, opts ...builderOption) Builder

type Bytes

type Bytes []byte

func (Bytes) String

func (a Bytes) String() string

func (Bytes) Type

func (a Bytes) Type() TermType

type Check

type Check struct {
	Queries []Rule
}

type Date

type Date time.Time

func (Date) String

func (a Date) String() string

func (Date) Type

func (a Date) Type() TermType

type Expression

type Expression []Op

type Fact

type Fact struct {
	Predicate
}

func (Fact) String

func (f Fact) String() string

type FactSet

type FactSet []Fact

func (FactSet) String

func (fs FactSet) String() string

type Integer

type Integer int64

func (Integer) String

func (a Integer) String() string

func (Integer) Type

func (a Integer) Type() TermType

type Op

type Op interface {
	Type() OpType
	// contains filtered or unexported methods
}

type OpType

type OpType byte
const (
	OpTypeValue OpType = iota
	OpTypeUnary
	OpTypeBinary
)

type ParsedAuthorizer

type ParsedAuthorizer struct {
	Policies []Policy
	Block    ParsedBlock
}

type ParsedBlock

type ParsedBlock struct {
	Facts  FactSet
	Rules  []Rule
	Checks []Check
}

type Policy

type Policy struct {
	Queries []Rule
	Kind    PolicyKind
}

type PolicyKind

type PolicyKind byte

type Predicate

type Predicate struct {
	Name string
	IDs  []Term
}

func (Predicate) String

func (p Predicate) String() string

type PublickKeyByIDProjection

type PublickKeyByIDProjection func(*uint32) (ed25519.PublicKey, error)

A PublickKeyByIDProjection inspects an optional ID for a public key and returns the corresponding public key, if any. If it doesn't recognize the ID or can't find the public key, or no ID is supplied and there is no default public key available, it should return an error satisfying errors.Is(err, ErrNoPublicKeyAvailable).

func WithRootPublicKeys

func WithRootPublicKeys(keysByID map[uint32]ed25519.PublicKey, defaultKey *ed25519.PublicKey) PublickKeyByIDProjection

WithRootPublicKeys supplies a mapping to public keys from their corresponding IDs, used to select which public key to use to verify the signatures on a biscuit's blocks based on the key ID embedded within the biscuit when it was created. If the biscuit has no key ID available, this function selects the optional default key instead. If no public key is available—whether for the biscuit's embedded key ID or a default key when no such ID is present—it returns ErrNoPublicKeyAvailable.

func WithSingularRootPublicKey

func WithSingularRootPublicKey(key ed25519.PublicKey) PublickKeyByIDProjection

WithSingularRootPublicKey supplies one public key to use as the root key with which to verify the signatures on a biscuit's blocks.

type Rule

type Rule struct {
	Head        Predicate
	Body        []Predicate
	Expressions []Expression
}

type Set

type Set []Term

func (Set) String

func (a Set) String() string

func (Set) Type

func (a Set) Type() TermType

type String

type String string

func (String) String

func (a String) String() string

func (String) Type

func (a String) Type() TermType

type Term

type Term interface {
	Type() TermType
	String() string
	// contains filtered or unexported methods
}

type TermType

type TermType byte
const (
	TermTypeSymbol TermType = iota
	TermTypeVariable
	TermTypeInteger
	TermTypeString
	TermTypeDate
	TermTypeBytes
	TermTypeBool
	TermTypeSet
)

type UnaryOp

type UnaryOp unaryOpType
const (
	UnaryUndefined UnaryOp = iota
	UnaryNegate
	UnaryParens
	UnaryLength
)

func (UnaryOp) Type

func (UnaryOp) Type() OpType

type Unmarshaler

type Unmarshaler struct {
	Symbols *datalog.SymbolTable
}

func (*Unmarshaler) Unmarshal

func (u *Unmarshaler) Unmarshal(serialized []byte) (*Biscuit, error)

type Value

type Value struct {
	Term Term
}

func (Value) Type

func (v Value) Type() OpType

type Variable

type Variable string

func (Variable) String

func (a Variable) String() string

func (Variable) Type

func (a Variable) Type() TermType

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL