Skip to content

Commit

Permalink
GH Actions: Improve secrets validation (#86)
Browse files Browse the repository at this point in the history
* validate_secrets.yml: Pass a "Could not install WWDR certificate" error through validation

* validate_secrets.yml: Improve annotation when a public Match-Secrets repo exists

* validate_secrets.yml: Rewrite Match-Secrets validation to be explicit about the Match-Secrets repository that will be used

When the GH account that the GH_PAT token was created under does not match the repository_owner of the LoopWorkspace repository, the validation routine used a different Match-Secrets repository than fastlane.

* validate_secrets.yml: Rewrite GH_PAT validation to capture scopes and distinguish between classic and fine-grained access tokens

* validate_secrets.yml: Fix syntax error in Match-Secrets validation job

* validate_secrets.yml: Depend less on patterns / read scopes from any token that provides them

* Provide HAS_WORKFLOW_PERMISSION as an output

* validate_secrets.yml: Annotate failures from unaccepted Apple PLAs

* validate_secrets.yml: Fix typo and improve annotation when GH_PAT is invalid

* validate_secrets.yml: Improve annotation when authorization fails and token format is unknown

* validate_secrets.yml: Minor wording tweak
  • Loading branch information
billybooth authored Sep 11, 2023
1 parent 59d7e50 commit a9e4404
Showing 1 changed file with 71 additions and 17 deletions.
88 changes: 71 additions & 17 deletions .github/workflows/validate_secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,61 @@ jobs:
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 Fastlane Access Token (GH_PAT)
# 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."
elif [ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/LoopWorkspace | jq --raw-output '.permissions.push')" != "true" ]; then
failed=true
echo "::error::The GH_PAT secret is set but invalid or lacking at least 'repo' permission scope ('repo, workflow' is okay too).\
Verify that token permissions are set correctly (or update them) at https://github.com/settings/tokens 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.
Expand All @@ -37,19 +81,27 @@ jobs:
- name: Validate Match-Secrets
run: |
# Validate Match-Secrets
if [ "$(gh repo list --json name | jq --raw-output 'any(.name=="Match-Secrets")')" != "true" ]; then
echo "A 'Match-Secrets' repository could not be found. Attempting to create one...";
# 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..."
if gh repo create Match-Secrets --private >/dev/null && [ "$(gh repo list --json name,visibility | jq --raw-output '.[] | select(.name=="Match-Secrets") | .visibility == "PRIVATE"')" == "true" ]; then
echo "Created a private 'Match-Secrets' repository."
# 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::Cannot access or create a private 'Match-Secrets' repository. The GH_PAT secret is lacking at least the 'repo' permission scope required to access or create the repository.\
Verify that token permissions are set correctly (or update them) at https://github.com/settings/tokens and try again."
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
elif [ "$(gh repo list --json name,visibility | jq --raw-output '.[] | select(.name=="Match-Secrets") | .visibility == "PUBLIC"')" == "true" ]; then
# Otherwise, if a Match-Secrets repository exists, but it is public, cause validation to fail.
elif [[ "$visibility" == "public" ]]; then
failed=true
echo "::error::A 'Match-Secrets' repository was found, but it is is public. Delete it and try again (a private repository will be created for you)."
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.
Expand All @@ -59,7 +111,7 @@ jobs:
validate-fastlane-secrets:
name: Fastlane
needs: validate-match-secrets
needs: [validate-access-token, validate-match-secrets]
runs-on: macos-13
env:
GH_PAT: ${{ secrets.GH_PAT }}
Expand Down Expand Up @@ -123,10 +175,12 @@ jobs:
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 "No code signing identity found" fastlane.log; then
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."
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
Expand Down

0 comments on commit a9e4404

Please sign in to comment.