Skip to content

Commit 856c8e5

Browse files
committed
ci: require imported macOS signing identity
1 parent 0eb645e commit 856c8e5

2 files changed

Lines changed: 48 additions & 6 deletions

File tree

.github/workflows/build-desktop-tauri.yml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,16 @@ jobs:
276276
run: |
277277
set -euo pipefail
278278
if [ -z "${APPLE_CERTIFICATE:-}" ]; then
279-
echo "APPLE_CERTIFICATE is not configured; skipping certificate import."
280-
printf 'signing_identity=\n' >> "$GITHUB_OUTPUT"
281-
exit 0
279+
echo "::error::APPLE_CERTIFICATE is required for macOS release builds." >&2
280+
exit 1
281+
fi
282+
if [ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]; then
283+
echo "::error::APPLE_CERTIFICATE_PASSWORD is required for macOS release builds." >&2
284+
exit 1
285+
fi
286+
if [ -z "${KEYCHAIN_PASSWORD:-}" ]; then
287+
echo "::error::KEYCHAIN_PASSWORD is required for macOS release builds." >&2
288+
exit 1
282289
fi
283290
CERT_PATH="$RUNNER_TEMP/certificate.p12"
284291
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain"
@@ -311,9 +318,20 @@ jobs:
311318
echo "::error::Failed to parse the imported Developer ID Application identity." >&2
312319
exit 1
313320
fi
321+
CERT_TEAM_ID="$({
322+
security find-certificate -c "$CERT_ID" -p "$KEYCHAIN_PATH" |
323+
openssl x509 -noout -subject -nameopt RFC2253
324+
} | tr ',' '\n' | awk -F= '$1 == "OU" { print $2; exit }')"
325+
if ! printf '%s' "$CERT_TEAM_ID" | grep -Eq '^[A-Z0-9]{10}$'; then
326+
echo "::error::Failed to extract a valid Apple Team ID from the imported certificate." >&2
327+
exit 1
328+
fi
314329
printf 'APPLE_SIGNING_IDENTITY=%s\n' "$CERT_ID" >> "$GITHUB_ENV"
330+
printf 'APPLE_TEAM_ID=%s\n' "$CERT_TEAM_ID" >> "$GITHUB_ENV"
315331
printf 'signing_identity=%s\n' "$CERT_ID" >> "$GITHUB_OUTPUT"
332+
printf 'team_id=%s\n' "$CERT_TEAM_ID" >> "$GITHUB_OUTPUT"
316333
echo "Imported signing identity: $CERT_ID"
334+
echo "Imported certificate team: $CERT_TEAM_ID"
317335
318336
- name: Prepare desktop resources (macOS)
319337
env:
@@ -369,11 +387,8 @@ jobs:
369387
ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS: ${{ vars.ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS || '' }}
370388
ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS: ${{ vars.ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS || '3' }}
371389
ASTRBOT_MACOS_BUILD_RETRY_SLEEP_SECONDS: ${{ vars.ASTRBOT_MACOS_BUILD_RETRY_SLEEP_SECONDS || '8' }}
372-
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
373-
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
374390
APPLE_ID: ${{ secrets.APPLE_ID }}
375391
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
376-
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
377392
GITHUB_TOKEN: ${{ github.token }}
378393
GH_TOKEN: ${{ github.token }}
379394
shell: bash

scripts/ci/build-desktop-tauri-workflow.test.mjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const RELEASE_JOB = 'release';
1313
const PREPARE_RESOURCES_RUN = /pnpm run prepare:resources/;
1414
const PRESIGN_BACKEND_RUN = /codesign-macos-nested\.sh\s+"resources\/backend"/;
1515
const BUILD_APP_BUNDLE_RUN = /cargo tauri build --verbose --target/;
16+
const IMPORT_APPLE_CERTIFICATE_RUN = /security import "\$CERT_PATH"/;
1617

1718
test('findStep supports predicate and regex matching', () => {
1819
const steps = [
@@ -70,6 +71,32 @@ test('macOS workflow prepares resources before optional pre-signing', async () =
7071
);
7172
});
7273

74+
test('macOS workflow requires one certificate and derives its team ID', async () => {
75+
const workflowObject = await readWorkflowObject(WORKFLOW_FILE);
76+
const steps = extractWorkflowJobSteps(workflowObject, BUILD_MACOS_JOB);
77+
const importStep = findStep(
78+
steps,
79+
'Import Apple Developer Certificate',
80+
(step) => IMPORT_APPLE_CERTIFICATE_RUN.test(step.run ?? ''),
81+
);
82+
const buildStep = findStep(
83+
steps,
84+
'Build desktop app bundle (macOS)',
85+
(step) => BUILD_APP_BUNDLE_RUN.test(step.run ?? ''),
86+
);
87+
88+
assert.equal(importStep.env?.APPLE_CERTIFICATE, '${{ secrets.APPLE_CERTIFICATE }}');
89+
assert.equal(importStep.env?.APPLE_CERTIFICATE_PASSWORD, '${{ secrets.APPLE_CERTIFICATE_PASSWORD }}');
90+
assert.match(importStep.run, /APPLE_CERTIFICATE is required for macOS release builds/);
91+
assert.match(importStep.run, /APPLE_CERTIFICATE_PASSWORD is required for macOS release builds/);
92+
assert.match(importStep.run, /Expected exactly one Developer ID Application identity/);
93+
assert.match(importStep.run, /openssl x509 -noout -subject -nameopt RFC2253/);
94+
assert.match(importStep.run, /APPLE_TEAM_ID=%s/);
95+
assert.equal(buildStep.env?.APPLE_CERTIFICATE, undefined);
96+
assert.equal(buildStep.env?.APPLE_CERTIFICATE_PASSWORD, undefined);
97+
assert.equal(buildStep.env?.APPLE_TEAM_ID, undefined);
98+
});
99+
73100
test('release workflow disables generated release notes for nightly builds', async () => {
74101
const workflowObject = await readWorkflowObject(WORKFLOW_FILE);
75102
const steps = extractWorkflowJobSteps(workflowObject, RELEASE_JOB);

0 commit comments

Comments
 (0)