docker: add verification script to production image

The verification script makes sure the hashes of the binaries inside of
a docker image match those of an official release.
The script first downloads all signatures, validates them, then compares
the hashes of the installed binaries to those contained in the detached
signature files.
This commit is contained in:
Oliver Gugger 2021-01-13 14:55:54 +01:00
parent 25ac071300
commit 97a141e7af
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
2 changed files with 154 additions and 2 deletions

@ -31,15 +31,19 @@ FROM alpine as final
# Define a root volume for data persistence. # Define a root volume for data persistence.
VOLUME /root/.lnd VOLUME /root/.lnd
# Add bash, jq and ca-certs, for quality of life and SSL-related reasons. # Add utilities for quality of life and SSL-related reasons. We also require
# curl and gpg for the signature verification script.
RUN apk --no-cache add \ RUN apk --no-cache add \
bash \ bash \
jq \ jq \
ca-certificates ca-certificates \
gnupg \
curl
# Copy the binaries from the builder image. # Copy the binaries from the builder image.
COPY --from=builder /go/bin/lncli /bin/ COPY --from=builder /go/bin/lncli /bin/
COPY --from=builder /go/bin/lnd /bin/ COPY --from=builder /go/bin/lnd /bin/
COPY --from=builder /go/src/github.com/lightningnetwork/lnd/scripts/verify-install.sh /
# Store the SHA256 hash of the binaries that were just produced for later # Store the SHA256 hash of the binaries that were just produced for later
# verification. # verification.

148
scripts/verify-install.sh Executable file

