Skip to content

Adopt a8c-secrets for the demo app secrets - #824

Merged
mokagio merged 14 commits into
trunkfrom
ainfra-1549-adopt-the-solution-in-gravatar-sdk-ios
Jul 28, 2026
Merged

Adopt a8c-secrets for the demo app secrets#824
mokagio merged 14 commits into
trunkfrom
ainfra-1549-adopt-the-solution-in-gravatar-sdk-ios

Conversation

@mokagio

@mokagio mokagio commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

See https://linear.app/a8c/issue/AINFRA-1549

Switches from .configure to a8c-secrets, building on top of what already established in terms of extracting outside the repo in #822.

In doing so, changes how the secrets decrypt build phase works a little bit. The rationale for the change came from @AliSoftware's comments in Automattic/pocket-casts-ios#4731, in particular the one about bypassing fastlane.

Here's what I propose:

  • The build phase is the central hub for secrets decryption, which works especially well in project like this one or Pocket Casts where the only secrets left are app-specific. So, it makes sense for the app build to call a8c-secrets decrypt to acquire the secrets it needs.
  • For open source apps, the process should allow external contributors to provide their own credentials. That is, not having access to a8c-secrets should not prevent building the app1

The build phase flow is:

  • If a8c-secrets available, assume internal contributor
    • Attempt to decrypt and copy to Derived Data, fail loudly if something doesn't work
  • Else, if the external secrets exist, assume external contributor2
    • Copy to Derived Data
  • Else, print high level3 instructions for both internal and external contributors

Footnotes

  1. We might want to refine the external contributor flow to "can build with placeholder credentials," meaning, an external contributor should only need to provide the credentials that are absolutely required for the app to work. Example: A valid Sentry DSN is not necessary because crash logging should not be a requirement for external contributors. The demo app here does not use Sentry, but the point stands.

  2. Alternative: Have a more generic "local override" file instead of an explicit "external" one and have local take precedence, maybe with an echo "warning: Using local secrets override..." message. This would allow internal devs to override secrets if needed without having to jump through hoops to ignored a8c-secrets. I'm not sure how likely that scenario is on a day to day basis, though, so not sure if it's worth implementing with the risk of local secrets remaining in use unintentionally given how a warning is easy to miss. On top of that, we want secrets to stay outside the repository, but adding a prioritized mechanism for internal contributors to track secrets in the repo seems like it would open a bigger door for possible leakages than what "you need to work around a8c-secrets to make this happen" does.

  3. High level instructions instead of specific commands such as "run a8c-secrets keys import followed by a8c-secrets decrypt" to avoid the instructions drifting from the tool's API. Sending users to the tool's README keeps it as the single source of truth.

Copilot AI review requested due to automatic review settings July 21, 2026 03:55
@mokagio
mokagio requested a review from a team as a code owner July 21, 2026 03:55
@mokagio mokagio self-assigned this Jul 21, 2026

Copilot AI left a comment

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.

Pull request overview

This PR migrates the demo app’s secret materialization workflow from the legacy .configure/configure_apply flow to a8c-secrets, so internal contributors decrypt secrets via a8c-secrets while external contributors continue using the templated, gitignored secrets file.

Changes:

  • Replace the build-phase secrets source with a8c-secrets which + versioned .a8c-secrets/Secrets.swift.age tracking, and avoid rewriting the derived Secrets.swift when content is unchanged.
  • Update contributor-facing documentation and Makefile targets to use a8c-secrets.
  • Remove the legacy .configure setup and encrypted artifact, and add repo metadata/public keys for a8c-secrets.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Sources/GravatarUI/GravatarUI.docc/GettingStarted.md Updates internal contributor setup instructions to use a8c-secrets.
Scripts/build-phases/generate-secrets.xcfilelist Tracks the versioned .age input so Xcode reruns the phase when secrets rotate.
Scripts/build-phases/generate-secrets.sh Uses a8c-secrets which as primary source; adds “copy only if changed” behavior.
Makefile Updates setup-secrets to run a8c-secrets decrypt.
Demo/Demo/Generated/README.md Updates explanation of how Secrets.swift is generated for the demo target.
.gitattributes Marks *.age as binary for Git attributes handling.
.configure-files/Secrets.swift.enc Removes legacy encrypted secrets artifact.
.configure Removes legacy .configure configuration file.
.a8c-secrets/Secrets.swift.age Adds the versioned encrypted secrets source for a8c-secrets.
.a8c-secrets/repo-id Adds a8c-secrets repo identifier metadata.
.a8c-secrets/keys.pub Adds public recipient keys used for age encryption (dev/ci).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Scripts/build-phases/generate-secrets.sh
Comment thread Demo/Demo/Generated/README.md Outdated
Comment thread Makefile Outdated
Comment thread Scripts/build-phases/generate-secrets.xcfilelist Outdated
@mokagio

mokagio commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Note

Blocked: this PR is waiting on Automattic/a8c-ci-toolkit-buildkite-plugin#218 (and the stacked #220) to merge and ship in a toolkit release.

