diff --git a/RELEASING/verify_release.py b/RELEASING/verify_release.py index 61bbf073ab0..9e0347bc146 100755 --- a/RELEASING/verify_release.py +++ b/RELEASING/verify_release.py @@ -56,8 +56,33 @@ def verify_sha512(filename: str) -> str: # Part 2: Verify RSA key - this is the same as running `gpg --verify {release}.asc {release}` and comparing the RSA key and email address against the KEYS file # noqa: E501 +KEYS_URL = "https://downloads.apache.org/superset/KEYS" + + +def ensure_keys_imported() -> None: + """Import the Apache Superset KEYS file into the local GPG keyring. + + Without this, `gpg --verify` returns "No public key" and the signature + cannot actually be verified — only the key ID in the signature metadata + is visible. + """ + try: + keys = requests.get(KEYS_URL, timeout=30) + except requests.RequestException as exc: + print(f"Warning: could not fetch KEYS file for import: {exc}") + return + if keys.status_code != 200: + print(f"Warning: could not fetch KEYS file (HTTP {keys.status_code})") + return + subprocess.run( # noqa: S603 + ["gpg", "--import"], # noqa: S607 + input=keys.content, + capture_output=True, + ) + + def get_gpg_info(filename: str) -> tuple[Optional[str], Optional[str]]: - """Run the GPG verify command and extract RSA key and email address.""" + """Run the GPG verify command and extract RSA/EDDSA key and email address.""" asc_filename = filename + ".asc" result = subprocess.run( # noqa: S603 ["gpg", "--verify", asc_filename, filename], # noqa: S607 @@ -65,25 +90,50 @@ def get_gpg_info(filename: str) -> tuple[Optional[str], Optional[str]]: ) output = result.stderr.decode() + # If no public key was available, import KEYS and retry so that + # `Good signature from "Name "` appears in the output. + if "No public key" in output: + ensure_keys_imported() + result = subprocess.run( # noqa: S603 + ["gpg", "--verify", asc_filename, filename], # noqa: S607 + capture_output=True, # noqa: S607 + ) + output = result.stderr.decode() + rsa_key = re.search(r"RSA key ([0-9A-F]+)", output) eddsa_key = re.search(r"EDDSA key ([0-9A-F]+)", output) - email = re.search(r'issuer "([^"]+)"', output) + + # Try multiple patterns — `Good signature from` is the most reliable + # source of the email; `issuer` is a fallback for older gpg output. + email_patterns = ( + r'Good signature from ".*?<([^>]+)>"', + r'aka ".*?<([^>]+)>"', + r'issuer "([^"]+)"', + ) + email_result: Optional[str] = None + for pattern in email_patterns: + match = re.search(pattern, output) + if match: + email_result = match.group(1) + break rsa_key_result = rsa_key.group(1) if rsa_key else None eddsa_key_result = eddsa_key.group(1) if eddsa_key else None - email_result = email.group(1) if email else None - key_result = rsa_key_result or eddsa_key_result - # Debugging: if key_result: print("RSA or EDDSA Key found") else: print("Warning: No RSA or EDDSA key found in GPG verification output.") if email_result: - print("email found") + print(f"Email found: {email_result}") else: print("Warning: No email address found in GPG verification output.") + if "No public key" in output: + print( + "Hint: public key is not in your keyring. Import it with:\n" + f" curl -s {KEYS_URL} | gpg --import" + ) return key_result, email_result