Commit graph

820 commits

Author SHA1 Message Date
Lukas Puehringer
f63dce6ddd Refactor metadata constructors and add factory
This commit better separates the Metadata class model from the
Metadata wireline format, by tailoring the constructors
towards class-based parameters and adding an additional
factory classmethod that creates Metadata objects based on the
wireline json/dictionary metadata representation. (pythonic
way of constructor overloading).

This 'from_dict' factory method recurses into the 'from_dict'
methods of each contained complex field/attribute that is also
represented by a class. Currently 'signed' is the only such
attribute.

This commit further:
- Changes optional constructor keyword arguments to mandatory
positional arguments: Reduces code and simplifies usage by
restricting it. For now, users are unlikely to call
constructor directly anyway, but the 'from_dict' factory (or
its 'from_json_file' wrapper) instead.

- Removes Signed.__expiration (datetime) vs. Signed.expires
(datestring) dichotomy: Keeping only one representation of the
same attribute in memory makes the interface simpler and less
ambiguous. We choose the datetime object, because it is more
convenient to modify. Transformation from and to the string
format required by the tuf wireline format is performed in the
corresponding metadata de/serialization methods, i.e.
('to_dict' and 'from_dict').

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
f738ea0273 Rename tuf metadata interface methods
Consistenly rename de/serialization interface methods, using
a 'from_' and 'to_' prefix.

read_from_json -> from_json_file
write_to_json  -> to_json_file
as_json        -> to_json
as_dict        -> to_dict
signed_bytes   -> to_canonical_bytes

The latter is also changed from a property to a method for
consistency with the other serialization methods.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
e61ae1bea3 Remove Signed.read_from_json metadata method
Remove metadata factory on Signed class, for the sake of API
simplicity/non-ambiguity, i.e. it's enough to have one
way of loading any Metadata, that is:
Metadata.read_from_json

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
21de660b66 Remove comments and unify quotes in api tests
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
08bdc171e4 Add simple sign + verify Metadata methods (+tests)
Add simple methods to create or verify signatures of the
canonical_signed property of a Metadata object.

See corresponding docstrings for behavior and design
considerations.

The commit also adds tests and updates the test setup to load
some test keys into memory.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:19 +02:00
Lukas Puehringer
5cc73353fa Add metadata model class and method docstrings
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:09:22 +02:00
Lukas Puehringer
088e94055f Replace _get_written_metadata with as_json method.
Add simple as_json Metadata method and use it instead of repository
lib's internal _get_written_metadata function in write_to_json.

This commit further adds code documentation and the possibility to
write compact json by excluding whitespace to write_to_json, and
also removes a call to the sign method from write_to_json.

The commit also adds tests.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:10 +02:00
Lukas Puehringer
e997097d1c Add generic Metadata.read_from_json class method
Add generic read from json class method that returns a Metadata
object with a signed field that contains the appropriate Signed
subclass, based on the signed._type field of the read metadata.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:10 +02:00
Lukas Puehringer
b1dd3d6787 Skip api tests on Python < 3.6
The new metadata module uses constructs that are only available
on Python >= 3.6 (typing, f-format strings, etc.).

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:01 +02:00
Lukas Puehringer
17f08ad200 Add simple TUF role metadata model (WIP)
Add metadata module with container classes for TUF role metadata, including
methods to read/serialize/write from and to JSON, perform TUF-compliant
metadata updates, and create and verify signatures.

The 'Metadata' class provides a container for inner TUF metadata objects (Root,
Timestamp, Snapshot, Targets) (i.e. OOP composition)

The 'Signed' class provides a base class to aggregate common attributes (i.e.
version, expires, spec_version) of the inner metadata classes. (i.e. OOP
inheritance). The name of the class also aligns with the 'signed' field of
the outer metadata container.

Based on prior observations in TUF's sister project in-toto, this architecture
seems to well represent the metadata model as it is defined in the
specification (see in-toto/in-toto#98 and in-toto/in-toto#142 for related
discussions).

This commits also adds tests.

**TODO: See doc header TODO list**

**Additional design considerations**
(also in regards to prior sketches of this module)

 - Aims at simplicity, brevity and recognizability of the wireline metadata
   format.

 - All attributes that correspond to fields in TUF JSON metadata are public.
   There doesn't seem to be a good reason to protect them with leading
   underscores and use setters/getters instead, it just adds more code, and
   impedes recognizability of the wireline metadata format.

 - Although, it might be convenient to have short-cuts on the Metadata class
   that point to methods and attributes that are common to all subclasses of
   the contained Signed class (e.g. Metadata.version instead of
   Metadata.signed.version, etc.), this also conflicts with goal of
   recognizability of the wireline metadata. Thus we won't add such short-cuts
   for now. See:
   https://github.com/theupdateframework/tuf/pull/1060#discussion_r452906629

 - Signing keys and a 'consistent_snapshot' boolean are not on the targets
   metadata class. They are a better fit for management code. See:
   https://github.com/theupdateframework/tuf/pull/1060#issuecomment-660056376,
   and #660.

 - Does not use sslib schema checks (see TODO notes about validation in
   doc header)

 - Does not use existing tuf utils, such as make_metadata_fileinfo,
   build_dict_conforming_to_schema, if it is easy and more explicit to
   just re-implement the desired behavior on the metadata classes.

 - All datetime's are treated as UTC. Since timezone info is not captured in
   the wireline metadata format it should not be captured in the internal
   representation either.

 - Does not use 3rd-party dateutil package, in order to minimize dependency
   footprint, which is especially important for update clients which often have
   to vendor their dependencies.
   However, compatibility between the more advanced dateutil.relativedelta (e.g
   handles leap years automatically) and timedelta is tested.

 - Uses PEP8 indentation (4 space) and Google-style doc string instead of
   sslab-style. See
   https://github.com/secure-systems-lab/code-style-guidelines/issues/20

 - Does not support Python =< 3.5

Co-authored-by: Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com>
Co-authored-by: Joshua Lock <jlock@vmware.com>
Co-authored-by: Teodora Sechkova <tsechkova@vmware.com>
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-08-20 12:14:40 +02:00
Teodora Sechkova
2553dff276
Update test_load_repository
Extend test_load_repository to check if targets file info is loaded
correctly.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-06-09 16:48:53 +03:00
Joshua Lock
5d40ffa3c4
Merge pull request #1034 from joshuagl/joshuagl/abstract-files-fixes
Fix and better test abstract files and directories support
2020-06-05 13:40:21 +01:00
Jesús Castro
f4121e8f75
Remove unused imports
Those imports are marked as a non used libraries.

Signed-off-by: Jesús Castro <x51v4n@gmail.com>
2020-06-04 19:18:33 -05:00
Joshua Lock
5e5c598769 Support abstract storage for timestamp metadata
This was erroneously absent in PR 1024, which added support for abstract
files and directories. Resolve by adding a storage_backend argument to
generate_timestamp_metadata() and using it so that the fileinfo (hashes
and length) for the snapshot file can be generated for a snapshot
metadata file on any supported storage.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-06-03 14:16:47 +01:00
Joshua Lock
d9ec10e894 Test abstract storage backend support
Add a class implementing StorageBackendInterface for testhing which
mutates filenames on put()/get(), such that trying to read the expected
file paths for TUF metadata from the local filesystem doesn't find the
files.

Use this class when creating a repository and writing metadata to test
abstract files and directories support for metadata writing.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-06-03 14:16:47 +01:00
William Woodruff
1e532e825a
tests: Fill in more returned role name use
Signed-off-by: William Woodruff <william@trailofbits.com>
2020-06-01 14:22:29 -04:00
William Woodruff
4327a980cd
tests: Use newly returned role name
Signed-off-by: William Woodruff <william@trailofbits.com>
2020-06-01 14:01:46 -04:00
Joshua Lock
4e7b7b40ea Allow generating targets metadata for non-local storage
Utilise the abstract files and directories support to enable generating
targets metadata for files which aren't necessarily locally accessible,
rather than requiring that metadata for non-local files be provided via
existing fileinfo structures.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-05-19 22:36:17 +01:00
Joshua Lock
a187377533 Make absence of fundamental roles fatal
The specification lists four fundamental roles: root, targets, snapshot
and timestamp. Loading a repository where those roles are not present
should not be supported, therefore convert debug messages on the absence
of metadata files for these fundamental roles into a RepositoryError
exception.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-05-19 22:36:17 +01:00
Joshua Lock
0c0aaa97eb Port to new securesystemslib w abstract filesystem
Switch to using the new abstract files and directories support in
securesystemslib by taking an object which implements
securesystemslib.storage.StorageBackendInterface in the Repository
constructor, passed in by tuf.repository_tool.create_new_repository() and
tuf.repository_tool.load_repository()

