Skip to content

Commit

Permalink
Resolve security warning for macOS users (#249)
Browse files Browse the repository at this point in the history
* Add support for macOS signing and notarization

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Use Docker to run the changelog generator locally

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
  • Loading branch information
luhring committed Nov 4, 2020
1 parent a52750b commit ecfc471
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 35 deletions.
31 changes: 31 additions & 0 deletions .github/scripts/mac-prepare-for-signing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -eu

CI_HOME="/Users/runner"
if [[ "${HOME}" != "${CI_HOME}" ]]; then
printf "WARNING! It looks like this isn't the CI environment. This script modifies the macOS Keychain setup in ways you probably wouldn't want for your own machine. It also requires an Apple Developer ID Certificate that you shouldn't have outside of the CI environment.\n\nExiting early to make sure nothing bad happens.\n"
exit 1
fi

# Install gon (see https://github.com/mitchellh/gon for details).
brew tap mitchellh/gon
brew install mitchellh/gon/gon

# Write signing certificate to disk from environment variable.
CERT_FILE="$HOME/developer_id_certificate.p12"
echo -n "$APPLE_DEVELOPER_ID_CERT" | base64 --decode > "$CERT_FILE"

# In order to have all keychain interactions avoid an interactive user prompt, we need to control the password for the keychain in question, which means we need to create a new keychain into which we'll import the signing certificate and from which we'll later access this certificate during code signing.
EPHEMERAL_KEYCHAIN="ci-ephemeral-keychain"
EPHEMERAL_KEYCHAIN_PASSWORD="$(openssl rand -base64 100)"
security create-keychain -p "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN}"

# Import signing certificate into the keychain. (This is a pre-requisite for gon, which is invoked via goreleaser.)
EPHEMERAL_KEYCHAIN_FULL_PATH="$HOME/Library/Keychains/${EPHEMERAL_KEYCHAIN}-db"
security import "${CERT_FILE}" -k "${EPHEMERAL_KEYCHAIN_FULL_PATH}" -P "${APPLE_DEVELOPER_ID_CERT_PASS}" -T "$(command -v codesign)"

# Setting the partition list for this certificate's private key to include "apple-tool:" and "apple:" allows the codesign command to access this keychain item without an interactive user prompt. (codesign is invoked by gon.)
security set-key-partition-list -S "apple-tool:,apple:" -s -k "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN_FULL_PATH}"

# Make this new keychain the user's default keychain, so that codesign will be able to find this certificate when we specify it during signing.
security default-keychain -d "user" -s "${EPHEMERAL_KEYCHAIN_FULL_PATH}"
16 changes: 16 additions & 0 deletions .github/scripts/mac-sign-and-notarize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -eu

IS_SNAPSHOT="$1" # e.g. "true", "false"

if [[ "${IS_SNAPSHOT}" == "true" ]]; then
# This is a snapshot build —— skipping signing and notarization...
exit 0
fi

GON_CONFIG="$2" # e.g. "gon.hcl"
NEW_DMG_NAME="$3" # e.g. "./dist/syft-0.1.0.dmg"
ORIGINAL_DMG_NAME="./dist/output.dmg" # This should match dmg output_path in the gon config file.

gon "${GON_CONFIG}"
mv -v "${ORIGINAL_DMG_NAME}" "${NEW_DMG_NAME}"
13 changes: 4 additions & 9 deletions .github/scripts/update-version-file.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@ fi
echo "creating and publishing version file"

# create a version file for version-update checks
echo "${VERSION}" | tee ${DISTDIR}/VERSION
VERSION_FILE="${DISTDIR}/VERSION"
echo "${VERSION}" | tee "${VERSION_FILE}"

# upload the version file that supports the application version update check
docker run --rm \
-i \
-e AWS_DEFAULT_REGION=us-west-2 \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-v $(pwd)/${DISTDIR}/:/distmount \
amazon/aws-cli \
s3 cp /distmount/VERSION s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
export AWS_DEFAULT_REGION=us-west-2
aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
8 changes: 2 additions & 6 deletions .github/workflows/acceptance-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ env:

jobs:
Build-Snapshot-Artifacts:
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
runs-on: macos-latest # We're creating these snapshot builds on macOS to be consistent with our release workflow's build process, which also takes place on macOS (due to code signing requirements).
steps:

- uses: actions/setup-go@v2
Expand All @@ -40,9 +39,6 @@ jobs:
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
run: make bootstrap

- name: Bootstrap CI dependencies
run: make ci-bootstrap

- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v2
Expand Down Expand Up @@ -162,4 +158,4 @@ jobs:
text: The syft acceptance tests have failed tragically!
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ failure() }}
if: ${{ failure() }}
15 changes: 8 additions & 7 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ env:

jobs:
wait-for-checks:
runs-on: ubuntu-latest
runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS.
steps:

- uses: actions/checkout@v2
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
release:
needs: [ wait-for-checks ]
runs-on: ubuntu-latest
runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS.
steps:

- uses: actions/setup-go@v2
Expand All @@ -93,6 +93,7 @@ jobs:
with:
fetch-depth: 0

# We are expecting this cache to have been created during the "Build-Snapshot-Artifacts" job in the "Acceptance" workflow.
- name: Restore bootstrap cache
id: cache
uses: actions/cache@v2
Expand All @@ -105,10 +106,6 @@ jobs:
${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Bootstrap dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: make ci-bootstrap

- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v2
Expand All @@ -132,6 +129,10 @@ jobs:
SIGNING_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
APPLE_DEVELOPER_ID_CERT: ${{ secrets.APPLE_DEVELOPER_ID_CERT }} # Used during macOS code signing.
APPLE_DEVELOPER_ID_CERT_PASS: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASS }} # Used during macOS code signing.
AC_USERNAME: ${{ secrets.ENG_CI_APPLE_ID }} # Used during macOS notarization.
AC_PASSWORD: ${{ secrets.ENG_CI_APPLE_ID_PASS }} # Used during macOS notarization.

- uses: 8398a7/action-slack@v3
with:
Expand All @@ -145,4 +146,4 @@ jobs:
- uses: actions/upload-artifact@v2
with:
name: artifacts
path: dist/**/*
path: dist/**/*
34 changes: 30 additions & 4 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,37 @@ release:
# If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing.
draft: true

signs:
- artifacts: checksum
args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
# This ensures any macOS signed artifacts get included with the release.
extra_files:
- glob: "./dist/*.dmg"

builds:
- binary: syft
id: syft
env:
- CGO_ENABLED=0
goos:
# windows not supported yet (due to jotframe)
# - windows
- linux
goarch:
- amd64
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags: |
-w
-s
-extldflags '-static'
-X github.com/anchore/syft/internal/version.version={{.Version}}
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
- binary: syft
id: syft-macos
env:
- CGO_ENABLED=0
goos:
- darwin
goarch:
- amd64
Expand All @@ -31,6 +50,12 @@ builds:
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
hooks:
post: ./.github/scripts/mac-sign-and-notarize.sh "{{.IsSnapshot}}" "gon.hcl" "./dist/syft_{{.Tag}}_{{.Target}}.dmg"

signs:
- artifacts: checksum
args: ["--output", "${signature}", "--detach-sign", "${artifact}"]

nfpms:
- license: "Apache 2.0"
Expand All @@ -50,7 +75,8 @@ brews:

archives:
- format: tar.gz
builds:
- syft # i.e. Linux only
format_overrides:
- goos: windows
format: zip

20 changes: 14 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ help:
ci-bootstrap:
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils

.PHONY:
ci-bootstrap-mac:
github_changelog_generator --version || sudo gem install github_changelog_generator

.PHONY: bootstrap
bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./tmp dir)
$(call title,Bootstrapping dependencies)
Expand Down Expand Up @@ -226,8 +230,7 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR)
changelog-release:
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
@echo "Current tag: $(VERSION)"
@docker run -i --rm \
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
@github_changelog_generator \
--user anchore \
--project $(BIN) \
-t ${GITHUB_TOKEN} \
Expand All @@ -241,8 +244,9 @@ changelog-release:

.PHONY: changelog-unreleased
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
@docker run -it --rm \
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
@docker run -it --rm \
-v "$(shell pwd)":/usr/local/src/your-app \
ferrarimarco/github-changelog-generator \
--user anchore \
--project $(BIN) \
-t ${GITHUB_TOKEN} \
Expand All @@ -258,8 +262,12 @@ changelog-unreleased: ## show the current changelog that will be produced on the
/CHANGELOG.md

.PHONY: release
release: clean-dist changelog-release ## Build and publish final binaries and packages
release: clean-dist ci-bootstrap-mac changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS.
$(call title,Publishing release artifacts)

# Prepare for macOS-specific signing process
.github/scripts/mac-prepare-for-signing.sh

# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
Expand Down Expand Up @@ -290,4 +298,4 @@ clean-dist:

.PHONY: clean-json-schema-examples
clean-json-schema-examples:
rm -f schema/json/examples/*
rm -f schema/json/examples/*
11 changes: 11 additions & 0 deletions gon.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
source = ["./dist/syft-macos_darwin_amd64/syft"] # The 'dist' directory path should ideally reference an env var, where the source of truth is the Makefile. I wasn't able to figure out how to solve this.
bundle_id = "com.anchore.toolbox.syft"

sign {
application_identity = "Developer ID Application: ANCHORE, INC. (9MJHKYX5AT)"
}

dmg {
output_path = "./dist/output.dmg"
volume_name = "Syft"
}
7 changes: 4 additions & 3 deletions test/acceptance/mac.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ skopeo --override-os linux copy docker://docker.io/${TEST_IMAGE} docker-archive:
ls -alh ${TEST_IMAGE_TAR}

# run syft
chmod 755 ${DISTDIR}/syft_darwin_amd64/syft
${DISTDIR}/syft_darwin_amd64/syft version
SYFT_CHECK_FOR_APP_UPDATE=0 ${DISTDIR}/syft_darwin_amd64/syft docker-archive://${TEST_IMAGE_TAR} -vv -o json > ${REPORT}
SYFT_PATH="${DISTDIR}/syft-macos_darwin_amd64/syft"
chmod 755 "${SYFT_PATH}"
"${SYFT_PATH}" version
SYFT_CHECK_FOR_APP_UPDATE=0 "${SYFT_PATH}" docker-archive://${TEST_IMAGE_TAR} -vv -o json > "${REPORT}"

# keep the generated report around
mkdir -p ${RESULTSDIR}
Expand Down

0 comments on commit ecfc471

Please sign in to comment.