-
Notifications
You must be signed in to change notification settings - Fork 3
RFC - Refine Trigger Authority Mechanism #14
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| # Refine Triggers Authority Mechanism | ||
|
|
||
| ## Context | ||
|
|
||
| Every trigger is composed of: | ||
|
|
||
| - An executable - a set of actions to execute | ||
| - An **authority** - an account ID, from under which trigger's actions are executed. | ||
| - A filter (or the _kind_ of trigger) - to define a condition where to execute the trigger, one of: | ||
| - Pipeline - to react on blocks/transactions | ||
| - Time - to execute at a certain time, either scheduled or pre-commit | ||
| - Trigger completed - to handle completion of other triggers | ||
| - Data - to react on state changes (e.g. domain registered) | ||
| - _Execute trigger_ filter, or _by-call_ filter[^1] - states that this trigger is not automatically executed, but only | ||
| via `ExecuteTrigger` instruction. | ||
|
|
||
| [^1]: By-call triggers are subject for removal in #5147 | ||
|
|
||
| Currently, the **authority** of the trigger behaves differently depending on the kind of trigger (i.e. on its filter): | ||
|
|
||
| | Trigger Kind | Trigger execution authority | | ||
| | -------------------------------------------------------------------------- | ------------------------------------------------------------ | | ||
| | Pipeline | Use trigger's | | ||
| | Time | Use trigger's | | ||
| | Trigger completed | Use trigger's | | ||
| | Data | Use caller's (the one who caused the data event) | | ||
| | By-call | Use caller's (who executed the `ExecuteTrigger` instruction) | | ||
| | Also, if any of the data triggers fail, the entire transaction also fails. | | | ||
|
|
||
| ## Motivation to change | ||
|
|
||
| ### Use case 1 - deploying on-chain functions callable by less-privileges users but executed with higher privileges | ||
|
|
||
| While building the [Hub Chain PoC](https://github.com/hyperledger-iroha/hub-chain-poc), we try to design an on-chain | ||
| agent that would accept an input from an untrusted external entity (a relay), validate it, and perform certain | ||
| administrative actions[^2]. | ||
|
|
||
| [^2]: Specifically, the on-chain agent would receive block headers of another chain and proofs that e.g. a transfer | ||
| happened there. Then, if the on-chain agent can prove, it would make e.g. a local transfer. | ||
|
|
||
| It would be convenient to register a by-call trigger that would accept transactions from a relay in a form of | ||
| `ExecuteTrigger { id: "our-special-trigger", args: ... }`, validate, and perform administrative actions. However, in the | ||
| current design the trigger would infer the authority of the caller, i.e. of the relay's account. Therefore, we would | ||
| have to give the relay's account sufficient permissions for the trigger to perform these actions. In this case, the | ||
| relay could perform these action by itself without an on-chain trust-less validation, which defeats the purpose. | ||
|
|
||
| #### Alternatives | ||
|
|
||
| - **Use Pipeline/Time filters**. These triggers are always executed using the `authority` set in the trigger. Therefore, | ||
| they can have administrative permissions, react on some state changes made by an untrusted source, and execute | ||
| administrative actions. | ||
| - **Use Trigger Completed filter**. Then, an un-trusted source could call some un-trusted trigger that would create some | ||
| state change, and then the administrative trigger would execute as a result of that trigger completion, performing | ||
| actions with elevated permissions. This works essentially the same as the Pipeline/Time variant, but with immediate | ||
| execution. | ||
| - **Use executor-level custom instructions**. Executor has "root privileges" and can perform any action. However, it is | ||
| not a modular and composable approach, as there is only one global executor in Iroha. With triggers, it is possible | ||
| for different unrelated authorities to deploy their own permission-ed logic without interfering with each other. It's | ||
| just more modular, come on! | ||
|
|
||
| ### Use case 2 | ||
|
|
||
| I (e.g. Alice) want to deploy a data trigger that reacts on someone else's (e.g. Bob's) asset balance change. This | ||
| trigger would, for example, make a transfer on _my_ behalf. | ||
|
|
||
| However, when Bob changes his own balance, my trigger would be executed with _Bob's_ permissions, not mine, and | ||
| therefore the trigger will fail to make the intended transfer. | ||
|
Comment on lines
+61
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an alternative, Alice could keep a multisig proposal pending and have it approved by a data trigger, which would achieve the same effect.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't it sound kind of complicated? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it may sound more complex — but that complexity allows us to avoid introducing fixed authorities and implicit privilege escalation. There is a clear trade-off here:
In a security-critical system like Iroha, it is better to accept a bit more design complexity than to adopt a mechanism that weakens the permission model. |
||
|
|
||
| ## Proposal | ||
|
|
||
| Current model--always run with caller's authority--is: | ||
|
|
||
| - Maximally safe - a trigger never does more than the caller can | ||
| - But useless for efficient privileged automations | ||
| - Reduces by-call and data triggers to extensions of the caller's transactions without autonomy | ||
|
|
||
| I see two options. | ||
|
|
||
| ### Option 1 - Always run with trigger authority | ||
|
|
||
| - Simple mental model: trigger is an autonomous actor. | ||
| - Security: developers must explicitly check caller identity inside the trigger logic. This isn't possible in case of | ||
| simple `Executable::Instructions`. | ||
|
|
||
| This is close to how Ethereum smart contracts work, AFAIK. | ||
|
|
||
| ### Option 2 - Mixed model | ||
|
|
||
| - Provide an ability to set whether a trigger should use a **pre-defined authority** or the **caller's** authority. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it specified in the trigger definition at the time of creation, or is it set when the trigger is executed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as now - defined at the time of registration, once.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only things that are mutable in triggers (after registration) are their:
|
||
| - This would apply to Data, By-Call, and Trigger-Completed triggers. Time and Pipeline triggers are "called" by the | ||
| host, so they must always have a pre-defined authority. | ||
|
|
||
| Definition example: | ||
|
|
||
| ```rust | ||
| enum VariableAuthority { | ||
| /// Always use this account | ||
| Specified(AccountId), | ||
| /// Use the caller's authority | ||
| Caller, | ||
| } | ||
| ``` | ||
|
|
||
| ## Consideration: triggers are a part of transaction? | ||
|
|
||
| Currently, data triggers are a part of the original transaction, and if data trigger fails - the entire transaction | ||
| fails. | ||
|
|
||
| This seems to be useful in some cases, but is not compatible with the case when Data trigger has a pre-defined | ||
| authority: Alice then could fail Bob's transactions if she wants. However, it may also be desirable: an admin may want | ||
| to fail someone's transaction by their data trigger. | ||
|
Comment on lines
+104
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The control of whether someone’s transaction succeeds or fails should be centralized in the permission system.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? Permission system allows for fine-grained access to resources and actions. I am not sure I understand why it should be centralized. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key issue is that the entity subscribing to an event is not necessarily the legitimate administrator.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, this makes total sense to me. Of course, if we introduce "attached/detached" modes, it would be essential to make attaching a trigger also guarded by a permission. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Historically, the permission system only governed state read/write access.
|
||
|
|
||
| Here, I would propose a mixed approach too: specify whether the trigger is **attached or detached** from the original | ||
| transaction. This must come with some default-executor-permission like `CanAttachTriggersTo { AccountId }`, so that only | ||
| users with right permissions could attach their triggers, and by default, all triggers would act as an independent | ||
| transaction. | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| - Risk of privilege escalation if trigger logic is careless | ||
| - Need to treat triggers with pre-defined authority as “contracts” that must validate caller input | ||
| - Possible DoS if malicious actors span triggers with invalid inputs | ||
|
Comment on lines
+118
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, fixing the authority of a data trigger means forcing a complete simulation of the event source at registration time, which can sometimes be difficult and, in my view, opens the door to unfortunate accidents.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could you elaborate, please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixing the authority of a data trigger effectively means that causing the trigger event is equivalent to acquiring that authority. As a result, the trigger author must design the subscription with extreme care to ensure that no unintended path can fire it. |
||
|
|
||
| However, some of these could be mitigated by permission system. E.g. currently default executor has | ||
| `CanExecuteTrigger { TriggerId }` permissions which could be used to have "global whitelisting". | ||
|
|
||
| ## Conclusion | ||
|
|
||
| So, I would personally prefer **Option 2** - the mixed approach. However, this requires some data model restructuring to | ||
| reflect that not every trigger can have a variable authority. | ||
|
|
||
| **Option 1** also seems better for me than what it is right now, because it enables privileged automation and simplifies | ||
| reasoning. As it is simple to implement and does not interfere with future Option 2, I'd welcome targeting it | ||
| immediately for the convenience of the Hub Chain PoC. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my view, the only clearly useful programmable execution systems currently identified are the following three:
I am skeptical about providing other types of triggers. For details, see hyperledger-iroha/iroha#5512.