mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
af8fe6f57a
Update the build scripts to properly set up the key for signing packages using `productsign`, and parameterise the bundle ID for packages in the packaging scripts.
224 lines
7 KiB
Bash
Executable file
224 lines
7 KiB
Bash
Executable file
#!/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
|
|
|
|
# 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/')"
|
|
# 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` and
|
|
# `/usr/bin/productsign` 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 \
|
|
-T /usr/bin/productsign
|
|
# 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
|
|
|