-
Notifications
You must be signed in to change notification settings - Fork 2.1k
release: Prepare for MacOS builds on GitHub Actions #23407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ffeeb07
db040b4
e0b8403
7a2cf91
a7778d8
6bc4534
244024f
8eb7884
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| #!/bin/bash | ||
| # | ||
| # keychain-setup.sh creates a MacOS keychain for application binary signing | ||
| # and for installer package signing. Each use separate keys that need to | ||
| # be loaded into a keychain. | ||
| # | ||
| # This is intended to be called from CI to set up the keychain for signing | ||
| # and notarizing MacOS binaries and packages/images. It can be called manually | ||
| # during development if you have the development keys and notarization | ||
| # username/password in your environment. | ||
| # | ||
| # This is MVP - the intention is to write all the keychain management, signing | ||
| # and notarizing as a Go program. | ||
| # | ||
| #----------------------------------------------------------------------------- | ||
| usage() { | ||
| cat <<EOF | ||
| Usage: ${0##*/} {<options>...} | ||
| Available options: | ||
| -a <envvar> take base64-encoded application key from <envvar> | ||
| -a @<file> take application key from <file> | ||
| -A <envvar> take password for application key from <envvar> | ||
| -i <envvar> take base64-encoded installation key from <envvar> | ||
| -i @<file> take installation key from <file> | ||
| -I <envvar> take password for installation key from <envvar> | ||
| -k <keychain> create <keychain>.keychain (default "build") | ||
| -p <password> use <password> on keychain (default "insecure") | ||
| -v verbose. Print commands before running them | ||
| -n dry run. Do not run commands, just print them | ||
| EOF | ||
| } | ||
|
|
||
| #----------------------------------------------------------------------------- | ||
| APPLICATION_KEY_FILE='' | ||
| APPLICATION_KEY_PASSWORD='' | ||
| INSTALLER_KEY_FILE='' | ||
| INSTALLER_KEY_PASSWORD='' | ||
| KEYCHAIN='build' | ||
| KEYCHAIN_PASSWORD='insecure' # Does not need to be secret on CI, as the keychain is removed after. | ||
| VERBOSE=false | ||
| DRY_RUN=false | ||
|
|
||
| # This should be an array of filenames, but bash on MacOS (3.2.57) seems to unset | ||
| # the array before the EXIT trap fires and we get an unset variable error when | ||
| # trying to remove the files listed in the array. | ||
| tmpfiles='' | ||
|
|
||
| #----------------------------------------------------------------------------- | ||
| main() { | ||
| set -euo pipefail | ||
|
codingllama marked this conversation as resolved.
|
||
|
|
||
| # Always remove temp files, even if dry run | ||
| trap 'DRY_RUN=false rm -f ${tmpfiles}' EXIT | ||
| parse_args "$@" | ||
|
|
||
| create_keychain "${KEYCHAIN}" "${KEYCHAIN_PASSWORD}" | ||
| add_key "${APPLICATION_KEY_FILE}" "${APPLICATION_KEY_PASSWORD}" "${KEYCHAIN}" "${KEYCHAIN_PASSWORD}" | ||
| add_key "${INSTALLER_KEY_FILE}" "${INSTALLER_KEY_PASSWORD}" "${KEYCHAIN}" "${KEYCHAIN_PASSWORD}" | ||
| } | ||
|
|
||
| # Create a keychain ($1) with a password ($2) and put it on the user keychain | ||
| # search path. | ||
| create_keychain() { | ||
| local keychain="$1" password="$2" | ||
| run security create-keychain -p "${password}" "${keychain}" | ||
| run security unlock-keychain -p "${password}" "${keychain}" | ||
| run security set-keychain-settings "${keychain}" # keep keychain unlocked | ||
|
|
||
| # Add the new keychain to the search path, otherwise codesign does not find the keys | ||
| local kpath | ||
| kpath="$(security list-keychains -d user | sed 's/.*"\([^"]*\)"/\1/')" | ||
|
codingllama marked this conversation as resolved.
|
||
| # shellcheck disable=SC2086 # Double quote to prevent globbing and word splitting. | ||
| # We want word splitting on ${kpath} | ||
| run security list-keychains -d user -s "${keychain}" ${kpath} | ||
| } | ||
|
|
||
| # Add a key from a file ($1) protected with a passphrase ($2) to a keychain ($3) | ||
| # protected with a password ($4). This is to allow `/usr/bin/codesign` to access | ||
| # the key. If the key file name is empty, add_key returns without doing anything. | ||
| add_key() { | ||
| local keyfile="$1" passphrase="$2" keychain="$3" keychain_password="$4" | ||
| if [[ -z "${keyfile}" ]]; then | ||
| return 0 | ||
| fi | ||
| run security import "${keyfile}" -k "${keychain}" -P "${passphrase}" -T /usr/bin/codesign | ||
| # Set ACLs so the key can be used for code signing. | ||
| # Note: This selects all the signing keys (-s) in the keychain to be usable | ||
| # for code signing. Not a problem because the keychain is just for that only | ||
| # and only contains the keys we've just added. | ||
| run security set-key-partition-list \ | ||
| -S 'apple-tool:,apple:,codesign:' \ | ||
| -s -k "${keychain_password}" "${keychain}" | ||
| } | ||
|
|
||
| #----------------------------------------------------------------------------- | ||
| parse_args() { | ||
| OPTSTRING=':A:I:a:i:k:np:v' | ||
| while getopts "${OPTSTRING}" opt; do | ||
| case "${opt}" in | ||
| a) | ||
| if [[ -n "${APPLICATION_KEY_FILE}" ]]; then | ||
| error 'Application key specified multiple times' | ||
| fi | ||
| APPLICATION_KEY_FILE="$(get_key "${OPTARG}" appkey)" | ||
| ;; | ||
| A) | ||
| require_var "${OPTARG}" | ||
| APPLICATION_KEY_PASSWORD="${!OPTARG}" | ||
| ;; | ||
| i) | ||
| if [[ -n "${INSTALLER_KEY_FILE}" ]]; then | ||
| error 'Installation key specified multiple times' | ||
| fi | ||
| INSTALLER_KEY_FILE="$(get_key "${OPTARG}" instkey)" | ||
| ;; | ||
| I) | ||
| require_var "${OPTARG}" | ||
| INSTALLER_KEY_PASSWORD="${!OPTARG}" | ||
| ;; | ||
| k) | ||
| KEYCHAIN="${OPTARG}" | ||
| ;; | ||
| p) | ||
| if [[ -z "${OPTARG}" ]]; then | ||
| error 'Keychain password cannot be empty' | ||
| fi | ||
| KEYCHAIN_PASSWORD="${OPTARG}" | ||
| ;; | ||
|
|
||
| n) | ||
| DRY_RUN=true | ||
| ;; | ||
| v) | ||
| VERBOSE=true | ||
| ;; | ||
| \?) | ||
| error_usage 'Invalid option: -%s\n' "${OPTARG}" >&2 | ||
| ;; | ||
| :) | ||
| error_usage 'Option -%s requires an argument\n' "${OPTARG}" >&2 | ||
| ;; | ||
| esac | ||
| done | ||
| shift $((OPTIND - 1)) | ||
|
|
||
| # Keychains have to end with ".keychain". Add it if necessary. | ||
| KEYCHAIN="${KEYCHAIN%.keychain}.keychain" | ||
| } | ||
|
|
||
| #----------------------------------------------------------------------------- | ||
| # Run a command. If $VERBOSE or $DRY_RUN is true, echo the command. If | ||
| # $DRY_RUN is true, don't actually run the command, only print it. | ||
| run() { | ||
| if "${VERBOSE}" || "${DRY_RUN}"; then | ||
| echo "$@" | ||
| fi | ||
| "${DRY_RUN}" || "$@" | ||
| } | ||
|
|
||
| # Require an environment variable be set and not empty | ||
| require_var() { | ||
| local var="$1" | ||
| if [[ -z "${!var:-}" ]]; then | ||
| error 'env var "%s" unset or empty' "${var}" | ||
| fi | ||
| return 0 | ||
| } | ||
|
|
||
| # Require a file exists | ||
| require_file() { | ||
| local file="$1" | ||
| if ! [[ -f "${file}" ]]; then | ||
| error 'File does not exist: %s' "${file}" | ||
| fi | ||
| return 0 | ||
| } | ||
|
|
||
| # Get a key from an argument. If the argument starts with @, the rest is taken | ||
| # as a filename containing the key. Otherwise it is taken as an environment | ||
| # variable name which contains a base64 encoded key, which is decoded and placed | ||
| # into a temp file. The filename of the key is output to stdout. If a temp file | ||
| # was created, it will be removed when the script exits. | ||
| get_key() { | ||
| local key="$1" keytype="$2" fname | ||
| # If the key starts with an @, take the rest as a filename. Otherwise | ||
| # its an environment variable name. | ||
| if [[ "${key}" =~ ^@ ]]; then | ||
| fname="${key:1}" | ||
| require_file "${fname}" | ||
| else | ||
| require_var "${key}" | ||
| fname="$(mktemp -t "${keytype}").p12" | ||
| tmpfiles="${tmpfiles} ${fname}" | ||
| printenv "${key}" | base64 --decode > "${fname}" | ||
| fi | ||
| echo "${fname}" | ||
| } | ||
|
|
||
| error() { | ||
| # shellcheck disable=SC2059 # (Don't use variables in the printf format string) | ||
| # we take a format string arg - the format string IS a variable | ||
| printf "$@" >&2 | ||
| printf '\n' >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| error_usage() { | ||
| # shellcheck disable=SC2059 # (Don't use variables in the printf format string) | ||
| # we take a format string arg - the format string IS a variable | ||
| printf "$@" >&2 | ||
| printf '\n' >&2 | ||
| usage >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| #----------------------------------------------------------------------------- | ||
| # Only run main if executed as a script and not sourced. | ||
| if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then main "$@"; fi | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # MacOS/Darwin variables for packaging, signing and notarizing. | ||
| # | ||
| # These are parameterized per environment, with `promote` for official | ||
| # releases and `build` for development testing. These environment names | ||
| # come from our configuration in GitHub Actions. | ||
|
|
||
| # Default environment name if not specified. This is currently for Drone | ||
| # which does not set `ENVIRONMENT_NAME`. Once migrated fully to GitHub | ||
| # actions, we should change this to `build` as the default. | ||
| ENVIRONMENT_NAME ?= promote | ||
|
|
||
| # Variables defined here are defined with the environment name suffix | ||
| # to specify the appropriate value for that environment. The unsuffixed | ||
| # names select the appropriate value based on `ENVIRONMENT_NAME` | ||
|
|
||
| # Developer "team" and keys. | ||
| # TEAMID is an Apple-assigned identifier for a developer. It has two keys, | ||
| # one for signing binaries (application) and one for signing packages/images | ||
| # (installer). The keys are identified by name per-environment which we use | ||
| # to extract the key IDs. Key names can be view by running `security find-identity`. | ||
| # | ||
| # NOTE: If you need to export the DEVELOPER_ID_{APPLICATION,INSTALLER} | ||
| # variables to the environment for a command, it should be done within the | ||
| # recipe containing the command using $(eval export DEVELOPER_ID_APPLICATION ...). | ||
| # This is so the `security` shell command is only run to extract the key ID | ||
| # if necessary. If exported at the top level, it will run every time `make` | ||
| # is run. | ||
| # | ||
| # e.g. | ||
| # pkg: | ||
| # $(eval export DEVELOPER_ID_APPLICATION DEVELOPER_ID_INSTALLER) | ||
| # ./build.assets/build-package.sh ... | ||
| # | ||
| TEAMID = $(TEAMID_$(ENVIRONMENT_NAME)) | ||
| DEVELOPER_ID_APPLICATION = $(call get_key_id,$(DEVELOPER_KEY_NAME_$(ENVIRONMENT_NAME))) | ||
| DEVELOPER_ID_INSTALLER = $(call get_key_id,$(INSTALLER_KEY_NAME_$(ENVIRONMENT_NAME))) | ||
|
|
||
| # Don't export DEVELOPER_ID_APPLICATION or DEVELOPER_ID_INSTALLER as it | ||
| # evaluates them. They should only be evaluated them if used. | ||
| unexport DEVELOPER_ID_APPLICATION DEVELOPER_ID_INSTALLER | ||
|
|
||
| # Bundle IDs identify packages/images. We use different bundle IDs for | ||
| # release and development. | ||
| TELEPORT_BUNDLEID = $(TELEPORT_BUNDLEID_$(ENVIRONMENT_NAME)) | ||
| TSH_BUNDLEID = $(TSH_BUNDLEID_$(ENVIRONMENT_NAME)) | ||
|
|
||
| # TSH_SKELETON is a directory name relative to build.assets/macos/ | ||
| TSH_SKELETON = $(TSH_SKELETON_$(ENVIRONMENT_NAME)) | ||
|
|
||
| # --- promote environment | ||
| # Key names can be found on https://goteleport.com/security | ||
| TEAMID_promote = QH8AA5B8UP | ||
| DEVELOPER_KEY_NAME_promote = Developer ID Application: Gravitational Inc. | ||
| INSTALLER_KEY_NAME_promote = Developer ID Installer: Gravitational Inc. | ||
| TELEPORT_BUNDLEID_promote = com.gravitational.teleport | ||
| TSH_BUNDLEID_promote = $(TEAMID).com.gravitational.teleport.tsh | ||
| TSH_SKELETON_promote = tsh | ||
|
|
||
| # --- build environment | ||
| TEAMID_build = K497G57PDJ | ||
| DEVELOPER_KEY_NAME_build = Developer ID Application: Ada Lin | ||
| INSTALLER_KEY_NAME_build = Developer ID Installer: Ada Lin | ||
|
Comment on lines
+61
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @adaadb6 This developer account is getting codified in our release pipeline. It is only used for signing binaries that are pending release. When they're ready to be promoted the signatures are stripped, and it is re-signed with the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wadells I'm not sure what the release process is meant to be - I was not planning on stripping any signatures and re-signing. The builds from master and branch/* would be built from source and signed with the |
||
| TELEPORT_BUNDLEID_build = com.goteleport.dev | ||
| TSH_BUNDLEID_build = $(TEAMID).com.goteleport.tshdev | ||
| TSH_SKELETON_build = tshdev | ||
|
|
||
| # --- utility | ||
| # Extract application/installer key ID from keychain. This looks at all | ||
| # keychains in the search path. It should be used with $(call ...). | ||
| # e.g. $(call get_key_id,Key Name goes here) | ||
| get_key_id = $(or $(word 2,$(shell $(get_key_id_cmd))), $(missing_key_error)) | ||
| get_key_id_cmd = security find-identity -v -s codesigning | grep --fixed-strings --max-count=1 "$(1)" | ||
| missing_key_error = $(error Could not find key named "$(1)" in keychain) | ||
|
|
||
| # Dont export missing_key_error or get_key_id as it evaluates them | ||
| unexport missing_key_error get_key_id | ||
Uh oh!
There was an error while loading. Please reload this page.