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

Remove microseconds from metadata API Signed.expires #1712

Conversation

ivanayov
Copy link
Collaborator

@ivanayov ivanayov commented Dec 7, 2021

This change removes microseconds from expiry in order to fit TUF
specification

Fixes #1678

  • The code follows the Code Style Guidelines
  • [n/a] Tests have been added for the bug fix or new feature
  • [n/a] Docs have been added for the bug fix or new feature

@coveralls
Copy link

coveralls commented Dec 7, 2021

Pull Request Test Coverage Report for Build 1584625344

  • 6 of 6 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.003%) to 97.579%

Totals Coverage Status
Change from base Build 1582933044: 0.003%
Covered Lines: 4097
Relevant Lines: 4182

💛 - Coveralls

@jku
Copy link
Member

jku commented Dec 8, 2021

We'll want think this through for all the different cases: deserialization, users calling the api , serialization... it's a little detail but could make life difficult in the future

  • I'd want to see docstrings for public API at least
  • is it correct that we accept non-spec-compliant dates on deserialize?
  • is it correct that we change the date to compliant on deserialize?

@ivanayov
Copy link
Collaborator Author

ivanayov commented Dec 8, 2021

@jku

is it correct that we accept non-spec-compliant dates on deserialize?

It think that spec-compliant should mean spec-compliant with no exception, so if metadata is not in the correct form, we should throw an error.

is it correct that we change the date to compliant on deserialize?

I think it's fine, when it comes to preventing a bug, coming not from the metadata, but from the implementation (and in this case - python specific) as we guarantee that it won't break the deserialisation. But we should guarantee that the metadata is correct at the same time.
May be I'd need to add a check, which is now missing.

@lukpueh
Copy link
Member

lukpueh commented Dec 8, 2021

I agree with @jku that we need to be careful here. This is what I think about the different cases:

  • deserialization:
    The deserializer shouldn't accept non-spec compliant metadata, regardless of its importance. If we start modifying the semantics of inputs during deserialization, we'll likely end up with determinism problems (goal: data == serialize(deserialize(data))

  • Object API:
    Ideally, we don't accept non-sepc compliant inputs here either. But in reality this is hard to enforce, because properties can be set in different ways. So I think it is easier and okay to only enforce spec compliance on the de/serialization boundaries.

  • Serialization:
    Under above assumptions, i.e. that we don't accept non-spec compliant metadata in deserialization, and that some properties that were modified during object life-time contain additional (unneeded) information, certain transformations should be fine, e.g. removing unnecessary microseconds. We need to find a balance between expected behaviour and usability.

@jku
Copy link
Member

jku commented Dec 8, 2021

I think I lean in the same direction (EDIT: I wrote this before seeing lukas' last message :) )

  • spec defines the date format exactly: we should only accept that in deserialization?
  • constructing a new object should probably accept microseconds and just remove them
  • deserialization and constructing new object currently use same code path (in the constructor)

so possibly

  • from_dict() should be strict, and should fail if format is not exactly as defined in spec
  • but the constructor should use set_expiry() as your PR does: so constructing new object does not fail on microseconds ...

I'm not 100% sure of this yet, but that's my thinking...

@jku jku changed the title Remove miscroseconds from metadata API Signed.expires Remove microseconds from metadata API Signed.expires Dec 8, 2021
@lukpueh
Copy link
Member

lukpueh commented Dec 8, 2021

I agree with the overall direction...

from_dict() should be strict, and should fail if format is not exactly as defined in spec

This makes sense, and we might need to revise existing data validation in from_dict/__init__-methods, because I saw that some is performed in one and some in the other.

but the constructor should use set_expiry() as your PR does: so constructing new object does not fail on microseconds

I guess we can do that, but we need to to be careful that there's no unexpected behavior, above all upon serialization, if the expires field is modified outside of the constructor

@jku
Copy link
Member

jku commented Dec 8, 2021

but we need to to be careful that there's no unexpected behavior, above all upon serialization, if the expires field is modified outside of the constructor

Yes, and should consider buggy code as well (obviously we'd never write bugs but just theoretically speaking): we should ensure we rather fail than persist metadata that is invalid. #1696 has some discussion on this

@ivanayov ivanayov force-pushed the no_microseconds_in_api_for_signed_expires branch from 00ac638 to ba59ae3 Compare December 9, 2021 19:05
@ivanayov
Copy link
Collaborator Author

ivanayov commented Dec 9, 2021

In the latest update I stick to:

  • __ini__() uses set_expiry(), so that we guarantee microseconds are not added on runtime
  • a format verification for "expires" in _common_fields_from_dict()
  • no format changes (which I pushed earlier) in _common_fields_to_dict()

Copy link
Collaborator

@kairoaraujo kairoaraujo left a comment

Choose a reason for hiding this comment

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

To test your code (lazily), I used the tests/metadata_api_tests/test_metadata_serialization.py and I added an invalid expire date entry to this data set

invalid_roots: utils.DataSet = {

        "invalid expire date": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \
            "expires": "2021-11-16T12:00:00.123456Z", "consistent_snapshot": false, \
            "keys": { \
                "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \
                "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \
            "roles": { \
                "root": {"keyids": ["keyid1"], "threshold": 1}, \
                "timestamp": {"keyids": ["keyid2"], "threshold": 1}, \
                "targets": {"keyids": ["keyid1"], "threshold": 1}, \
                "snapshot": {"keyids": ["keyid2"], "threshold": 1}} \
            }',

It gave me the expected raise:

  File "/Users/kdearaujo/devel/TUF/python-tuf/tuf/api/metadata.py", line 476, in _common_fields_from_dict
    raise exceptions.InvalidConfigurationError(
tuf.exceptions.InvalidConfigurationError: 
            Role root should have "expires" metadata of the format 
            "yyyy-mm-ddThh:mm:ssZ" but got 2021-11-16T12:00:00.123456Z instead

But I am not sure if the right to a place to add the test or even we want to test it there. I noticed that we don't test these custom exceptions in tests_metadata_serialization.py

Maybe some maintainer could comment :)

tuf/api/metadata.py Outdated Show resolved Hide resolved
@lukpueh
Copy link
Member

lukpueh commented Dec 10, 2021

Thanks for working on this @ivanayov. I think it's a fair approach for the first two use cases discussed above. We still need to figure out what to do for the 3rd use case, that is do we allow serialisation of invalid metadata. But maybe we don't have to do this here. Would you mind adding a test for what you have so far?

@ivanayov ivanayov force-pushed the no_microseconds_in_api_for_signed_expires branch from ba59ae3 to 6210b54 Compare December 10, 2021 14:45
@ivanayov
Copy link
Collaborator Author

@lukpueh @kairoaraujo Just pushed an update with latest comments.

@ivanayov ivanayov force-pushed the no_microseconds_in_api_for_signed_expires branch from 6210b54 to c3887a2 Compare December 10, 2021 14:59
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 left some comments, I think the core of this PR is the new API (set_expiry()) which allows both construction of new metadata and modifications to existing expiry to be done safely: I think this is the correct direction but new API needs to to be polished (include API documentation and so on).

The big question is, do we need to solve the related #1727 at the same time (at least on paper)?

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

jku commented Dec 15, 2021

We still need to figure out what to do for the 3rd use case, that is do we allow serialisation of invalid metadata

Martin opened #1696 about this relevant question in general: maybe we can continue there (the issue is big enough to maybe have a chat as well, to discuss some possible ways to deal with it)

@jku
Copy link
Member

jku commented Dec 15, 2021

oh a new idea: since one of the problems here is that our expiry is a datetime but most datetimes are not valid expiries (because microseconds), how about we hide the variable and make expiry a property (where the setter is Ivanas function) -- that would effectively prevent a user from accidentally setting signed.expiry to an invalid value: something like signed.expiry = utcnow() + relativedelta(day=3) would now work correctly because it would use the property setter.

@ivanayov ivanayov force-pushed the no_microseconds_in_api_for_signed_expires branch from c3887a2 to e3f2ce1 Compare December 15, 2021 20:52
This change removes microseconds from expiry in order to fit TUF
specification

Fixes theupdateframework#1678

Signed-off-by: Ivana Atanasova <iyovcheva@vmware.com>
@ivanayov ivanayov force-pushed the no_microseconds_in_api_for_signed_expires branch from e3f2ce1 to c5ace07 Compare December 15, 2021 20:56
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.

This feels like the correct solution, thank you.

  • API user just provides a datetime, we make sure it's valid (removing microseconds)
  • the Metadata object always matches the serialized output (since the microseconds are never stored in the object)
  • type "enforcement" is about as good as it gets with python (static checks will catch wrong types and we use the datetime API in the setter so even without static checks things will break on type mismatches)

Looks good to me, leaving open for @lukpueh to see as well

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.

Seconding Jussi's assessment. Thanks for the clean fix, @ivanayov!

@lukpueh lukpueh merged commit 1f3654f into theupdateframework:develop Dec 21, 2021
lukpueh added a commit to lukpueh/tuf that referenced this pull request Dec 22, 2021
Remove `bump_expiration()` method, which is unlikely to be used as
is, i.e.  bump to "current expiration date plus delta". A more
realistic use case is to bump to "now plus delta" (see theupdateframework#1727 for
details).

Moreover, bump_expiration can either way easily be replaced by a
one-liner expression using the 'datetime' module. A corresponding
code snippet is added to the `expires` property's docstring.  Note:
`expires` became a property with a millisec-removing setter (for
spec conformance) in  theupdateframework#1712, which further reduces the need for a
convenience bump_expiration method.

This patch also removes a related unit test and updates another
one.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
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.

metadata api: Signed.expires string representation should not contain microseconds
5 participants