From 86690739c040cbc771878c8331cebe3ba81a6fd1 Mon Sep 17 00:00:00 2001 From: Renata Date: Sat, 24 Jun 2023 01:39:45 +0200 Subject: [PATCH] refact, docs: added logging decorator library, keystore and metadata api docs --- setup.py | 1 + taf/api/keystore.py | 81 ++++++++++++++++++------- taf/api/metadata.py | 143 +++++++++++++++++++++++++++++++++----------- taf/api/roles.py | 11 ++-- taf/api/targets.py | 11 ++-- 5 files changed, 176 insertions(+), 71 deletions(-) diff --git a/setup.py b/setup.py index 33d30138..53f3a6c5 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ def finalize_options(self): "pygit2==1.9.*", "pyOpenSSL==22.1.*", "cattrs==1.*", + "logdecorator==2.*", ], "extras_require": { "ci": ci_require, diff --git a/taf/api/keystore.py b/taf/api/keystore.py index 29c6c538..a0036ac4 100644 --- a/taf/api/keystore.py +++ b/taf/api/keystore.py @@ -1,3 +1,5 @@ +from logging import DEBUG, INFO +from logdecorator import log_on_start, log_on_end from pathlib import Path from tuf.repository_tool import ( generate_and_write_rsa_keypair, @@ -7,25 +9,64 @@ from taf.api.roles import _initialize_roles_and_keystore from taf.constants import DEFAULT_ROLE_SETUP_PARAMS from taf.keys import get_key_name +from taf.log import taf_logger + + +@log_on_start(DEBUG, "Generating '{key_path:s}'", logger=taf_logger) +@log_on_end(INFO, "Generated '{key_path:s}", logger=taf_logger) +def _generate_rsa_key(key_path, password, bits=None): + """ + Generate public and private key + + Arguments: + key_path (optional): The path to write the private key to. + password (optional): An encryption password. + bits (optional): The number of bits of the generated RSA key. + + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + StorageError: Key files cannot be written. + + Side Effects: + Writes key files to disk. + Overwrites files if they already exist. + + Returns: + None + """ + if password: + generate_and_write_rsa_keypair(filepath=key_path, bits=bits, password=password) + else: + generate_and_write_unencrypted_rsa_keypair(filepath=key_path, bits=bits) def generate_keys(keystore, roles_key_infos, delegated_roles_key_infos=None): """ - - Generate public and private keys and writes them to disk. Names of keys correspond to names - of the TUF roles. If more than one key should be generated per role, a counter is appended - to the role's name. E.g. root1, root2, root3 etc. - - keystore: - Location where the generated files should be saved - roles_key_infos: - A dictionary whose keys are role names, while values contain information about the keys. - This includes: - - passwords of the keystore files - - number of keys per role (optional, defaults to one if not provided) - - key length (optional, defaults to TUF's default value, which is 3072) - Names of the keys are set to names of the roles plus a counter, if more than one key - should be generated. + Generate public and private keys and writes them to disk. Names of keys correspond to names + of TUF roles. If more than one key should be generated per role, a counter is appended + to the role's name. E.g. root1, root2, root3 etc. + + Arguments: + keystore: Location where the generated files should be saved + roles_key_infos: A dictionary whose keys are role names, while values contain information about the keys. + This includes: + - passwords of the keystore files + - number of keys per role (optional, defaults to one if not provided) + - key length (optional, defaults to TUF's default value, which is 3072) + Names of the keys are set to names of the roles plus a counter, if more than one key + should be generated. + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: One or more keys not properly specified + StorageError: Key files cannot be written. + + Side Effects: + Writes key files to disk. + Overwrites files if they already exist. + + Returns: + None """ if delegated_roles_key_infos is not None: roles_key_infos = delegated_roles_key_infos @@ -43,15 +84,9 @@ def generate_keys(keystore, roles_key_infos, delegated_roles_key_infos=None): for key_num in range(num_of_keys): if not is_yubikey: key_name = get_key_name(role_name, key_num, num_of_keys) + key_path = str(Path(keystore, key_name)) password = passwords[key_num] - path = str(Path(keystore, key_name)) - print(f"Generating {path}") - if password: - generate_and_write_rsa_keypair( - filepath=path, bits=bits, password=password - ) - else: - generate_and_write_unencrypted_rsa_keypair(filepath=path, bits=bits) + _generate_rsa_key(key_path, password, bits) if key_info.get("delegations"): delegations_info = {"roles": key_info["delegations"]} generate_keys(keystore, roles_key_infos, delegations_info) diff --git a/taf/api/metadata.py b/taf/api/metadata.py index f2da2d54..b8f22b43 100644 --- a/taf/api/metadata.py +++ b/taf/api/metadata.py @@ -1,28 +1,33 @@ import datetime +from logging import DEBUG, ERROR, INFO from pathlib import Path +from logdecorator import log_on_end, log_on_error, log_on_start from taf.exceptions import TargetsMetadataUpdateError from taf.git import GitRepository from taf.keys import load_signing_keys from taf.constants import DEFAULT_RSA_SIGNATURE_SCHEME from taf.repository_tool import Repository, is_delegated_role +from taf.log import taf_logger def check_expiration_dates( repo_path, interval=None, start_date=None, excluded_roles=None ): """ - - Check if any metadata files (roles) are expired or will expire in the next days. - Prints a list of expired roles. - - repo_path: - Authentication repository's location - interval: - Number of days ahead to check for expiration - start_date: - Date from which to start checking for expiration - excluded_roles: - List of roles to exclude from the check + Check if any metadata files (roles) are expired or will expire in the next days. + Prints a list of expired roles. + + Arguments: + repo_path: Authentication repository's location. + interval: Number of days ahead to check for expiration. + start_date: Date from which to start checking for expiration. + excluded_roles: List of roles to exclude from the check. + + Side Effects: + Prints lists of roles that expired or are about to expire. + + Returns: + None """ repo_path = Path(repo_path) taf_repo = Repository(repo_path) @@ -59,6 +64,29 @@ def update_metadata_expiration_date( start_date=None, no_commit=False, ): + """ + Update expiration dates of the specified roles and all other roles that need + to be signed in order to guarantee validity of the repository e.g. snapshot + and timestamp need to be signed after a targets role is updated. + + Arguments: + repo_path: Authentication repository's location. + roles: A list of roles whose expiration dates should be updated. + interval: Number of days added to the start date in order to calculate the + expiration date. + keystore (optional): Keystore directory's path + scheme (optional): Signature scheme. + start_date (optional): Date to which expiration interval is added. + Set to today if not specified. + no_commit (optional): Prevents automatic commit if set to True + + Side Effects: + Updates metadata files, saves changes to disk and commits changes + unless no_commit is set to True. + + Returns: + None + """ if start_date is None: start_date = datetime.datetime.now() @@ -80,27 +108,11 @@ def update_metadata_expiration_date( for role in roles_to_update: try: - keys, yubikeys = load_signing_keys( - taf_repo, - role, - loaded_yubikeys=loaded_yubikeys, - keystore=keystore, - scheme=scheme, + _update_expiration_date_of_role( + taf_repo, role, loaded_yubikeys, keystore, start_date, interval, scheme ) - # sign with keystore - if len(keys): - taf_repo.update_role_keystores( - role, keys, start_date=start_date, interval=interval - ) - if len(yubikeys): # sign with yubikey - taf_repo.update_role_yubikeys( - role, yubikeys, start_date=start_date, interval=interval - ) except Exception as e: - print(f"Could not update expiration date of {role}. {str(e)}") return - else: - print(f"Updated expiration date of {role}") if no_commit: print("\nNo commit was set. Please commit manually. \n") @@ -110,9 +122,53 @@ def update_metadata_expiration_date( auth_repo.commit(commit_message) +@log_on_end(INFO, "Updated expiration date of {role:s}", logger=taf_logger) +@log_on_error( + ERROR, + "Could not update expiration date of {role:s} {e!r}", + logger=taf_logger, + reraise=True, +) +def _update_expiration_date_of_role( + taf_repo, role, loaded_yubikeys, keystore, start_date, interval, scheme +): + keys, yubikeys = load_signing_keys( + taf_repo, + role, + loaded_yubikeys=loaded_yubikeys, + keystore=keystore, + scheme=scheme, + ) + # sign with keystore + if len(keys): + taf_repo.update_role_keystores( + role, keys, start_date=start_date, interval=interval + ) + if len(yubikeys): # sign with yubikey + taf_repo.update_role_yubikeys( + role, yubikeys, start_date=start_date, interval=interval + ) + + def update_snapshot_and_timestamp( - taf_repo, keystore, roles_infos, scheme=DEFAULT_RSA_SIGNATURE_SCHEME, write_all=True + taf_repo, keystore, scheme=DEFAULT_RSA_SIGNATURE_SCHEME, write_all=True ): + """ + Sign snapshot and timestamp metadata files. + + Arguments: + taf_repo: Authentication repository. + keystore: Keystore directory's path. + scheme (optional): Signature scheme. + write_all (optional): If True, writes authentication repository's + changes to disk. + + Side Effects: + Updates metadata files, saves changes to disk if write_all is True + + Returns: + None + """ loaded_yubikeys = {} for role in ("snapshot", "timestamp"): @@ -135,13 +191,30 @@ def update_target_metadata( added_targets_data, removed_targets_data, keystore, - roles_infos, write=False, scheme=DEFAULT_RSA_SIGNATURE_SCHEME, ): - """Update given targets data with an appropriate role, as well as snapshot and - timestamp roles. + """Given dictionaries containing targets that should be added and targets that should + be removed, update and sign target metadata files and, if write is True, also + sign snapshot and timestamp. + + Sing snapshot and timestamp metadata files + + Arguments: + taf_repo: Authentication repository. + added_targets_data(dict): Dictionary containing targets data that should be added. + removed_targets_data(dict): Dictionary containing targets data that should be removed. + keystore: Keystore directory's path. + write (optional): If True, updates snapshot and timestamp and write changes to disk. + scheme (optional): Signature scheme. + + Side Effects: + Updates metadata files, saves changes to disk if write_all is True + + Returns: + None """ + added_targets_data = {} if added_targets_data is None else added_targets_data removed_targets_data = {} if removed_targets_data is None else removed_targets_data @@ -181,4 +254,4 @@ def update_target_metadata( ) if write: - update_snapshot_and_timestamp(taf_repo, keystore, roles_infos, scheme=scheme) + update_snapshot_and_timestamp(taf_repo, keystore, scheme=scheme) diff --git a/taf/api/roles.py b/taf/api/roles.py index 25b04e7c..c1ab65fa 100644 --- a/taf/api/roles.py +++ b/taf/api/roles.py @@ -80,7 +80,7 @@ def add_role( ) _update_role(auth_repo, parent_role, keystore, roles_infos, scheme=scheme) if commit: - update_snapshot_and_timestamp(auth_repo, keystore, roles_infos, scheme=scheme) + update_snapshot_and_timestamp(auth_repo, keystore, scheme=scheme) commit_message = input("\nEnter commit message and press ENTER\n\n") auth_repo.commit(commit_message) @@ -95,7 +95,7 @@ def add_role_paths( parent_role_obj.add_paths(paths, delegated_role) _update_role(auth_repo, parent_role, keystore) if commit: - update_snapshot_and_timestamp(auth_repo, keystore, None, None) + update_snapshot_and_timestamp(auth_repo, keystore) commit_message = input("\nEnter commit message and press ENTER\n\n") auth_repo.commit(commit_message) @@ -220,7 +220,7 @@ def add_signing_key( for parent_role in parent_roles: _update_role(taf_repo, parent_role, keystore, roles_infos, scheme) - update_snapshot_and_timestamp(taf_repo, keystore, roles_infos, scheme=scheme) + update_snapshot_and_timestamp(taf_repo, keystore, scheme=scheme) def _enter_roles_infos(keystore, roles_key_infos): @@ -486,7 +486,6 @@ def remove_role( added_targets_data, removed_targets_data, keystore, - roles_infos=None, write=False, scheme=DEFAULT_RSA_SIGNATURE_SCHEME, ) @@ -504,7 +503,7 @@ def remove_role( json.dumps(repositories_json, indent=4) ) - update_snapshot_and_timestamp(auth_repo, keystore, None, scheme) + update_snapshot_and_timestamp(auth_repo, keystore, scheme=scheme) if commit: commit_message = input("\nEnter commit message and press ENTER\n\n") auth_repo.commit(commit_message) @@ -521,7 +520,7 @@ def remove_paths(paths, keystore, commit=True, auth_repo=None, auth_path=None): _remove_path_from_role_info(path, parent_role, delegated_role, auth_repo) _update_role(auth_repo, parent_role, keystore) if commit: - update_snapshot_and_timestamp(auth_repo, keystore, None, None) + update_snapshot_and_timestamp(auth_repo, keystore) commit_message = input("\nEnter commit message and press ENTER\n\n") auth_repo.commit(commit_message) diff --git a/taf/api/targets.py b/taf/api/targets.py index 9761dc1e..f7bda64d 100644 --- a/taf/api/targets.py +++ b/taf/api/targets.py @@ -108,13 +108,12 @@ def add_target_repo( added_targets_data, removed_targets_data, keystore, - roles_infos=None, write=False, scheme=scheme, ) # update snapshot and timestamp calls write_all, so targets updates will be saved too - update_snapshot_and_timestamp(auth_repo, keystore, None, scheme=scheme) + update_snapshot_and_timestamp(auth_repo, keystore, scheme=scheme) commit_message = input("\nEnter commit message and press ENTER\n\n") auth_repo.commit(commit_message) @@ -255,8 +254,7 @@ def register_target_files( added_targets_data, removed_targets_data, keystore, - roles_infos, - scheme, + scheme=scheme, ) if commit: @@ -302,19 +300,18 @@ def remove_target_repo( added_targets_data, removed_targets_data, keystore, - roles_infos=None, write=False, ) update_snapshot_and_timestamp( - auth_repo, keystore, None, scheme=DEFAULT_RSA_SIGNATURE_SCHEME + auth_repo, keystore, scheme=DEFAULT_RSA_SIGNATURE_SCHEME ) auth_repo.commit(f"Remove {target_name} target") # commit_message = input("\nEnter commit message and press ENTER\n\n") remove_paths([target_name], keystore, commit=False, auth_repo=auth_repo) update_snapshot_and_timestamp( - auth_repo, keystore, None, scheme=DEFAULT_RSA_SIGNATURE_SCHEME + auth_repo, keystore, scheme=DEFAULT_RSA_SIGNATURE_SCHEME ) auth_repo.commit(f"Remove {target_name} from delegated paths") # update snapshot and timestamp calls write_all, so targets updates will be saved too