Skip to content

Commit

Permalink
WIP: TUF Python Client Example/Tutorial
Browse files Browse the repository at this point in the history
It is a simple example of TUF ngclient implementation.

This example contains a README.rst that is a tutorial/how-to-use
this simple client using static test data from TUF repository.

The code aims to be straightforward implementation, using basic
concepts from Python and Command Line Interface.

This is part of theupdateframework#1518

Signed-off-by: Kairo de Araujo <[email protected]>
  • Loading branch information
Kairo de Araujo committed Nov 15, 2021
1 parent fa7990c commit 6890b04
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 0 deletions.
87 changes: 87 additions & 0 deletions examples/client_example/1.root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"signatures": [
{
"keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb",
"sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981"
}
],
"signed": {
"_type": "root",
"consistent_snapshot": false,
"expires": "2030-01-01T00:00:00Z",
"keys": {
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "rsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----"
},
"scheme": "rsassa-pss-sha256"
},
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
},
"scheme": "ed25519"
},
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815"
},
"scheme": "ed25519"
},
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4"
},
"scheme": "ed25519"
}
},
"roles": {
"root": {
"keyids": [
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d"
],
"threshold": 1
},
"targets": {
"keyids": [
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758"
],
"threshold": 1
}
},
"spec_version": "1.0.0",
"version": 1
}
}
92 changes: 92 additions & 0 deletions examples/client_example/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
Python Client Example
#####################

Introduction
============

Python Client Example, using ``python-tuf``.

For information about installing ``python-tuf``, please refer to the
`Installation documentation <https://theupdateframework.readthedocs.io/en/latest/INSTALLATION.html>`_.


Preparing
=========

To have the example working in your machine, clone the ``python-tuf`` in your
system.

.. code:: console
$ git clone [email protected]:theupdateframework/python-tuf.git
Repository
==========

As this example demonstrates how to use the ``python-tuf`` to build a
client application, the repository will use static files.

The static files are available in the ``python-tuf`` repository, same as this.
The static repository files are in
``tests/repository_data/repository``.

Run the repository using the Python3 built-in HTTP module, and keep this
session running.

.. code:: console
$ python3 -m http.server -d tests/repository_data/repository
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
Client Example
==============

The `source code is available entirely <./client_example.py>`_ in this
repository.

How to use the Client Example:

1. Initialize the Client

.. code:: console
$ ./client_example.py --init
This action is to create the client infrastructure properly.

This infrastructure consists in:
- Metadata repository
- Download folder for targets
- Bootstrap 1.root.json


2. Download the ``file1.txt``

.. code:: console
$ ./client_example.py download file1.txt
[INFO] Top-level metadata is refreshed.
[INFO] Target info gotten.
[INFO] File downloaded available in ./downloads/file2.txt.
3. Download a not available ``file_na.txt``

.. code:: console
$ ./client_example.py download file_na.txt
[INFO] Top-level metadata is refreshed.
[INFO] Target info gotten.
[ERROR] Target file not found.
4. Download again ``file1.txt``

.. code:: console
$ ./client_example.py download file1.txt
[INFO] Top-level metadata is refreshed.
[INFO] Target info gotten.
[INFO] File is already available in ./downloads/file1.txt.
145 changes: 145 additions & 0 deletions examples/client_example/client_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python
import argparse
import os
import shutil
import sys
from logging import exception
from pathlib import Path

from requests.exceptions import ConnectionError

from tuf.ngclient import Updater

# define directory constants
HOME_DIR = Path.home() # user home dir
DOWNLOAD_DIR = "./downloads" # download dir
METADATA_DIR = f"{HOME_DIR}/.local/share/tuf_metadata_example" # metadata dir
CLIENT_EXAMPLE_DIR = os.path.dirname(os.path.abspath(__file__)) # example dir


def init():
"""
Initialize the TUF Client infrastructure
This function initializes the creation of the download and TUF metadata
directory.
"""

if not os.path.isdir(DOWNLOAD_DIR):
os.mkdir(DOWNLOAD_DIR)

print(f"[INFO] Download directory [{DOWNLOAD_DIR}] is created.")

if not os.path.isdir(METADATA_DIR):
os.makedirs(METADATA_DIR)

print(f"[INFO] Metadata folder [{METADATA_DIR}] is created.")

if not os.path.isfile(f"{METADATA_DIR}/root.json"):
shutil.copy(
f"{CLIENT_EXAMPLE_DIR}/1.root.json", f"{METADATA_DIR}/root.json"
)
print(f"[INFO] Bootstrap initial root metadata.")


def tuf_updater():
"""
This function implement the ``tuf.ngclient.Updater`` and returns
the updater.
"""
url = "http://127.0.0.1:8000"

try:
updater = Updater(
repository_dir=METADATA_DIR,
metadata_base_url=f"{url}/metadata/",
target_base_url=f"{url}/targets/",
target_dir=DOWNLOAD_DIR,
)

except FileNotFoundError:
print("[ERROR] The Example Client not initiated. Try using '--init'.")
sys.exit(1)

return updater


def download(target):
"""
Download the target file using the TUF ``nglcient`` Updater process.
The Updater refreshes the top-level metadata, get the target information,
verifies if the target is already cached, and in case it is not cached,
downloads the target file.
"""

try:
updater = tuf_updater()

except ConnectionError:
print("[ERROR] Failed to connect http://127.0.0.1:8000")
sys.exit(1)

updater.refresh()
print("[INFO] Top-level metadata is refreshed.")

info = updater.get_targetinfo(target)
print("[INFO] Target info gotten.")

if info is None:
print("[ERROR] Target file not found.")
sys.exit(1)

path = updater.find_cached_target(info)
if path:
print(
f"[INFO] File is already available in {DOWNLOAD_DIR}/{info.path}."
)
sys.exit(0)

path = updater.download_target(info)

print(f"[INFO] File downloaded available in {DOWNLOAD_DIR}/{info.path}.")


if __name__ == "__main__":

client_args = argparse.ArgumentParser(
description="TUF Python Client Example"
)

# Global arguments
client_args.add_argument(
"--init",
default=False,
help="Force register a new Engine.",
action="store_true",
)

# Sub commands
sub_commands = client_args.add_subparsers(dest="sub_commands")

# Download
download_parser = sub_commands.add_parser(
"download",
help="Download a target file",
)

download_parser.add_argument(
"target",
metavar="TARGET",
help="Target file",
)

command_args = vars(client_args.parse_args())
sub_commands_args = command_args.get("sub_commands")

if command_args.get("init") is True:
init()

elif not sub_commands_args:
client_args.print_help()

if sub_commands_args == "download":
target = command_args.get("target")
download(target)

0 comments on commit 6890b04

Please sign in to comment.