Check Certificates #4476
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
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-certificates.md | |
name: Check Certificates | |
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows | |
on: | |
create: | |
push: | |
paths: | |
- '.github/workflows/check-certificates.ya?ml' | |
pull_request: | |
paths: | |
- '.github/workflows/check-certificates.ya?ml' | |
schedule: | |
# Run every 10 hours. | |
- cron: '0 */10 * * *' | |
workflow_dispatch: | |
repository_dispatch: | |
env: | |
# Begin notifications when there are less than this many days remaining before expiration. | |
EXPIRATION_WARNING_PERIOD: 30 | |
jobs: | |
run-determination: | |
runs-on: ubuntu-latest | |
outputs: | |
result: ${{ steps.determination.outputs.result }} | |
steps: | |
- name: Determine if the rest of the workflow should run | |
id: determination | |
run: | | |
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" | |
REPO_SLUG="arduino/arduino-ide" | |
if [[ | |
( | |
# Only run on branch creation when it is a release branch. | |
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. | |
"${{ github.event_name }}" != "create" || | |
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX | |
) && | |
( | |
# Only run when the workflow will have access to the certificate secrets. | |
# This could be done via a GitHub Actions workflow conditional, but makes more sense to do it here as well. | |
( | |
"${{ github.event_name }}" != "pull_request" && | |
"${{ github.repository }}" == "$REPO_SLUG" | |
) || | |
( | |
"${{ github.event_name }}" == "pull_request" && | |
"${{ github.event.pull_request.head.repo.full_name }}" == "$REPO_SLUG" | |
) | |
) | |
]]; then | |
# Run the other jobs. | |
RESULT="true" | |
else | |
# There is no need to run the other jobs. | |
RESULT="false" | |
fi | |
echo "result=$RESULT" >> $GITHUB_OUTPUT | |
check-certificates: | |
name: ${{ matrix.certificate.identifier }} | |
needs: run-determination | |
if: needs.run-determination.outputs.result == 'true' | |
runs-on: ubuntu-latest | |
strategy: | |
fail-fast: false | |
matrix: | |
certificate: | |
# Additional certificate definitions can be added to this list. | |
- identifier: macOS signing certificate # Text used to identify certificate in notifications. | |
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # Name of the secret that contains the certificate. | |
password-secret: KEYCHAIN_PASSWORD # Name of the secret that contains the certificate password. | |
type: pkcs12 | |
- identifier: Windows signing certificate | |
certificate-secret: INSTALLER_CERT_WINDOWS_CER | |
# The password for the Windows certificate is not needed, because its not a container, but a single certificate. | |
type: x509 | |
steps: | |
- name: Set certificate path environment variable | |
run: | | |
# See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable | |
echo "CERTIFICATE_PATH=${{ runner.temp }}/certificate.p12" >> "$GITHUB_ENV" | |
- name: Decode certificate | |
env: | |
CERTIFICATE: ${{ secrets[matrix.certificate.certificate-secret] }} | |
run: | | |
echo "${{ env.CERTIFICATE }}" | base64 --decode > "${{ env.CERTIFICATE_PATH }}" | |
- name: Verify certificate | |
env: | |
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }} | |
run: | | |
( | |
openssl ${{ matrix.certificate.type }} \ | |
-in "${{ env.CERTIFICATE_PATH }}" \ | |
-legacy \ | |
-noout \ | |
-passin env:CERTIFICATE_PASSWORD | |
) || ( | |
echo "::error::Verification of ${{ matrix.certificate.identifier }} failed!!!" | |
exit 1 | |
) | |
- name: Slack notification of certificate verification failure | |
if: failure() | |
env: | |
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
SLACK_MESSAGE: | | |
:warning::warning::warning::warning: | |
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} verification failed!!! | |
:warning::warning::warning::warning: | |
SLACK_COLOR: danger | |
MSG_MINIMAL: true | |
uses: rtCamp/action-slack-notify@v2 | |
- name: Get days remaining before certificate expiration date | |
env: | |
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }} | |
id: get-days-before-expiration | |
run: | | |
if [[ ${{ matrix.certificate.type }} == "pkcs12" ]]; then | |
EXPIRATION_DATE="$( | |
( | |
openssl pkcs12 \ | |
-in "${{ env.CERTIFICATE_PATH }}" \ | |
-clcerts \ | |
-legacy \ | |
-nodes \ | |
-passin env:CERTIFICATE_PASSWORD | |
) | ( | |
openssl x509 \ | |
-noout \ | |
-enddate | |
) | ( | |
grep \ | |
--max-count=1 \ | |
--only-matching \ | |
--perl-regexp \ | |
'notAfter=(\K.*)' | |
) | |
)" | |
elif [[ ${{ matrix.certificate.type }} == "x509" ]]; then | |
EXPIRATION_DATE="$( | |
( | |
openssl x509 \ | |
-in ${{ env.CERTIFICATE_PATH }} \ | |
-noout \ | |
-enddate | |
) | ( | |
grep \ | |
--max-count=1 \ | |
--only-matching \ | |
--perl-regexp \ | |
'notAfter=(\K.*)' | |
) | |
)" | |
fi | |
DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))" | |
# Display the expiration information in the log. | |
echo "Certificate expiration date: $EXPIRATION_DATE" | |
echo "Days remaining before expiration: $DAYS_BEFORE_EXPIRATION" | |
echo "days=$DAYS_BEFORE_EXPIRATION" >> $GITHUB_OUTPUT | |
- name: Check if expiration notification period has been reached | |
id: check-expiration | |
run: | | |
if [[ ${{ steps.get-days-before-expiration.outputs.days }} -lt ${{ env.EXPIRATION_WARNING_PERIOD }} ]]; then | |
echo "::error::${{ matrix.certificate.identifier }} will expire in ${{ steps.get-days-before-expiration.outputs.days }} days!!!" | |
exit 1 | |
fi | |
- name: Slack notification of pending certificate expiration | |
# Don't send spurious expiration notification if verification fails. | |
if: failure() && steps.check-expiration.outcome == 'failure' | |
env: | |
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
SLACK_MESSAGE: | | |
:warning::warning::warning::warning: | |
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} will expire in ${{ steps.get-days-before-expiration.outputs.days }} days!!! | |
:warning::warning::warning::warning: | |
SLACK_COLOR: danger | |
MSG_MINIMAL: true | |
uses: rtCamp/action-slack-notify@v2 |