diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9a5e428 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: ci +run-name: ${{ github.actor }} is running ci + +on: + pull_request: + push: + workflow_dispatch: + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Authenticate with container registry + uses: docker/login-action@v3 + with: + username: ${{ vars.CR_USER }} + registry: ${{ vars.CR_REGISTRY }} + password: ${{ secrets.CR_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + push: true + tags: ${{ vars.CR_REGISTRY }}/${{ vars.CR_OWNER }}/seal-c:latest + secrets: | + signid=${{ secrets.SIGNMYDATA_ID }} + apikey=${{ secrets.SIGNMYDATA_APIKEY }} + diff --git a/CI.md b/CI.md new file mode 100644 index 0000000..9145dc2 --- /dev/null +++ b/CI.md @@ -0,0 +1,40 @@ +# Continuous Integration + +In order to build the project automatically in GitHub follow these steps: + +1. Fork the project +2. In your fork select the "Settings" option +3. On the left, expand "Secrets and variables" +4. Select "Actions" +5. Choose the "Variables" tab +6. Create the following "repository variables": + 1. `CR_OWNER` - the value of this should be what's before the slash in the GitHub repository name, either your GitHub username or the name of the organization which owns the repository + 2. `CR_REGISTRY` - the container registry to push to, in this case ghcr.io for the GitHub Container Registry + 3. `CR_USER` - the user who owns the Personal Access Token with rights to upload the container (likely your GitHub username, this might be the same as `CR_OWNER` if this repo is not part of an organization) +7. Click on the "Secrets" tab +8. In another tab or browser window, open the [Classic Personal Access Tokens](https://github.com/settings/tokens) settings page (for more information on this process you can see GitHub's [Creating a personal access token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) guidance). +9. Click "Generate a new token" and choose "Generate a new token (classic)" +10. Put something meaningful in the note (such as: _Project name_ deployment token) +11. Choose an expiration date - you'll have to create a new token and update the value in the repo as frequently as you select here. "No expiration" is obviously most convenient and least secure. +12. For scopes, select the following: + 1. `repo` + 2. `write:packages` + 3. `read:packages` + 4. `delete:packages` +13. Choose "Generate token" +14. Optionally: save the token in your secure password manager in case you lose it +15. Go back to your other tab/browser window and click "New repository secret" +16. Name this secret `CR_TOKEN` and put enter your token into the Secret box +17. Click "Add secret" +18. If you want to run all of the tests on each build, add secrets named `SIGNMYDATA_APIKEY` and `SIGNMYDATA_ID` so that you can use [signmydata.com](https://signmydata.com/) for remote tests. +19. Kick off a manual workflow run: + 1. Navigate to the repo + 2. Click the "Actions" tab at the top + 3. Select the "ci" workflow from the left nav + 4. Click the "Run workflow" dropdown on the right + 5. Choose the green "Run workflow" button +20. Once the build has succeded it pushes a container into the GitHub Container Registry - though it may not be linked to the project. To link it: + 1. Navigate to your GitHub profile page (click your icon in the top right and choose "Profile") + 2. Choose the "Packages" tab at the top + 3. You should see a container registry named after the project, click it + diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..f73fb93 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,35 @@ +# SEAL-C in docker + +SEAL-C has a Dockerfile and you are able to build the project in a container and use it directly from Docker. + +## Building + +Once you clone the repository, with Docker or similar (e.g. Podman) you shoudl be able to build the Docker container with: + +`docker build -t sealtool .` + +## Using the container + +You can replace wherever you would use the `sealtool` command with the following in Linux/macOS: + +- Using your locally-built image: `docker run --rm -v ${PWD}:/root sealtool:latest` +- Using the image from the GitHub Container Registry: `docker run --rm -v ${PWD}:/root ghcr.io/hackerfactor/seal-c:latest` + +In this command: + +1. Docker is running the container +2. `--rm` means to delete the container after use +3. `-v ${PWD}:/root` says to map the current directory to `/root` inside the container. That way if you have files to SEAL or configuration files in your current directory they are accessible inside the container. + +### Example + +Starting from scratch, you can perform the following (this follows the steps under "Local Signing" in BUILD.md): + +1. Make a new directory. In that directory place a file to sign (e.g. banana.jpg). +2. Run: `docker run --rm -v ${PWD}:/root ghcr.io/hackerfactor/seal-c:latest -g -K rsa -k seal-rsa.key -D seal-rsa.dns` +3. Once the container exits, you'll see the two files (`seal-rsa.key` and `seal-rsa.dns`) in your current directory. +4. Add the contents of the `seal-rsa.dns` to a `TXT` record under your domain. +5. Now sign your image file: `docker run --rm -v ${PWD}:/root ghcr.io/hackerfactor/seal-c:latest -s -d example.com -K rsa -k seal-rsa.key banana.jpg` +6. You should now see your signed file in the directory +7. Verify your signed file with `docker run --rm -v ${PWD}:/root ghcr.io/hackerfactor/seal-c:latest banana-seal.jpg` + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e98bd39 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:stable AS build + +WORKDIR /usr/local/src + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + g++ \ + libcurl4-openssl-dev \ + libperl-dev \ + libssl-dev \ + make + +RUN curl -L https://sourceforge.net/projects/exiftool/files/Image-ExifTool-13.41.tar.gz/download > exiftool.tgz \ + && tar -xf exiftool.tgz \ + && cd Image-ExifTool-* \ + && perl Makefile.PL \ + && make install + +WORKDIR /app + +COPY . ./ + +RUN make all + +RUN --mount=type=secret,id=apikey,env=SIGNMYDATA_APIKEY \ + --mount=type=secret,id=signid,env=SIGNMYDATA_ID \ + cd tests \ + && ./test-all-ci.sh + +FROM debian:stable-slim AS publish + +WORKDIR /root + +RUN apt-get update \ + && apt-get install --no-install-recommends -y libcurl4-openssl-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build "/app/bin/sealtool" "/usr/local/bin/" + +ENTRYPOINT ["/usr/local/bin/sealtool"] + diff --git a/tests/config.sample b/tests/config.sample new file mode 100644 index 0000000..29c80e5 --- /dev/null +++ b/tests/config.sample @@ -0,0 +1,9 @@ +# Sample config file for running remote tests +# Add "apikey" and "id" and populate them +domain=signmydata.com +digestalg=sha256 +keyalg=rsa +kv=1 +sf=date:hex +apiurl=https://signmydata.com/?sign +outfile=./%b-seal%e diff --git a/tests/test-all-ci.sh b/tests/test-all-ci.sh new file mode 100755 index 0000000..4b8d30f --- /dev/null +++ b/tests/test-all-ci.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Runs all test scripts in the tests folder. + +readonly CI_STARTAT=$SECONDS + +# Everything is relative to this test directory. +cd $(dirname "$0") + +MISSING_CONFIG=false + +if [[ -n ${XDG_CONFIG_HOME} && -f "${XDG_CONFIG_HOME}/seal/config" ]]; then + echo "*** Using configuration file in ${XDG_CONFIG_HOME} for tests" +elif [[ -f "${HOME}/.config/seal/config" ]]; then + echo "*** Using configuration file for tests" +elif [[ -n "${SIGNMYDATA_APIKEY}" && -n "${SIGNMYDATA_ID}" ]]; then + echo "*** Building configuration file with environment data" + mkdir -p "${HOME}/.config/seal" + cp config.sample "${HOME}/.config/seal/config" + echo "apikey=${SIGNMYDATA_APIKEY}" >> "${HOME}/.config/seal/config" + echo "id=${SIGNMYDATA_ID}" >> "${HOME}/.config/seal/config" +else + MISSING_CONFIG=true +fi + +if [[ "$MISSING_CONFIG" == true ]]; then + echo "***************************************" + echo "Missing configuration, local tests only" + echo "***************************************" + readonly TEST_SCRIPTS=( + "test-local.sh:Local Signing" + "test-inline.sh:Inline Signing" + "test-revoke.sh:Revocation" + ) +else + readonly TEST_SCRIPTS=( + "test-local.sh:Local Signing" + "test-remote.sh:Remote Signing" + "test-inline.sh:Inline Signing" + "test-revoke.sh:Revocation" + "test-manual.sh:Manual" + ) +fi + +PASSED_TESTS=() +FAILED_TESTS=() +OVERALL_STATUS=0 + +for test_info in "${TEST_SCRIPTS[@]}"; do + script="${test_info%%:*}" + description="${test_info#*:}" + + TEST_STARTAT=$SECONDS + echo "===========================" + echo "Running ${description} Tests" + echo "===========================" + output=$(./"$script" 2>&1) + status=$? + if [ $status -ne 0 ]; then + OVERALL_STATUS=1 + FAILED_TESTS+=("$script") + echo "ERROR: $script failed in $((SECONDS - TEST_STARTAT)) seconds." + echo "------- SCRIPT OUTPUT -------" + echo "$output" + echo "-----------------------------" + else + PASSED_TESTS+=("$script") + echo "Completed Successfully in $((SECONDS - TEST_STARTAT)) seconds" + fi + echo "" +done + +echo "" +echo "=====================" +echo "Test Execution Summary" +echo "=====================" +echo "Passed: ${#PASSED_TESTS[@]}" +echo "Failed: ${#FAILED_TESTS[@]}" +if [ ${#FAILED_TESTS[@]} -gt 0 ]; then + echo "Failed tests: ${FAILED_TESTS[*]}" +fi +echo "Total elapsed: $((SECONDS - CI_STARTAT)) seconds" +echo "=====================" + +exit $OVERALL_STATUS diff --git a/tests/test-all.sh b/tests/test-all.sh old mode 100644 new mode 100755