Skip to content

Import each signing certificate a build passes, rather than one named for Developer ID - #206

Merged
agoodkind merged 3 commits into
mainfrom
ci-distribution-cert-secret
Aug 8, 2026
Merged

agoodkind merged 3 commits into
mainfrom
ci-distribution-cert-secret

Conversation

@agoodkind

@agoodkind agoodkind commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

A build imports each signing certificate it passes, instead of one slot named for Developer ID.

What was wrong

There was a single certificate slot, APPLE_DEVELOPER_ID_P12_BASE64, and both the CI workflow and the release workflow read it.

That works for a repository that signs everything with Developer ID. It does not work for one whose CI runner is not a registered device. Such a repository cannot use development provisioning, so its CI signs with an Apple Distribution certificate and App Store profiles, which carry no device list. It still has to release with Developer ID, because a downloadable app cannot be App Store signed. One slot, two certificates: it could hold one or the other and never both.

iphone-cell-tunnel is in exactly that state. Its APPLE_DEVELOPER_ID_P12_BASE64 deliberately holds its Apple Distribution certificate so CI can sign, and its release then fails resolving a Developer ID identity that is not there.

The change

Each certificate is its own input and its own secret. A caller passes whichever ones it signs with, and signing-identity-name selects which one a given build uses, so passing both is normal for a repository that signs its CI and its release differently.

The import action creates the keychain itself and guards each import on whether that certificate was passed, so neither import has to know about the other. It refuses only when no certificate was passed at all, and a failed identity lookup now lists what was imported, which is what separates supplying the wrong certificate from the right one failing to validate.

The release workflow passes the Developer ID certificate explicitly, because a release signs with Developer ID whatever a repository's CI signs with.

Consumers

None. All five consumers call the reusable workflow with secrets: inherit, and this only adds declared names. No repository calls the ci-gate action directly, so the input rename reaches nobody. Four of the five sign CI with Developer ID and hold only that pair; their behavior is identical.

Verified per repository rather than assumed: macos-smc-fan, macos-fan-curve, lmd, and stickies-improved each name a Developer ID identity and hold only Developer ID secrets. iphone-cell-tunnel names an Apple Distribution identity, and is the one this unblocks.

Gate green.

Copilot AI lite review requested due to automatic review settings August 8, 2026 22:10
@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agoodkind, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 241d9b13-df07-4018-ab28-241cc4763fd4

📥 Commits

Reviewing files that changed from the base of the PR and between 434e79d and 89d1680.

📒 Files selected for processing (5)
  • .github/actions/ci-gate/action.yml
  • .github/actions/import-signing-cert/action.yml
  • .github/actions/workflow-helper/workflow-helper.swift
  • .github/workflows/_ci.yml
  • .github/workflows/_release.yml
📝 Walkthrough

Walkthrough

The CI workflow declares optional Apple Distribution certificate secrets. Verify and Extra Targets signing gates use these secrets when set and fall back to existing Developer ID credentials.

Changes

Apple Distribution CI Signing

Layer / File(s) Summary
Signing secret contract
.github/workflows/_ci.yml
The reusable workflow declares optional Apple Distribution certificate and password secrets.
Signing gate resolution
.github/workflows/_ci.yml
Verify and Extra Targets pool gates and hosted retries prefer Apple Distribution credentials and fall back to Developer ID credentials.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: importing the signing certificate required by each build.
Description check ✅ Passed The description directly explains the certificate separation, CI fallback behavior, release behavior, and affected consumer repositories.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci-distribution-cert-secret

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the reusable CI workflow to support signing with a dedicated Apple Distribution certificate in CI, while preserving the existing behavior (and keeping release signing tied to the Developer ID certificate).

Changes:

  • Adds optional reusable-workflow secrets APPLE_DISTRIBUTION_P12_BASE64 and APPLE_DISTRIBUTION_P12_PASSWORD.
  • Updates CI-gate invocations to prefer the distribution certificate secrets when present, otherwise fall back to the Developer ID secrets.
Suppressed comments (3)

.github/workflows/_ci.yml:419

  • The fallback logic selects the base64 and password independently. If a consumer sets only one of the APPLE_DISTRIBUTION_* secrets (or accidentally leaves one empty), CI can end up pairing the distribution .p12 with the developer-id password (or vice versa), which will fail during certificate import with a confusing error. Prefer using the distribution pair only when both distribution secrets are set, otherwise fall back entirely to the Developer ID pair.
          apple-developer-id-p12-base64: ${{ secrets.APPLE_DISTRIBUTION_P12_BASE64 || secrets.APPLE_DEVELOPER_ID_P12_BASE64 }}
          apple-developer-id-p12-password: ${{ secrets.APPLE_DISTRIBUTION_P12_PASSWORD || secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}

.github/workflows/_ci.yml:545

  • The fallback logic selects the base64 and password independently. If a consumer sets only one of the APPLE_DISTRIBUTION_* secrets (or accidentally leaves one empty), CI can end up pairing the distribution .p12 with the developer-id password (or vice versa), which will fail during certificate import with a confusing error. Prefer using the distribution pair only when both distribution secrets are set, otherwise fall back entirely to the Developer ID pair.
          apple-developer-id-p12-base64: ${{ secrets.APPLE_DISTRIBUTION_P12_BASE64 || secrets.APPLE_DEVELOPER_ID_P12_BASE64 }}
          apple-developer-id-p12-password: ${{ secrets.APPLE_DISTRIBUTION_P12_PASSWORD || secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}

.github/workflows/_ci.yml:592

  • The fallback logic selects the base64 and password independently. If a consumer sets only one of the APPLE_DISTRIBUTION_* secrets (or accidentally leaves one empty), CI can end up pairing the distribution .p12 with the developer-id password (or vice versa), which will fail during certificate import with a confusing error. Prefer using the distribution pair only when both distribution secrets are set, otherwise fall back entirely to the Developer ID pair.
          apple-developer-id-p12-base64: ${{ secrets.APPLE_DISTRIBUTION_P12_BASE64 || secrets.APPLE_DEVELOPER_ID_P12_BASE64 }}
          apple-developer-id-p12-password: ${{ secrets.APPLE_DISTRIBUTION_P12_PASSWORD || secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/_ci.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/_ci.yml:
- Around line 371-372: Update the signing credential assignments at
.github/workflows/_ci.yml lines 371-372, 418-419, 544-545, and 591-592 to select
credentials atomically: condition the branch on both Apple Distribution secrets
being present, then assign both the base64 certificate and password from that
same credential set; otherwise use both Apple Developer ID fallbacks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: d0c87eba-e2fb-4fb2-8c0e-8e3f3d0ce81c

📥 Commits

Reviewing files that changed from the base of the PR and between dd49aa7 and 434e79d.

📒 Files selected for processing (1)
  • .github/workflows/_ci.yml

Comment thread .github/workflows/_ci.yml Outdated
… for Developer ID

A repository whose CI runner is not a registered device signs CI with Apple Distribution and releases with Developer ID, and there was one certificate slot for both, named for Developer ID and read by the release workflow too, so it could hold one certificate or the other and never both. Each certificate is now its own input and its own secret, a caller passes whichever ones it signs with, and signing-identity-name selects which one a given build uses.
Copilot AI review requested due to automatic review settings August 8, 2026 22:24
@agoodkind
agoodkind force-pushed the ci-distribution-cert-secret branch from 434e79d to fa75b62 Compare August 8, 2026 22:24
@agoodkind agoodkind changed the title Let CI sign with its own certificate, apart from the release one Import each signing certificate a build passes, rather than one named for Developer ID Aug 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

…ository

The release workflow referenced the action only at main, so a change to the action and its caller together had this repository test the previous action against the current workflow. The certificate import then received no certificate and the release build failed. The CI gate already splits local and remote for this reason.
Copilot AI review requested due to automatic review settings August 8, 2026 22:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/actions/import-signing-cert/action.yml:107

  • The keychain password is never written to $GITHUB_OUTPUT (it currently prints literal ******), but later steps reference steps.create-keychain.outputs.password. This makes keychain-password empty for apple-actions/import-codesign-certs, which can break imports/unlocking.
        printf 'password=%s\n' "$keychain_password" >> "$GITHUB_OUTPUT"

.github/actions/import-signing-cert/action.yml:104

  • The keychain is created at KEYCHAIN_NOEXT, but the rest of the action (delete/unlock, identity resolution, and the exported keychain output) uses KEYCHAIN_PATH (...keychain-db). This inconsistency can make later security commands and codesign --keychain fail if the file created doesn’t match the path used afterward. Create the keychain at KEYCHAIN_PATH to match subsequent uses.

This issue also appears on line 107 of the same file.

        security create-keychain -p "$keychain_password" "$KEYCHAIN_NOEXT"
        # Keep it unlocked for the job: the default is a six-hour idle relock,
        # which a long build can cross, and a relocked keychain fails signing
        # with a prompt nobody can answer.
        security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"

Under the keychain directory the Security framework appends -db to the path it is given, so creating with the extensionless path produced a file ending in a bare -db while set-keychain-settings, unlock, import, and the identity lookup all named a .keychain-db file that did not exist. Locally that errors; on a session-less runner the step hung for six minutes. Also replaces the password generator, whose pipe made the reader exit first and kill the writer.
Copilot AI review requested due to automatic review settings August 8, 2026 22:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@agoodkind
agoodkind merged commit f701c2b into main Aug 8, 2026
24 checks passed
@agoodkind
agoodkind deleted the ci-distribution-cert-secret branch August 8, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants