mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-19 01:58:44 +08:00
release: Prepare for MacOS builds on GitHub Actions (#23407)
* release: Move Mac signing vars from script to Makefile Move the variables for Mac signing from the `build-common.sh` shell script to the `Makefile`. These vars will need to be passed to other build processes to parameterize the signing for different GitHub Actions build environments. The switch on `ENVIRONMENT_NAME` allows different secrets to be available in GitHub Actions for production (promote) vs developer (build) builds. The default environment name is `promote` so as to be compatible with the existing Drone setup, which does not define `ENVIRONMENT_NAME`. * release: Determine Mac signing key IDs automatically Remove the hard-coded MacOS signing key IDs from the Makefile and find them dynamically based on the name of the key. This allows GitHub Actions to be set up with new keys different to the ones on the Drone builders. As long as we keep the same name on the keys, we can rotate the keys without needing to update the IDs in the Makefile. This requires us to be more judicious about exporting the variables as exporting them causes them to be evaluated. We do not want to evaluate them on non-darwin targets, and on darwin, we should only evaluate it if needed for a recipe. So use a dynamic `eval` in the recipes that need the environment variables. * release: Pass key & team ID to notarize tool Override the hard-coded values in `notarize-apple-binaries` and pass the values we get based on the GitHub Actions environment. This allows us to sign and notarize software in a development branch more easily when working on the signing and notarizing process. This will not happen automatically, but it is expected that a developer can manually trigger a workflow to perform building, signing and notarizing from a dev branch where the workflow has temporarily changed the environment to `build`. A similar change to the `Makefile` in the teleport.e repository goes with this change. This adds a new bundle ID of `com.goteleport.dev` for the dev build of Teleport. This follows the same pattern as used for the dev build of the `tsh` binary and the current production bundle ID for Teleport. Previously there was no dev signing/notarizing process for the set of Teleport binaries. * release: Add script to setup the MacOS keychain for signing Add a script for setting up the MacOS keychain for signing applications and packages. It encapsulates the `security` commands to add either or both application keys and installer keys. The keys can be either base64-encoded in environment variables, or `.p12` files on disk, making it useful for local development. * release: Split MacOS signing vars into separate mk file Put the MacOS signing variables into a separate `.mk` file and include it from the main `Makefile`. Add more comments to document the purpose of the vars and where some of the values come from. * release: Add some more comments to keychain-setup.sh Explain that the purpose of the script is to be run on CI, but can also be run manually. Add the default values used to the usage message for the keychain and password. * Address PR comments on keychain-setup.sh script * Change shebang to /bin/bash * Use heredoc instead of multiple printfs for usage message * Move `local` declaration next to setting of kpath var * release: Export DEVELOPER_ID_APPLICATION in release-darwin The sub-make for enterprise needs this to be set or it cannot sign the enterprise binaries. Export it if we are doing signing/notarizing.
This commit is contained in:
@@ -429,18 +429,25 @@ build-archive:
|
||||
release-unix: clean full build-archive
|
||||
@if [ -f e/Makefile ]; then $(MAKE) -C e release; fi
|
||||
|
||||
include darwin-signing.mk
|
||||
|
||||
.PHONY: release-darwin-unsigned
|
||||
release-darwin-unsigned: RELEASE:=$(RELEASE)-unsigned
|
||||
release-darwin-unsigned: clean full build-archive
|
||||
|
||||
.PHONY: release-darwin
|
||||
# Only run signing/notarization if Apple username/pass are provided.
|
||||
# Export DEVELOPER_ID_APPLICATION so it can be used by e/Makefile.
|
||||
release-darwin: ABSOLUTE_BINARY_PATHS:=$(addprefix $(CURDIR)/,$(BINARIES))
|
||||
release-darwin: release-darwin-unsigned
|
||||
# Only run if Apple username/pass for notarization are provided
|
||||
if [ -n "$$APPLE_USERNAME" -a -n "$$APPLE_PASSWORD" ]; then \
|
||||
$(eval export DEVELOPER_ID_APPLICATION) \
|
||||
cd ./build.assets/tooling/ && \
|
||||
go run ./cmd/notarize-apple-binaries/*.go \
|
||||
--log-level=debug $(ABSOLUTE_BINARY_PATHS); \
|
||||
--developer-id=$(DEVELOPER_ID_APPLICATION) \
|
||||
--bundle-id=$(TELEPORT_BUNDLEID) \
|
||||
--log-level=debug \
|
||||
$(ABSOLUTE_BINARY_PATHS); \
|
||||
fi
|
||||
$(MAKE) build-archive
|
||||
@if [ -f e/Makefile ]; then $(MAKE) -C e release; fi
|
||||
@@ -1116,6 +1123,7 @@ endif
|
||||
# build .pkg
|
||||
.PHONY: pkg
|
||||
pkg:
|
||||
$(eval export DEVELOPER_ID_APPLICATION DEVELOPER_ID_INSTALLER)
|
||||
mkdir -p $(BUILDDIR)/
|
||||
cp ./build.assets/build-package.sh ./build.assets/build-common.sh $(BUILDDIR)/
|
||||
chmod +x $(BUILDDIR)/build-package.sh
|
||||
@@ -1127,6 +1135,7 @@ pkg:
|
||||
# build tsh client-only .pkg
|
||||
.PHONY: pkg-tsh
|
||||
pkg-tsh:
|
||||
$(eval export DEVELOPER_ID_APPLICATION DEVELOPER_ID_INSTALLER)
|
||||
./build.assets/build-pkg-tsh.sh -t oss -v $(VERSION) $(TARBALL_PATH_SECTION)
|
||||
mkdir -p $(BUILDDIR)/
|
||||
mv tsh*.pkg* $(BUILDDIR)/
|
||||
|
||||
@@ -6,25 +6,6 @@
|
||||
# Toggle this via flags in your main script.
|
||||
DRY_RUN_PREFIX=''
|
||||
|
||||
# Teleport / tsh certificates/info.
|
||||
# Used by other scripts.
|
||||
#shellcheck disable=SC2034
|
||||
readonly DEVELOPER_ID_APPLICATION='0FFD3E3413AB4C599C53FBB1D8CA690915E33D83'
|
||||
#shellcheck disable=SC2034
|
||||
readonly DEVELOPER_ID_INSTALLER='82B625AD327C241B378A54B4B254BB08CE71B5DF'
|
||||
readonly TEAMID='QH8AA5B8UP'
|
||||
#shellcheck disable=SC2034
|
||||
readonly TSH_BUNDLEID="$TEAMID.com.gravitational.teleport.tsh"
|
||||
#shellcheck disable=SC2034
|
||||
readonly TSH_SKELETON='tsh' # relative to build.assets/macos/
|
||||
|
||||
# tshdev certs/info.
|
||||
#readonly DEVELOPER_ID_APPLICATION='A5604F285B0957134EA099AC515BD9E0787228AC'
|
||||
#readonly DEVELOPER_ID_INSTALLER='C1A831A974DF69563432C87A4979F7982DD91FBE'
|
||||
#readonly TEAMID='K497G57PDJ'
|
||||
#readonly TSH_BUNDLEID="$TEAMID.com.goteleport.tshdev"
|
||||
#readonly TSH_SKELETON='tshdev' # relative to build.assets/macos/
|
||||
|
||||
# TARBALL_CACHE is used by find_or_fetch_tarball.
|
||||
readonly TARBALL_CACHE=/tmp/teleport-tarballs
|
||||
|
||||
|
||||
Executable
+219
@@ -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
|
||||
|
||||
# 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` 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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
Reference in New Issue
Block a user