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

Add support for succinct roles (TAP 15) #2010

Merged
merged 9 commits into from Jun 17, 2022

Conversation

MVrachev
Copy link
Collaborator

@MVrachev MVrachev commented May 20, 2022

Related to the previous pr: #1948 and issue #1909

This pr includes 11 breaking changes:

In Delegations class from tuf/api/metadata.py :

  1. roles argument is made optional
  2. unrecognized_fields argument becomes the 4-th rather than the 3-rd
    as it used to be

In Root class from tuf/api/metadata.py:

  1. The "role" and "key" arguments in "Root.add_key()" are in reverse
    order - "key" becomes first and "role" second.
  2. "Root.remove_key()" has been renamed to "Root.remove_key()"
  3. The "role" and "keyid" arguments in "Root.revoke_key()" are in
    reverse order - "keyid" becomes first and "role" second.

In Targets class from tuf/api/metadata.py:

  1. The "role" and "key" arguments in "Targets.add_key()" are in reverse
    order - "key" becomes first and "role" second
  2. "Targets.remove_key()" has been renamed to "Targets.revoke_key()"
  3. The "role" and "keyid" arguments in "Targets.revoke_key()" are in
    reverse order - "keyid" becomes first and "role" second.
  4. In both methods "Targets.add_key()" and "Targets.revoke_key()" the
    "role" argument becomes an optional with a default value of None

Those changes are made in an effort to make those two methods logical
for both cases when standard roles and succinct_roles are used.

Description of the changes being introduced by the pull request:

TAP 15 was created on June 23-rd 2020 and was last modified on July 6-th 2020.
Since then the TAP has been put to draft status meaning it needs a prototype implementation before it's accepted as a future specification change.
Given that this TAP underlines an efficient way of handling succinct hash bin delegations, meaning it would be really useful when python-tuf is integrated into Warehouse, it's logical that we should not only create a prototype but directly work to integrate it in python-tuf.

The outcome of the TAP 15 implementation is

  1. Support reading, updating, and saving target metadata files that are using succinct_roles.
  2. Support in ngclient for calculating the delegated role name on a given target path.
  3. An exemplary document inside examples/ showcasing how succinct_roles can be utilized in practice.

This pr covers points 1 and 2.
Point 3 will be done in another pr.

Please verify and check that the pull request fulfills the following
requirements
:

  • The code follows the Code Style Guidelines
  • Tests have been added for the bug fix or new feature
  • Docs have been added for the bug fix or new feature

@MVrachev
Copy link
Collaborator Author

I know the code is a lot, but a big chunk of it is testing.

@MVrachev MVrachev requested review from jku and lukpueh May 20, 2022 12:05
@coveralls
Copy link

coveralls commented May 20, 2022

Pull Request Test Coverage Report for Build 2514955908

  • 135 of 137 (98.54%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.3%) to 98.012%

Changes Missing Coverage Covered Lines Changed/Added Lines %
tuf/api/metadata.py 126 128 98.44%
Totals Coverage Status
Change from base Build 2507249080: -0.3%
Covered Lines: 1295
Relevant Lines: 1313

💛 - Coveralls

Copy link
Member

@jku jku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks like a good direction I think and code does not seem to have issues (bugs). I have left some individual code comments (no bugs or anything, just opinions). These are the concerns:

  • this adds a large amount of new API, 10 new methods or something like that: let's focus on whether all of that is actually used and needed, whether it needs to be public API and whether there are other options to adding the API (ie the add_key() case). I've left some comments
  • this is also an API change: I think it's one we want to do but let's take changes seriously

I've not checked if the test coverage is "complete": let's figure out the API size first.

tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tests/repository_simulator.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
@jku
Copy link
Member

jku commented May 31, 2022

Documenting some possible options WRT add_key() and remove_key():

  • as in PR: add new methods for succinct case (so users need to remember to call different Targets method depending on what the value of Targets.delegations.succinct_delegations is)
  • just document that role argument to the *_key() functions is unused whenever succinct delegations are used and make the existing functions work in succinct case
  • Modify *_key() API so that instead of self, role: str, key: Key the arguments are self, key: Key, role: Optional[str] = None

Copy link
Member

@lukpueh lukpueh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really great, @MVrachev. I left a few comments inline, but nothing big a deal.

I also have a few more naming suggestions for SuccinctRoles methods, to consolidate with the rest of the API...

get_bin_name --> get_role(_for_index)
get_all_bin_names --> get_roles
find_bin --> get_role_for_target
is_bin --> is delegated_role

... and for Delegations:

get_all_delegations -> get_roles_for_target

What do you think?

tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 2, 2022

get_bin_name --> get_role(_for_index)
get_all_bin_names --> get_roles
find_bin --> get_role_for_target
is_bin --> is delegated_role

I understand where you come from and why those changes make sense for you.
I am worried about using the word role inside the SuccinctRole method names and especially the phrase delegated role because I don't want to confuse our users with Delegations.roles or the DelegatedRole class instances.
That's why I opt-in for a more concrete naming scheme including bin instead of role.
Also, after your docstring suggestion, it seems a lot clearer what a bin actually is.

After I update the pr with your docstring suggestions would you have a look again and tell me if you think those changes are still worth it?

@MVrachev MVrachev force-pushed the tap15-final-design branch 2 times, most recently from d11f86b to 4a8c9de Compare June 2, 2022 17:44
@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 2, 2022

I have addressed all of your comments @jku and @lukpueh.
For Targets.*_key() methods I decided to change the order of the arguments and make role an optional.
One downside in this (besides it's a breaking change) is that those methods no longer have the same signature as Root.*_key() methods.

Also, I have mentioned in the commit messages about the breaking API changes and also updated the pr description with information about them.

The pr is smaller by a big margin and it's ready for another review.

@MVrachev MVrachev requested review from lukpueh and jku June 2, 2022 17:51
@lukpueh
Copy link
Member

lukpueh commented Jun 3, 2022

get_bin_name --> get_role(_for_index)
get_all_bin_names --> get_roles
find_bin --> get_role_for_target
is_bin --> is delegated_role

I understand where you come from and why those changes make sense for you. I am worried about using the word role inside the SuccinctRole method names and especially the phrase delegated role because I don't want to confuse our users with Delegations.roles or the DelegatedRole class instances. That's why I opt-in for a more concrete naming scheme including bin instead of role. Also, after your docstring suggestion, it seems a lot clearer what a bin actually is.

After I update the pr with your docstring suggestions would you have a look again and tell me if you think those changes are still worth it?

My idea was to point out exactly that connection, i.e. that a bin is a delegated targets role. But I won't insist. I'm fine with the names you picked as well.

What about my other suggestion get_all_delegations -> get_roles_for_target? The idea behind this one is that the function does not really return delegations but delegated roles (or actually their names, and their terminating property)

I'm a bit pedantic about this, because roles and delegations are confused so often.

@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 3, 2022

My idea was to point out exactly that connection, i.e. that a bin is a delegated targets role. But I won't insist. I'm fine with the names you picked as well.

What about my other suggestion get_all_delegations -> get_roles_for_target? The idea behind this one is that the function does not really return delegations but delegated roles (or actually their names, and their terminating property)

I'm a bit pedantic about this, because roles and delegations are confused so often.

Hmm... more and more when I think about it probably you have some point here.
I keep considering the SuccinctRoles instances as one delegated role which is not what is implied even from the class title... SuccinctRoles instance just represents a group of delegated roles instead of using a dictionary. Probably mentally it's easier to think SuccinctRoles is just one role as keyids and threshold is shared through all delegated roles represented by that instance.
Probably it will be useful in using the same delegated role phrase for SuccinctRoles roles as well instead of promoting a new bin term.

Will rename the methods as you have suggested here:

get_all_bin_names --> get_roles
find_bin --> get_role_for_target
is_bin --> is delegated_role

About get_all_delegations -> get_roles_for_target.
Yea, I missed it honestly as there were so many renames. I agree with that suggestion. Will do it.

@MVrachev MVrachev force-pushed the tap15-final-design branch 2 times, most recently from cda9c08 to 391c8a3 Compare June 3, 2022 16:46
@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 3, 2022

I followed @lukpueh advice and did the following renames:

  • get_all_bin_names --> get_roles
  • find_bin --> get_role_for_target
  • is_bin --> is delegated_role
  • get_all_delegations -> get_roles_for_target

EDIT: I forced pushed once more to fix the commit message mentioning the old methods name.

@jku
Copy link
Member

jku commented Jun 6, 2022

Looks good. The only thing that sticks out to me is the key API.

- def add_key(self, role: str, key: Key) -> None:
+ def add_key(self, key: Key, role: Optional[str] = None) -> None:

this looks ok to me -- it's easy to notice with static checks and we could even add a specific error message if isinstance(role, Key):...

- def remove_key(self, role: str, keyid: str) -> None:
+ def remove_key(self, keyid: str, role: Optional[str] = None) -> None:

... but I hadn't thought about this one all the way through: both argument types are str here. It really is the most annoying kind of API change, very easy to mess up.

@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 6, 2022

Looks good. The only thing that sticks out to me is the key API.

- def add_key(self, role: str, key: Key) -> None:
+ def add_key(self, key: Key, role: Optional[str] = None) -> None:

this looks ok to me -- it's easy to notice with static checks and we could even add a specific error message if isinstance(role, Key):...

Maybe this could be useful. Preventing users from mistaking the arguments order.

- def remove_key(self, role: str, keyid: str) -> None:
+ def remove_key(self, keyid: str, role: Optional[str] = None) -> None:

... but I hadn't thought about this one all the way through: both argument types are str here. It really is the most annoying kind of API change, very easy to mess up.

Yea... if only the types were different. I was thinking if we can use the insider knowledge we have about keyid.
For example that it will have a length longer than X number of symbols, but then what stops a user from having very long delegated role names? Nothing...

I hope we can make this API change as still there aren't so many users, especially in the 1.0.0 version of python-tuf and calls like targets.add_key(None, key_obj) and ``targets.add_key(None, keyid)` are not really nice...

@jku
Copy link
Member

jku commented Jun 6, 2022

I was thinking if we can use the insider knowledge we have about keyid.

I don't think we have any insider knowledge about it -- theoretically it's a hash but

  • we've explicitly avoided using that in python-tuf as it seems like a mistake: it should be just a unique id
  • even if we know keyid is a hash, nothing prevents rolenames from also being hashes (in fact I could imagine it would be a good way to handle rolenames in some cases)

@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 6, 2022

@lukpueh any thoughts?

@jku
Copy link
Member

jku commented Jun 7, 2022

I think there's still several possibilities here that wouldn't require surprisingly breaking applications:

  • keep current API (maybe allow None for rolename), just document it well enough. I think this is completely within reason. It's a wart in the API but that happens.
  • @overload magic so that we could have both remove_key(rolename, keyid) and remove_key(keyid) -- the code will not be very pretty but should work.
  • just rename the API: remove_key() stays as is for a few releases and we have a new method with different API -- the lovely linux naming tradition would give us remove_key2() 😬
  • probably something else still?

@lukpueh
Copy link
Member

lukpueh commented Jun 7, 2022

I think there's still several possibilities here that wouldn't require surprisingly breaking applications:

  • keep current API (maybe allow None for rolename), just document it well enough. I think this is completely within reason. It's a wart in the API but that happens.

This would be an okay solution.

  • @overload magic so that we could have both remove_key(rolename, keyid) and remove_key(keyid) -- the code will not be very pretty but should work.

I'm not sure about this one. It is definitely unidiomatic (~> surprising). And the code to do this would likely stain the otherwise elegant metadata module.

  • just rename the API: remove_key() stays as is for a few releases and we have a new method with different API -- the lovely linux naming tradition would give us remove_key2() 😬

I like this suggestion best, at least the first part. But I'd rather not have a remove_key2 in our API. And I also don't think we need to support the old method for a few releases, if we make this release a breaking one.

So what about replacing remove_key right away with a name that also makes sense and that we like. Some ideas: revoke_key, deauthorize, unauthorize, something with delegation in its name, etc...

Some consistency considerations:

  • Will the new name work as counterpart to add_key or do we need to rename that too?
  • What about Root's {add, remove}_key methods. Should we adopt a rename there too? And the argument order? This would make it a bigger cut, but API consistency seems important.
  • probably something else still?

Couldn't think of anything else.

@jku
Copy link
Member

jku commented Jun 7, 2022

Yes, agreed that providing the old deprecated method isn't terribly important if using the new method is basically a copy-paste fix

Copy link
Member

@lukpueh lukpueh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great tests, @MVrachev. I just reviewed:

docs/api/tuf.api.metadata.supporting.rst
docs/repository-library-design.md
examples/repo_example/hashed_bin_delegation.py
tests/test_api.py
tests/test_metadata_eq_.py

I'll review the rest tomorrow.

examples/repo_example/hashed_bin_delegation.py Outdated Show resolved Hide resolved
tests/test_api.py Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_metadata_eq_.py Outdated Show resolved Hide resolved
@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 7, 2022

I think revoke_key and the counterpart add_key sounds okay.

If we decide to do a rename to remove_key then does this mean that we are okay with its counterpart - add_key and its changes after I add a check from the type if isinstance(role, Key) as Jussi has suggested preventing mistakes?

@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 13, 2022

Yes, you are right that probably some summary of my changes will be good.
All of them were addressing Lukas comments, so those comments can give you some idea what I changed + I renamed the Root/Targets.remove_key() to Root/Targets.revoke_key() as we had discussed.

What I did ca be summarized like this:

  1. Removed the zero_padding test in favor of a more complicated test_get_roles_in_succinct_roles test.
  2. Moved a comment about bin names inside test_is_delegated_role_in_succinct_roles a few lines below.
  3. Updated the test about __eq__ implementation for SuccinctRoles after the merge of Tests: simplify and shorten test_metadata_eq_.py #2017.
  4. Some typo fixes.
  5. Renamed RepositorySimulator. add_succinct_roles_to_delegations() to RepositorySimulator.add_succinct_roles() and added a better docstring.
  6. Added a helper RepositorySimulator._get_delegator() which removes code dublication in add_target, add_delegation and add_succinct_roles in RepositorySimulator.
  7. Updated test_succinct_roles_graph_traversal with many suggestions by @lukas from this comment Add support for succinct roles (TAP 15) #2010 (comment)
  8. Renamed Root/Targets.remove_key() to Root/Targets.revoke_key() plus updated the pr description and commit description mentioning those API changes.

tuf/api/metadata.py Outdated Show resolved Hide resolved
@MVrachev MVrachev force-pushed the tap15-final-design branch 2 times, most recently from a4890dc to c27c2c2 Compare June 14, 2022 10:24
@MVrachev
Copy link
Collaborator Author

I have updated the pr by making Root.*_key() API methods with consistent naming and argument order as the ones in Targets.*_key().

Copy link
Member

@jku jku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm happy with this -- left some nitpicks but they're not critical. The main question I have is documentation:

  • The documentation currently does not make it clear that this feature is not a TUF specification feature: maybe it should?

As TODO items (that might exist already):

  • we should improve the succinct delegation docs and update the example script -- this is something we should do before we release this
  • for completeness, actual download tests should contain succinct delegations: it may make sense to refactor the download tests at that point

tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
Add zero padding to bin names inside SuccinctRoles.
Zero padding ensures that the bin names always have the same length.

This characteristic is implied in the example given by TAP 15 where
the third bin is named "alice.hbd-03". For context read TAP 15:
https://github.com/theupdateframework/taps/blob/master/tap15.md

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
@MVrachev
Copy link
Collaborator Author

Updated the pr with the following changes:

  • added a note that SuccinctRoles is not supported by the spec
  • addressed all other changes mentioned by @jku

tuf/api/metadata.py Outdated Show resolved Hide resolved
@MVrachev
Copy link
Collaborator Author

Accepted @jku suggested how to document that succinct_roles is not in the spec yet and moved that documentation next to the succinct_roles argument description in Delegations.

@jku
Copy link
Member

jku commented Jun 16, 2022

Yes, looks good to me. Thanks everyone! The rough plan here is:

  • Merge this PR: succinct delegations are then usable on "develop" branch
  • Get any remaining polish done on TAP-15 and move it to "accepted" state Move TAP 15 to Accepted taps#148
  • release python-tuf with TAP-15 support (likely means v2.0.0)
  • Maybe TAP-15 gets integrated into spec at some point?

@lukpueh, would you like to do a final review of the PR as the system thinks your review is pending? Checking "merge without waiting for requirements to be met" makes me feel criminal...

@MVrachev would you mind having a last look to identify any issues we may need to file for future work?

  • Updating the example code is one (if it does not exist already)
  • Maybe also one for better parameterised target download tests that use succinct bins and normal bins -- there is a PR for the latter but I think we can do better

@MVrachev
Copy link
Collaborator Author

MVrachev commented Jun 16, 2022

@MVrachev would you mind having a last look to identify any issues we may need to file for future work?

  • Updating the example code is one (if it does not exist already)
  • Maybe also one for better parameterised target download tests that use succinct bins and normal bins -- there is a PR for the latter but I think we can do better

We have an issue #1909 where we keep track of that stuff.
Only three items are left on the list and I am not sure there is a need for separate issues.

Copy link
Member

@lukpueh lukpueh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 💯

tests/test_updater_delegation_graphs.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
Add two helper methods in SuccinctRoles.
Those methods proved useful in the testing code, but I believe they have
a potential value for production code as well.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Clarify explicitly that exactly one of "paths" and "path_hash_prefixes"
must be set inside DelegatedRole.
Also simplify the check for "paths" and "path_hash_prefixes".
Finally, add a test case inside the "test_metadata_serialization.py"
test file about wrong keyids type for "Role" serialization.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
This commit contains 2 API changes in "Delegations" class from
tuf/api/metadata.py:
1. roles argment is made optional
2. unrecognized_fields argument becomes the 4-th rather than the 3-rd
as it used to be

In this commit, I add support for succinct_roles roles inside
Delegations class. This change is related to TAP 15 proposal.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Here is the list of all breaking API changes:
1) The "role" and "key" arguments in "Root.add_key()" are in reverse
order - "key" becomes first and "role" second.
2) "Root.remove_key()" has been renamed to "Root.revoke_key()".
3) The "role" and "keyid" arguments in "Root.revoke_key()" are in
reverse order - "keyid" becomes first and "role" second.
4) The "role" and "key" arguments in "Targets.add_key()" are in reverse
order - "key" becomes first and "role" second.
5) "Targets.remove_key()" has been renamed to "Targets.revoke_key()".
6) The "role" and "keyid" arguments in "Targets.revoke_key()" are in
reverse order - "keyid" becomes first and "role" second.
7) In both methods "Targets.add_key()" and "Targets.revoke_key()" the
"role" argument becomes an optional with a default value of None.

Those changes are made in an effort to make those methods logical
for both cases when standard roles and succinct_roles are used.
The "Root" API change was done in order to preserve naming and argument
order consistency with "Targets" API.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Add support for Targets using delegation with succinct_roles.
For that purpose, we needed a method that can add succinct_roles
information with its all corresponding bins to the target metadata
and self.md_delegates attribute in RepositorySimulator.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Test traversing the delegation tree when there is a Targets using a
delegation with succinct roles.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
@lukpueh lukpueh merged commit f2609ab into theupdateframework:develop Jun 17, 2022
@MVrachev MVrachev deleted the tap15-final-design branch June 17, 2022 13:16
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

Successfully merging this pull request may close these issues.

None yet

4 participants