diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d507034e..3eaff6fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,30 +8,6 @@ on: inputs: jobs: - deb_build: - name: deb build - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Docker build - run: ./docker/build/host-refresh.sh "$GITHUB_WORKSPACE" amd64 - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v5.3.0 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - - name: Docker run - run: ./docker/build/host-build.sh "$GITHUB_WORKSPACE" amd64 deb - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: wordfence_cli_deb - path: | - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.deb - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.deb.sha256 - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.deb.asc - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.deb.sha256.asc linux_standalone_build: name: Linux standalone build runs-on: ubuntu-20.04 @@ -47,14 +23,9 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Docker build - run: ./docker/build/host-refresh.sh "$GITHUB_WORKSPACE" "$ARCHITECTURE" + run: ./docker/build/host-refresh.sh "$GITHUB_WORKSPACE" "$ARCHITECTURE" standalone env: ARCHITECTURE: ${{ matrix.arch }} - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v5.3.0 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Docker run run: ./docker/build/host-build.sh "$GITHUB_WORKSPACE" "$ARCHITECTURE" standalone env: @@ -63,11 +34,22 @@ jobs: uses: actions/upload-artifact@v3 with: name: wordfence_cli_${{ matrix.arch }} - path: | - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.tar.gz - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.tar.gz.sha256 - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.tar.gz.asc - ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.tar.gz.sha256.asc + path: ${{ github.workspace }}/docker/build/volumes/output/wordfence_*.tar.gz + deb_build: + name: deb build + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Docker build + run: ./docker/build/host-refresh.sh "$GITHUB_WORKSPACE" amd64 deb + - name: Docker run + run: ./docker/build/host-build.sh "$GITHUB_WORKSPACE" amd64 deb + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: wordfence_cli_deb + path: ${{ github.workspace }}/docker/build/volumes/output/wordfence.deb python_build: name: Python build runs-on: ubuntu-20.04 @@ -78,25 +60,10 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.8' - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v5.3.0 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Python build run: | pip install build~=0.10 python3 -m build - - name: Create checksums and signatures - run: | - VERSION=$(python3 -c "from wordfence import version; print(version.__version__)") - cd dist - sha256sum "wordfence-${VERSION}.tar.gz" > "wordfence-${VERSION}.tar.gz.sha256" - sha256sum "wordfence-${VERSION}-py3-none-any.whl" > "wordfence-${VERSION}-py3-none-any.whl.sha256" - gpg --detach-sign --armor --local-user '=Wordfence ' "wordfence-${VERSION}.tar.gz" - gpg --detach-sign --armor --local-user '=Wordfence ' "wordfence-${VERSION}-py3-none-any.whl" - gpg --detach-sign --armor --local-user '=Wordfence ' "wordfence-${VERSION}.tar.gz.sha256" - gpg --detach-sign --armor --local-user '=Wordfence ' "wordfence-${VERSION}-py3-none-any.whl.sha256" - name: Upload artifacts uses: actions/upload-artifact@v3 with: @@ -106,3 +73,41 @@ jobs: ${{ github.workspace }}/dist/*.whl ${{ github.workspace }}/dist/*.sha256 ${{ github.workspace }}/dist/*.asc + generate_checksums: + name: Generate checksums + runs-on: ubuntu-22.04 + needs: + - linux_standalone_build + - deb_build + - python_build + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + - name: Create checksums + run: | + touch checksums.txt + for artifact in \ + wordfence_cli_amd64 \ + wordfence_cli_arm64 \ + wordfence_cli_deb \ + wordfence_cli_python + do + pushd "$artifact" + sha256sum * >> ../checksums.txt + popd + done + cat checksums.txt + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v5.3.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + - name: Sign checksums file + run: gpg --detach-sign --armor --local-user '=Wordfence ' checksums.txt + - name: Upload checksums and signature + uses: actions/upload-artifact@v3 + with: + name: wordfence_cli_checksums + path: | + ${{ github.workspace }}/checksums.txt + ${{ github.workspace }}/checksums.txt.asc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b92a135a..999f321e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,19 +94,13 @@ jobs: uses: softprops/action-gh-release@v1 with: files: | - wordfence_cli_python/*.tar.gz - wordfence_cli_python/*.whl - wordfence_cli_python/*.sha256 - wordfence_cli_python/*.asc wordfence_cli_amd64/*.tar.gz - wordfence_cli_amd64/*.sha256 - wordfence_cli_amd64/*.asc wordfence_cli_arm64/*.tar.gz - wordfence_cli_arm64/*.sha256 - wordfence_cli_arm64/*.asc wordfence_cli_deb/*.deb - wordfence_cli_deb/*.sha256 - wordfence_cli_deb/*.asc + wordfence_cli_python/*.whl + wordfence_cli_python/*.tar.gz + wordfence_cli_checksums/checksums.txt + wordfence_cli_checksums/checksums.txt.asc target_commitish: ${{ steps.get-commit-hash.outputs.BUILD_COMMIT_HASH }} tag_name: ${{ inputs.release_tag }} draft: true diff --git a/README.md b/README.md index 0a30b3c1..6254a371 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ If you'd like to install Wordfence CLI manually or use CLI for development, you - Python >= 3.8 - The C library `libpcre` >= 8.38 - Python packages: - - `packaging` >= 23.1 + - `packaging` >= 21.0 - `requests` >= 2.3 ### Obtaining a license diff --git a/docker/build/build.Dockerfile b/docker/build/build-deb.Dockerfile similarity index 84% rename from docker/build/build.Dockerfile rename to docker/build/build-deb.Dockerfile index dd1f92ee..2c0ec631 100644 --- a/docker/build/build.Dockerfile +++ b/docker/build/build-deb.Dockerfile @@ -1,8 +1,7 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y debsigs \ - dpkg-sig \ +RUN apt-get update && apt-get install -y \ devscripts \ debhelper \ dh-python \ diff --git a/docker/build/build.Dockerfile.dockerignore b/docker/build/build-deb.Dockerfile.dockerignore similarity index 100% rename from docker/build/build.Dockerfile.dockerignore rename to docker/build/build-deb.Dockerfile.dockerignore diff --git a/docker/build/build-standalone.Dockerfile b/docker/build/build-standalone.Dockerfile new file mode 100644 index 00000000..458c9b5f --- /dev/null +++ b/docker/build/build-standalone.Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:18.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + python3.8 \ + python3.8-dev \ + python3-pip \ + libffi-dev + +COPY ./docker/build/entrypoint.sh /root/entrypoint.sh +COPY ./ /root/wordfence-cli + +RUN chmod +x /root/entrypoint.sh + +ENTRYPOINT ["/bin/bash"] +CMD ["/root/entrypoint.sh"] diff --git a/docker/build/entrypoint.sh b/docker/build/entrypoint.sh index a747eff0..620a33f3 100755 --- a/docker/build/entrypoint.sh +++ b/docker/build/entrypoint.sh @@ -4,17 +4,17 @@ set -e cd /root/wordfence-cli ARCHITECTURE=$(dpkg --print-architecture) -VERSION=$(python3 -c 'from wordfence import version; print(version.__version__)') -GPG_USER='=Wordfence ' - -# install build requirements -pip install --upgrade pip -pip install -r requirements.txt --force-reinstall if [ "$PACKAGE_TYPE" = 'deb' ] || [ "$PACKAGE_TYPE" = 'all' ]; then # build deb package + VERSION=$(python3 -c 'from wordfence import version; print(version.__version__)') + + # install build requirements + python3 -m pip install --upgrade pip + python3 -m pip install -r requirements.txt --force-reinstall + export DEBFULLNAME='Wordfence' export DEBEMAIL='opensource@wordfence.com' echo 'Generating changelog' @@ -29,30 +29,10 @@ if [ "$PACKAGE_TYPE" = 'deb' ] || [ "$PACKAGE_TYPE" = 'all' ]; then # build the package dpkg-buildpackage -us -uc -b + # copy to output volume pushd .. - - # sign and generate checksum DEB_FILENAME="wordfence_${VERSION}_all" - sha256sum "${DEB_FILENAME}.deb" > "${DEB_FILENAME}.deb.sha256" - gpg \ - --homedir "$CONTAINER_GPG_HOME_DIR" \ - --detach-sign \ - --armor \ - --local-user "$GPG_USER" \ - "${DEB_FILENAME}.deb" - gpg \ - --homedir "$CONTAINER_GPG_HOME_DIR" \ - --detach-sign \ - --armor \ - --local-user "$GPG_USER" \ - "${DEB_FILENAME}.deb.sha256" - cp \ - "${DEB_FILENAME}.deb" \ - "${DEB_FILENAME}.deb.asc" \ - "${DEB_FILENAME}.deb.sha256" \ - "${DEB_FILENAME}.deb.sha256.asc" \ - /root/output - + cp "${DEB_FILENAME}.deb" /root/output/wordfence.deb popd fi @@ -60,6 +40,14 @@ fi if [ "$PACKAGE_TYPE" = 'standalone' ] || [ "$PACKAGE_TYPE" = 'all' ]; then # build standalone executable + + VERSION=$(python3.8 -c 'from wordfence import version; print(version.__version__)') + + # install build requirements + python3.8 -m pip install --upgrade pip + python3.8 -m pip install -r requirements.txt --force-reinstall + # Ubuntu 18.04 requires this additional package (as well as the OS package libffi-dev) + python3.8 -m pip install cffi pyinstaller \ --name wordfence \ @@ -76,31 +64,11 @@ if [ "$PACKAGE_TYPE" = 'standalone' ] || [ "$PACKAGE_TYPE" = 'all' ]; then --hidden-import wordfence.cli.version.definition \ main.py + # compress and copy to output volume pushd /root/wordfence-cli/dist - - # compress the standalone executable, checksum and sign it, and copy both to the output directory STANDALONE_FILENAME="wordfence_${VERSION}_${ARCHITECTURE}_linux_exec" tar -czvf "${STANDALONE_FILENAME}.tar.gz" wordfence - sha256sum "${STANDALONE_FILENAME}.tar.gz" > "${STANDALONE_FILENAME}.tar.gz.sha256" - gpg \ - --homedir "$CONTAINER_GPG_HOME_DIR" \ - --detach-sign \ - --armor \ - --local-user "$GPG_USER" \ - "${STANDALONE_FILENAME}.tar.gz" - gpg \ - --homedir "$CONTAINER_GPG_HOME_DIR" \ - --detach-sign \ - --armor \ - --local-user "$GPG_USER" \ - "${STANDALONE_FILENAME}.tar.gz.sha256" - cp \ - "${STANDALONE_FILENAME}.tar.gz" \ - "${STANDALONE_FILENAME}.tar.gz.asc" \ - "${STANDALONE_FILENAME}.tar.gz.sha256" \ - "${STANDALONE_FILENAME}.tar.gz.sha256.asc" \ - /root/output - + cp "${STANDALONE_FILENAME}.tar.gz" "/root/output/wordfence_${ARCHITECTURE}.tar.gz" popd fi diff --git a/docker/build/host-build.sh b/docker/build/host-build.sh index 8690a8c8..e9817b11 100755 --- a/docker/build/host-build.sh +++ b/docker/build/host-build.sh @@ -15,8 +15,8 @@ elif [ "$2" != "amd64" ] && [ "$2" != "arm64" ]; then elif [ -z ${3:+x} ]; then echo "You must provide the package type as the third argument" exit 1 -elif [ "$3" != "deb" ] && [ "$3" != "standalone" ] && [ "$3" != "all" ]; then - echo "Invalid package type (must be deb, standalone, or all)" +elif [ "$3" != "deb" ] && [ "$3" != "standalone" ]; then + echo "Invalid package type (must be deb or standalone)" exit 1 fi @@ -24,15 +24,9 @@ PROJECT_DIR=$(realpath "$1") echo "output path: $PROJECT_DIR/docker/build/volumes/output" ARCHITECTURE="$2" PACKAGE_TYPE="$3" -GPG_HOME_DIR=$(gpgconf --list-dirs homedir) -GPG_SOCKET=$(gpgconf --list-dirs agent-socket) -CONTAINER_GPG_HOME_DIR="/var/run/host_gpg_home_dir" docker run \ --name "wfcli-build-container-${ARCHITECTURE}" \ --platform "linux/${ARCHITECTURE}" \ -v "${PROJECT_DIR}/docker/build/volumes/output/:/root/output:rw" \ - -v "${GPG_HOME_DIR}:${CONTAINER_GPG_HOME_DIR}:rw" \ - -v "${GPG_SOCKET}:${CONTAINER_GPG_HOME_DIR}/S.gpg-agent:rw" \ - -e "CONTAINER_GPG_HOME_DIR=${CONTAINER_GPG_HOME_DIR}" \ -e "PACKAGE_TYPE=${PACKAGE_TYPE}" \ "wfcli-build-$ARCHITECTURE" diff --git a/docker/build/host-refresh.sh b/docker/build/host-refresh.sh index e176e32d..3d16a112 100755 --- a/docker/build/host-refresh.sh +++ b/docker/build/host-refresh.sh @@ -12,15 +12,22 @@ elif [ -z ${2:+x} ]; then elif [ "$2" != "amd64" ] && [ "$2" != "arm64" ]; then echo "Invalid architecture (must be amd64 or arm64)" exit 1 +elif [ -z ${3:+x} ]; then + echo "You must provide the package type as the third argument" + exit 1 +elif [ "$3" != "deb" ] && [ "$3" != "standalone" ]; then + echo "Invalid package type (must be deb or standalone)" + exit 1 fi PROJECT_DIR=$(realpath "$1") ARCHITECTURE="$2" +PACKAGE_TYPE="$3" docker rmi -f "wfcli-build-$ARCHITECTURE" 2>/dev/null docker build \ --no-cache \ -t "wfcli-build-${ARCHITECTURE}" \ --platform "linux/${ARCHITECTURE}" \ - -f "${PROJECT_DIR}/docker/build/build.Dockerfile" \ + -f "${PROJECT_DIR}/docker/build/build-${PACKAGE_TYPE}.Dockerfile" \ "$PROJECT_DIR" diff --git a/docs/Installation.md b/docs/Installation.md index 3e2a68e1..ce0ee0e3 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -91,26 +91,24 @@ You may need to install the `libpcre` library. ## Verifying the Authenticity of a Release Asset -Each of our downloadable release assets are signed using GPG as a part of the build process. We recommend verifying the authenticity of these downloads prior to extracting and executing any code. This example below uses the Wordfence CLI source code archive file, but the process is the same for any of the release assets. You can find our public key used to verify the signatures here: +The checksum file for all release assets is sgned using GPG as part of the build process. We recommend verifying the authenticity of the checksum file and then verifying the checksum of the downloaded release asset prior to extracting, installing, or executing any code. - https://www.wordfence.com/wp-content/uploads/public.asc - -To verify a signed binary, download both the gzipped binary and the .asc armor file. +To verify the signature of the checksums file, first download and import our public GPG key: wget https://www.wordfence.com/wp-content/uploads/public.asc gpg --import public.asc -You can optionally sign the public key with your own private key: +You can optionally sign our public key with your own private key: gpg --lsign-key 00B225C7030F26FF4A3D3481F82623ECE1DB0FBB -Each release asset has a .asc armor file that should be downloaded in addition to the asset. To verify the download run the following code (replace the file names with the ones you've downloaded): +Download the `checksums.txt` and `checksums.txt.asc` files from GitHub releases. You can then verify the authenticity of the `checksums.txt` file (replace the filenames with paths to the copies you've downloaded): - gpg --assert-signer 00B225C7030F26FF4A3D3481F82623ECE1DB0FBB --verify wordfence.tar.gz.asc wordfence.tar.gz + gpg --assert-signer 00B225C7030F26FF4A3D3481F82623ECE1DB0FBB --verify checksums.txt.asc checksums.txt If your version of GPG doesn't include `--assert-signer` you can just run (you may see a warning using this method): - gpg --verify wordfence_1.0.0_amd64_linux_exec.tar.gz.asc wordfence_1.0.0_amd64_linux_exec.tar.gz + gpg --verify checksums.txt.asc checksums.txt You should see output similar to this: @@ -118,4 +116,8 @@ You should see output similar to this: gpg: using EDDSA key 00B225C7030F26FF4A3D3481F82623ECE1DB0FBB gpg: issuer "opensource@wordfence.com" gpg: Good signature from "Wordfence " [ultimate] - gpg: signer '00B225C7030F26FF4A3D3481F82623ECE1DB0FBB' matched \ No newline at end of file + gpg: signer '00B225C7030F26FF4A3D3481F82623ECE1DB0FBB' matched + +Now that you've verified the checksums file, you can confirm that the checksum of your download matches. For example, if you downloaded the `wordfence_2.0.1_amd64_linux_exec.tar.gz` (the standalone Linux executable for the `x86_64` architecture) to the same directory as `checksums.txt`, you can verify the checksum matches with: + + sha256sum --ignore-missing -c checksums.txt diff --git a/pyproject.toml b/pyproject.toml index cf5463b8..00317b54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Topic :: Security" ] dependencies = [ - "packaging>=23.1", + "packaging>=21.0", "requests>=2.3" ] dynamic = [ "version" ] diff --git a/requirements.txt b/requirements.txt index 097bec17..df5fd408 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Runtime dependencies -packaging >= 23.1 +packaging >= 21.0 requests >= 2.3 # Build requirements build ~= 0.10