The Build Demo job currently fails with make: a8c-secrets: No such file or directoryA8C_SECRETS_IDENTITY is provisioned on the agent, but the binary is not installed. Rather than add a raw curl | bash bootstrap here (flagged as a supply-chain risk on pocket-casts-ios#4731), this repo will call the toolkit’s checksum-verified install_a8c_secrets once it is released, and bump CI_TOOLKIT in .buildkite/shared-pipeline-vars accordingly.

Posted by Claude Code (Opus 4.8) on behalf of @mokagio with approval.


Update: This has now been addressed.

@mokagio
mokagio force-pushed the ainfra-1549-adopt-the-solution-in-gravatar-sdk-ios branch from e93d28f to 04e0f6f Compare July 21, 2026 04:38
@mokagio
mokagio marked this pull request as draft July 21, 2026 06:59
mokagio and others added 5 commits July 22, 2026 15:01
Resolve the decrypted `Secrets.swift` with `a8c-secrets which` instead of a hardcoded path.
The location then follows `.a8c-secrets/repo-id`, and the docs no longer have to spell out where decrypted secrets sit on disk, which a reviewer flagged as worth avoiding in #822.

Track the committed `.age` as the build phase input.
It is the versioned source of the decrypted file, so Xcode can still skip the phase — a runtime-resolved path alone leaves nothing to declare, forcing the phase to run every build.
Guard the copy on content because a checkout can bump the `.age` mtime without the secret changing, and an unconditional copy would recompile `Secrets.swift` each time.

Call `a8c-secrets decrypt` directly rather than wrapping fastlane's `configure_apply`, so contributors drive the tool itself.
Document the one-time `a8c-secrets keys import`, without which `decrypt` fails.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0127eCAM2kNwzftdKuNHqXg6
Nothing reads these now that the demo secrets come from `a8c-secrets`.
`.configure` described only the `Secrets.swift` copy, and code signing goes through `match` rather than the `configure` plugin, so dropping it leaves no other consumer behind.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0127eCAM2kNwzftdKuNHqXg6
The demo build needs `a8c-secrets` on `PATH` to decrypt `Secrets.swift`, and
the agents don't ship it. `install_a8c-secrets_binary`, added in CI toolkit
6.2.0, pins the release and verifies its SHA-256 before putting anything on
disk — so the two changes are coupled: the command does not exist before 6.2.0.

The step's inline command moves to `.buildkite/commands/build-demo.sh` because
Buildkite interpolates `$HOME` and `$PATH` in `pipeline.yml` at upload time.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.8 <noreply@anthropic.com>
@mokagio
mokagio force-pushed the ainfra-1549-adopt-the-solution-in-gravatar-sdk-ios branch from 04e0f6f to 3d0f5d2 Compare July 22, 2026 05:06
@wpmobilebot

wpmobilebot commented Jul 22, 2026

Copy link
Copy Markdown
App Icon📲 You can test the changes from this Pull Request in Gravatar Prototype Build by scanning the QR code below to install the corresponding build.
App NameGravatar Prototype Build
Build Number2548
VersionPR #824
Bundle IDcom.automattic.gravatar-sdk-demo-uikit.prototype-build
Commit8105a96
Installation URL53l6gfu0gq0c8
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

mokagio and others added 2 commits July 22, 2026 15:21
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
@mokagio
mokagio marked this pull request as ready for review July 22, 2026 05:42
Comment thread Scripts/build-phases/generate-secrets.xcfilelist Outdated
Comment thread Sources/GravatarUI/GravatarUI.docc/GettingStarted.md Outdated
Comment thread Scripts/build-phases/generate-secrets.sh Outdated
@mokagio
mokagio marked this pull request as draft July 22, 2026 10:42
mokagio and others added 2 commits July 23, 2026 14:27
The phase compiled whatever `a8c-secrets decrypt` last wrote into the
decrypted tree, which can predate a rotation of the tracked `.age`.
Xcode did re-run the phase on such a rotation, since the `.age` is a
declared input, but the script then copied the superseded plaintext and
the demo built against a secret that no longer applied.

Dropping the `cmp` guard accepts a wasted copy when a checkout bumps the
`.age` mtime without the secret changing.
The guard avoided that by skipping the write, which left the output older
than its input and so kept the phase permanently out of date.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VaF5AyEaPBpo5tarMErbtM
A `warning:` on a failed decrypt let the secrets from a previous run compile
silently, which is the failure running `decrypt` in the phase was meant to
prevent.
For a contributor who has a8c-secrets installed, a decrypt that fails is an
error, not something to paper over.

`a8c-secrets which` failing now errors for the same reason, rather than falling
through to the external-contributor file.
That file stays a fallback rather than an override: it is reached only when
a8c-secrets is not installed at all.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.8 <noreply@anthropic.com>
Comment thread Demo/Demo/Generated/README.md Outdated
Comment thread Scripts/build-phases/generate-secrets.sh
Should help avoid drift.

Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
@mokagio
mokagio marked this pull request as ready for review July 23, 2026 06:28
@mokagio

mokagio commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@AliSoftware @iangmaia I'd appreciate your thoughts on this. Having basically built it from scratch (only previous iteration #821) I think it's a good place where to iron out how to best integrate a8c-secret via Xcode build phase. And given we have to run build phase because we need to decrypt the Swift secrets in the derived data folder, we might as well centralize in there as much as possible.

Thanks!


# Xcode build phases have access to a different PATH than the host.
# So, we need to explicitly add a8c-secrets' default install location to the PATH.
export PATH="$HOME/.local/bin:$PATH"

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.

This was not needed for CI builds, but it was on my machine. Of course, I double checked that I could run a8c-secrets decrypt successfully on my system.

@mokagio
mokagio enabled auto-merge July 27, 2026 05:21
@AliSoftware

Copy link
Copy Markdown
Contributor

Alternative: Have a more generic "local override" file instead […] I'm not sure how likely that scenario is on a day to day basis, though, so not sure if it's worth implementing with the risk of local secrets remaining in use unintentionally given how a warning is easy to miss. On top of that, we want secrets to stay outside the repository, but adding a prioritized mechanism for internal contributors to track secrets in the repo seems like it would open a bigger door for possible leakages than what "you need to work around a8c-secrets to make this happen" does.

100% agree with the "cons" here. I think it's not worth it and also makes it too easy to do things wrong (local secrets, etc) so better avoid this.

@AliSoftware AliSoftware left a comment

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.

Left a suggestion to make the script more scalable from when we'll start adopting it in more repos. But not a blocker for this repo in particular.

# Keep the substitution in an assignment: inside `apply "$(...)"` its exit
# status would be discarded and a decrypt that produced nothing would fall
# through to the external-contributor branch.
SECRETS_FILE_NAME="Secrets.swift"

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.

Thinking of scaling (especially when we'll be ready to adopt this technique for more repos): I wonder what would be the best way to handle multiple secret files.

  1. Maybe we should write this code as a bash array + for loop, even if in the case of Gravatar-SDK-iOS there will be only one item, just so it's scalable
  2. An alternative to hardcoding the list of files to decrypt could be to iterate of all the files in the directory returned by a8c-secrets which, i.e. all the decrypted files. But I don't think that'd be a good idea, because that could risk copying files that are there but we don't need in the Xcode project, and thus copying too much. So I think hardcoding the list of files in the generate-secrets.sh script is still the right move, even if that means that we'll need to keep this script in sync every time we might need to add a new secret file to the Xcode project. But we'll need to add said secret file to the .xcodeproj anyway too, so I think that's fine.

So something like this, maybe? (not tested)

SECRETS_FILES_NAMES=(Secrets.swift)
if command -v a8c-secrets >/dev/null 2>&1; then
  a8c-secrets decrypt --non-interactive >/dev/null \ || { echo "error: …"; exit 1; }

  for secret_file_name in "${SECRETS_FILES_NAMES[@]}" do
    secret_file=$(a8c-secrets which "${secret_file_name}") \ || { echo "error: …"; exit 1; }
    apply "${secret_file}"
  done
else
  for secret_file_name in "${SECRETS_FILES_NAMES[@]}" do
    basename = "${secret_file_name%.*}"
    extension = "${secret_file_name##*.}"
    external = ${SRCROOT}/Demo/${basename}.external-contributor.${extension}"
    if [ -f "$external" ]; then
      apply "${external}"
    else
      template = ${SRCROOT}/Demo/${basename}.template.${extension}"
      echo "error: No secret found for file '${secret_file_name}'. Internal contributors: install a8c-secrets and follow its setup instructions; see https://github.com/Automattic/a8c-secrets. External contributors: copy '${template}' to '${external}' and fill in your own credentials."
      exit 1
    fi
  done
fi

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.

Thinking of scaling (especially when we'll be ready to adopt this technique for more repos): I wonder what would be the best way to handle multiple secret files.

Good point. What do you think of passing the file names as arguments? That would allow us to have a single version of the script to either copy paste (i.e. keep in sync with the help of AI) or embed in a8c-secrets as a command. However, if we were to make it part of a8c-secrets we'd also need to both harden and make optional the external contributors worflow.

Regardless, using an array would definitely be a flexibility improvement. I'll keep it in mind through the adoption for when I'll run into a repo with more than one build-time secret to decrypt.

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.

Yep I like it. In fact, adding a dedicated a8c-secrets xcode-script-build-phase subcommand or the like to a8c-secrets — to DRY the logic of whatever script we end up using once we refined and set this up across our repos to find the best common denominator and logic that works well for all projects — was already in my list of ideas for the next stage of the project.

We're not totally there yet, but having the script be closer to being generic enough to become a single version of the script we can copy paste is definitively gonna get us closer to this 👍

Comment thread .gitattributes
@@ -0,0 +1 @@
*.age binary

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.

I was surprised to see the Serets.swift.age file in this PR be in clear and was about to suggest we add exactly that… but I see that you covered this already; so I'm guessing it will not be taken into account by GitHub until after this is merged into trunk 🙃

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.

That's the conclusion I came to as well...

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.

Doubly strange: If I look at Secrets.swift.age now that the PR has been merged:

  • Not only does GitHub not show it as "This file is binary" despite this line in the .gitattributes
  • But also it shows its content as looking like Japanese text… while the binary content of the .age file is definitively not looking like that (even if it were misinterpreted with the wrong text encoding).
    • In fact, the .age file format specification shows that .age files start with a plain ASCII text header (age-encryption.org/v1 as its first line, then a mix of plain text + Base64 data) before its actually-binary payload post-header.
    • So it's a mystery how even those first bytes of the file, which end up being regular ASCII text, end up making GitHub interpret those as Japanese characters… 🤷
image image image

@mokagio
mokagio merged commit bf976cf into trunk Jul 28, 2026
7 checks passed
@mokagio
mokagio deleted the ainfra-1549-adopt-the-solution-in-gravatar-sdk-ios branch July 28, 2026 15:08
mokagio added a commit to Automattic/simplenote-macos that referenced this pull request Jul 30, 2026
This mirrors the agreed implementation in Gravatar SDK iOS.
See Automattic/Gravatar-SDK-iOS#824
mokagio added a commit to wordpress-mobile/WordPress-iOS that referenced this pull request Aug 5, 2026
Matches the shape agreed in Gravatar-SDK-iOS, whose build phase copies without
comparing first: Automattic/Gravatar-SDK-iOS#824
The phase declares its inputs and output, so Xcode already skips it when
nothing changed.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants