Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1091,6 +1098,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
Expand All @@ -1102,6 +1110,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)/
Expand Down
19 changes: 0 additions & 19 deletions build.assets/build-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
219 changes: 219 additions & 0 deletions build.assets/keychain-setup.sh
Comment thread
camscale marked this conversation as resolved.
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
Comment thread
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/')"
Comment thread
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

76 changes: 76 additions & 0 deletions darwin-signing.mk
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 Gravitational Inc certificates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 promote keys. This is how the pipelines currently work. Are we changing the release process around this?

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