| #!/usr/bin/env python |
| |
| # Copyright 2012 - 2018, New York University and the TUF contributors |
| # SPDX-License-Identifier: MIT OR Apache-2.0 |
| # |
| # A modification of client.py from the Python implementation: |
| # https://github.com/theupdateframework/tuf/blob/v0.11.1/tuf/scripts/client.py |
| |
| """ |
| <Program Name> |
| client.py |
| |
| <Author> |
| Vladimir Diaz <vladimir.v.diaz@gmail.com> |
| |
| <Started> |
| September 2012. |
| |
| <Copyright> |
| See LICENSE-MIT OR LICENSE for licensing information. |
| |
| <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 |
| 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 |
| update of all the files provided by their repository mirror(s). |
| |
| 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. |
| |
| 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> |
| $ client.py --repo http://localhost:8001 <target> |
| $ client.py --repo http://localhost:8001 --verbose 3 <target> |
| |
| <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'. |
| """ |
| |
| # 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 |
| |
| import sys |
| import traceback |
| import argparse |
| import logging |
| |
| import tuf |
| import tuf.client.updater |
| import tuf.settings |
| import tuf.log |
| |
| # See 'log.py' to learn how logging is handled in TUF. |
| logger = logging.getLogger('tuf.scripts.client') |
| |
| |
| def update_client(parsed_arguments): |
| """ |
| <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 |
| the 'root.json' metadata file. |
| |
| <Arguments> |
| parsed_arguments: |
| An argparse Namespace object, containing the parsed arguments. |
| |
| <Exceptions> |
| tuf.exceptions.Error, if 'parsed_arguments' is not a Namespace object. |
| |
| <Side Effects> |
| Connects to a repository mirror and updates the local metadata files and |
| any target files. Obsolete, local targets are also removed. |
| |
| <Returns> |
| None. |
| """ |
| |
| if not isinstance(parsed_arguments, argparse.Namespace): |
| raise tuf.exceptions.Error('Invalid namespace object.') |
| |
| else: |
| logger.debug('We have a valid argparse Namespace object.') |
| |
| # Set the local repositories directory containing all of the metadata files. |
| tuf.settings.repositories_directory = '.' |
| |
| # Set the repository mirrors. This dictionary is needed by the Updater |
| # class of updater.py. |
| repository_mirrors = {'mirror': {'url_prefix': parsed_arguments.repo, |
| 'metadata_path': 'repository', 'targets_path': 'repository/targets', |
| 'confined_target_dirs': ['']}} |
| |
| # Create the repository object using the repository name 'repository' |
| # and the repository mirrors defined above. |
| updater = tuf.client.updater.Updater('tufrepo', repository_mirrors) |
| |
| # The local destination directory to save the target files. |
| destination_directory = './tuftargets' |
| |
| # Refresh the repository's top-level roles... |
| updater.refresh(unsafely_update_root_if_necessary=False) |
| |
| # ... 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. |
| for target in updated_targets: |
| try: |
| updater.download_target(target, destination_directory) |
| |
| except tuf.exceptions.DownloadError: |
| pass |
| |
| # Remove any files from the destination directory that are no longer being |
| # tracked. |
| updater.remove_obsolete_targets(destination_directory) |
| |
| |
| |
| |
| |
| def parse_arguments(): |
| """ |
| <Purpose> |
| Parse the command-line options and set the logging level |
| as specified by the user through the --verbose option. |
| 'client' expects the '--repo' to be set by the user. |
| |
| Exampl: |
| $ client.py --repo http://localhost:8001 <target> |
| |
| 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> |
| The parsed_arguments (i.e., a argparse Namespace object). |
| """ |
| |
| parser = argparse.ArgumentParser( |
| description='Retrieve file from TUF repository.') |
| |
| # Add the options supported by 'basic_client' to the option parser. |
| 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') |
| |
| parser.add_argument('-r', '--repo', type=str, required=True, metavar='<URI>', |
| help='Specify the remote repository\'s URI' |
| ' (e.g., http://www.example.com:8001/tuf/). The client retrieves' |
| ' updates from the remote repository.') |
| |
| parser.add_argument('targets', nargs='+', metavar='<file>', help='Specify' |
| ' the target files to retrieve from the specified TUF repository.') |
| |
| parsed_arguments = parser.parse_args() |
| |
| |
| # Set the logging level. |
| if parsed_arguments.verbose == 5: |
| tuf.log.set_log_level(logging.CRITICAL) |
| |
| elif parsed_arguments.verbose == 4: |
| tuf.log.set_log_level(logging.ERROR) |
| |
| elif parsed_arguments.verbose == 3: |
| tuf.log.set_log_level(logging.WARNING) |
| |
| elif parsed_arguments.verbose == 2: |
| tuf.log.set_log_level(logging.INFO) |
| |
| elif parsed_arguments.verbose == 1: |
| tuf.log.set_log_level(logging.DEBUG) |
| |
| else: |
| tuf.log.set_log_level(logging.NOTSET) |
| |
| # Return the repository mirror containing the metadata and target files. |
| return parsed_arguments |
| |
| |
| |
| if __name__ == '__main__': |
| |
| # Parse the command-line arguments and set the logging level. |
| arguments = parse_arguments() |
| |
| # Perform an update of all the files in the 'targets' directory located in |
| # the current directory. |
| try: |
| update_client(arguments) |
| |
| except (tuf.exceptions.NoWorkingMirrorError, tuf.exceptions.RepositoryError, |
| tuf.exceptions.FormatError) as e: |
| traceback.print_exc() |
| sys.stderr.write('Error: ' + str(e) + '\n') |
| sys.exit(1) |
| |
| # Successfully updated the client's target files. |
| sys.exit(0) |