@ -0,0 +1,148 @@
#!/bin/bash
REPO=lightningnetwork
PROJECT=lnd
RELEASE_URL=https://github.com/$REPO/$PROJECT/releases
API_URL=https://api.github.com/repos/$REPO/$PROJECT/releases
SIGNATURE_SELECTOR=". | select(.name | test(\"manifest-.*(\\\\.txt\\\\.asc)$\")) | .name"
HEADER_JSON="Accept: application/json"
HEADER_GH_JSON="Accept: application/vnd.github.v3+json"
# All keys that can sign lnd releases. The key must be downloadable/importable
# from the URL given after the space.
KEYS=()
KEYS+=("F4FC70F07310028424EFC20A8E4256593F177720 https://keybase.io/guggero/pgp_keys.asc")
KEYS+=("15E7ECF257098A4EF91655EB4CA7FE54A6213C91 https://keybase.io/carlakirkcohen/pgp_keys.asc")
KEYS+=("9C8D61868A7C492003B2744EE7D737B67FA592C7 https://keybase.io/bitconner/pgp_keys.asc")
KEYS+=("E4D85299674B2D31FAA1892E372CBD7633C61696 https://keybase.io/roasbeef/pgp_keys.asc")
KEYS+=("729E9D9D92C75A5FBFEEE057B5DD717BEF7CA5B1 https://keybase.io/wpaulino/pgp_keys.asc")
KEYS+=("7E81EF6B9989A9CC93884803118759E83439A9B1 https://keybase.io/eugene_/pgp_keys.asc")
function check_command() {
echo -n "Checking if $1 is installed... "
if ! command -v "$1"; then
echo "ERROR: $1 is not installed or not in PATH!"
exit 1
fi
}
check_command curl
check_command jq
check_command gpg
check_command lnd
check_command lncli
LND_BIN=$(which lnd)
LNCLI_BIN=$(which lncli)
LND_VERSION=$(lnd --version | cut -d'=' -f2)
LNCLI_VERSION=$(lncli --version | cut -d'=' -f2)
LND_SUM=$(sha256sum $LND_BIN | cut -d' ' -f1)
LNCLI_SUM=$(sha256sum $LNCLI_BIN | cut -d' ' -f1)
echo "Detected lnd version $LND_VERSION with SHA256 sum $LND_SUM"
echo "Detected lncli version $LNCLI_VERSION with SHA256 sum $LNCLI_SUM"
# Make sure lnd and lncli are installed with the same version.
if [[ "$LNCLI_VERSION" != "$LND_VERSION" ]]; then
echo "ERROR: Version $LNCLI_VERSION of lncli does not match $LND_VERSION of lnd!"
exit 1
fi
# If we're inside the docker image, there should be a shasums.txt file in the
# root directory. If that's the case, we first want to make sure we still have
# the same hash as we did when building the image.
if [[ -f /shasums.txt ]]; then
if ! grep -q "$LND_SUM" /shasums.txt; then
echo "ERROR: Hash $LND_SUM for lnd not found in /shasums.txt: "
cat /shasums.txt
exit 1
fi
if ! grep -q "$LNCLI_SUM" /shasums.txt; then
echo "ERROR: Hash $LNCLI_SUM for lnd not found in /shasums.txt: "
cat /shasums.txt
exit 1
fi
fi
# Import all the signing keys.
for key in "${KEYS[@]}"; do
KEY_ID=$(echo $key | cut -d' ' -f1)
IMPORT_URL=$(echo $key | cut -d' ' -f2)
echo "Downloading and importing key $KEY_ID from $IMPORT_URL"
curl -L -s $IMPORT_URL | gpg --import
# Make sure we actually imported the correct key.
if ! gpg --list-key "$KEY_ID"; then
echo "ERROR: Imported key from $IMPORT_URL doesn't match ID $KEY_ID."
fi
done
echo ""
# Download the JSON of the release itself. That'll contain the release ID we need for the next call.
RELEASE_JSON=$(curl -L -s -H "$HEADER_JSON" "$RELEASE_URL/$LND_VERSION")
TAG_NAME=$(echo $RELEASE_JSON | jq -r '.tag_name')
RELEASE_ID=$(echo $RELEASE_JSON | jq -r '.id')
echo "Release $TAG_NAME found with ID $RELEASE_ID"
# Now download the asset list and filter by manifests and signatures.
ASSETS=$(curl -L -s -H "$HEADER_GH_JSON" "$API_URL/$RELEASE_ID" | jq -c '.assets[]')
SIGNATURES=$(echo $ASSETS | jq -r "$SIGNATURE_SELECTOR")
# Download all "manifest-*.txt.asc" as those contain both the hashes that were
# signed and the signature itself (=detached sig).
TEMP_DIR=$(mktemp -d /tmp/lnd-sig-verification-XXXXXX)
for signature in $SIGNATURES; do
echo "Downloading $signature"
curl -L -s -o "$TEMP_DIR/$signature" "$RELEASE_URL/download/$LND_VERSION/$signature"
done
echo ""
cd $TEMP_DIR || exit 1
NUM_CHECKS=0
for signature in $SIGNATURES; do
# First make sure the downloaded signature file is valid.
echo "Verifying $signature"
if ! gpg --verify "$signature" 2>&1 | grep -q "Good signature"; then
echo "ERROR: Did not get valid signature for $signature!"
exit 1
fi
echo "Signature for $signature checks out: "
gpg --verify "$signature" 2>&1 | grep "using"
echo ""
# Then make sure that the hash of the installed binaries can be found in the
# signed list of hashes.
if ! grep -q "$LND_SUM" "$signature"; then
echo "ERROR: Hash $LND_SUM for lnd not found in $signature: "
cat "$signature"
exit 1
fi
if ! grep -q "$LNCLI_SUM" "$signature"; then
echo "ERROR: Hash $LNCLI_SUM for lncli not found in $signature: "
cat "$signature"
exit 1
fi
echo "Verified lnd and lncli hashes against $signature"
((NUM_CHECKS=NUM_CHECKS+1))
done
# We want at least one signature that signs the hashes of the binaries we have
# installed. If we arrive here without exiting, it means no signature manifests
# were uploaded (yet) with the correct naming pattern.
if [[ $NUM_CHECKS -lt 1 ]]; then
echo "ERROR: No valid signatures found!"
echo "Make sure the release $LND_VERSION contains any signed manifests."
exit 1
fi
echo ""
echo "SUCCESS! Verified lnd and lncli against $NUM_CHECKS signature(s)."