2013-03-17 22:16:58 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
2018-01-24 22:31:42 +00:00
|
|
|
# Copyright 2012 - 2018, New York University and the TUF contributors
|
2017-11-30 18:23:38 +00:00
|
|
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
|
|
2013-02-11 00:19:15 +00:00
|
|
|
"""
|
|
|
|
|
<Program Name>
|
2018-01-24 22:31:42 +00:00
|
|
|
client.py
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Author>
|
|
|
|
|
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
|
|
|
|
|
|
|
|
|
<Started>
|
2018-01-24 22:31:42 +00:00
|
|
|
September 2012.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Copyright>
|
2018-02-05 16:31:19 +00:00
|
|
|
See LICENSE-MIT OR LICENSE for licensing information.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Purpose>
|
|
|
|
|
Provide a basic TUF client that can update all of the metatada and target
|
|
|
|
|
files provided by the user-specified repository mirror. Updated files are
|
|
|
|
|
saved to the 'targets' directory in the current working directory. The
|
|
|
|
|
repository mirror is specified by the user through the '--repo' command-
|
|
|
|
|
line option.
|
|
|
|
|
|
|
|
|
|
Normally, a software updater integrating TUF will develop their own costum
|
|
|
|
|
client module by importing 'tuf.client.updater', instantiating the required
|
|
|
|
|
object, and calling the desired methods to perform an update. This basic
|
2016-11-09 19:46:05 +00:00
|
|
|
client is provided to users who wish to give TUF a quick test run without the
|
|
|
|
|
hassle of writing client code. This module can also used by updaters that do
|
|
|
|
|
not need the customization and only require their clients to perform an
|
2013-02-11 00:19:15 +00:00
|
|
|
update of all the files provided by their repository mirror(s).
|
|
|
|
|
|
2016-11-09 19:46:05 +00:00
|
|
|
For software updaters that DO require customization, see the
|
|
|
|
|
'example_client.py' script. The 'example_client.py' script provides an
|
|
|
|
|
outline of the client code that software updaters may develop and then tailor
|
|
|
|
|
to their specific software updater or package manager.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
Additional tools for clients running legacy applications will also be made
|
|
|
|
|
available. These tools will allow secure software updates using The Update
|
|
|
|
|
Framework without the need to modify the original application.
|
|
|
|
|
|
|
|
|
|
<Usage>
|
2018-01-29 22:05:01 +00:00
|
|
|
$ client.py --repo http://localhost:8001 <target>
|
|
|
|
|
$ client.py --repo http://localhost:8001 --verbose 3 <target>
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Options>
|
|
|
|
|
--verbose:
|
|
|
|
|
Set the verbosity level of logging messages. Accepts values 1-5.
|
|
|
|
|
|
|
|
|
|
--repo:
|
|
|
|
|
Set the repository mirror that will be responding to client requests.
|
|
|
|
|
E.g., 'http://locahost:8001'.
|
|
|
|
|
"""
|
|
|
|
|
|
2014-04-29 18:27:34 +00:00
|
|
|
# Help with Python 3 compatibility, where the print statement is a function, an
|
|
|
|
|
# implicit relative import is invalid, and the '/' operator performs true
|
|
|
|
|
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
from __future__ import division
|
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
2013-02-11 00:19:15 +00:00
|
|
|
import sys
|
2018-01-29 22:05:01 +00:00
|
|
|
import argparse
|
2013-02-11 00:19:15 +00:00
|
|
|
import logging
|
|
|
|
|
|
2013-08-15 18:33:35 +00:00
|
|
|
import tuf
|
2013-02-11 00:19:15 +00:00
|
|
|
import tuf.client.updater
|
2017-01-13 16:34:41 +00:00
|
|
|
import tuf.settings
|
2013-02-11 00:19:15 +00:00
|
|
|
import tuf.log
|
|
|
|
|
|
|
|
|
|
# See 'log.py' to learn how logging is handled in TUF.
|
2018-01-24 22:31:42 +00:00
|
|
|
logger = logging.getLogger('tuf.scripts.client')
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
def update_client(parsed_arguments):
|
2013-02-11 00:19:15 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Perform an update of the metadata and target files located at
|
|
|
|
|
'repository_mirror'. Target files are saved to the 'targets' directory
|
|
|
|
|
in the current working directory. The current directory must already
|
|
|
|
|
include a 'metadata' directory, which in turn must contain the 'current'
|
|
|
|
|
and 'previous' directories. At a minimum, these two directories require
|
2014-01-27 16:35:38 +00:00
|
|
|
the 'root.json' metadata file.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
2018-01-29 22:05:01 +00:00
|
|
|
parsed_arguments:
|
|
|
|
|
An argparse Namespace object, containing the parsed arguments.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2018-01-29 22:05:01 +00:00
|
|
|
tuf.exceptions.Error, if 'parsed_arguments' is not a Namespace object.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2018-01-29 22:05:01 +00:00
|
|
|
Connects to a repository mirror and updates the local metadata files and
|
|
|
|
|
any target files. Obsolete, local targets are also removed.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
if not isinstance(parsed_arguments, argparse.Namespace):
|
|
|
|
|
raise tuf.exceptions.Error('Invalid namespace object.')
|
2017-01-13 16:34:41 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
else:
|
|
|
|
|
logger.debug('We have a valid argparse Namespace object.')
|
2017-01-13 16:34:41 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
# Set the local repositories directory containing all of the metadata files.
|
|
|
|
|
tuf.settings.repositories_directory = '.'
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
# Set the repository mirrors. This dictionary is needed by the Updater
|
|
|
|
|
# class of updater.py.
|
2018-01-29 22:05:01 +00:00
|
|
|
repository_mirrors = {'mirror': {'url_prefix': parsed_arguments.repo,
|
|
|
|
|
'metadata_path': 'metadata', 'targets_path': 'targets',
|
|
|
|
|
'confined_target_dirs': ['']}}
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
# Create the repository object using the repository name 'repository'
|
|
|
|
|
# and the repository mirrors defined above.
|
2018-01-29 22:05:01 +00:00
|
|
|
updater = tuf.client.updater.Updater('tufrepo', repository_mirrors)
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
# The local destination directory to save the target files.
|
2018-01-29 22:05:01 +00:00
|
|
|
destination_directory = './tuftargets'
|
2013-02-11 00:19:15 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
# Refresh the repository's top-level roles...
|
2016-10-07 15:33:01 +00:00
|
|
|
updater.refresh(unsafely_update_root_if_necessary=False)
|
2013-02-11 00:19:15 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
# ... and store the target information for the target file specified on the
|
|
|
|
|
# command line, and determine which of these targets have been updated.
|
|
|
|
|
target_fileinfo = []
|
|
|
|
|
for target in parsed_arguments.targets:
|
|
|
|
|
target_fileinfo.append(updater.get_one_valid_targetinfo(target))
|
|
|
|
|
|
|
|
|
|
updated_targets = updater.updated_targets(target_fileinfo, destination_directory)
|
|
|
|
|
|
|
|
|
|
# Retrieve each of these updated targets and save them to the destination
|
|
|
|
|
# directory.
|
2013-02-11 00:19:15 +00:00
|
|
|
for target in updated_targets:
|
2017-01-13 16:34:41 +00:00
|
|
|
try:
|
2013-02-11 00:19:15 +00:00
|
|
|
updater.download_target(target, destination_directory)
|
2017-01-13 16:34:41 +00:00
|
|
|
|
|
|
|
|
except tuf.exceptions.DownloadError:
|
2013-02-11 00:19:15 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Remove any files from the destination directory that are no longer being
|
|
|
|
|
# tracked.
|
|
|
|
|
updater.remove_obsolete_targets(destination_directory)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
def parse_arguments():
|
2013-02-11 00:19:15 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Parse the command-line options and set the logging level
|
|
|
|
|
as specified by the user through the --verbose option.
|
2018-01-24 22:31:42 +00:00
|
|
|
'client' expects the '--repo' to be set by the user.
|
2013-02-11 00:19:15 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
Exampl:
|
|
|
|
|
$ client.py --repo http://localhost:8001 <target>
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
If the required option is unset, a parser error is printed
|
|
|
|
|
and the scripts exits.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
Sets the logging level for TUF logging.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
2018-01-29 22:05:01 +00:00
|
|
|
The parsed_arguments (i.e., a argparse Namespace object).
|
2013-02-11 00:19:15 +00:00
|
|
|
"""
|
|
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description='Retrieve file from TUF repository.')
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
# Add the options supported by 'basic_client' to the option parser.
|
2018-01-29 22:05:01 +00:00
|
|
|
parser.add_argument('-v', '--verbose', type=int, default=2,
|
|
|
|
|
choices=range(0, 6), help='Set the verbosity level of logging messages.'
|
|
|
|
|
' The lower the setting, the greater the verbosity. Supported logging'
|
|
|
|
|
' levels: 0=UNSET, 1=DEBUG, 2=INFO, 3=WARNING, 4=ERROR,'
|
|
|
|
|
' 5=CRITICAL')
|
|
|
|
|
|
2018-02-08 22:14:15 +00:00
|
|
|
parser.add_argument('-r', '--repo', type=str, required=True, metavar='<URI>',
|
|
|
|
|
help='Specify the remote repository\'s URI'
|
2018-01-29 22:05:01 +00:00
|
|
|
' (e.g., http://www.example.com:8001/tuf/). The client retrieves'
|
2018-02-08 22:14:15 +00:00
|
|
|
' updates from the remote repository.')
|
2018-01-29 22:05:01 +00:00
|
|
|
|
2018-02-08 22:14:15 +00:00
|
|
|
parser.add_argument('targets', nargs='+', metavar='<file>', help='Specify'
|
|
|
|
|
' the target files to retrieve from the specified TUF repository.')
|
2013-02-11 00:19:15 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
parsed_arguments = parser.parse_args()
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set the logging level.
|
2018-01-29 22:05:01 +00:00
|
|
|
if parsed_arguments.verbose == 5:
|
2013-02-11 00:19:15 +00:00
|
|
|
tuf.log.set_log_level(logging.CRITICAL)
|
2018-01-29 22:05:01 +00:00
|
|
|
|
|
|
|
|
elif parsed_arguments.verbose == 4:
|
2013-02-11 00:19:15 +00:00
|
|
|
tuf.log.set_log_level(logging.ERROR)
|
2018-01-29 22:05:01 +00:00
|
|
|
|
|
|
|
|
elif parsed_arguments.verbose == 3:
|
2013-02-11 00:19:15 +00:00
|
|
|
tuf.log.set_log_level(logging.WARNING)
|
2018-01-29 22:05:01 +00:00
|
|
|
|
|
|
|
|
elif parsed_arguments.verbose == 2:
|
2013-02-11 00:19:15 +00:00
|
|
|
tuf.log.set_log_level(logging.INFO)
|
2018-01-29 22:05:01 +00:00
|
|
|
|
|
|
|
|
elif parsed_arguments.verbose == 1:
|
2013-02-11 00:19:15 +00:00
|
|
|
tuf.log.set_log_level(logging.DEBUG)
|
2018-01-29 22:05:01 +00:00
|
|
|
|
2013-02-11 00:19:15 +00:00
|
|
|
else:
|
|
|
|
|
tuf.log.set_log_level(logging.NOTSET)
|
|
|
|
|
|
|
|
|
|
# Return the repository mirror containing the metadata and target files.
|
2018-01-29 22:05:01 +00:00
|
|
|
return parsed_arguments
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2017-01-13 16:34:41 +00:00
|
|
|
|
2018-01-29 22:05:01 +00:00
|
|
|
# Parse the command-line arguments and set the logging level.
|
2018-04-17 19:29:17 +00:00
|
|
|
arguments = parse_arguments()
|
2013-02-11 00:19:15 +00:00
|
|
|
|
|
|
|
|
# Perform an update of all the files in the 'targets' directory located in
|
|
|
|
|
# the current directory.
|
|
|
|
|
try:
|
2018-04-17 19:29:17 +00:00
|
|
|
update_client(arguments)
|
2017-01-13 16:34:41 +00:00
|
|
|
|
2018-04-06 17:16:41 +00:00
|
|
|
except (tuf.exceptions.NoWorkingMirrorError, tuf.exceptions.RepositoryError,
|
2018-06-28 16:38:55 +00:00
|
|
|
tuf.exceptions.FormatError, tuf.exceptions.Error) as e:
|
2017-01-13 16:34:41 +00:00
|
|
|
sys.stderr.write('Error: ' + str(e) + '\n')
|
2013-02-11 00:19:15 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
# Successfully updated the client's target files.
|
|
|
|
|
sys.exit(0)
|