Validate Secrets (dev) #2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: 1. Validate Secrets | |
run-name: Validate Secrets (${{ github.ref_name }}) | |
on: [workflow_call, workflow_dispatch] | |
jobs: | |
validate-access-token: | |
name: Access | |
runs-on: macos-13 | |
env: | |
GH_PAT: ${{ secrets.GH_PAT }} | |
GH_TOKEN: ${{ secrets.GH_PAT }} | |
outputs: | |
HAS_WORKFLOW_PERMISSION: ${{ steps.access-token.outputs.has_workflow_permission }} | |
steps: | |
- name: Validate Access Token | |
id: access-token | |
run: | | |
# Validate Access Token | |
# Ensure that gh exit codes are handled when output is piped. | |
set -o pipefail | |
# Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens. | |
GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$' | |
GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$' | |
# Validate Access Token (GH_PAT) | |
if [ -z "$GH_PAT" ]; then | |
failed=true | |
echo "::error::The GH_PAT secret is unset or empty. Set it and try again." | |
else | |
if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then | |
provides_scopes=true | |
echo "The GH_PAT secret is a structurally valid classic token." | |
elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then | |
echo "The GH_PAT secret is a structurally valid fine-grained token." | |
else | |
unknown_format=true | |
echo "The GH_PAT secret does not have a known token format." | |
fi | |
# Attempt to capture the x-oauth-scopes scopes of the token. | |
if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then | |
failed=true | |
if [ $unknown_format ]; then | |
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again." | |
else | |
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again." | |
fi | |
elif [[ $scopes =~ workflow ]]; then | |
echo "The GH_PAT secret has repo and workflow permissions." | |
echo "has_workflow_permission=true" >> $GITHUB_OUTPUT | |
elif [[ $scopes =~ repo ]]; then | |
echo "The GH_PAT secret has repo (but not workflow) permissions." | |
elif [ $provides_scopes ]; then | |
failed=true | |
if [ -z "$scopes" ]; then | |
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes." | |
else | |
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes" | |
fi | |
echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again." | |
else | |
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present." | |
echo "has_workflow_permission=true" >> $GITHUB_OUTPUT | |
fi | |
fi | |
# Exit unsuccessfully if secret validation failed. | |
if [ $failed ]; then | |
exit 2 | |
fi | |
validate-match-secrets: | |
name: Match-Secrets | |
needs: validate-access-token | |
runs-on: macos-13 | |
env: | |
GH_TOKEN: ${{ secrets.GH_PAT }} | |
steps: | |
- name: Validate Match-Secrets | |
run: | | |
# Validate Match-Secrets | |
# Ensure that gh exit codes are handled when output is piped. | |
set -o pipefail | |
# If a Match-Secrets repository does not exist, attempt to create one. | |
if ! visibility=$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase'); then | |
echo "A '${{ github.repository_owner }}/Match-Secrets' repository could not be found using the GH_PAT secret. Attempting to create one..." | |
# Create a private Match-Secrets repository and verify that it exists and that it is private. | |
if gh repo create ${{ github.repository_owner }}/Match-Secrets --private >/dev/null && [ "$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase')" == "private" ]; then | |
echo "Created a private '${{ github.repository_owner }}/Match-Secrets' repository." | |
else | |
failed=true | |
echo "::error::Unable to create a private '${{ github.repository_owner }}/Match-Secrets' repository. Create a private 'Match-Secrets' repository manually and try again. If a private 'Match-Secrets' repository already exists, verify that the token permissions of the GH_PAT are set correctly (or update them) at https://github.com/settings/tokens and try again." | |
fi | |
# Otherwise, if a Match-Secrets repository exists, but it is public, cause validation to fail. | |
elif [[ "$visibility" == "public" ]]; then | |
failed=true | |
echo "::error::A '${{ github.repository_owner }}/Match-Secrets' repository was found, but it is public. Change the repository visibility to private (or delete it) and try again. If necessary, a private repository will be created for you." | |
else | |
echo "Found a private '${{ github.repository_owner }}/Match-Secrets' repository to use." | |
fi | |
# Exit unsuccessfully if secret validation failed. | |
if [ $failed ]; then | |
exit 2 | |
fi | |
validate-fastlane-secrets: | |
name: Fastlane | |
needs: [validate-access-token, validate-match-secrets] | |
runs-on: macos-13 | |
env: | |
GH_PAT: ${{ secrets.GH_PAT }} | |
GH_TOKEN: ${{ secrets.GH_PAT }} | |
FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} | |
FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} | |
FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} | |
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
TEAMID: ${{ secrets.TEAMID }} | |
steps: | |
- name: Checkout Repo | |
uses: actions/checkout@v4 | |
# Install project dependencies | |
- name: Install Project Dependencies | |
run: bundle install | |
- name: Validate Fastlane Secrets | |
run: | | |
# Validate Fastlane Secrets | |
# Validate TEAMID | |
if [ -z "$TEAMID" ]; then | |
failed=true | |
echo "::error::The TEAMID secret is unset or empty. Set it and try again." | |
elif [ ${#TEAMID} -ne 10 ]; then | |
failed=true | |
echo "::error::The TEAMID secret is set but has wrong length. Verify that it is set correctly and try again." | |
elif ! [[ $TEAMID =~ ^[A-Z0-9]+$ ]]; then | |
failed=true | |
echo "::error::The TEAMID secret is set but invalid. Verify that it is set correctly (only uppercase letters and numbers) and try again." | |
fi | |
# Validate MATCH_PASSWORD | |
if [ -z "$MATCH_PASSWORD" ]; then | |
failed=true | |
echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again." | |
fi | |
# Ensure that fastlane exit codes are handled when output is piped. | |
set -o pipefail | |
# Validate FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY | |
FASTLANE_KEY_ID_PATTERN='^[A-Z0-9]+$' | |
FASTLANE_ISSUER_ID_PATTERN='^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$' | |
if [ -z "$FASTLANE_ISSUER_ID" ] || [ -z "$FASTLANE_KEY_ID" ] || [ -z "$FASTLANE_KEY" ]; then | |
failed=true | |
[ -z "$FASTLANE_ISSUER_ID" ] && echo "::error::The FASTLANE_ISSUER_ID secret is unset or empty. Set it and try again." | |
[ -z "$FASTLANE_KEY_ID" ] && echo "::error::The FASTLANE_KEY_ID secret is unset or empty. Set it and try again." | |
[ -z "$FASTLANE_KEY" ] && echo "::error::The FASTLANE_KEY secret is unset or empty. Set it and try again." | |
elif [ ${#FASTLANE_KEY_ID} -ne 10 ]; then | |
failed=true | |
echo "::error::The FASTLANE_KEY_ID secret is set but has wrong length. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." | |
elif ! [[ $FASTLANE_KEY_ID =~ $FASTLANE_KEY_ID_PATTERN ]]; then | |
failed=true | |
echo "::error::The FASTLANE_KEY_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." | |
elif ! [[ $FASTLANE_ISSUER_ID =~ $FASTLANE_ISSUER_ID_PATTERN ]]; then | |
failed=true | |
echo "::error::The FASTLANE_ISSUER_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." | |
elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then | |
failed=true | |
echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again." | |
elif ! bundle exec fastlane validate_secrets 2>&1 | tee fastlane.log; then | |
if grep -q "bad decrypt" fastlane.log; then | |
failed=true | |
echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again." | |
elif grep -q -e "required agreement" -e "license agreement" fastlane.log; then | |
failed=true | |
echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the latest developer program license agreement has been accepted at https://developer.apple.com/account (review and accept any updated agreement), then wait a few minutes for changes to propagate and try again." | |
elif ! grep -q -e "No code signing identity found" -e "Could not install WWDR certificate" fastlane.log; then | |
failed=true | |
echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again." | |
fi | |
fi | |
# Exit unsuccessfully if secret validation failed. | |
if [ $failed ]; then | |
exit 2 | |
fi |