From a322e920615f88a315dfcccbd561ed90017f2c0f Mon Sep 17 00:00:00 2001 From: per1234 Date: Tue, 15 Jun 2021 13:21:16 -0700 Subject: [PATCH] Add template workflow to check for problems with signing certificates The workflow runs on a schedule to check for problems with the signing certificates and notify if any are found. If a problem is found, a notification is posted to the Slack channel configured via the SLACK_WEBHOOK secret. --- workflow-templates/check-certificates.md | 53 +++++++ workflow-templates/check-certificates.yml | 130 ++++++++++++++++++ .../.github/workflows/check-certificates.yml | 130 ++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 workflow-templates/check-certificates.md create mode 100644 workflow-templates/check-certificates.yml create mode 100644 workflow-templates/dependabot/workflow-template-copies/.github/workflows/check-certificates.yml diff --git a/workflow-templates/check-certificates.md b/workflow-templates/check-certificates.md new file mode 100644 index 00000000..b00ea2fe --- /dev/null +++ b/workflow-templates/check-certificates.md @@ -0,0 +1,53 @@ +# "Check Certificates" workflow + +Workflow file: [check-certificates.yml](check-certificates.yml) + +Check code signing certificates for problems or pending expiration. + +## Setting up Slack webhook + +1. Open https://arduino.slack.com/apps/A0F7XDUAZ-incoming-webhooks +1. Click the "Add to Slack" button +1. From the "Post to Channel" menu, select the appropriate channel. +1. Click the "Add Incoming WebHooks integration" +1. From the "Customize Icon" section, click the "Choose an emoji" button +1. Enter `:warning:` +1. Click the "Save Settings" button +1. Copy the text in the "Webhook URL" field +1. Save the webhook URL to a [repository secret](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository) named `SLACK_WEBHOOK` + - Make sure there is no newline at the end of the secret, otherwise the slack post process will fail. + +## Readme badge + +Markdown badge: + +```markdown +[![Check Certificates status](https://github.com/REPO_OWNER/REPO_NAME/actions/workflows/check-certificates.yml/badge.svg)](https://github.com/REPO_OWNER/REPO_NAME/actions/workflows/check-certificates.yml) +``` + +Replace the `REPO_OWNER` and `REPO_NAME` placeholders in the URLs with the final repository owner and name ([example](https://raw.githubusercontent.com/arduino-libraries/ArduinoIoTCloud/master/README.md)). + +--- + +Asciidoc badge: + +```adoc +image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-certificates.yml/badge.svg["Check Certificates status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-certificates.yml"] +``` + +Define the `{repository-owner}` and `{repository-name}` attributes and use them throughout the readme ([example](https://raw.githubusercontent.com/arduino-libraries/WiFiNINA/master/README.adoc)). + +## Commit message + +``` +Add CI workflow to check for problems with signing certificates + +The workflow runs on a schedule to check for problems with the signing certificates and notify if any are found. +If a problem is found, a notification is posted to the Slack channel configured via the SLACK_WEBHOOK secret. +``` + +## PR message + +```markdown +The workflow runs on a schedule to check for problems with the signing certificates and notify if any are found. If a problem is found, a notification is posted to the Slack channel configured via the `SLACK_WEBHOOK` secret. +``` diff --git a/workflow-templates/check-certificates.yml b/workflow-templates/check-certificates.yml new file mode 100644 index 00000000..ebf79d25 --- /dev/null +++ b/workflow-templates/check-certificates.yml @@ -0,0 +1,130 @@ +# 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: + 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: + check-certificates: + name: ${{ matrix.certificate.identifier }} + # Only run when the workflow will have access to the certificate secrets. + # TODO: Update repository name. + if: > + (github.event_name != 'pull_request' && github.repository == 'REPO_OWNER/REPO_NAME') || + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'REPO_OWNER/REPO_NAME') + 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: INSTALLER_CERT_MAC_P12 # Name of the secret that contains the certificate. + password-secret: INSTALLER_CERT_MAC_PASSWORD # Name of the secret that contains the certificate password. + + 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 pkcs12 \ + -in "${{ env.CERTIFICATE_PATH }}" \ + -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: | + EXPIRATION_DATE="$( + ( + openssl pkcs12 \ + -in "${{ env.CERTIFICATE_PATH }}" \ + -clcerts \ + -nodes \ + -passin env:CERTIFICATE_PASSWORD + ) | ( + openssl x509 \ + -noout \ + -enddate + ) | ( + grep \ + --max-count=1 \ + --only-matching \ + --perl-regexp \ + 'notAfter=(\K.*)' + ) + )" + + 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 "::set-output name=days::$DAYS_BEFORE_EXPIRATION" + + - 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 diff --git a/workflow-templates/dependabot/workflow-template-copies/.github/workflows/check-certificates.yml b/workflow-templates/dependabot/workflow-template-copies/.github/workflows/check-certificates.yml new file mode 100644 index 00000000..ebf79d25 --- /dev/null +++ b/workflow-templates/dependabot/workflow-template-copies/.github/workflows/check-certificates.yml @@ -0,0 +1,130 @@ +# 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: + 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: + check-certificates: + name: ${{ matrix.certificate.identifier }} + # Only run when the workflow will have access to the certificate secrets. + # TODO: Update repository name. + if: > + (github.event_name != 'pull_request' && github.repository == 'REPO_OWNER/REPO_NAME') || + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'REPO_OWNER/REPO_NAME') + 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: INSTALLER_CERT_MAC_P12 # Name of the secret that contains the certificate. + password-secret: INSTALLER_CERT_MAC_PASSWORD # Name of the secret that contains the certificate password. + + 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 pkcs12 \ + -in "${{ env.CERTIFICATE_PATH }}" \ + -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: | + EXPIRATION_DATE="$( + ( + openssl pkcs12 \ + -in "${{ env.CERTIFICATE_PATH }}" \ + -clcerts \ + -nodes \ + -passin env:CERTIFICATE_PASSWORD + ) | ( + openssl x509 \ + -noout \ + -enddate + ) | ( + grep \ + --max-count=1 \ + --only-matching \ + --perl-regexp \ + 'notAfter=(\K.*)' + ) + )" + + 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 "::set-output name=days::$DAYS_BEFORE_EXPIRATION" + + - 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