Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(releasing): adding SHA512 and RSA signature validation script to verify releases #26278

Merged
merged 4 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions RELEASING/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,23 @@ The script will generate the email text that should be sent to dev@superset.apac

## Validating a release

Official instructions:
https://www.apache.org/info/verification.html

We now have a handy script for anyone validating a release to use. The core of it is in this very folder, `verify_release.py`. Just make sure you have all three release files in the same directory (`{some version}.tar.gz`, `{some version}.tar.gz.asc` and `{some version}tar.gz.sha512`). Then you can pass this script the path to the `.gz` file like so:
`python verify_release.py ~/path/tp/apache-superset-{version/candidate}-source.tar.gz`


If all goes well, you will see this result in your terminal:
```bash
SHA-512 verified
RSA key verified
```

There are also additional support scripts leveraging this to make it easy for those downloading a release to test it in-situ. You can do either of the following to validate these release assets:
* `cd` into `superset-frontend` and run `npm run validate-release`
* `cd` into `RELEASES` and run `./validate_this_release.sh`

## Publishing a successful release

Upon a successful vote, you'll have to copy the folder into the non-"dev/" folder.
Expand Down
54 changes: 54 additions & 0 deletions RELEASING/validate_this_release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

#!/bin/bash

# Function to determine Python command
get_python_command() {
if command -v python3 &>/dev/null; then
echo "python3"
else
echo "python"
fi
}

# Function to determine Pip command
get_pip_command() {
if command -v pip3 &>/dev/null; then
echo "pip3"
else
echo "pip"
fi
}

PYTHON=$(get_python_command)
PIP=$(get_pip_command)

# Get the release directory's path. If you unzip an Apache release and just run the npm script to validate the release, this will be a file name like `apache-superset-x.x.xrcx-source.tar.gz`
RELEASE_DIR_NAME="../../$(basename "$(dirname "$(pwd)")").tar.gz"

# Install dependencies from requirements.txt if the file exists
if [ -f "path/to/requirements.txt" ]; then
echo "Installing Python dependencies..."
$PYTHON -m $PIP install -r path/to/requirements.txt
fi

# echo $PYTHON
# echo $RELEASE_DIR_NAME

# Run the Python script with the parent directory name as an argument
$PYTHON ../RELEASING/verify_release.py "$RELEASE_DIR_NAME"
101 changes: 101 additions & 0 deletions RELEASING/verify_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import re
import subprocess
import sys
from typing import Optional

import requests

# Part 1: Verify SHA512 hash - this is the same as running `shasum -a 512 {release}` and comparing it against `{release}.sha512`


def get_sha512_hash(filename: str) -> str:
"""Run the shasum command on the file and return the SHA512 hash."""
result = subprocess.run(["shasum", "-a", "512", filename], stdout=subprocess.PIPE)
sha512_hash = result.stdout.decode().split()[0]
return sha512_hash


def read_sha512_file(filename: str) -> str:
"""Read the corresponding .sha512 file and process its contents."""
sha_filename = filename + ".sha512"
with open(sha_filename) as file:
lines = file.readlines()
processed_sha = "".join(lines[1:]).replace(" ", "").replace("\n", "").lower()
return processed_sha


def verify_sha512(filename: str) -> str:
"""Verify if the SHA512 hash of the file matches with the hash in the .sha512 file."""
sha512_hash = get_sha512_hash(filename)
sha512_file_content = read_sha512_file(filename)

if sha512_hash == sha512_file_content:
return "SHA verified"
else:
return "SHA failed"


# 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


def get_gpg_info(filename: str) -> tuple[Optional[str], Optional[str]]:
"""Run the GPG verify command and extract RSA key and email address."""
asc_filename = filename + ".asc"
result = subprocess.run(
["gpg", "--verify", asc_filename, filename], capture_output=True
)
output = result.stderr.decode()

rsa_key = re.search(r"RSA key ([0-9A-F]+)", output)
email = re.search(r'issuer "([^"]+)"', output)

rsa_key_result = rsa_key.group(1) if rsa_key else None
email_result = email.group(1) if email else None

# Debugging: print warnings if rsa_key or email is not found
if rsa_key_result is None:
print("Warning: No RSA key found in GPG verification output.")
if email_result is None:
print("Warning: No email address found in GPG verification output.")

return rsa_key_result, email_result


def verify_rsa_key(rsa_key: str, email: Optional[str]) -> str:
"""Fetch the KEYS file and verify if the RSA key and email match."""
url = "https://downloads.apache.org/superset/KEYS"
response = requests.get(url)
if response.status_code == 200:
if rsa_key not in response.text:
return "RSA key not found on KEYS page"

# Check if email is None or not in response.text
if email and email in response.text:
return "RSA key and email verified against Apache KEYS file"
elif email:
return "RSA key verified, but Email not found on KEYS page"
else:
return "RSA key verified, but Email not available for verification"
else:
return "Failed to fetch KEYS file"


def verify_sha512_and_rsa(filename: str) -> None:
"""Verify SHA512 hash and RSA key."""
sha_result = verify_sha512(filename)
print(sha_result)

rsa_key, email = get_gpg_info(filename)
if rsa_key:
rsa_result = verify_rsa_key(rsa_key, email)
print(rsa_result)
else:
print("GPG verification failed: RSA key or email not found")


if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py <filename>")
else:
filename = sys.argv[1]
verify_sha512_and_rsa(filename)
3 changes: 2 additions & 1 deletion superset-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development start-storybook -p 6006",
"tdd": "cross-env NODE_ENV=test jest --watch",
"test": "cross-env NODE_ENV=test jest",
"type": "tsc --noEmit"
"type": "tsc --noEmit",
"validate-release": "../RELEASING/validate_this_release.sh"
},
"browserslist": [
"last 3 chrome versions",
Expand Down
Loading