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

Syntax for an unattenuated delegation #131

Closed
Tracked by #132
Gozala opened this issue Nov 21, 2022 · 30 comments
Closed
Tracked by #132

Syntax for an unattenuated delegation #131

Gozala opened this issue Nov 21, 2022 · 30 comments
Assignees
Labels
Milestone

Comments

@Gozala
Copy link
Contributor

Gozala commented Nov 21, 2022

Pulling this out of https://github.com/web3-storage/specs/pull/7/files#r1027388060. I would like to propose clear syntax for an unattenuated delegation between principals.

I am not sure if "with": "own://did:key:zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV/*" implies exactly that, if so it does not seem very intuitive, I would find "with": "*" far more intuitive and seems like it would align with "can": "*" too.

That said I think following would be most compact and most on point IMO:

{
  iss: "did:key:zFrom",
  aud: "did:key:zTo",
  exp: null,
}

I would expect ☝️ to be equivalent of "also known as" in DID spec or elsewhere, implying that did:key:zTo could be used anywhere in place of did:key:zFrom.

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

I mean yes, but I wish UCAN had a more explicit thing than relying on DID specifics

Tell me more! Can you describe more about the kind of properties that you wish it had?

Does above description clarify it ?

In regards to DID specifics, I have hard time putting a finger on it. I suppose it is indirection. Explaining that ability to did/rotate implies principal has authority over all the capabilities of that did at hand is not easy and is somewhat distracting from the point itself which is did:key:zTo can be used in place of did:key:zFrom regardless of did document update takes place or not.

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

Ok I have a better explanation. In the context of https://github.com/web3-storage/specs/pull/7/files we would like some key to represent did:dns:web3.storage which is different from the key actual DID document will map to.

If we were to use did/rotate it would sort of imply that we are updating did key of the document to another key, but that is not what's happening. We are not actually rotating keys, or updating DID documents we just want to delegate complete authority to some other key.

@expede
Copy link
Member

expede commented Nov 21, 2022

Clarity

Does above description clarify it ?

AHA! Oh wow okay this is what we've been talking past each other for many months about!

Yes, I see what you want now. The diagrams and wording are so similar. I think I was missing the pipelining idea.

Pipelining

Aside from some lingering intuition about possible attacks, I think that your "pipeline" idea is awesome. If this were a "live"/dynamic ocap system, this would probably get modeled as a forwarder proxy. UCANs are "static", but I don't see why we can't express the same thing. You can always revoke the relevant UCAN.

I'm going to ask around to see if my intuition on danger is valid, but otherwise we should make this happen IMO.

with: *

I would find "with": "*" far more intuitive

Hmm, but also with: * is not clear which sense of * is scoped to. Everything in the UCAN proofs? All of the resources that you directly own? Everything that anyone ever has or ever will every delegate to you? We need to be extremely clear that this means the last case. With great power comes great responsibility! From a human-factors level, such an unbelievably powerful capability should be noisier than a single character IMO.

This is what own:// tries to do: scope it to the resources that the principal directly owns, not what's been delegated to it. It turns out that naming things is hard!

This proposal is a bit like a forwarder. I wonder if we can make it noisier by making the ability really clear perhaps?

Another option, that I'm not super keen on but here for completeness, is that we could make this a first-class UCAN feature. For example, another wrapper format like how revocation works, but for "forwarding/pipelining". It's a bit inelegant, but an option 🤷‍♀️

Rotation vs Forwarding

If we were to use did/rotate it would sort of imply that we are updating did key of the document to another key, but that is not what's happening. We are not actually rotating keys, or updating DID documents we just want to delegate complete authority to some other key.

Agreed 💯

@expede
Copy link
Member

expede commented Nov 21, 2022

Even though pipelining is a really cool idea, I also wonder if we can solve this with DIDs directly. Let me noodle on it 🤔

@expede
Copy link
Member

expede commented Nov 21, 2022

Revocation Forwarding

Would this also grant you the ability to revoke UCANs that the root DID delegated in a different chain?

zAlice ---> zBob ---> zCarol
  |          ^
 "all"       |
  |        revoke
  V          |
zIrakli -----+
  |
  V
zBrooke

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

Hmm, but also with: * is not clear which sense of * is scoped to. Everything in the UCAN proofs? All of the resources that you directly own? Everything that anyone ever has or ever will every delegate to you? We need to be extremely clear that this means the last case. With great power comes great responsibility! From a human-factors level, such an unbelievably powerful capability should be noisier than a single character IMO.

👍

For what it's worth I think can: "file/*" has same problem, I found myself explaining bunch of times how it is different from just listing out specific capabilities.

This is what own:// tries to do: scope it to the resources that the principal directly owns, not what's been delegated to it. It turns out that naming things is hard!

That was my interpretation as well, which is why it did not cover this

Another option, that I'm not super keen on but here for completeness, is that we could make this a first-class UCAN feature. For example, another wrapper format like how revocation works, but for "forwarding/pipelining". It's a bit inelegant, but an option 🤷‍♀️

I would argue that this kind of meets that criteria but also isn't necessarily different from UCAN itself

{
  iss: "did:key:zFrom",
  aud: "did:key:zTo",
  exp: null,
}

@expede
Copy link
Member

expede commented Nov 21, 2022

I found myself explaining bunch of times how it is different from just listing out specific capabilities.

...is it different? 😳

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

...is it different? 😳

If we create another can: "file/restore" it would be covered by can: file/*, but if you just list out abilities that exist today it would not cover ones that we makeup in the future.

I feel it is equivalent to what I want to do with resources I want to express delegation to resources that I own but also ones that I may own in the future.

@expede
Copy link
Member

expede commented Nov 21, 2022

Oh, indeed it covers any extension that you would add later

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

Would this also grant you the ability to revoke UCANs that the root DID delegated in a different chain?

I assumed it would not, but perhaps it should. I think I could be convinced into either

@expede
Copy link
Member

expede commented Nov 21, 2022

That was my interpretation as well, which is why it did not cover this

Yup yup! I meant it more as an example of trying to being explicit about the selector.

I assumed it would not, but perhaps it should. I think I could be convinced into either

I'm not necessarily endorsing the idea yet, but we should definitely think about it more.

From a workflow perspective, this pipe-lining idea basically replaces a traditional DID document. In a lot of ways, it says "this other DID is fully equivalent to me". If that's the case, and I'm going to say rotate (or lose!) my key, I probably want the ability to disable misbehaving delegates. One could argue that this is mixing concerns, and is better handled at an identity layer.

As I've been thinking about the pipeline/forwarder in "live" ocap terms, this on/off switch is a capability that I could hand to someone else.

It does add a lot of possible states to the system. The forwarder idea in general adds some minor complexity to delegation checking, too, but possibly pays off.

Root Key Revocation

🤔 This also doesn't solve for the case where the root key is misbehaving.

zAlice ---all---> zBob
  |
  |
  V
zCarol

Alice can always revoke Bob. One could argue that Bob should never be able to revoke Alice, because it would immedietly negate the revocation. If you want to do "true" group management (admins can revoke each other), then a DID is probably the correct layer.

Anyhow I'm getting off topic 😅

@expede expede added the ⏭️ autoforwarding Autoforwarding label Nov 21, 2022
@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

As I've been thinking about the pipeline/forwarder in "live" ocap terms, this on/off switch is a capability that I could hand to someone else.

I’m not sure I fully follow, but that someone in our scenario was second key in chain of command. It can switch on/off rotating keys ability to represent did

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

One could argue that Bob should never be able to revoke Alice

I would agree.

Now that I think about it I also don’t like idea of zBob revoking zCarol or vise versa.

I think it would be a lot simpler to not allow revoking delegations not issued by the key & say that say they should accomplish that by going up the command chain.

This does seem inconvenient in my scenario as after we burn key revoking delegation from it would involve waking the warm key, but then again we could insert one more key in the command chain to address it.

The thing that bothers me the most however is that if we revoke in the parent we affect whole lot of other delegations as opposed to individual one we’d like to revoke

@expede
Copy link
Member

expede commented Nov 21, 2022

I’m not sure I fully follow, but that someone in our scenario was second key in chain of command. It can switch on/off rotating keys ability to represent did

Sorry, the original text included pictures in my head that I failed to actually write down. Yes, basically a "second in command" like you described 💯

In a live, running ocap system, you push messages around like in the actor model (or like messages over a network). A fairly common pattern is to delegate to proxies instead of handling out access to objects directly. This the "second in command". I could add all of my capabilities to this object (in a big table or something), and have others delegate to it instead of me. I then grant you access to everything in the proxy. If I go offline, my proxy is the one being delegated to.

This version has more moving pieces, but they're all built up from consistent parts ("just" a design pattern). It depends on local state and the proxy being reachable, though.

In that dynamic scenario, I would keep updating that table "live" as things came in. With UCAN, this is deferred to read time using a static certificate that communicates my intent to implement this kind of capability forwarding.

@expede
Copy link
Member

expede commented Nov 21, 2022

The thing that bothers me the most however is that if we revoke in the parent we affect whole lot of other delegations as opposed to individual one we’d like to revoke

Same here. As much as I'd there to be a workaround, I don't think it's possible without reference to some external state (e.g. updating a DID document).

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

using a static certificate that communicates my intent to implement this kind of capability forwarding.

this is precisely what I want form pipelining. Capture just enough of the table in signed form to proof ability to proxy

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

Maybe there’s some clues on how to approach revocation in live systems ?

@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2022

Just thinking out load here:

The way we validate if claimed capability is warranted is:

  1. Select proofs who’s resources contains claimed resource.
  2. Select from ☝️ones that contain claimed action
  3. Select from ☝️ones that satisfy claimed nb.
  4. If we have any matches pick each and repeat the above steps

It seems to me that we’d need slightly different approach if resource is * (will use this for now for simplicity). Specifically step (4) will have to not pick the match and descend, but rather keep current claim and descend (now it actually acts like proxy)

When evaluated as such I think it seems reasonable to allow such a proxy to revoke any capability it proxied and therefor allow it to be revoked.

@expede
Copy link
Member

expede commented Nov 21, 2022

Maybe there’s some clues on how to approach revocation in live systems ?

In a live system, you have mutable state and a single source of truth for where it lives. On one hand this is very convenient because... well you can turn things off and on at will. The downside is that it often requires making network calls, so it's not partition tolerant and you have to check this state every time.

@expede
Copy link
Member

expede commented Nov 21, 2022

In a live system

Let me know if it would be good to talk through more how these systems work. I recently finished a re-read of Mark Miller's dissertation, so I have a lot of this hot loaded.

The way we validate if claimed capability is warranted is:

Something like this algorithm makes sense to me, yeah 💯

When evaluated as such I think it seems reasonable to allow such a proxy to revoke any capability it proxied and therefor allow it to be revoked.

Just to make sure we're on the same page:

alice -> bob -> carol
  
alice ---[proxyall]--> david
 
david --[revoke]--> bob

☝️ David is proxied all from Alice, and can thus revoke Bob (and Carol) in the top chain

👇 But we cannot let David revoke Alice, because if so, we end up in this situation:

david --[revoke]--> alice
alice --[revoke]--> david

If David can revoke Alice, who gets revoked depends on the order that you receive the messages. I'm pretty sure that Alice always has to remain the source from which all authority flows

@alanhkarp
Copy link

Sorry for being late to the game. I finally got a chance to catch up on the discussion on wildcards.

I use the definition:

A capability is an unforgeable, transferrable permission to use the thing it designates.

By that definition, a UCAN delegation chain that only contains with:* is not a capability, since it cannot be used to designate the resource you wish to operate on. However, a UCAN delegation that only contains w:* can be used in a proof. (Something that isn't a capability appearing in a capability delegation chain seems odd to me.) The implication is that given such a delegation chain, you have to delegate one more time to create a UCAN that designates the specific resource you wish to use. Has that been noted in the proposal?

@expede
Copy link
Member

expede commented Mar 16, 2023

Thanks @alanhkarp! I'd love to get your thoughts on how you'd solve what we're trying to solve in a noninteractive setting (i.e. we can't rely on live processes that can hold state and respond to messages).

Scenario

Alice has two devices: a phone and a laptop. She wants anything delegated to her phone to be automatically redelegated to her laptop.

(You can imagine more scenarios, but using devices makes this easy to reason about)

Classic Solution

We'd use a powerbox, and grant both devices access control over the powerbox

DID Solution

With more complex DIDs than did:key, you tie together multiple keys to a single identity. This is not very flexible (you can't easily give the laptop more capabilities than the phone), and it requires a bunch of external mechanisms that require us to be online to continually check the DID's state.

Proposed Solution

We think that we don't need anything other than certificate capability.

Using with: * automatically redelegates (essentially proxies) all UCANs from Alice's phone to her laptop. When Bob delegates to Alice's phone's DID, the laptop autroatically gains those capabilities, but crutually not vice versa. Further, the phone can revoke that link if the laptop goes rogue.

Our reasoning has been that this is a bit like a powerbox, but given as a noninteractive expression of intention rather than a live process.

Does that make sense, or are we on the wrong track here?

@alanhkarp
Copy link

@expede That makes perfect sense. Using such a UCAN in an invocation is the missing piece. When Alice wants to read one of her files, she needs to designate which file. A UCAN containing with:* doesn't to that. If instead she designates the file elsewhere in the invocation, she's separated designation from authorization, the root cause of the confused deputy. The answer is for Alice to delegate to herself a new UCAN containing with:my_file and the with:* UCAN as the proof.

This pattern is one I don't recall seeing before, a delegation with a proof that isn't a capability but that unambiguously proves the specific permission. I'll bring this up at our capability group meeting tomorrow. Let me know if you'd like to participate.

The invocations in all the UCAN use cases I've seen involve designating only a single resource. Do you envision invocations that involve more than one resource, e.g., copy filea fileb or that involve more than one service? That's when things get interesting as I showed in my transitive access paper.

@expede
Copy link
Member

expede commented Mar 16, 2023

Thanks @alanhkarp!

Using such a UCAN in an invocation is the missing piece.

Yeah absolutley. I think that you won't be able to construct an invocation that uses with: *. (i.e. the set of invocations directly on * is uninhabited, since it acts roughly like a generic: you need to specalize it to a concrete resource at invocation time). We've separated out the delegation and invocation formats, so they have slightly different rules in the current picture. We explored an invoke: true field, but it was unsatisfying for a number of reasons (including wanting to redelegate the underlying authority when acting as an invocation proxy, making the contract differences with promises clearer by not allowing them in delegation, etc)

Do you envision invocations that involve more than one resource

Depending on how you mean. In IPVM, we have atomic invocations (e.g. compare and swap) that require multiple read and write access to multiple resources at the same time.

If instead she designates the file elsewhere in the invocation, she's separated designation from authorization, the root cause of the confused deputy.

Hmm interesting. Using that analogy, how would I grant access to any file on a networked file system? Would I need to delegate each file separately?

I haven't had a chance to read your transitive access paper at time of writing this comment, but is that more like a more coarse-grained solution by granting access to a filesystem managing agent that in turns controls the file system and all of its files by parenthood?

This pattern is one I don't recall seeing before

Same!

I'll bring this up at our capability group meeting tomorrow.

🙏

Let me know if you'd like to participate.

I'd love to, depending on the time!

transitive access paper

👀

At least from the abstact, we're attempting to do this in various places in production, yeah

@expede
Copy link
Member

expede commented Mar 16, 2023

I just finished the transative access paper. A bunch of it felt familiar! I don't see any reason why the proposal here would go against the content of the paper.

Perhaps to clarify the flow of authority with the wildcard, in case it helps explain the intention:

MONDAY
Alice ==[*]==> Bob

TUESDAY
SomeStorageService ==[read some file]==> Alice

WEDNESDAY
SomeStorageService ==[read some file]==> Alice ==[*]==> Bob     ]- DELEGATION
    ^                                                    |
    |                                                    |
    +––––––––––––––[invoke: read some file]––––––––––––––+      ]- INVOCATION

@alanhkarp
Copy link

We've separated out the delegation and invocation formats

I believe that's the correct thing to do. At least it's what we did in our Zebra Copy work.

In your invocation you designate the resource as

"on": "mailto:[email protected]"

which is not connected to the capability at all. You can run into problems when the invocation involves multiple resources. What you should do instead is specify the capability UCAN, perhaps as

"on":UCAN-ID

or some such, where UCAN-ID is the unique identifier of the UCAN at the end of the delegation chain in the proof. That UCAN MUST grant the requested permission, "call/send," to the specific resource you are operating on, "mailto:[email protected]" in your example.

Using that analogy, how would I grant access to any file on a networked file system? Would I need to delegate each file separately?

Alice can delegate to Bob a UCAN granting him permission to read everything in a particular directory, e.g., /alice/photos/*. Bob can then delegate to the program he's planning to run a UCAN good for just the single file he wants to read, e.g., /alice/photos/vegas.jpg. That's actually good practice anyway, since it's enforcing the Principle of Least Privilege on the program Bob is running.

Depending on how you mean. In IPVM, we have atomic invocations (e.g. compare and swap) that require multiple read and write access to multiple resources at the same time.

That's the kind of thing that can get you in trouble if you separate designation from authorization. For example, with your invocation scheme, you can have a single can:*, with:* UCAN that grants permission to both files. If someone else has specified the file to be written you've got the potential for a confused deputy.

The meeting is every Friday (50 weeks a year) at 10 AM Pacific time. Contact me by email if you or someone you designate wants to join via Google Meet.

@alanhkarp
Copy link

Our comments "crossed in the mail."

I just finished the transitive access paper. A bunch of it felt familiar! I don't see any reason why the proposal here would go against the content of the paper.

I agree as long as you're careful not to separate designation from authorization.

Perhaps to clarify the flow of authority with the wildcard,

I believe my previous comment explains how that works. If Alice gives Bob a UCAN specifying with:/alice/photos/*, Bob will have permission to read any photo in that directory no matter when it was added.

@expede
Copy link
Member

expede commented Mar 17, 2023

I believe that's the correct thing to do. At least it's what we did in our Zebra Copy work.

Good to hear 🙌

I've seen the Zebra Copy paper before, but always good for me to brush up 👀

which is not connected to the capability at all.

The invocation token has a field prf which contains the array [UCAN-ID] (written as [&UCAN] in the IPLD Schema syntax), which supplies the invocation with the required authority. I could be misunderstanding the concern, though!

If Alice gives Bob a UCAN specifying with:/alice/photos/*, Bob will have permission to read any photo in that directory no matter when it was added.

Agreed!

[...] /alice/photos/*. Bob can then delegate to the program he's planning to run a UCAN good for just the single file he wants to read, e.g., /alice/photos/vegas.jpg. That's actually good practice anyway, since it's enforcing the Principle of Least Privilege on the program Bob is running.

Agreed 💯

This may be signal that we need to expand some text in the invocation spec — thanks! Our approach for invocations to get really specific with what it's operating on, which is equal-to-or narrower than the UCAN (normal delegation rules).

We experimented with adding another layer of UCAN that scopes down to the percise resource, but found a couple subtle problems, though the big one is that the contract is confusing with promises. People thought that the promise scoped the authority down to the output of the previous step, but that's not posisble to enforce if both steps are executed by the same agent. Hence, you need to be clear about the scope of what's actually being delegated, and showing that you're delegating e.g. alice/photos/* instead of alice/photos/{result of promise} makes that clear. However, I could be thinking about this wrong — if so please do let me know!

@alanhkarp
Copy link

The invocation token has a field prf which contains the array [UCAN-ID] (written as [&UCAN] in the IPLD Schema syntax), which supplies the invocation with the required authority. I could be misunderstanding the concern, though!

The important thing is to associate each argument with its corresponding capability. The simplest approach is to use the appropriate capability as the argument, e.g.,

{
  "on": 
     "prf": [
        UCAN4photoServiceCopy            /* Permission to invoke copy */
  ],
  "input": [
          UCANdelegatedToPhotoService/alice/photos/vegas.jpg  /* Read permission only */
  ],
  "output": [
          UCANUCANdelegatedToPhotoService/bob/alice.jpg      /* Write permission */
  ]
}

but any approach that unambiguously associates the argument with the capability works. Note that the method being invoked is designated with a proof.

In the above example, the photo service has chosen an API that takes a delegation for the input and output parameters in case it wants to outsource those functions, as in the transitive access example. It could, of course, also accept a proof, but then outsourcing would be a breaking change.

This may be signal that we need to expand some text in the invocation spec — thanks! Our approach for invocations to get really specific with what it's operating on, which is equal-to-or narrower than the UCAN (normal delegation rules).

Since anyone holding a UCAN is able to create an attenuated delegation, there's no reason not to require that the capability designate exactly the desired resource or method as in the above example. You not only better enforce Least Privilege on the program running on your behalf, but you avoid several potential foot guns. For example, Bob could have R/W permission to Alice's photo. Using a R/O capability protects him from certain mistakes.

We experimented with adding another layer of UCAN that scopes down to the percise resource, but found a couple subtle problems, though the big one is that the contract is confusing with promises.

I have worked with two different capability systems that supported promises, but neither used certificates as capabilities. I'll have to see an example of the problem to understand the issue.

Meta comment

I have found that only using simple use cases, like read or copy, leads to designs that work poorly for other realistic uses. I picked the backup service in the transitive access paper as the simplest example that covered every usage pattern I could think of.

@expede
Copy link
Member

expede commented Jul 12, 2023

Closed by #132

@expede expede closed this as completed Jul 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants