diff --git a/.github/workflows/check_certs.yml b/.github/workflows/check_certs.yml new file mode 100644 index 00000000000000..0c093247cab6c6 --- /dev/null +++ b/.github/workflows/check_certs.yml @@ -0,0 +1,34 @@ +name: certificates +on: + # Our certificates are good for ~1 year, but we regenerate them every + # month to be sure. Also support manually triggering the workflow. + schedule: + - cron: 0 0 * * 1 + workflow_dispatch: +jobs: + update: + runs-on: ubuntu-20.04 + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Checkout + uses: actions/checkout@v4 + - name: Check certificates + id: checkCerts + # Use a conditional step instead of a conditional job to work around #20700. + if: github.repository == 'web-platform-tests/wpt' + run: | + python wpt make-hosts-file | sudo tee -a /etc/hosts + python wpt check-certs --days=120 + continue-on-error: true + - name: Create an issue + # Use a conditional step instead of a conditional job to work around #20700. + if: github.repository == 'web-platform-tests/wpt' && steps.checkCerts.outcome == 'failure' + uses: peter-evans/create-issue-from-file@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: WPT Certificates Must Be Updated + content-filepath: ./tools/ci/cert_update.md + labels: infra,priority:urgent diff --git a/.github/workflows/regen_certs.yml b/.github/workflows/regen_certs.yml index bb0cea9f396c41..f4c1f57a9236ae 100644 --- a/.github/workflows/regen_certs.yml +++ b/.github/workflows/regen_certs.yml @@ -24,7 +24,7 @@ jobs: - name: Commit and create pull request # Use a conditional step instead of a conditional job to work around #20700. if: github.repository == 'web-platform-tests/wpt' - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} author: wpt-pr-bot diff --git a/tools/ci/cert_update.md b/tools/ci/cert_update.md new file mode 100644 index 00000000000000..ce4da59aa20fec --- /dev/null +++ b/tools/ci/cert_update.md @@ -0,0 +1,11 @@ +# WPT Certificates Update Required + +@web-platform-tests/wpt-core-team + +The wpt certificates are nearing their expiration date and must be regenerated. + +There should be an open PR from github-actions with the title +[Regenerate WPT Certificates](https://github.com/web-platform-tests/wpt/pulls?q=is%3Apr+is%3Aopen+%22Regenerate+WPT+certificates%22+) +to be merged. Please ensure this is merged ASAP to avoid problems from certificates expiring. + +If this PR is missing you can run `wpt regen-certs` locally to create new certificates. diff --git a/tools/ci/commands.json b/tools/ci/commands.json index f26baddacadefc..a5417498fcbc02 100644 --- a/tools/ci/commands.json +++ b/tools/ci/commands.json @@ -24,8 +24,8 @@ }, "regen-certs": { "path": "regen_certs.py", - "script": "run", - "parser": "get_parser", + "script": "run_regen", + "parser": "get_parser_regen", "help": "Regenerate the WPT certificates", "virtualenv": false }, diff --git a/tools/ci/regen_certs.py b/tools/ci/regen_certs.py index 8f3abdcad691bd..64514a9ae6cf1e 100644 --- a/tools/ci/regen_certs.py +++ b/tools/ci/regen_certs.py @@ -27,16 +27,6 @@ ] """ - -def get_parser(): - parser = argparse.ArgumentParser() - parser.add_argument("--checkend-seconds", type=int, default=5184000, - help="The number of seconds the certificates must be valid for") - parser.add_argument("--force", action="store_true", - help="Regenerate certificates even if not reaching expiry") - return parser - - def check_cert(certificate, checkend_seconds): """Checks whether an x509 certificate will expire within a set period. @@ -87,7 +77,16 @@ def calculate_spki(cert_path): return base64.b64encode(dgst_output).decode('utf-8') -def run(**kwargs): +def get_parser_regen(): + parser = argparse.ArgumentParser() + parser.add_argument("--checkend-seconds", type=int, default=5184000, + help="The number of seconds the certificates must be valid for") + parser.add_argument("--force", action="store_true", + help="Regenerate certificates even if not reaching expiry") + return parser + + +def run_regen(**kwargs): logging.basicConfig() if kwargs["force"]: @@ -100,3 +99,51 @@ def run(**kwargs): regen_chrome_spki() else: logger.info("Certificates are still valid for at least %s seconds, skipping regeneration" % checkend_seconds) + + +def get_parser_checkend(): + parser = argparse.ArgumentParser() + parser.add_argument("--days", type=int, default=None, + help="The number of days the certificates must be valid for") + parser.add_argument("--hours", type=int, default=None, + help="The additional number of hours the certificates must be valid for") + parser.add_argument("--minutes", type=int, default=None, + help="The additional number of minutes the certificates must be valid for") + parser.add_argument("--seconds", type=int, default=None, + help="The additional number of seconds the certificates must be valid for") + return parser + + +def run_checkend(**kwargs): + logging.basicConfig() + + time_delta_params = {key: value for key, value in kwargs.items() + if key in ["days", "hours", "minutes", "seconds"] and value is not None} + if time_delta_params: + allowed_delta = datetime.timedelta(**time_delta_params) + else: + allowed_delta = None + + valid_expiry = True + + + for cert in ["tools/certs/cacert.pem", + "tools/certs/web-platform.test.pem"]: + expires_time = get_end_time(cert) + now = datetime.datetime.now(expires_time.tzinfo) + time_to_expiry: datetime.timedelta = expires_time - now + + if time_to_expiry.total_seconds() > 0: + msg = (f"Certificate {cert} expires in {time_to_expiry.days} days, {time_to_expiry.seconds} seconds") + cert_valid_expiry = allowed_delta is None or time_to_expiry > allowed_delta + else: + msg = f"Certificate {cert} is expired" + cert_valid_expiry = False + + if not cert_valid_expiry: + valid_expiry = False + logger.error(msg) + else: + logger.info(msg) + + return 0 if valid_expiry else 1