Repository: WordPress/Requests (language: php)

Issue:
Task: automate updating the certificates bundle
Thoughts:
* Use GH Actions with a daily cron schedule
* Check for changes to this page and/or RSS feed if available: https://curl.se/docs/caextract.html
* Use similar logic as used in the website update workflow to auto-generate a PR when a new bundle has been published.


Discussion / hints:
We can simply wget https://curl.se/ca/cacert.pem and create PR if changes are detected.
FYI: I have this ready and waiting for a new certificate bundle to be published, so I can actually test that the workflow works as expected ;-)

Fix patch:
diff --git a/.github/release-checklist.md b/.github/release-checklist.md
index 866621dfd..1daec26a7 100644
--- a/.github/release-checklist.md
+++ b/.github/release-checklist.md
@@ -5,7 +5,6 @@ Template to use for release PRs from `develop` to `stable`
 
 PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH DAY YEAR**.
 - [ ] Check if any dependencies need updating.
-- [ ] Check if the `certificates/cacert.pem` file needs updating.
 - [ ] Update the version constant in `src/Requests.php`.
 - [ ] Add changelog for the release - PR #xxx
 - [ ] Merge this PR.
diff --git a/.github/workflows/update-cacert.yml b/.github/workflows/update-cacert.yml
new file mode 100644
index 000000000..473955011
--- /dev/null
+++ b/.github/workflows/update-cacert.yml
@@ -0,0 +1,99 @@
+name: Certificates
+
+on:
+  # Run every day at 4:20.
+  schedule:
+    - cron: '20 4 * * *'
+  # Run on every push to `stable` and `develop`.
+  # Not using path selection here as it appears only the files in the last commit from the push are looked at.
+  push:
+    branches:
+      - 'stable'
+      - 'develop'
+  # And whenever this workflow is updated or a PR attempts to update the certificate files.
+  pull_request:
+    paths:
+      - '.github/workflows/update-cacert.yml'
+      - 'certificates/cacert.pem'
+      - 'certificates/cacert.pem.sha256'
+  # Also allow manually triggering the workflow.
+  workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+  # The concurrency group contains the workflow name and the branch name.
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  certificate-check:
+    name: "Check for updated certificate bundle"
+    # Don't run the cron job on forks.
+    if: ${{ github.event_name != 'schedule' || github.repository == 'WordPress/Requests' }}
+
+    runs-on: ubuntu-latest
+    steps:
+      - name: Determine branches to use
+        id: branches
+        env:
+          HEAD_REF: ${{ github.head_ref }}
+        run: |
+          if [[ "${{ github.event_name }}" == 'schedule' ]]; then
+            echo "::set-output name=BASE::develop"
+          elif [[ "${{ github.event_name }}" == 'push' ]]; then
+            # Pull requests should always go to develop, even when triggered via stable.
+            echo "::set-output name=BASE::develop"
+          else # = PR or manual run.
+            echo "::set-output name=BASE::$HEAD_REF"
+          fi
+
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      - name: Restore etags cache for certificate files
+        uses: actions/cache@v2
+        with:
+          path: certificates/etag-*.txt
+          key: curl-etag-${{ hashFiles('certificates/cacert.pem') }}-${{ hashFiles('certificates/cacert.pem.sha256') }}
+          restore-keys: |
+            curl-etag-
+
+      - name: Get current certificate bundle if changed
+        working-directory: ./certificates
+        run: curl --etag-compare etag-cert.txt --etag-save etag-cert.txt --remote-name https://curl.se/ca/cacert.pem
+
+      - name: Get current SHA256 checksum file for the bundle if changed
+        working-directory: ./certificates
+        run: curl --etag-compare etag-sha.txt --etag-save etag-sha.txt --remote-name https://curl.se/ca/cacert.pem.sha256
+
+      - name: Verify the checksum of the downloaded bundle
+        working-directory: ./certificates
+        run: sha256sum --check cacert.pem.sha256
+
+      - name: "Debug info: Show git status"
+        run: git status -vv --untracked=all
+
+      # http://man7.org/linux/man-pages/man1/date.1.html
+      - name: "Get date"
+        id: get-date
+        run: echo "::set-output name=DATE::$(/bin/date -u "+%F")"
+
+      - name: Create pull request
+        uses: peter-evans/create-pull-request@v3
+        with:
+          base: ${{ steps.branches.outputs.BASE }}
+          branch: "feature/auto-update-cacert"
+          delete-branch: true
+          commit-message: ":lock_with_ink_pen: Update certificate bundle"
+          title: ":lock_with_ink_pen: Update certificate bundle"
+          body: |
+            Updated certificate bundle as of ${{ steps.get-date.outputs.DATE }}.
+
+            Source: https://curl.se/docs/caextract.html
+
+            This PR is auto-generated by [create-pull-request](https://github.com/peter-evans/create-pull-request) using the `.github/workflows/update-cacert.yml` workflow.
+          labels: |
+            Type: enhancement
+          reviewers: |
+            jrfnl
+            schlessera
diff --git a/.gitignore b/.gitignore
index 13a6c9956..7e541c7f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,6 @@ phpcs.xml
 phpdoc.xml
 build/ghpages/.phpdoc
 build/ghpages/artifacts
+
+# Ignore temporary files for certificate downloads.
+certificates/etag-*.txt
diff --git a/certificates/cacert.pem.sha256 b/certificates/cacert.pem.sha256
new file mode 100644
index 000000000..6dc41e38a
--- /dev/null
+++ b/certificates/cacert.pem.sha256
@@ -0,0 +1,1 @@
+ae31ecb3c6e9ff3154cb7a55f017090448f88482f0e94ac927c0c67a1f33b9cf  cacert.pem


Test patch: