✅ Status: Implemented
This design is fully implemented and functional.
Bootstrap and Access Control
This design document describes the bootstrap mechanism for requesting access to databases and the global permission system for open access.
Overview
Bootstrap provides a “knocking” mechanism for clients to request access to databases they don’t have permissions for. Global permissions provide an alternative for databases that want to allow open access without requiring bootstrap requests.
Problem Statement
When a client wants to sync a database they don’t have access to:
- No Direct Access: Client’s key is not in the database’s auth settings
- Need Permission Grant: Requires an admin to add the client’s key
- Coordination Challenge: Client and admin need a way to coordinate the access grant
- Public Databases: Some databases should be openly accessible without coordination
Proposed Solution
Two complementary mechanisms:
- Global Permissions: For databases that want open access
- Bootstrap Protocol: For databases that want controlled access grants
Global Permissions
Global Permission
A database can grant universal permissions by setting a global permission in its auth settings. The global permission is stored in the "global" sub-object of _settings.auth, separate from per-key entries in "keys" and delegations in "delegations":
// AuthSettings stores data in a Doc with three sub-objects:
// "keys" - per-key auth entries (SigKey → AuthKey)
// "delegations" - delegated tree references
// "global" - global permission (applies to all clients)
How It Works
When a client attempts to sync a database:
- Check for global permission: If a global permission exists in
_settings.auth, grant the specified permission to any client - No key required: Client doesn’t need their key in the database’s auth settings
- Immediate access: No bootstrap request or approval needed
Use Cases
Public Read Access: Set global permission to Read to allow anyone to read the database. Clients can sync immediately without bootstrap.
Open Collaboration: Set global permission to Write to allow anyone to write (use carefully).
Hybrid Model: Combine global Read permission with specific Write/Admin permissions for named keys. This allows public read access while restricting modifications to specific users.
Security Considerations
- Read-only common: Most appropriate for public data
- Write carefully: Global write allows any client to modify the database
- Per-database: Each database controls its own global permission settings
Bootstrap Protocol
Overview
Bootstrap provides a request/approval workflow for controlled access grants:
Client Server User (with Admin key)
| | |
|-- Sync Request -------→ | |
| |-- Check Auth Settings |
| | (no matching key) |
| | |
|←- Auth Required --------| (if no global permissions) |
| | |
|-- Bootstrap Request --→ | |
| (with key & perms) | |
| |-- Store in _sync DB -------→|
| | |
|←- Request Pending ------| (Bootstrap ID returned) |
| | |
| [Wait for approval] | |
| | |
| | ←-- List Pending -|
| | --- Pending [] -->|
| | |
| | ←-- Approve ------|
| |←- Add Key to DB Auth -------|
| | (using user's Admin key) |
| | |
|-- Retry Normal Sync --→ | |
| |-- Check Auth (now has key) |
|←- Sync Success ---------| (access granted) |
Client Bootstrap Request
When a client needs access to a database:
- Client attempts normal sync
- If auth is required, client calls
user.request_database_access() - Server stores bootstrap request in
_syncdatabase - Client receives pending status and waits for approval
Bootstrap Request Storage
Bootstrap requests are stored in the _sync database:
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BootstrapRequest {
/// Database being requested
pub tree_id: ID,
/// Client's public key (for verification)
pub requesting_pubkey: String,
/// Client's key name (to add to auth settings)
pub requesting_key_name: String,
/// Permission level requested
pub requested_permission: Permission,
/// When request was made
pub timestamp: String,
/// Current status
pub status: RequestStatus,
/// Client's network address
pub peer_address: Address,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RequestStatus {
Pending,
Approved {
approved_by: String,
approval_time: String,
},
Rejected {
rejected_by: String,
rejection_time: String,
},
}
Approval by User with Admin Permission
Any logged-in user who has a key with Admin permission for the database can approve the request:
- User logs in with
instance.login_user() - Lists pending requests with
user.pending_bootstrap_requests(&sync) - User selects a key they own that has Admin permission on the target database
- Calls
user.approve_bootstrap_request(&mut sync, request_id, approving_key_id) - System validates the user owns the specified key
- System retrieves the signing key from the user’s key manager
- System explicitly validates the key has Admin permission on the target database
- Creates transaction using the user’s signing key
- Adds requesting key to database’s auth settings
- Updates request status to Approved in the sync database
Permission Validation Strategy
Bootstrap approval and rejection use explicit permission validation:
-
Approval: The system explicitly checks that the approving user has Admin permission on the target database before adding the requesting key. This provides clear error messages (
InsufficientPermission) and fails fast if the user lacks the required permission. -
Rejection: The system explicitly checks that the rejecting user has Admin permission on the target database before allowing rejection. Since rejection only modifies the sync database (not the target database), explicit validation is necessary to enforce the Admin permission requirement.
Rationale: Explicit validation provides:
- Clear, informative error messages for users
- Fast failure before attempting database modifications
- Consistent permission checking across both operations
- Better debugging experience when permission issues occur
Client Retry After Approval
Once approved, the client retries with normal sync after waiting or polling periodically. If access was granted, the sync succeeds and the client can use the database.
Key Requirements
For Bootstrap Request:
- Client must have generated a keypair
- Client specifies the permission level they’re requesting
For Approval:
- User must be logged in
- User must have a key with Admin permission for the target database
- That key must be in the database’s auth settings
For Rejection:
- User must be logged in
- User must have a key with Admin permission for the target database
- That key must be in the database’s auth settings
- System explicitly validates Admin permission before allowing rejection
Verification of Transferred Entries
A successful bootstrap transfers the full database state to the client. Those
entries are not trusted because the server sent them: like any synced
data they are stored Unverified (the wire protocol carries no way for a
peer to assert a verification status — see the
service architecture).
Promotion is a local decision on the client:
Database::verify()validates each received entry against the_settingsit pins and promotes it, prefix-closed (an entry becomesVerifiedonly once its whole ancestor history is);- a normal read also triggers an opportunistic verification pass when a tip
is still
Unverified(the access-time hook).
Until then, default reads expose only the Verified frontier, so a
freshly bootstrapped database may read as empty for the instant before
verification completes; .allow_unverified() opts into the pre-verification
view. Because the bootstrap root is genesis/TOFU, verification bottoms out at
a self-authorising root and the rest cascades from there.
Design Decisions
Auto-Approval via Global Permissions
Bootstrap requests are auto-approved when the database has a global permission that covers the requested permission level:
- Global Permissions: A database with global permission set to
Write(10)auto-approves any request forWrite(10)or lower (includingRead) - Manual Approval: Requests exceeding global permissions require explicit approval by a user with Admin permission
Rationale:
- Simple model: global permissions define open access boundaries
- Clear security: requests beyond global permissions need explicit approval
- No per-request policy evaluation needed
- Bootstrap combines both open and controlled access patterns
API Design
Global Permissions API
Global permissions are managed through the AuthSettings API via the set_global_permission method:
// Set global permission
let mut auth_settings = AuthSettings::new();
auth_settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
Bootstrap API
impl Sync {
/// List pending bootstrap requests
pub fn pending_bootstrap_requests(&self) -> Result<Vec<(String, BootstrapRequest)>>;
/// Get specific bootstrap request
pub fn get_bootstrap_request(&self, request_id: &str) -> Result<Option<(String, BootstrapRequest)>>;
/// Approve a bootstrap request (low-level, requires signing key)
pub fn approve_bootstrap_request_with_key(
&self,
request_id: &str,
signing_key: SigningKey,
approving_key_id: &str,
) -> Result<()>;
/// Reject a bootstrap request (low-level, requires signing key)
pub fn reject_bootstrap_request_with_key(
&self,
request_id: &str,
signing_key: SigningKey,
rejecting_key_id: &str,
) -> Result<()>;
/// Request bootstrap access (low-level, requires key details)
pub async fn sync_with_peer_for_bootstrap_with_key(
&self,
address: &Address,
tree_id: &ID,
public_key: &str,
key_id: &str,
requested_permission: Permission,
) -> Result<()>;
}
impl User {
/// Get all pending bootstrap requests from the sync system
pub fn pending_bootstrap_requests(
&self,
sync: &Sync,
) -> Result<Vec<(String, BootstrapRequest)>>;
/// Approve a bootstrap request (requires Admin permission)
/// The approving_key_id must be owned by this user and have Admin permission on the target database
pub fn approve_bootstrap_request(
&self,
sync: &Sync,
request_id: &str,
approving_key_id: &PublicKey,
) -> Result<()>;
/// Reject a bootstrap request (requires Admin permission)
/// The rejecting_key_id must be owned by this user and have Admin permission on the target database
pub fn reject_bootstrap_request(
&self,
sync: &Sync,
request_id: &str,
rejecting_key_id: &PublicKey,
) -> Result<()>;
/// Request database access via bootstrap (client-side with user-managed keys)
pub async fn request_database_access(
&self,
sync: &Sync,
address: &Address,
database_id: &ID,
key_id: &PublicKey,
requested_permission: Permission,
) -> Result<()>;
}
Security Considerations
Global Permissions
- Public Exposure: Global permissions make databases publicly accessible
- Write Risk: Global write allows anyone to modify data
- Audit Trail: All modifications still signed by individual keys
- Revocation: Admins can remove global permission at any time
Bootstrap Protocol
- Request Validation: Verify requesting public key matches signature
- Permission Limits: Clients request permission, approving user decides what to grant
- Admin Permission Required: Only users with Admin permission on the database can approve
- Request Expiry: Consider implementing request expiration
- Rate Limiting: Prevent spam bootstrap requests
Future Enhancements
- Request Expiration: Automatically expire old pending requests
- Notification System: Notify users with Admin permission of new bootstrap requests
- Permission Negotiation: Allow approving user to grant different permission than requested
- Batch Approval: Approve multiple requests at once
- Bootstrap Policies: Configurable rules for auto-rejection (e.g., block certain addresses)
- Audit Log: Track all bootstrap requests and decisions
Conclusion
The bootstrap and access control system provides:
Global Permissions:
- Simple open access for public databases
- Flexible permission levels (Read, Write, Admin)
- Per-database control
Bootstrap Protocol:
- Secure request/approval workflow
- User-controlled access grants
- Integration with Users system for authentication
Together, these mechanisms support both open and controlled access patterns for Eidetica databases.