mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
ci: sign macOS release artifacts with Azure Key Vault (#26252)
## Why The public Codex release workflow needs to sign and notarize macOS binaries and DMGs without placing the Developer ID private key in GitHub. This moves the private-key operation behind the protected `codesigning` environment and uses GitHub OIDC with Azure Key Vault PKCS#11, while preserving the existing external `build_unsigned` / `promote_signed` fallback. ## What changed - Add a reusable AKV PKCS11 setup action that authenticates to Azure with OIDC, downloads pinned signing tools, verifies their SHA-256 digests, and loads the public signing certificate from Key Vault. - Replace the legacy macOS signing action with scripts that support AKV-backed `rcodesign`, notarize signed binaries and DMGs, and staple DMG notarization tickets. - Restructure `rust-release.yml` so macOS builds produce unsigned artifacts first, protected jobs perform signing and notarization, macOS runners package and verify the results, and release publishing waits for verified artifacts. - Preserve the manual external-signing handoff flow and make manual-mode conditions explicit. - Move the Codex entitlements file alongside the signing scripts and update CODEOWNERS for the new signing surfaces. ## Verification - [Live protected signing workflow run](https://github.com/openai/codex/actions/runs/26903610631) completed successfully for both macOS architectures, including binary signing/notarization, DMG signing/notarization, and final artifact verification. - Downloaded both signed DMGs and independently verified their checksums and strict signatures. - Confirmed `xcrun stapler validate` succeeds and Gatekeeper accepts both DMGs as `Notarized Developer ID`. - Mounted both DMGs and confirmed the contained `codex` and `codex-responses-api-proxy` binaries have valid Developer ID signatures for the expected architectures. --------- Co-authored-by: shijie-openai <shijie.rao@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
c143a86de8
commit
ad2012d645
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Submits a signed standalone macOS binary to Apple notarization through
|
||||
# rcodesign. Standalone binaries cannot carry a stapled ticket, so the binary
|
||||
# is submitted in a ZIP and the successful notarization log is retained.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: notarize_macos_binary_with_rcodesign.sh --binary PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
|
||||
|
||||
Options:
|
||||
--binary PATH Signed standalone macOS binary to notarize.
|
||||
--report-dir PATH Directory for notarization logs.
|
||||
--max-wait-seconds SECONDS Maximum rcodesign notarization wait time.
|
||||
EOF
|
||||
}
|
||||
|
||||
binary_path=""
|
||||
report_dir="${RUNNER_TEMP:-/tmp}/macos-binary-notarization-verification"
|
||||
max_wait_seconds="600"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--binary)
|
||||
binary_path="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--report-dir)
|
||||
report_dir="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--max-wait-seconds)
|
||||
max_wait_seconds="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown notarization argument: $1" >&2
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$binary_path" ]]; then
|
||||
echo "--binary is required." >&2
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f "$binary_path" ]]; then
|
||||
echo "Binary does not exist: $binary_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
|
||||
echo "--max-wait-seconds must be a non-negative integer." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for command_name in rcodesign zip; do
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
echo "$command_name was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
missing_environment=0
|
||||
for variable_name in \
|
||||
APPLE_NOTARIZATION_ISSUER_ID \
|
||||
APPLE_NOTARIZATION_KEY_ID \
|
||||
APPLE_NOTARIZATION_KEY_P8
|
||||
do
|
||||
if [[ -z "${!variable_name:-}" ]]; then
|
||||
echo "$variable_name must be set from CI secrets before notarizing a binary." >&2
|
||||
missing_environment=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$missing_environment" -ne 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$report_dir"
|
||||
|
||||
notarization_temp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$notarization_temp_dir" >/dev/null' EXIT
|
||||
|
||||
private_key_path="$notarization_temp_dir/AuthKey_${APPLE_NOTARIZATION_KEY_ID}.p8"
|
||||
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 --decode >"$private_key_path" 2>/dev/null; then
|
||||
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 -D >"$private_key_path" 2>/dev/null; then
|
||||
echo "APPLE_NOTARIZATION_KEY_P8 must be a base64-encoded .p8 private key." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
chmod 600 "$private_key_path"
|
||||
|
||||
api_key_path="$notarization_temp_dir/app-store-connect-api-key.json"
|
||||
rcodesign encode-app-store-connect-api-key \
|
||||
--output-path "$api_key_path" \
|
||||
"$APPLE_NOTARIZATION_ISSUER_ID" \
|
||||
"$APPLE_NOTARIZATION_KEY_ID" \
|
||||
"$private_key_path" \
|
||||
>"$report_dir/encode-app-store-connect-api-key.log" 2>&1
|
||||
|
||||
binary_name="$(basename "$binary_path")"
|
||||
archive_path="$notarization_temp_dir/${binary_name}.zip"
|
||||
(
|
||||
cd "$(dirname "$binary_path")"
|
||||
zip -q "$archive_path" "$binary_name"
|
||||
)
|
||||
|
||||
notarization_log="$report_dir/${binary_name}-notarization.log"
|
||||
rcodesign notarize \
|
||||
--api-key-file "$api_key_path" \
|
||||
--max-wait-seconds "$max_wait_seconds" \
|
||||
--wait \
|
||||
"$archive_path" \
|
||||
2>&1 | tee "$notarization_log"
|
||||
|
||||
{
|
||||
echo "binary_name=$binary_name"
|
||||
echo "max_wait_seconds=$max_wait_seconds"
|
||||
echo "binary_sha256=$(shasum -a 256 "$binary_path" | awk '{ print $1 }')"
|
||||
echo "rcodesign_notarize=completed"
|
||||
} >"$report_dir/${binary_name}-notarization-summary.txt"
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Notarizes and staples a signed macOS DMG through rcodesign.
|
||||
#
|
||||
# This is the Linux-compatible notarization path for the AKV/PKCS#11 signing
|
||||
# flow. It records notarization inputs and logs so workflow artifacts can be
|
||||
# audited without exposing the App Store Connect private key.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: notarize_macos_dmg_with_rcodesign.sh --dmg PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
|
||||
|
||||
Options:
|
||||
--dmg PATH Signed DMG to submit to Apple notarization.
|
||||
--report-dir PATH Directory for notarization logs.
|
||||
--max-wait-seconds SECONDS Maximum rcodesign notarization wait time.
|
||||
EOF
|
||||
}
|
||||
|
||||
dmg_path=""
|
||||
report_dir="${RUNNER_TEMP:-/tmp}/macos-notarization-verification"
|
||||
max_wait_seconds="600"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dmg)
|
||||
dmg_path="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--report-dir)
|
||||
report_dir="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--max-wait-seconds)
|
||||
max_wait_seconds="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown notarization argument: $1" >&2
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$dmg_path" ]]; then
|
||||
echo "--dmg is required." >&2
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f "$dmg_path" ]]; then
|
||||
echo "DMG does not exist: $dmg_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
|
||||
echo "--max-wait-seconds must be a non-negative integer." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v rcodesign > /dev/null 2>&1; then
|
||||
echo "rcodesign was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missing_environment=0
|
||||
for variable_name in \
|
||||
APPLE_NOTARIZATION_ISSUER_ID \
|
||||
APPLE_NOTARIZATION_KEY_ID \
|
||||
APPLE_NOTARIZATION_KEY_P8
|
||||
do
|
||||
if [[ -z "${!variable_name:-}" ]]; then
|
||||
echo "$variable_name must be set from CI secrets before notarizing a DMG." >&2
|
||||
missing_environment=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$missing_environment" -ne 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$report_dir"
|
||||
|
||||
notarization_temp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$notarization_temp_dir" > /dev/null' EXIT
|
||||
|
||||
private_key_path="$notarization_temp_dir/AuthKey_${APPLE_NOTARIZATION_KEY_ID}.p8"
|
||||
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 --decode > "$private_key_path" 2> /dev/null; then
|
||||
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 -D > "$private_key_path" 2> /dev/null; then
|
||||
echo "APPLE_NOTARIZATION_KEY_P8 must be a base64-encoded .p8 private key." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
chmod 600 "$private_key_path"
|
||||
|
||||
api_key_path="$notarization_temp_dir/app-store-connect-api-key.json"
|
||||
rcodesign encode-app-store-connect-api-key \
|
||||
--output-path "$api_key_path" \
|
||||
"$APPLE_NOTARIZATION_ISSUER_ID" \
|
||||
"$APPLE_NOTARIZATION_KEY_ID" \
|
||||
"$private_key_path" \
|
||||
> "$report_dir/encode-app-store-connect-api-key.log" 2>&1
|
||||
|
||||
notarization_log="$report_dir/dmg-notarization.log"
|
||||
rcodesign notarize \
|
||||
--api-key-file "$api_key_path" \
|
||||
--max-wait-seconds "$max_wait_seconds" \
|
||||
--staple \
|
||||
"$dmg_path" \
|
||||
2>&1 | tee "$notarization_log"
|
||||
|
||||
{
|
||||
echo "dmg_path=$dmg_path"
|
||||
echo "max_wait_seconds=$max_wait_seconds"
|
||||
echo "dmg_sha256=$(shasum -a 256 "$dmg_path" | awk '{ print $1 }')"
|
||||
echo "rcodesign_notarize_staple=completed"
|
||||
} > "$report_dir/dmg-notarization-summary.txt"
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Small compatibility wrapper around native codesign and rcodesign.
|
||||
#
|
||||
# Existing packaging scripts call this instead of choosing a signing backend
|
||||
# directly. OAI_CODESIGN_BACKEND=akv-pkcs11 routes signing through rcodesign
|
||||
# while preserving the option, entitlement, identifier, timestamp, and deep
|
||||
# signing surface used by the native codesign path.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: sign_macos_code.sh --target PATH --identity IDENTITY [options]
|
||||
|
||||
Options:
|
||||
--deep true|false
|
||||
--entitlements PATH
|
||||
--identifier IDENTIFIER
|
||||
--identity IDENTITY
|
||||
--options FLAGS
|
||||
--target PATH
|
||||
--timestamp true|false|none
|
||||
EOF
|
||||
}
|
||||
|
||||
target=""
|
||||
identity=""
|
||||
options=""
|
||||
entitlements_file=""
|
||||
identifier=""
|
||||
deep="false"
|
||||
timestamp="true"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--deep)
|
||||
deep="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--entitlements)
|
||||
entitlements_file="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--identifier)
|
||||
identifier="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--identity)
|
||||
identity="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--options)
|
||||
options="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--target)
|
||||
target="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--timestamp)
|
||||
timestamp="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown signing argument: $1" >&2
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
echo "--target is required." >&2
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -e "$target" ]]; then
|
||||
echo "Signing target does not exist: $target" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$deep" in
|
||||
true|false) ;;
|
||||
*)
|
||||
echo "--deep must be true or false, got '$deep'." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$timestamp" in
|
||||
true|false|none) ;;
|
||||
*)
|
||||
echo "--timestamp must be true, false, or none, got '$timestamp'." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
sign_with_codesign() {
|
||||
if [[ -z "$identity" ]]; then
|
||||
echo "Native codesign requires --identity." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
local -a args
|
||||
args=(--force)
|
||||
|
||||
if [[ "$deep" == "true" ]]; then
|
||||
args+=(--deep)
|
||||
fi
|
||||
|
||||
if [[ -n "$options" ]]; then
|
||||
args+=(--options "$options")
|
||||
fi
|
||||
|
||||
case "$timestamp" in
|
||||
true)
|
||||
args+=(--timestamp)
|
||||
;;
|
||||
false|none)
|
||||
args+=(--timestamp=none)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$entitlements_file" ]]; then
|
||||
args+=(--entitlements "$entitlements_file")
|
||||
fi
|
||||
|
||||
if [[ -n "$identifier" ]]; then
|
||||
args+=(--identifier "$identifier")
|
||||
fi
|
||||
|
||||
args+=(--sign "$identity" "$target")
|
||||
codesign "${args[@]}"
|
||||
}
|
||||
|
||||
append_rcodesign_flags() {
|
||||
local raw_options="$1"
|
||||
local option=""
|
||||
|
||||
if [[ -z "$raw_options" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
IFS=',' read -ra split_options <<< "$raw_options"
|
||||
for option in "${split_options[@]}"; do
|
||||
option="${option//[[:space:]]/}"
|
||||
[[ -z "$option" ]] && continue
|
||||
|
||||
case "$option" in
|
||||
host|hard|kill|expires|restrict|library|runtime|linker-signed)
|
||||
rcodesign_args+=(--code-signature-flags "$option")
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported rcodesign code signature option: $option" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
rcodesign_options_require_notarization() {
|
||||
local raw_options="$1"
|
||||
local option=""
|
||||
|
||||
if [[ -z "$raw_options" || "$timestamp" != "true" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS=',' read -ra split_options <<< "$raw_options"
|
||||
for option in "${split_options[@]}"; do
|
||||
option="${option//[[:space:]]/}"
|
||||
if [[ "$option" == "runtime" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
sign_with_rcodesign() {
|
||||
: "${OAI_AKV_PKCS11_LIBRARY:?OAI_AKV_PKCS11_LIBRARY is required for AKV PKCS11 signing.}"
|
||||
: "${OAI_AKV_SIGNING_CERTIFICATE_PEM:?OAI_AKV_SIGNING_CERTIFICATE_PEM is required for AKV PKCS11 signing.}"
|
||||
: "${OAI_AKV_KEY_LABEL:?OAI_AKV_KEY_LABEL is required for AKV PKCS11 signing.}"
|
||||
|
||||
if ! command -v rcodesign >/dev/null 2>&1; then
|
||||
echo "rcodesign was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local -a rcodesign_args
|
||||
rcodesign_args=(
|
||||
sign
|
||||
--config-file /dev/null
|
||||
--pkcs11-library "$OAI_AKV_PKCS11_LIBRARY"
|
||||
--pkcs11-certificate-file "$OAI_AKV_SIGNING_CERTIFICATE_PEM"
|
||||
--pkcs11-key-label "$OAI_AKV_KEY_LABEL"
|
||||
)
|
||||
|
||||
if [[ "$deep" == "false" ]]; then
|
||||
rcodesign_args+=(--shallow)
|
||||
fi
|
||||
|
||||
case "$timestamp" in
|
||||
true)
|
||||
;;
|
||||
false|none)
|
||||
rcodesign_args+=(--timestamp-url none)
|
||||
;;
|
||||
esac
|
||||
|
||||
append_rcodesign_flags "$options"
|
||||
if rcodesign_options_require_notarization "$options"; then
|
||||
rcodesign_args+=(--for-notarization)
|
||||
fi
|
||||
|
||||
if [[ -n "$entitlements_file" ]]; then
|
||||
rcodesign_args+=(--entitlements-xml-file "$entitlements_file")
|
||||
fi
|
||||
|
||||
if [[ -n "$identifier" ]]; then
|
||||
rcodesign_args+=(--binary-identifier "$identifier")
|
||||
fi
|
||||
|
||||
rcodesign_args+=("$target")
|
||||
rcodesign "${rcodesign_args[@]}"
|
||||
}
|
||||
|
||||
case "${OAI_CODESIGN_BACKEND:-codesign}" in
|
||||
codesign|"")
|
||||
sign_with_codesign
|
||||
;;
|
||||
akv-pkcs11)
|
||||
sign_with_rcodesign
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OAI_CODESIGN_BACKEND: ${OAI_CODESIGN_BACKEND}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user