Skip to content

Commit 033a573

Browse files
Paranoid checks of configurations in order to prevent interposition cycles.
1 parent 8281d3b commit 033a573

File tree

5 files changed

+76
-16
lines changed

5 files changed

+76
-16
lines changed

tuf/interposition/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def instancemethod( self, url, ... )
3232

3333
```javascript
3434
{
35-
"network_locations": {
35+
"configurations": {
3636
"seattle.cs.washington.edu": {
3737
"repository_directory": "client/",
3838
"repository_mirrors" : {
@@ -60,3 +60,8 @@ with regular expressions, TUF will work over any path under the given network
6060
location. However, if you do specify it, you are then telling TUF how to
6161
transform a specified path into another one, and TUF will *not* recognize any
6262
unspecified path for the given network location.
63+
64+
## Limitations (at the time of writing)
65+
66+
- The entire `urllib` or `urllib2` contract is not honoured.
67+
- Downloads are not thread safe.

tuf/interposition/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def configure(
9090
Example of a TUF interposition configuration JSON object:
9191
9292
{
93-
"network_locations": {
93+
"configurations": {
9494
"seattle.cs.washington.edu": {
9595
"repository_directory": "client/",
9696
"repository_mirrors" : {
@@ -118,20 +118,20 @@ def configure(
118118

119119
INVALID_TUF_CONFIGURATION = "Invalid configuration for {network_location}!"
120120
INVALID_TUF_INTERPOSITION_JSON = "Invalid configuration in {filename}!"
121-
NO_NETWORK_LOCATIONS = "No network locations found in configuration in {filename}!"
121+
NO_CONFIGURATIONS = "No configurations found in configuration in {filename}!"
122122

123123
try:
124124
with open( filename ) as tuf_interposition_json:
125125
tuf_interpositions = json.load( tuf_interposition_json )
126-
network_locations = tuf_interpositions.get( "network_locations", {} )
126+
configurations = tuf_interpositions.get( "configurations", {} )
127127

128128
# TODO: more input sanity checks
129-
if len( network_locations ) == 0:
129+
if len( configurations ) == 0:
130130
raise InvalidConfiguration(
131-
NO_NETWORK_LOCATIONS.format( filename = filename )
131+
NO_CONFIGURATIONS.format( filename = filename )
132132
)
133133
else:
134-
for network_location, configuration in network_locations.iteritems():
134+
for network_location, configuration in configurations.iteritems():
135135
try:
136136
Updater.build_updater(
137137
Configuration.load_from_json(

tuf/interposition/configuration.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,34 @@ def load_from_json(
8484
)
8585
)
8686

87-
# Parse TUF server repository mirrors
87+
# Parse TUF server repository mirrors.
8888
repository_mirrors = configuration[ "repository_mirrors" ]
89+
repository_mirror_network_locations = set()
90+
8991
for repository_mirror in repository_mirrors:
9092
mirror_configuration = repository_mirrors[ repository_mirror ]
9193
try:
9294
url_prefix = mirror_configuration[ "url_prefix" ]
9395
parsed_url = urlparse.urlparse( url_prefix )
9496
mirror_hostname = parsed_url.hostname
9597
mirror_port = parsed_url.port or 80
98+
mirror_netloc = "{hostname}:{port}".format(
99+
hostname = mirror_hostname,
100+
port = mirror_port
101+
)
102+
96103
# No single-edge cycle in interposition.
97-
assert hostname != mirror_hostname or port != mirror_port
104+
# GOOD: A -> { A:XYZ, ... }
105+
# BAD: A -> { A, ... }
106+
assert not ( mirror_hostname == hostname and mirror_port == port )
107+
108+
# Unique network location over repository mirrors.
109+
# GOOD: A -> { A:X, A:Y, ... }
110+
# BAD: A -> { A:X, A:X, ... }
111+
assert mirror_netloc not in repository_mirror_network_locations
112+
113+
# Remember this mirror's network location to check the rest of the mirrors.
114+
repository_mirror_network_locations.add( mirror_netloc )
98115
except:
99116
error_message = INVALID_REPOSITORY_MIRROR.format(
100117
repository_mirror = repository_mirror

tuf/interposition/updater.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import tuf.client.updater
99
import tuf.conf
1010

11-
from configuration import Configuration
11+
from configuration import Configuration, InvalidConfiguration
1212
from utility import Logger, InterpositionException
1313

1414

@@ -25,9 +25,10 @@ class Updater( object ):
2525
which you can get and use later.
2626
"""
2727

28-
# A private collection of Updaters;
29-
# network_location: str -> updater: Updater
28+
# A private map of Updaters (network_location: str -> updater: Updater)
3029
__updaters = {}
30+
# A private set of repository mirror hostnames
31+
__repository_mirror_hostnames = set()
3132

3233
def __init__( self, configuration ):
3334
self.configuration = configuration
@@ -42,13 +43,50 @@ def __init__( self, configuration ):
4243

4344
@staticmethod
4445
def build_updater( configuration ):
46+
INVALID_REPOSITORY_MIRROR = \
47+
"Invalid repository mirror {repository_mirror}!"
48+
49+
# Updater has a "global" view of configurations, so it performs
50+
# additional checks after Configuration's own local checks.
4551
assert isinstance( configuration, Configuration )
4652

47-
# Restrict each hostname to correspond to a single updater;
48-
# this prevents interposition cycles, amongst other things.
53+
# Restrict each (incoming, outgoing) hostname pair to be unique across
54+
# configurations; this prevents interposition cycles, amongst other
55+
# things.
56+
# GOOD: A -> { A:X, A:Y, B, ... }, C -> { D }, ...
57+
# BAD: A -> { B }, B -> { C }, C -> { A }, ...
4958
assert configuration.hostname not in Updater.__updaters
59+
assert configuration.hostname not in Updater.__repository_mirror_hostnames
60+
61+
# Parse TUF server repository mirrors.
62+
repository_mirrors = configuration.repository_mirrors
63+
repository_mirror_hostnames = set()
64+
65+
for repository_mirror in repository_mirrors:
66+
mirror_configuration = repository_mirrors[ repository_mirror ]
67+
try:
68+
url_prefix = mirror_configuration[ "url_prefix" ]
69+
parsed_url = urlparse.urlparse( url_prefix )
70+
mirror_hostname = parsed_url.hostname
71+
72+
# Restrict each (incoming, outgoing) hostname pair to be unique
73+
# across configurations; this prevents interposition cycles,
74+
# amongst other things.
75+
assert mirror_hostname not in Updater.__updaters
76+
assert mirror_hostname not in Updater.__repository_mirror_hostnames
77+
78+
# Remember this mirror's hostname for the next network_location.
79+
repository_mirror_hostnames.add( mirror_hostname )
80+
except:
81+
error_message = INVALID_REPOSITORY_MIRROR.format(
82+
repository_mirror = repository_mirror
83+
)
84+
Logger.error( error_message )
85+
raise InvalidConfiguration( error_message )
5086

87+
# If all is well, build and store an Updater, and remember hostnames.
5188
Updater.__updaters[ configuration.hostname ] = Updater( configuration )
89+
Updater.__repository_mirror_hostnames.update( repository_mirror_hostnames )
5290

5391
def download_target( self, target_filepath ):
5492
"""Downloads target with TUF as a side effect."""

tuf/tests/system_tests/util_test_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def init_tuf(root_repo, url):
347347
# In order to implement interposition we need to have a config file with
348348
# the following dictionary JSON-serialized.
349349
# tuf_url: http://localhost:port/root_repo/tuf_repo/
350-
interposition_dict = {"network_locations":
350+
interposition_dict = {"configurations":
351351
{"localhost":
352352
{"repository_directory": tuf_client+'/',
353353
"repository_mirrors" :
@@ -397,4 +397,4 @@ def tuf_refresh_repo(root_repo, keyids):
397397
signerlib.build_release_file(keyids, metadata_dir)
398398

399399
# Regenerate the 'timestamp.txt' metadata file.
400-
signerlib.build_timestamp_file(keyids, metadata_dir)
400+
signerlib.build_timestamp_file(keyids, metadata_dir)

0 commit comments

Comments
 (0)