Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DloD: DDB's list of Death #108

Closed
coyotespike opened this issue May 16, 2018 · 3 comments
Closed

DloD: DDB's list of Death #108

coyotespike opened this issue May 16, 2018 · 3 comments
Assignees

Comments

@coyotespike
Copy link

coyotespike commented May 16, 2018

This is the minimum set of issues we gotta close to use orbit the way we intend to

Please see this gist for a more complete breakdown. In particular, section 3 explains the architecture

@coyotespike coyotespike self-assigned this May 16, 2018
@thiagodelgado111 thiagodelgado111 added this to the Sprint 8 milestone Sep 3, 2018
@thiagodelgado111 thiagodelgado111 self-assigned this Sep 3, 2018
@coyotespike coyotespike removed this from the Sprint 8 milestone Sep 17, 2018
@thiagodelgado111 thiagodelgado111 added ddb and removed ddb labels Oct 2, 2018
@thiagodelgado111 thiagodelgado111 changed the title DDB: Allow dynamic permissions DloD: DDB's list of Death Oct 19, 2018
@collinvine
Copy link

Copying the gist below for posterity.


User Stories

  1. As a user, I want to store my information and access colony information.
  2. As a user, I want to be able to change machines, and still access my profile and change boards.
  3. As a user, I want to add and remove people from boards.

Progress on Each

  • The first user story is met by the dApp-Orbit integration work (Laurent).
  • The second user story depends on the pinningService and on a solution to keys.
  • The third user story depends on dynamic database permissions.

The dApp-Orbit integration work involves data modeling. The implementation is
similar, at least in principle, to the PoC we've already completed. This is known territory.

The pinningService is mostly complete. It is ready to be improved through usage.

The solution to keys is WIP. We have ideas but they are not yet proven.

Dynamic database permissions has several suggested implementations, but is not
yet implemented, and there's a fair amount of work to do. This is a known
unknown.

1. Keys

Keystore

  • Repo: https://github.com/orbitdb/orbit-db-keystore

  • State: accepts generic storage, intended to work with LocalStorage. Gen, sign, verify

  • Problem statement: This library generates a keypair for the local orbit node,
    and stores it locally. To continue to sign entries (update boards with same
    identity), a user must transfer the key to the new device.

  • Problem statement: MetaMask does not give access to keypair.

  • Problem statement: if we use Ethereum wallet keypair, user must manually sign entries.

  • Keystore API Proposal

  • Keystore API Question

  • Unique username / subdomain is tied to an ethereum address. Can we tie the address to a database, or set of databases?

IPFS-log interface to contract and DDB

  • Repo: https://github.com/orbitdb/ipfs-log/blob/master/src/entry.js
  • State: IPFS-log takes a keystore argument, and uses this to sign and verify entries.
  • Problem: signEntry and verifyEntry don't allow for alternative keystore design. They assume orbit-db-keystore design.
  • Architecture: IPFS-log underlies all Orbit databases.
  • Goal: make IPFS-log use signing and verifying in a way that works with a keystore, another OrbitDB, or a contract
  • Goal: generic extendable interface for access controller, for orbitdb instance, IPFS, or a keystore, or a contract.

Generating keys

Wallets

From Thiago's Notes:

Wallets we need to cater for specifically

  • Ethereum wallet
  • MEW
  • Metamask
  • hardware wallets (trezor, ledger nano, bitbox)

Generating keys, notes from Thiago's research

  • it's possible to generate keys using something like a HD wallet
  • how to tie it back with other types of wallets is unclear.
  • having a passphrase separated for orbit keys would be easier.
  • we could also try to find a way to submit a proof that you own the address that is allowed to write to orbit, this way we wouldn't need to link the keys upwards
  • We could also try using a contract that maps keys to addresses to solve that part of the problem

2. Permissions

OrbitDB issue

DSGuard Contract

shamb0t's example

Access Controller

Laurent's plan

Permissions Model

  • Capability-based or role-based model

  • Whitelist model and signing payloads

3. Architecture

This architecture, once fully understood, largely explains the proposed changes :-)

OrbitDB.js uses ipfs-access-controller (which inherits from access-controller) to create and save into IPFS an access-controller.

  1. OrbitDB.js uses keystore.js to create a key, and puts this in the ipfs-access-controller array.
  2. The ipfs-access-controller is saved to IPFS, and its hash is then included in
    the new database hash. For that reason the permissions are immutable. Change
    permissions, and the database hash changes. This is an elegantly simple security
    model.
  3. the access controller (and key) are passed through the database types to orbit-db-store.
  4. orbit-db-store double-checks OrbitDB's work, using the keystore and key if passed down, and if not also creating defaults.
  5. orbit-db-store passes this key, and the access array, to ipfs-log.
  6. ipfs-log checks if the key is in the access array, and if so adds an entry.
  7. ipfs-log/entry.js uses the keystore and key to sign and verify entries.

Further Notes

ipfs-log/log.js, log.append, checks for write permissions. It doesn't verify, but decorates the entry.

ipfs-log/log.js, log.join, checks for write permissions, and also verifies each entry.

  • orbit-db-store Replicator loads entries by creating a new Log for each one,
    then flattening and finding uniques, then fires 'load.end' event.
  • orbit-db-store Store attaches onLoadCompleted to that event, and runs
    oplog.join for each entry.
  • Thus, we append for our own entries, where we check we're in the write permissions,
    and then decorate the entry with signature and public key.
  • For all other entries, we join, where we check write permissions and verify the entries.

4. Plan of Attack

Permissions

  • Create an access controller interface which can accept any type of access controller, or create a base class
  • Change src/OrbitDB.js to create the database using a passed-in access controller, or a default.
  • Change orbit-db-store in the same way
  • Change ipfs-log so that it does not sign or verify entries
    • It merely receives and returns entries, and updates and merges them.
    • Alternatively, ipfs-log receives any ol' sign/verify functions and applies them
  • Change access-controller so that it is a base class for any access controller
  • Change ipfs-access-controller so that it handles signing and verifying, not just storage
  • Create orbit-access-controller, contract-access-controller
  • Create AccessControllerWithChildKeys (probably its own epic)

Keystore and Keys

  • Generate orbit keys from Ethereum keys
  • Keystore

5. Future Database Changes

Orbit

  • js-ipfs garbage collection
  • data migrations
  • Don't store orbit keys in localStorage
  • Can the database be pinned if the client restricts read access?

Colony

  • Can we filter by unread messages in inbox?
  • Can we encrypt readness?
  • Should not have to press 'refresh' button to get new messages
  • Followers benchmarking

@collinvine
Copy link

Laurent's Gist on OrbitDB Permissions for posterity.


The idea of dogfooding for permissions
would be perfect to deal with external systems.

One limitation I see is that it limits us to using orbitdb-backed approach, which might
not be as fine grained as we'd like to.

For example, we would like to:

  • let a user write to a store if, and only if, they own it according to on-chain information,
  • let a user edit a kvstore value if, and only if, they own the key (it's their public ethereum address). To build decentralized catalogs.

A simple approach would be to let app developpers customize the create entry and the validate entry operations:

Could orbitdb implement an inversion of control / middleware approach to let us define different permission protocols?

the IPFS manifest and the (wip) store approaches would be provided as default middlewares.
We would be able to define access control specific to our needs & combine them.

Auth-related code would be refactored out of orbitdb ipfs-logs to separate concerns more clearly:

- IPFS: store & Exchange Data
- Orbit Log: update & merge operations
- Access Middleware: add auth info to updates & validate their permissions.

The access middleware would look something like:

interface AccessMiddleware:
  isWriteAllowed: (entry) => bool
  - Accept or reject entries
  
  decorateEntry: (entry) => entry
  - Update an entry payload with permission informations
  

A few practical examples:

Scenario: Allow everything.

class AccessControllerAllowAll {
  this.isWriteAllowed = async (entry) => true
  this.decorateEntry = async (entry) => entry
}

Scenario: Allow based on ipfs manifest:

class AccessControllerIPFSManifest {
  constructor(key, ipfs, manifestAddress) {
    this.keyStore = keyStore;
    this.manifest = await loadManifest(ipfs, manifestAddress)
  }
  
  async isWriteAllowed(entry) {
    if (!this.keyStore.verify(entry, entry.signature) === entry.key) {
      return false; // wrong signature
    }
    
    // manifest allows this key
    return (this.manifest.writes.include('*') || this.manifest.writes.include(entry.key));
  }
  
  async decorateEntry(entry) {
    entry.key = this.keyStore.publicKey;
    entry.signature = this.keyStore.sign(entry);
  }
}

Scenario: Allow writes depending on roles defined on chain.

class AccessControllerAllowFromBlockchain {
  async isWriteAllowed(entry) {
    if (!(await myBlockchainAPI.isUserAdmin(entry.key))) {
      return false;
    }
    
    return this.keyStore.verify(entry, entry.signature) === entry.key;
  }
  
  decorateEntry: (entry) => {
    entry.key = this.keyStore.publicKey;
    entry.signature = this.keyStore.sign(entry);
    return entry;
}

Scenario: We want roles defined on-chain: "I'm the owner of a Colony", "I'm the owner of a user ID".
AND we want users to be able to change devices.
We don't want to have them sign every orbitdb update with their hardware / metamask wallet.

For this we'd need to verify that a payload was signed by a key (localKey)
and that this localKey is owned by the owner of an ethereum address (globalKey).

const AccessControllerWithChildKeys {
  constructor(rootKeyStore) {
    this.localKeyStore = generateKeyPairs();
    this.localKeySignature = rootKeyStore.sign(this.childKeyStore.publicKey); // happens once
  }
  
  async isWriteAllowed(entry) {
    // Check the payload was signed by a given local key
    const verified = this.localKeyStore.verify(entry, entry.signature) === entry.key;
    
    if (!verified) {
      return false;
    }
    
    // Check that the local key was signed by the owner of a ressource on chain
    const globalKey = this.keyStore.verify(entry.key, entry.rootSignature);
    return (await myBlockchainAPI.isUserAdmin(globalKey))
  }
  
  async decorateEntry(entry) {
    entry.key = this.keyStore.publicKey;
    entry.signature = this.localKeyStore.sign(entry);
    entry.rootSignature = this.localKeySignature;
    return entry;
  }
}

We'd pass a controller, either as an instance, or as a factory (that takes this.ipfs and other parameters).

function myStore() {
  const accessController = new AccessControllerWithChildKeys(rootKeyStore);
  const store = orbitdb.kvstore({accessController});
}

This would let us:

  • define much finer authentication protocols
    • Depend on external resources,
    • Accept updates on specific messages. For example, a user can update a kvstore's key, value, if and only if, they own the rights to the key.
  • combine different key stores & access controller.

Limitations:

  • Key Revokation:
    • We'll need a way to signal orbit a key has been revoked and when, so that a store can trim unauthorized edits.
    • This could be implemented by providing a revokeAt(key, time) to the Access Controller. On change the user can call this and trigger an orbitdb trimming process.

Code change (WIP):

@collinvine
Copy link

Thiago notes.md


Wallets we need to cater for specifically

  • Ethereum wallet
  • MEW
  • Metamask
  • hardware wallets (trezor, ledger nano, bitbox)

NOTE: We can’t plug in a wallet into orbit. Taking actions to sign every entry, it’d be a terrible experience for users and we need to be able to revoke keys, doing that per ethereum account would get pretty annoying too.

Permissions

  • We need to be able to control permissions on the blockchain side of things, where we have consensus, reputation , etc.
  • Access controllers: we need to be flexible. Allow devs to use orbit, ipfs, ethereum, whatever
  • Model: we can use either a role-based model or a capabilities-based model (see how our beta handles permissions)
  • Capabilities-based model will demand some changes because nowadays, ipfs-log entries are based on a key so the signature can be checked in other peers. How dow we do that if we need to verify a signature and if the author of that entry has the permission to perform a given operation?

NOTE: In the case that ethereum node isn’t up-to-date, it might be necessary to put new entries in some kind of queue to be retried later otherwise they’d get invalidated

NOTE2: Is it possible for us to have a contract that just generates a unique id per entry and sign it if the wallet plugged to the contract is allowed to do the change?

NOTE3: orbit-db-keystore, ipfs-log and access controllers should be changed. ipfs-log shouldn’t know about keystore implementation details (use sign and verify functions passed via constructor to the log instead), the keystore has a few unused methods, the access controller should get the keystore and interface with the ipfs-log instead of passing the keystore to the log

Keys

NOTE: Currently, orbit stores users keys on the localStorage. That needs to change!

In the ideal scenario, we generate a master-key from an ethereum account that will be used to manage permissions on the blockchain and that master-key generates child keys that are used to sign entries on orbit (per db instance or per dapp)

  • We might need a way to map ethereum addresses to keys in a contract that controls permissions
  • We might wanna keep a blacklist in a contract so orbit peers can take that from the blockchain instead of trying to keep its own list
  • Maybe another option to the ideal scenario above, we could submit a “proof” that is something predefined and signed by an account so the contract can recover the address and use it. In this case, it wouldn’t be necessary for the master key to be derived from the address

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants