mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Merge pull request #352 from vladimir-v-diaz/support_wildcards_in_trusted_paths
Support Unix shell-style wildcards in trusted paths
This commit is contained in:
commit
5f7dc52fa4
6 changed files with 56 additions and 59 deletions
|
|
@ -1087,7 +1087,7 @@ def test_delegate(self):
|
|||
rolename = 'tuf'
|
||||
list_of_targets = [target1_filepath, target2_filepath]
|
||||
threshold = 1
|
||||
restricted_paths = [self.targets_directory]
|
||||
restricted_paths = [self.targets_directory + '/*']
|
||||
path_hash_prefixes = ['e3a3', '8fae', 'd543']
|
||||
|
||||
self.targets_object.delegate(rolename, public_keys, list_of_targets,
|
||||
|
|
@ -1334,7 +1334,7 @@ def test_add_restricted_paths(self):
|
|||
|
||||
restricted_path = os.path.join(self.targets_directory, 'tuf_files')
|
||||
os.mkdir(restricted_path)
|
||||
restricted_paths = [restricted_path]
|
||||
restricted_paths = [restricted_path + '/*']
|
||||
self.targets_object.add_restricted_paths(restricted_paths, 'tuf')
|
||||
|
||||
# Retrieve 'targets_object' roleinfo, and verify the roleinfo contains
|
||||
|
|
@ -1344,7 +1344,7 @@ def test_add_restricted_paths(self):
|
|||
targets_object_roleinfo = tuf.roledb.get_roleinfo(self.targets_object.rolename)
|
||||
|
||||
delegated_role = targets_object_roleinfo['delegations']['roles'][0]
|
||||
self.assertEqual(['/tuf_files/'], delegated_role['paths'])
|
||||
self.assertEqual(['/tuf_files/*'], delegated_role['paths'])
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
|
|
|
|||
|
|
@ -969,7 +969,7 @@ def test_6_target(self):
|
|||
# the metadata store) so that it can be found later.
|
||||
filepath, fileinfo = target_files.popitem()
|
||||
target_files[filepath] = fileinfo
|
||||
|
||||
|
||||
target_fileinfo = self.repository_updater.target(filepath)
|
||||
self.assertTrue(tuf.formats.TARGETFILE_SCHEMA.matches(target_fileinfo))
|
||||
self.assertEqual(target_fileinfo['filepath'], filepath)
|
||||
|
|
@ -982,6 +982,7 @@ def test_6_target(self):
|
|||
# Test updater.target() backtracking behavior (enabled by default.)
|
||||
targets_directory = os.path.join(self.repository_directory, 'targets')
|
||||
foo_directory = os.path.join(targets_directory, 'foo')
|
||||
foo_pattern = os.path.join(foo_directory, 'foo*.tar.gz')
|
||||
os.makedirs(foo_directory)
|
||||
|
||||
foo_package = os.path.join(foo_directory, 'foo1.1.tar.gz')
|
||||
|
|
@ -993,10 +994,11 @@ def test_6_target(self):
|
|||
|
||||
|
||||
repository.targets.delegate('role2', [self.role_keys['targets']['public']],
|
||||
[], restricted_paths=[foo_directory])
|
||||
[], restricted_paths=[foo_pattern])
|
||||
|
||||
repository.targets.delegate('role3', [self.role_keys['targets']['public']],
|
||||
[foo_package], restricted_paths=[foo_directory])
|
||||
[foo_package], restricted_paths=[foo_pattern])
|
||||
|
||||
repository.targets.load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role2').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role3').load_signing_key(self.role_keys['targets']['private'])
|
||||
|
|
@ -1026,9 +1028,9 @@ def test_6_target(self):
|
|||
|
||||
# Ensure we delegate in trusted order (i.e., 'role2' has higher priority.)
|
||||
repository.targets.delegate('role2', [self.role_keys['targets']['public']],
|
||||
[], backtrack=False, restricted_paths=[foo_directory])
|
||||
[], backtrack=False, restricted_paths=[foo_pattern])
|
||||
repository.targets.delegate('role3', [self.role_keys['targets']['public']],
|
||||
[foo_package], restricted_paths=[foo_directory])
|
||||
[foo_package], restricted_paths=[foo_pattern])
|
||||
|
||||
repository.targets('role2').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role3').load_signing_key(self.role_keys['targets']['private'])
|
||||
|
|
|
|||
|
|
@ -506,7 +506,7 @@ def test_C4_ensure_all_targets_allowed(self):
|
|||
],
|
||||
"name": "targets/warehouse",
|
||||
"paths": [
|
||||
"/file1.txt", "/README.txt", '/warehouse/'
|
||||
"/file*.txt", "/README.txt", '/warehouse/*'
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
|
|
@ -540,7 +540,7 @@ def test_C4_ensure_all_targets_allowed(self):
|
|||
|
||||
# Test for target file that is not allowed by the parent role.
|
||||
self.assertRaises(tuf.ForbiddenTargetError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', ['file8.txt'], parent_delegations)
|
||||
'targets/warehouse', ['file1.zip'], parent_delegations)
|
||||
|
||||
self.assertRaises(tuf.ForbiddenTargetError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', ['file1.txt', 'bad-README.txt'],
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@
|
|||
import shutil
|
||||
import time
|
||||
import random
|
||||
import fnmatch
|
||||
|
||||
import tuf
|
||||
import tuf.conf
|
||||
|
|
@ -911,7 +912,7 @@ def verify_target_file(target_file_object):
|
|||
if self.consistent_snapshot:
|
||||
target_digest = random.choice(list(file_hashes.values()))
|
||||
dirname, basename = os.path.split(target_filepath)
|
||||
target_filepath = os.path.join(dirname, target_digest+'.'+basename)
|
||||
target_filepath = os.path.join(dirname, target_digest + '.' + basename)
|
||||
|
||||
return self._get_file(target_filepath, verify_target_file,
|
||||
'target', file_length, compression=None,
|
||||
|
|
@ -2598,9 +2599,8 @@ def target(self, target_filepath):
|
|||
|
||||
# Raise an exception if the target information could not be retrieved.
|
||||
if target is None:
|
||||
message = target_filepath + ' not found.'
|
||||
logger.error(target_filepath + ' not found.')
|
||||
raise tuf.UnknownTargetError(message)
|
||||
raise tuf.UnknownTargetError(target_filepath + ' not found.')
|
||||
|
||||
# Otherwise, return the found target.
|
||||
else:
|
||||
|
|
@ -2835,13 +2835,17 @@ def _visit_child_role(self, child_role, target_filepath, parent_delegations):
|
|||
|
||||
elif child_role_paths is not None:
|
||||
for child_role_path in child_role_paths:
|
||||
# A child role path may be a filepath or directory. The child
|
||||
# role 'child_role_name' is added if 'target_filepath' is located
|
||||
# under 'child_role_path'. Explicit filepaths are also added.
|
||||
prefix = os.path.commonprefix([target_filepath, child_role_path])
|
||||
if prefix == child_role_path:
|
||||
# A child role path may be an explicit path or pattern (Unix
|
||||
# shell-style wildcards). The child role 'child_role_name' is added if
|
||||
# 'target_filepath' is equal or matches 'child_role_path'. Explicit
|
||||
# filepaths are also added.
|
||||
if fnmatch.fnmatch(target_filepath, child_role_path):
|
||||
child_role_is_relevant = True
|
||||
|
||||
else:
|
||||
logger.debug('Target path' + repr(target_filepath) + ' does not'
|
||||
' match child role path ' + repr(child_role_path))
|
||||
|
||||
else:
|
||||
# 'role_name' should have been validated when it was downloaded.
|
||||
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
|
||||
|
|
|
|||
|
|
@ -1728,22 +1728,22 @@ def target_files(self):
|
|||
|
||||
|
||||
|
||||
def add_restricted_paths(self, list_of_directory_paths, child_rolename):
|
||||
def add_restricted_paths(self, restricted_paths, child_rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Add 'list_of_directory_paths' to the restricted paths of 'child_rolename'.
|
||||
Add 'restricted_paths' to the restricted paths of 'child_rolename'.
|
||||
The updater client verifies the target paths specified by child roles, and
|
||||
searches for targets by visiting these restricted paths. A child role may
|
||||
only provide targets specifically listed in the delegations field of the
|
||||
parent, or a target that falls under a restricted path.
|
||||
parent, or a target that matches a restricted path.
|
||||
|
||||
>>>
|
||||
>>>
|
||||
>>>
|
||||
|
||||
<Arguments>
|
||||
list_of_directory_paths:
|
||||
A list of directory paths 'child_rolename' should also be restricted to.
|
||||
restricted_paths:
|
||||
A list of paths that 'child_rolename' should be restricted to.
|
||||
|
||||
child_rolename:
|
||||
The child delegation that requires an update to its restricted paths,
|
||||
|
|
@ -1751,8 +1751,8 @@ def add_restricted_paths(self, list_of_directory_paths, child_rolename):
|
|||
'unclaimed').
|
||||
|
||||
<Exceptions>
|
||||
tuf.Error, if a directory path in 'list_of_directory_paths' is not a
|
||||
directory, or not under the repository's targets directory. If
|
||||
tuf.Error, if a restricted path in 'restricted_paths' is not a string
|
||||
path, doesn't live under the repository's targets directory, or if
|
||||
'child_rolename' has not been delegated yet.
|
||||
|
||||
<Side Effects>
|
||||
|
|
@ -1766,46 +1766,40 @@ def add_restricted_paths(self, list_of_directory_paths, child_rolename):
|
|||
# Ensure the arguments have the appropriate number of objects and object
|
||||
# types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if there is a mismatch.
|
||||
tuf.formats.PATHS_SCHEMA.check_match(list_of_directory_paths)
|
||||
tuf.formats.PATHS_SCHEMA.check_match(restricted_paths)
|
||||
tuf.formats.ROLENAME_SCHEMA.check_match(child_rolename)
|
||||
|
||||
# A list of verified paths to be added to the child role's entry in the
|
||||
# parent's delegations.
|
||||
directory_paths = []
|
||||
# A list of relative and verified paths to be added to the child role's
|
||||
# entry in the parent's delegations.
|
||||
relative_paths = []
|
||||
|
||||
# Ensure the 'child_rolename' has been delegated, otherwise it will not
|
||||
# have an entry in the parent role's delegations field.
|
||||
if not tuf.roledb.role_exists(child_rolename):
|
||||
raise tuf.Error(repr(child_rolename) + ' has not been delegated.')
|
||||
|
||||
# Are the paths in 'list_of_directory_paths' valid?
|
||||
for directory_path in list_of_directory_paths:
|
||||
directory_path = os.path.abspath(directory_path)
|
||||
if not os.path.isdir(directory_path):
|
||||
raise tuf.Error(repr(directory_path) + ' is not a directory.')
|
||||
|
||||
# Are the paths in the repository's targets directory? Append a trailing
|
||||
# path separator with os.path.join(path, '').
|
||||
for restricted_path in restricted_paths:
|
||||
# Do the restricted paths fall under the repository's targets directory?
|
||||
# Append a trailing path separator with os.path.join(path, '').
|
||||
targets_directory = os.path.join(self._targets_directory, '')
|
||||
directory_path = os.path.join(directory_path, '')
|
||||
if not directory_path.startswith(targets_directory):
|
||||
raise tuf.Error(repr(directory_path) + ' is not under the'
|
||||
if not restricted_path.startswith(targets_directory):
|
||||
raise tuf.Error(repr(restricted_path) + ' does not live under the'
|
||||
' Repository\'s targets directory: ' + repr(self._targets_directory))
|
||||
|
||||
directory_paths.append(directory_path[len(self._targets_directory):])
|
||||
relative_paths.append(restricted_path[len(self._targets_directory):])
|
||||
|
||||
# Get the current role's roleinfo, so that its delegations field can be
|
||||
# updated.
|
||||
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
||||
|
||||
# Update the restricted paths of 'child_rolename'.
|
||||
# Update the restricted paths of 'child_rolename' to add relative paths.
|
||||
for role in roleinfo['delegations']['roles']:
|
||||
if role['name'] == child_rolename:
|
||||
restricted_paths = role['paths']
|
||||
|
||||
for directory_path in directory_paths:
|
||||
if directory_path not in restricted_paths:
|
||||
restricted_paths.append(directory_path)
|
||||
for relative_path in relative_paths:
|
||||
if relative_path not in restricted_paths:
|
||||
restricted_paths.append(relative_path)
|
||||
|
||||
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
||||
|
||||
|
|
@ -2178,7 +2172,7 @@ def delegate(self, rolename, public_keys, list_of_targets, threshold=1,
|
|||
|
||||
for target in list_of_targets:
|
||||
target = os.path.abspath(target)
|
||||
if not target.startswith(self._targets_directory+os.sep):
|
||||
if not target.startswith(self._targets_directory + os.sep):
|
||||
raise tuf.Error(repr(target) + ' is not under the Repository\'s'
|
||||
' targets directory: ' + repr(self._targets_directory))
|
||||
|
||||
|
|
@ -2190,13 +2184,11 @@ def delegate(self, rolename, public_keys, list_of_targets, threshold=1,
|
|||
|
||||
if restricted_paths is not None:
|
||||
for path in restricted_paths:
|
||||
path = os.path.abspath(path) + os.sep
|
||||
if not path.startswith(self._targets_directory + os.sep):
|
||||
raise tuf.Error(repr(path) + ' is not under the Repository\'s'
|
||||
' targets directory: ' +repr(self._targets_directory))
|
||||
|
||||
# Append a trailing path separator with os.path.join(path, '').
|
||||
path = os.path.join(path, '')
|
||||
relative_restricted_paths.append(path[targets_directory_length:])
|
||||
|
||||
# Create a new Targets object for the 'rolename' delegation. An initial
|
||||
|
|
|
|||
21
tuf/util.py
21
tuf/util.py
|
|
@ -32,6 +32,7 @@
|
|||
import shutil
|
||||
import logging
|
||||
import tempfile
|
||||
import fnmatch
|
||||
|
||||
import tuf
|
||||
import tuf.hash
|
||||
|
|
@ -584,13 +585,12 @@ def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations):
|
|||
function does not raise an exception if 'rolename' is 'targets'.
|
||||
|
||||
Targets allowed are either exlicitly listed under the 'paths' field, or
|
||||
implicitly exist under a subdirectory of a parent directory listed
|
||||
under 'paths'. A parent role may delegate trust to all files under a
|
||||
particular directory, including files in subdirectories, by simply
|
||||
listing the directory (e.g., '/packages/source/Django/', the equivalent
|
||||
of '/packages/source/Django/*'). Targets listed in hashed bins are
|
||||
also validated (i.e., its calculated path hash prefix must be delegated
|
||||
by the parent role).
|
||||
match one of the patterns (i.e., Unix shell-style wildcards) listed there.
|
||||
A parent role may delegate trust to all files under a particular directory,
|
||||
including files in subdirectories by using wildcards (e.g.,
|
||||
'/packages/source/Django/*', '/packages/django*.tar.gzip).
|
||||
Targets listed in hashed bins are also validated (i.e., its calculated path
|
||||
hash prefix must be delegated by the parent role).
|
||||
|
||||
TODO: Should the TUF spec restrict the repository to one particular
|
||||
algorithm when calcutating path hash prefixes (currently restricted to
|
||||
|
|
@ -666,7 +666,7 @@ def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations):
|
|||
if allowed_child_path_hash_prefixes is not None:
|
||||
consistent = paths_are_consistent_with_hash_prefixes
|
||||
|
||||
# 'actual_child_tarets' (i.e., 'list_of_targets') should have lenth
|
||||
# 'actual_child_targets' (i.e., 'list_of_targets') should have lenth
|
||||
# greater than zero due to the tuf.format check above.
|
||||
if not consistent(actual_child_targets,
|
||||
allowed_child_path_hash_prefixes):
|
||||
|
|
@ -684,8 +684,7 @@ def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations):
|
|||
# different roles/developers.
|
||||
for child_target in actual_child_targets:
|
||||
for allowed_child_path in allowed_child_paths:
|
||||
prefix = os.path.commonprefix([child_target, allowed_child_path])
|
||||
if prefix == allowed_child_path:
|
||||
if fnmatch.fnmatch(child_target, allowed_child_path):
|
||||
break
|
||||
|
||||
else:
|
||||
|
|
@ -707,7 +706,7 @@ def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations):
|
|||
# 'rolename' child role.
|
||||
else:
|
||||
raise tuf.RepositoryError('The parent role has not delegated to '+\
|
||||
repr(rolename)+'.')
|
||||
repr(rolename) + '.')
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue