diff --git a/check_fips_changes.py b/check_fips_changes.py new file mode 100755 index 0000000..3ebd6d4 --- /dev/null +++ b/check_fips_changes.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Check for FIPS protected directory changes in a range of git commits. + +Exits 0 if no FIPS changes found (or --fips-override is set). +Exits 1 if FIPS changes are found without override. +""" + +import argparse +import subprocess +import sys + +from kt.ktlib.ciq_helpers import FIPS_PROTECTED_DIRECTORIES, check_for_fips_protected_changes + + +def main(): + parser = argparse.ArgumentParser(description="Check for FIPS protected directory changes") + parser.add_argument("--repo", help="Path to git repository", default=".") + parser.add_argument("--base-ref", help="Base ref (exclusive start of range)", required=True) + parser.add_argument("--target-ref", help="Target ref (inclusive end of range)", required=True) + parser.add_argument("--fips-override", help="Override FIPS check abort", action="store_true") + args = parser.parse_args() + + print(f"[fips-check] Checking for FIPS protected changes in {args.base_ref}..{args.target_ref}") + print(f"[fips-check] Protected directories: {', '.join(d.decode() for d in FIPS_PROTECTED_DIRECTORIES)}") + + try: + fips_commits = check_for_fips_protected_changes(args.repo, args.base_ref, args.target_ref) + except RuntimeError as e: + print(f"[fips-check] ERROR: {e}", file=sys.stderr) + sys.exit(1) + + if not fips_commits: + print("[fips-check] No FIPS protected changes found") + sys.exit(0) + + print("\n[fips-check] ========================================") + print("[fips-check] FIPS protected changes detected") + print("[fips-check] ========================================") + print(f"[fips-check] {len(fips_commits)} commit(s) touch FIPS protected directories:\n") + + for sha, dirs in fips_commits.items(): + result = subprocess.run( + ["git", "show", "--stat", sha.decode()], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=args.repo, + ) + print(f"## Commit {sha.decode()}") + if result.returncode == 0: + print(result.stdout.decode("utf-8", "backslashreplace")) + else: + print(" (could not show commit details)") + for d in sorted(dirs): + print(f" FIPS directory: {d.decode()}") + print() + + if args.fips_override: + print("[fips-check] --fips-override set, continuing despite FIPS protected changes") + sys.exit(0) + + print("[fips-check] Please contact the CIQ FIPS / Security team for further instructions") + print("[fips-check] Use --fips-override to bypass this check") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/kt/ktlib/ciq_helpers.py b/kt/ktlib/ciq_helpers.py index 8791770..8c9253a 100644 --- a/kt/ktlib/ciq_helpers.py +++ b/kt/ktlib/ciq_helpers.py @@ -480,6 +480,102 @@ def read_spec_el_version(spec_lines): return _read_spec_define(spec_lines, "el_version", r"\d+") +FIPS_PROTECTED_DIRECTORIES = [ + b"arch/x86/crypto/", + b"crypto/asymmetric_keys/", + b"crypto/", + b"drivers/crypto/", + b"drivers/char/random.c", + b"include/crypto", +] + + +def check_for_fips_protected_changes(repo_path, start_ref, end_ref): + """Check for changes to FIPS protected directories in a range of commits. + + Iterates over commits in start_ref..end_ref and checks whether any + modified files fall under a FIPS protected directory. Uses bytestrings + throughout to avoid encoding issues with international contributor names + in git output. + + Parameters: + repo_path: Path to the git repository. + start_ref: The starting ref (exclusive) for the commit range. + end_ref: The ending ref (inclusive) for the commit range. + + Returns: + dict mapping commit SHA (bytes) -> set of matched FIPS directory prefixes (bytes) + for each commit that touches FIPS protected paths. Empty dict if none found. + """ + print("[fips-check] Checking for FIPS protected changes") + print(f"[fips-check] Getting SHAS {start_ref}..{end_ref}") + results = subprocess.run( + ["git", "log", "--pretty=%H", f"{start_ref}..{end_ref}"], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + cwd=repo_path, + ) + if results.returncode != 0: + print(results.stderr) + raise RuntimeError(f"git log failed for range {start_ref}..{end_ref}") + + num_commits = len(results.stdout.split(b"\n")) + print("[fips-check] Number of commits to check: ", num_commits) + shas_to_check = {} + commits_checked = 0 + + progress_interval = max(1, num_commits // 10) + + print("[fips-check] Checking modifications of shas") + for sha in results.stdout.split(b"\n"): + commits_checked += 1 + if commits_checked % progress_interval == 0: + print(f"[fips-check] Checked {commits_checked} of {num_commits} commits") + if sha == b"": + continue + res = subprocess.run( + ["git", "show", "--name-only", "--pretty=%H %s", f"{sha.decode()}"], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + cwd=repo_path, + ) + if res.returncode != 0: + print(res) + print(res.stderr) + raise RuntimeError(f"git show failed for {sha}") + + sha_hash_and_subject = b"" + touched_fips_files = set() + + for line in res.stdout.split(b"\n"): + if sha_hash_and_subject == b"": + sha_hash_and_subject = line + continue + if line == b"": + continue + + add_to_check = False + + for dir in FIPS_PROTECTED_DIRECTORIES: + if line.startswith(dir): + add_to_check = True + if dir not in touched_fips_files: + touched_fips_files.add(dir) + + if add_to_check: + shas_to_check[sha_hash_and_subject.split(b" ")[0]] = touched_fips_files + + if touched_fips_files: + print(f"[fips-check] Checked commit {sha} touched {len(touched_fips_files)} FIPS protected files") + for f in touched_fips_files: + print(f" - {f}") + sha_hash_and_subject = b"" + + print(f"[fips-check] {len(shas_to_check)} of {num_commits} commits have FIPS protected changes") + + return shas_to_check + + def run_cve_search(vulns_repo, kernel_repo, query) -> tuple[bool, Optional[str]]: """ Run the cve_search script from the vulns repo. diff --git a/rolling-release-update.py b/rolling-release-update.py index 645dae0..9f5e52a 100644 --- a/rolling-release-update.py +++ b/rolling-release-update.py @@ -6,16 +6,10 @@ import git -from kt.ktlib.ciq_helpers import get_backport_commit_data - -FIPS_PROTECTED_DIRECTORIES = [ - b"arch/x86/crypto/", - b"crypto/asymmetric_keys/", - b"crypto/", - b"drivers/crypto/", - b"drivers/char/random.c", - b"include/crypto", -] +from kt.ktlib.ciq_helpers import ( + check_for_fips_protected_changes, + get_backport_commit_data, +) DEBUG = False @@ -108,84 +102,6 @@ def get_branch_tag_sha_list(repo, branch, minor_version=False): return tags, last_resf_tag -def check_for_fips_protected_changes(repo, branch, common_tag): - print("[rolling release update] Checking for FIPS protected changes") - repo.git.checkout(branch) - print(f"[rolling release update] Getting SHAS {common_tag.decode()}..HEAD") - results = subprocess.run( - ["git", "log", "--pretty=%H", f"{common_tag.decode()}..HEAD"], - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, - cwd=repo.working_dir, - ) - if results.returncode != 0: - print(results.stderr) - exit(1) - - num_commits = len(results.stdout.split(b"\n")) - print("[rolling release update] Number of commits to check: ", num_commits) - shas_to_check = {} - commits_checked = 0 - - progress_interval = max(1, num_commits // 10) - - print("[rolling release update] Checking modifications of shas") - if DEBUG: - print(results.stdout.split(b"\n")) - for sha in results.stdout.split(b"\n"): - commits_checked += 1 - if commits_checked % progress_interval == 0: - print(f"[rolling release update] Checked {commits_checked} of {num_commits} commits") - if sha == b"": - continue - res = subprocess.run( - ["git", "show", "--name-only", "--pretty=%H %s", f"{sha.decode()}"], - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, - cwd=repo.working_dir, - ) - if res.returncode != 0: - print(res) - print(res.stderr) - exit(1) - - sha_hash_and_subject = b"" - touched_fips_files = set() - - for line in res.stdout.split(b"\n"): - if sha_hash_and_subject == b"": - sha_hash_and_subject = line - continue - if line == b"": - continue - - add_to_check = False - - for dir in FIPS_PROTECTED_DIRECTORIES: - if line.startswith(dir): - if DEBUG: - print(f"FIPS protected directory {dir} change found in commit {sha}") - print(sha_hash_and_subject) - add_to_check = True - if dir not in touched_fips_files: - touched_fips_files.add(dir) - - if add_to_check: - shas_to_check[sha_hash_and_subject.split(b" ")[0]] = touched_fips_files - - if touched_fips_files: - print( - f"[rolling release update] Checked commit {sha} touched {len(touched_fips_files)} FIPS protected files" - ) - for f in touched_fips_files: - print(f" - {f}") - sha_hash_and_subject = b"" - - print(f"[rolling release update] {len(shas_to_check)} of {num_commits} commits have FIPS protected changes") - - return shas_to_check - - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Rolling release update") parser.add_argument("--repo", help="Repository path", required=True) @@ -250,7 +166,12 @@ def check_for_fips_protected_changes(repo, branch, common_tag): print(repo.git.show('--pretty="%H %s"', "-s", common_sha.decode())) print("[rolling release update] Checking for FIPS protected changes between the common tag and HEAD") - shas_to_check = check_for_fips_protected_changes(repo, args.new_base_branch, common_sha) + repo.git.checkout(args.new_base_branch) + try: + shas_to_check = check_for_fips_protected_changes(args.repo, common_sha.decode(), "HEAD") + except RuntimeError as e: + print(f"[rolling release update] {e}") + exit(1) if shas_to_check and args.fips_override is False: for sha, dir in shas_to_check.items(): print(f"## Commit {sha.decode()}")