blob: 8dc6eca4eb62bef9336937968bde1c2541643127 [file] [log] [blame]
#!/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)