Skip to content

Commit ecc9810

Browse files
committed
WIP: Test promiscuous delegation behavior in updater
Adds a new test method to test_updater, test_6_get_one_valid_targetinfo__promiscuous() (The nomenclature in the test files is a bit constraining.) The new test tests updater.get_one_valid_targetinfo() in two scenarios where multiple roles delegate to the same role (which should be fine). Delegations are performed, and the updater attempts to gather target info for files delegated down the two delegation pathways to the same final role. WIP Signed-off-by: Sebastien Awwad <[email protected]>
1 parent e5ce022 commit ecc9810

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

tests/test_updater.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,183 @@ def test_6_get_one_valid_targetinfo(self):
11261126

11271127

11281128

1129+
def test_6_get_one_valid_targetinfo__promiscuous(self):
1130+
"""
1131+
Test behavior when multiple roles have delegated to the same role,
1132+
in two scenarios.
1133+
1134+
In all cases, the delegation structure is:
1135+
Targets delegates foo/* to roleA
1136+
Targets delegates foo/* to roleB
1137+
roleA delegates foo/bar/* to roleC
1138+
roleB delegates foo/baz/* to roleC
1139+
"""
1140+
1141+
# Prepare for the two test scenarios:
1142+
1143+
# Modify delegations on the remote repository to test behavior in cases
1144+
# where multiple roles delegate to one role.
1145+
repo = repo_tool.load_repository(self.repository_directory)
1146+
1147+
targets_directory = os.path.join(self.repository_directory, 'targets')
1148+
1149+
# Patterns for delegating the directories
1150+
foo_pattern = '/foo/*'
1151+
bar_pattern = '/foo/bar/*'
1152+
baz_pattern = '/foo/baz/*'
1153+
1154+
os.makedirs(os.path.join(targets_directory, 'foo', 'bar'))
1155+
os.makedirs(os.path.join(targets_directory, 'foo', 'baz'))
1156+
1157+
# Filepaths within repo namespace (for retrieving from repo)
1158+
bar_target_fname = os.path.join('foo', 'bar', 'a.txt')
1159+
baz_target_fname = os.path.join('foo', 'baz', 'b.txt')
1160+
1161+
# Full filepaths on-disk (for creating files and adding to repo)
1162+
bar_target_full_fname = os.path.join(targets_directory, bar_target_fname)
1163+
baz_target_full_fname = os.path.join(targets_directory, baz_target_fname)
1164+
1165+
# Create the target files that roleC will list.
1166+
with open(bar_target_full_fname, 'w') as fobj:
1167+
fobj.write('bar')
1168+
with open(baz_target_full_fname, 'w') as fobj:
1169+
fobj.write('baz')
1170+
1171+
# Pick some sample keys to use for the roles.
1172+
# We'll have A expect one key from C and B expect a different key from C.
1173+
# (pubkey_C_A being the key A expects C to be signed with, and
1174+
# pubkey_C_B being the key B expects C to be signed with)
1175+
# Be sure to use keys for C_A and C_B that would not be currently loaded by
1176+
# some other role, due to a quirk in current repository_tool behavior.
1177+
# (See Issue #646)
1178+
pubkey_A = self.role_keys['targets']['public']
1179+
prikey_A = self.role_keys['targets']['private']
1180+
pubkey_B = self.role_keys['targets']['public']
1181+
prikey_B = self.role_keys['targets']['private']
1182+
pubkey_C_A = self.role_keys['root']['public']
1183+
prikey_C_A = self.role_keys['root']['private']
1184+
pubkey_C_B = self.role_keys['role1']['public']
1185+
prikey_C_B = self.role_keys['role1']['private']
1186+
1187+
# Delegate
1188+
repo.targets.delegate('roleA', [pubkey_A], [foo_pattern])
1189+
repo.targets.delegate('roleB', [pubkey_B], [foo_pattern])
1190+
repo.targets('roleA').delegate('roleC', [pubkey_C_A], [bar_pattern])
1191+
repo.targets('roleB').delegate('roleC', [pubkey_C_B], [baz_pattern])
1192+
1193+
# Have C specify two targets: /foo/bar/a.txt, /foo/baz/b.txt
1194+
repo.targets('roleC').add_targets(
1195+
[bar_target_full_fname, baz_target_full_fname])
1196+
1197+
1198+
1199+
# SCENARIO 2
1200+
# roleC is signed by the key that B expects of it, but not the key that
1201+
# A expects of it.
1202+
# We should still be able to traverse Targets->roleB->roleC and retrieve
1203+
# target info for targets delegated through that path, but not through
1204+
# Targets->roleA->roleC.
1205+
# Further, the inability to validate roleC through the roleA path should
1206+
# not prevent us from validating roleC through the roleB path.
1207+
# See TUF Specification repo's Issue #16 for more about the latter:
1208+
# https://github.com/theupdateframework/specification/issues/15
1209+
1210+
# IN PROGRESS - WIP
1211+
1212+
# Unload signing key that role A expects of role C.
1213+
# Load signing keys.
1214+
repo.targets('roleA').load_signing_key(prikey_A)
1215+
repo.targets('roleB').load_signing_key(prikey_B)
1216+
#repo.targets('roleC').load_signing_key(prikey_C_A)
1217+
repo.targets('roleC').load_signing_key(prikey_C_B)
1218+
1219+
# Updating these delegated role files doesn't mark timestamp or snapshot
1220+
# dirty, so we'll have to manually write them all (or mark them all dirty).
1221+
repo.targets.load_signing_key(self.role_keys['targets']['private'])
1222+
repo.snapshot.load_signing_key(self.role_keys['snapshot']['private'])
1223+
repo.timestamp.load_signing_key(self.role_keys['timestamp']['private'])
1224+
for role in ['roleC', 'roleB', 'roleA', 'targets','snapshot', 'timestamp']:
1225+
repo.write(role)
1226+
1227+
# Move the freshly written metadata to the hosted metadata directory.
1228+
shutil.rmtree(os.path.join(self.repository_directory, 'metadata'))
1229+
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
1230+
os.path.join(self.repository_directory, 'metadata'))
1231+
1232+
# Update top-level metadata on the client.
1233+
self.repository_updater.refresh()
1234+
1235+
# We should be able to find target info for the file listed by roleC in the
1236+
# Targets->RoleB->RoleC traversal, but not the Targets->RoleA->RoleC,
1237+
# since we're missing the signature on roleC that roleA expects, but we
1238+
# have the signature on roleC that roleB expects.
1239+
with self.assertRaises(tuf.exceptions.NoWorkingMirrorError):
1240+
self.repository_updater.get_one_valid_targetinfo(bar_target_fname)
1241+
# TODO: Consider checking inside NoWorkingMirrorError to make sure that
1242+
# there is a BadSignatureError('roleC') in it.
1243+
1244+
with self.assertRaises(tuf.exceptions.NoWorkingMirrorError):
1245+
self.repository_updater.get_one_valid_targetinfo('/' + bar_target_fname)
1246+
# TODO: Consider checking inside NoWorkingMirrorError to make sure that
1247+
# there is a BadSignatureError('roleC') in it.
1248+
1249+
import ipdb; ipdb.set_trace()
1250+
1251+
self.repository_updater.get_one_valid_targetinfo(baz_target_fname)
1252+
self.repository_updater.get_one_valid_targetinfo('/' + baz_target_fname)
1253+
1254+
1255+
1256+
1257+
1258+
1259+
1260+
# SCENARIO 1:
1261+
# All roles are signed by all keys expected of them.
1262+
# (e.g. C is signed by a threshold of all keys that A and B expect of
1263+
# it for the delegations listed)
1264+
1265+
# Load signing keys.
1266+
repo.targets('roleA').load_signing_key(prikey_A)
1267+
repo.targets('roleB').load_signing_key(prikey_B)
1268+
repo.targets('roleC').load_signing_key(prikey_C_A)
1269+
repo.targets('roleC').load_signing_key(prikey_C_B)
1270+
1271+
# Updating these delegated role files doesn't mark timestamp or snapshot
1272+
# dirty, so we'll have to manually write them all (or mark them all dirty).
1273+
repo.targets.load_signing_key(self.role_keys['targets']['private'])
1274+
repo.snapshot.load_signing_key(self.role_keys['snapshot']['private'])
1275+
repo.timestamp.load_signing_key(self.role_keys['timestamp']['private'])
1276+
for role in ['roleC', 'roleB', 'roleA', 'targets','snapshot', 'timestamp']:
1277+
repo.write(role)
1278+
1279+
# Move the freshly written metadata to the hosted metadata directory.
1280+
shutil.rmtree(os.path.join(self.repository_directory, 'metadata'))
1281+
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
1282+
os.path.join(self.repository_directory, 'metadata'))
1283+
1284+
1285+
# Update top-level metadata on the client.
1286+
self.repository_updater.refresh()
1287+
1288+
# We should be able to find target info for the two files listed by roleC,
1289+
# in one case traversing Targets->RoleA->RoleC, and in the other case
1290+
# Targets->RoleB->RoleC. Both traversals should find the appropriate
1291+
# signatures for Scenario 1.
1292+
self.repository_updater.get_one_valid_targetinfo(bar_target_fname)
1293+
self.repository_updater.get_one_valid_targetinfo('/' + bar_target_fname)
1294+
self.repository_updater.get_one_valid_targetinfo(baz_target_fname)
1295+
self.repository_updater.get_one_valid_targetinfo('/' + baz_target_fname)
1296+
1297+
1298+
1299+
1300+
1301+
1302+
1303+
1304+
1305+
11291306
def test_6_download_target(self):
11301307
# Create temporary directory (destination directory of downloaded targets)
11311308
# that will be passed as an argument to 'download_target()'.

0 commit comments

Comments
 (0)