Skip to content

Commit

Permalink
feat(releasing): adding SHA512 and RSA signature validation script to…
Browse files Browse the repository at this point in the history
… verify releases (apache#26278)
  • Loading branch information
rusackas authored and sfirke committed Mar 22, 2024
1 parent 08702f6 commit dfcd5c7
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 1 deletion.
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 [email protected]

## 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

0 comments on commit dfcd5c7

Please sign in to comment.