The Updater class in tuf.client.updater does not specify a storage backend
and instead allows the functions in securesystemslib to perform the
default action of instantiating a LocalFilesystemBackend, that is the
updater does not currently support abstract filesystem backends and always
defaults to using local storage.

Finally we drop support for tuf.settings.CONSISTENT_METHOD as it's not as
clear how different copying modes should work when the details of the
underlying storage are abstracted away.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-05-12 22:16:50 +01:00
Joshua Lock
4487a98020 Remove redundant test logic
Support for compressed files was removed in tuf v0.10.x leaving behind
some vestiges like the test logic in test_repository_lib, which is
duplicated below and carries a redundant comment, and setting compression
on in generate_project_data.py

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-05-12 22:16:38 +01:00
Martin Vrachev
c7f878b2dc Remove six.PY2 and platform checks and add warning
After a discussion with Joshua Lock, we agreed that for
Windows users it would be good to provide the option to use
SimpleHTTPRequestHandler, but still leave a warning about it,
knowing that this caused an error before.
See: 7dbb30ae10

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2020-05-07 18:06:09 +03:00
Martin Vrachev
b42ca297e5 Remove PIPE arg and make QuiteHandler the default
Passing a pipe to the subprocess, but not reading from it
conceals helpful error messages.
As the code redirects all of the stderr from the subprocess
to nowhere, the error output of the process is never read.
If we remove the PIPEs from the tests we should see some
error messages on the console/logger that can
help us understand what went wrong.

On another hand, when we stop passing stderr=subprocess.PIPE arg
to the subprocess.Popen function call there are a lot of
HTTP messages together with the helpful error messages.
One decision is to make QuietHTTPRequestHandler
the default. That way we receive the helpful error messages
without the HTTP messages.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2020-05-07 15:53:01 +03:00
Martin Vrachev
dae1a1ba66 Fix [Errno 111] Connection refused
Fixes issue: https://github.com/theupdateframework/tuf/issues/1010

When running the tests this error appears
"[Errno 111] Connection refused". After some digging Lukas
found another error "No module named tests.simple_server"
which is the root case for error 111.

With the help of Joshua Lock, we found out that the simple_server.py
was not being found because subprocess.Popen was being passed
a cwd kwarg which moved the current working directory
away from tuf/tests.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2020-05-07 15:35:33 +03:00
Teodora Sechkova
f035cf4e34
Replace os.sep with '/' in _check_path()
Use a hard-coded unix separator ('/') so that an
exception is also raised for paths starting with '/'
when executing on Windows systems.

Update test_check_path to explicitly test invalid paths
starting with Windows style separator.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-04-08 18:58:27 +03:00
Teodora Sechkova
a71b3c2b67
Update TUTORIAL and test_tutorial
Improve the coding style in TUTORIAL in the case
where absolute path to a file is needed to perform file system
access and at the same time is rejected by Targets methods.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-04-08 18:58:27 +03:00
Teodora Sechkova
57b40518c8
Update test_delegate_hashed_bins()
Test is updated to include checks for incorrect target paths.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-04-08 18:58:26 +03:00
Teodora Sechkova
77f5f730f3
Add and update tests for _check_path() method
- add a test for _check_path() method of Targets class.
- update all tests calling _check_path() respectively
- update test_tutorial

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-04-08 18:58:26 +03:00
Joshua Lock
c8174f5a9f Use consistent default value for num of hashed bins
delegate_hashed_bins() has a number_of_bins parameter which defaults to
1024. add_target_to_bin() and remove_target_from_bin() both have a
number_of_bins parameter with no default. This means that in the
(somewhat) unlikely case that someone is using the default
number_of_bins when creating hashed bins they will need know what that
default value is and pass it to add_target_to_bin() and
remove_target_from_bin().

In order to be consistent and simpler to use define the default number
of bins as a module level constant and use it as the default value for
the number_of_bins argument for each of:
* delegate_hashed_bins()
* add_target_to_bin()
* remove_target_from_bin()

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-02 21:26:24 +01:00
Joshua Lock
62e4364c59 Add a test for add_target_to_bin that adds a fileinfo
Add some additional checks to test_add_target_to_bin to ensure the code
to add a target passing a fileinfo is tested.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:10:58 +01:00
Joshua Lock
694da236ba Test at least one delegated bin has path_hash_prefixes
When testing delegate_hashed_bins to ensure that hash_path_prefixes
map to the generated name of the bin, also check to ensure that at least
one of the delegations contains one or more path_hash_prefixes.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:09:27 +01:00
Joshua Lock
01498049da Add test for repo without access to target files
Test the newly added functionality to:
* add a target to the repository without access to the target file on disk
* write targets metadata without access to target files on disk, by using
  the existing fileinfo data from the roledb

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:09:27 +01:00
Joshua Lock
085c5480a7 Only call logger once for hashed bin info
Merge the logger calls reporting information about the hashed bin
delegations into a single logger.info() call to ensure the messages
will be grouped together even when integrated into a logging system
with multiple parallel sources.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:09:27 +01:00
Joshua Lock
6c69daefa2 Enable passing a fileinfo to add_target[_to_bin]()
Add an additional optional parameter to add_target() and
add_target_to_bin() which is a fileinfo object matching
tuf.formats.FILEINFO_OBJECT

This parameter and the custom parameter are mutually exclusive and
thus cannot be passed at the same time.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:09:18 +01:00
Joshua Lock
095fd40dcb Do not check target file exists in add_target[s]()
The file isn't strictly needed on-disk at the time add_target() and
add_targets() are called and this duplicates the check for the file's
presence in write[_all]()

By removing this check we allow extra versatility in adding targets.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:06:50 +01:00
Joshua Lock
ab788e306a Refactor add_target_to_bin & remove_target_from_bin
Vastly simplify the implementation, using the _get_hash() and
_find_bin_for_hash() helpers added in earlier commits.

Furthermore, enable passing of the custom parameter to
add_target_to_bin() to better match add_target()

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-04-01 12:06:50 +01:00
Joshua Lock
40d1dcfa6c Add _find_bin_for_hash() helper to repository_tool
Add a helper function to determine the name of a bin that a hashed
targetfile will be delegated to.

Based sketches by Lukas Puehringer in issues #994 & #995

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-30 19:03:52 +01:00
Joshua Lock
e2407a1f66 Simplify delegated hash bin tests
As we are adding and removing items from the hashed bins and checking
for their presence/absence it's simplest if we being with the hashed
bins initially empty.

If we pass a list of targets when we call delgate_hashed_bins() the
delegated roles have an initial set of targets delegated to them,
which complicates testing of adding then removing a target to a
delegated bin.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-26 11:10:56 +00:00
Joshua Lock
790f21fefe Test to ensure delegated bin names match prefixes
Add test to ensure delegated bin names are consistent with the hash
prefixes that are delegated to the role.

This is an implicit assumption of the current implementation, the
testing of which will enable us to modify the code with greater
confidence.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-26 11:10:50 +00:00
lukpueh
1cf085a360
Merge pull request #988 from joshuagl/joshuagl/issue-933
Remove root from snapshot
2020-03-11 14:34:09 +01:00
lukpueh
256aef8695
Merge pull request #989 from joshuagl/logger
Use __name__ for loggers, per convention
2020-03-11 14:33:35 +01:00
Joshua Lock
3720b2358e Re-generate repository and client test metadata
Re-generate metadata to adopt the change that root.json is no longer
listed in snapshot.json

```
 # Remove repository and client data
cd tests/repository_data && rm -rf repository client
 # Generate metadata
python generate.py
 # Duplicate metadata files
cp -r client/test_repository1 client/test_repository2
 # Recover non-signed file
git checkout client/map.json
```

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-11 11:35:37 +00:00
Joshua Lock
4bd9b5ef6b Improve determinism of test repository generator
One of the created target files has its file permissions encoded in the
targets metadata via the custom attribute of the add_target() function.
On Linux-based OS the umask value of the environment the script is run
in can result in different octal permissions for the created file, i.e.
on Fedora the default umask is 0002 (default permissions 664) whereas
on Debian/Ubuntu the default umask is 0022 (default permissions 644).

Explicitly chown 'file1' to octal permissions 644 so that the generated
data has the same custom attributes for targets regardless of which
Linux host they are generated on.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-11 11:35:37 +00:00
Joshua Lock
a134db0a43 Update test repository data generator
* Fix the path referenced in the Purpose
* Change add_target() calls to pass file paths relative to targets dir

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-11 11:35:37 +00:00
Joshua Lock
8f13fe5add Update tests for removal of root.json from snapshot.json
Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-11 11:15:06 +00:00
Joshua Lock
292b18926b Use __name__ for loggers, per convention
Replace hard-coded logger names with __name__. For the most part this just uses
the standard conventions to create the same logger hierarchy as existed before.
The only real difference is that loggers created for printing during tests are
no longer part of the 'tuf' hierarchy.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-03-03 10:36:39 +00:00
Lukas Puehringer
842f843210 Remove duplicate testing simple_server.py
tests/simple_server.py was copied to tuf/scripts/ to "make testing
easier" (cf84d3f51f), although with
the current test setup the original (and recently patched to fix an
Windows/Py2 test issue) test simple_server.py can be used just as
well.

This commit:
- removes tuf/scripts/simple_server.py
  Note: that version slightly differed from the original test
  server, probably due to demands by the linter that is only executed
  on the tuf core code and not on the tests. However, for the testing
  purposes of simple_server.py these changes (i.e., `SystemRandom()`,
  `if __name__ =='__main__':`) are not necessary.
- updates the tests that used tuf.scripts.simple_server to instead
  use tests.simple_server,
- updates setup.py to not install the simple_server module as
  script, when installing tuf, as it is only a testing script and
  not meant for end-user usage.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-02-25 14:55:36 +01:00
Lukas Puehringer
7dbb30ae10 Fix failing AppVeyor Python2.7 tests
Since #885 the tests in TestUpdater and TestKeyRevocation fail on
Appveyor Python 2.7 builds. After some live debugging, it turns out
that the tests fail due to the extra amount of http requests to
the simple http server (see tests/simple_server.py) that were
added in #885.

The simple server runs in a subprocess and is re-used for the
entire TestCase. After a certain amount of requests it becomes
unresponsive. Note that neither the subprocess exits (ps -W), nor
does the port get closed (netstat -a). It just doesn't serve the
request, making it time out and fail the test.

The following script can be used to reproduce the issue (run in
tests directory):

```python
import subprocess
import requests
import random

counter = 0

port = random.randint(30000, 45000)
command = ['python', 'simple_server.py', str(port)]
server_process = subprocess.Popen(command, stderr=subprocess.PIPE)
url = 'http://localhost:'+str(port) + '/'

sess = requests.Session()

try:
  while True:
    sess.get(url, timeout=3)
    counter +=1

finally:
  print(counter)
  server_process.kill()
```

It fails repeatedly on the 69th request, but only if
`stderr=subprocess.PIPE` is passed to Popen. Given that for each
request the simple server writes about ~60 characters to stderr,
e.g. ...
```
127.0.0.1 - - [24/Feb/2020 12:01:23] "GET / HTTP/1.1" 200 -
```
... it looks a lot like a full pipe buffer of size 4096. Note that the
`bufsize` argument to Popen does not change anything.

As a simple work around we silence the test server on
Windows/Python2 to not fill the buffer.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-02-24 16:32:26 +01:00
Lukas Puehringer
1a826cb4b0 Fix tests that use non-public sslib module
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-02-06 17:40:29 +01:00
Lukas Puehringer
67a3a7ab92 Update docs and comments in sig.py and test_sig.py
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-01-10 11:26:07 +01:00