From 7b3c0edc63512b15fe7b6be1ae4e94efb829e969 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Wed, 12 Feb 2025 21:51:06 -0500 Subject: [PATCH 1/9] Add Python code to render site, and Github workflow to serve site. The site rendering definitely works, the Github workflow is gonna take a few tries to get to work. --- .github/workflows/pages.yaml | 57 ++++++ .gitignore | 1 + Pipfile | 15 ++ Pipfile.lock | 167 +++++++++++++++ config.yaml | 28 +++ .../apply-for-inclusion.md | 0 policy.md => content/index.md | 80 ++++---- .../moving-forward-together.md | 0 .../policy-archive}/policy-version-1-0.md | 0 .../policy-archive}/policy-version-1-1.md | 0 .../policy-archive}/policy-version-1-2.md | 0 .../policy-archive}/policy-version-1-3.md | 0 .../policy-archive}/policy-version-1-4.md | 0 .../policy-archive}/policy-version-1-5.md | 0 content/static/crp.css | 116 +++++++++++ content/versions.json.jinja2 | 3 + makefile | 11 + src/generate.py | 190 ++++++++++++++++++ templates/base.html | 40 ++++ templates/versions.json.jinja2 | 4 + 20 files changed, 672 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/pages.yaml create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 config.yaml rename apply-for-inclusion.md => content/apply-for-inclusion.md (100%) rename policy.md => content/index.md (97%) rename moving-forward-together.md => content/moving-forward-together.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-0.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-1.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-2.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-3.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-4.md (100%) rename {policy-archive => content/policy-archive}/policy-version-1-5.md (100%) create mode 100644 content/static/crp.css create mode 100644 content/versions.json.jinja2 create mode 100644 makefile create mode 100644 src/generate.py create mode 100644 templates/base.html create mode 100644 templates/versions.json.jinja2 diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml new file mode 100644 index 0000000..766f01c --- /dev/null +++ b/.github/workflows/pages.yaml @@ -0,0 +1,57 @@ +name: Build and Deploy Site + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "main" branch + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains two jobs: "build" and "deploy" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + + # Runs a single command using the runners shell + - name: Build Site + id: build + run: | + pipenv install + url=$(gh api "repos/$GITHUB_REPOSITORY/pages" --jq '.html_url') + echo $url + pipenv run python src/generate.py --context base_url $url + + # Upload site, does not deploy + - name: Upload static files as artifact + id: deployment + uses: actions/upload-pages-artifact@v3 # or specific "vX.X.X" version tag for this action + with: + path: output_html/ + + # Deployment job + deploy: + needs: build + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61e0e82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +output_html/ diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..a2a6c8e --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +markdown = "*" +jinja2 = "*" +pyyaml = "*" + +[dev-packages] + +[requires] +python_version = "3.13" +python_full_version = "3.13.1" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..49d8dc7 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,167 @@ +{ + "_meta": { + "hash": { + "sha256": "c31a29ca3b94a5fdc2ccd22ddecfdccbab2c3a041d8b1c2bb1df76b399666f88" + }, + "pipfile-spec": 6, + "requires": { + "python_full_version": "3.13.1", + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "jinja2": { + "hashes": [ + "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", + "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.1.5" + }, + "markdown": { + "hashes": [ + "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", + "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.7" + }, + "markupsafe": { + "hashes": [ + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + } + }, + "develop": {} +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..ef16456 --- /dev/null +++ b/config.yaml @@ -0,0 +1,28 @@ +context: + base_url: "http://localhost:8000" + current_version: 1.6 + versions: + - path: policy-archive/policy-version-1-0.html + version: 1.0 + date: "2022-03-01" + - path: policy-archive/policy-version-1-1.html + version: 1.1 + date: "2022-06-01" + - path: policy-archive/policy-version-1-2.html + version: 1.2 + date: "2022-09-01" + - path: policy-archive/policy-version-1-3.html + version: 1.3 + date: "2023-01-06" + - path: policy-archive/policy-version-1-4.html + version: 1.4 + date: "2023-03-03" + - path: policy-archive/policy-version-1-4.html + version: 1.5 + date: "2024-01-16" + - path: / + version: 1.6 + date: "2025-02-15" +input_dir: content +template_dir: templates +output_dir: output_html diff --git a/apply-for-inclusion.md b/content/apply-for-inclusion.md similarity index 100% rename from apply-for-inclusion.md rename to content/apply-for-inclusion.md diff --git a/policy.md b/content/index.md similarity index 97% rename from policy.md rename to content/index.md index e060f81..e3a4962 100644 --- a/policy.md +++ b/content/index.md @@ -55,9 +55,9 @@ Bookmark this page as [https://g.co/chrome/root-policy](https://g.co/chrome/root Google Chrome relies on Certification Authority systems (herein referred to as "CAs") to issue certificates to websites. Chrome uses these certificates to help ensure the connections it makes on behalf of its users are properly secured. Chrome accomplishes this by verifying that a website's certificate was issued by a recognized CA, while also performing additional evaluations of the HTTPS connection's security properties. Certificates not issued by a CA trusted by Chrome or a user's local settings can cause users to see warnings and error pages. -When making HTTPS connections, Chrome refers to a list of self-signed root certificates from CAs that have demonstrated why continued trust in them is justified. This list is known as a "Root Store." CA certificates included in the [Chrome Root Store](https://g.co/chrome/root-store) are selected on the basis of publicly available and verified information, such as that within the Common CA Database ([CCADB](https://ccadb.org/)), and ongoing reviews by the Chrome Root Program. +When making HTTPS connections, Chrome refers to a list of self-signed root certificates from CAs that have demonstrated why continued trust in them is justified. This list is known as a "Root Store." CA certificates included in the [Chrome Root Store](https://g.co/chrome/root-store) are selected on the basis of publicly available and verified information, such as that within the Common CA Database ([CCADB](https://ccadb.org/)), and ongoing reviews by the Chrome Root Program. -The Chrome Root Program Policy below establishes the minimum requirements for CA certificates to be included as trusted in a default installation of Chrome. +The Chrome Root Program Policy below establishes the minimum requirements for CA certificates to be included as trusted in a default installation of Chrome. ### Apply for Inclusion @@ -112,10 +112,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S This policy considers a "CA Owner" to be the organization or legal entity that is either: -- represented in the subject DN of the CA certificate; or +- represented in the subject DN of the CA certificate; or - in possession or control of the corresponding private key capable of issuing new certificates, if not the same organization or legal entity directly represented in the subject DN of the certificate. -This policy considers an "Applicant" to be an organization or legal entity that has an open "Root Inclusion Request" submitted to Google Chrome in the [CCADB](https://ccadb.org/). +This policy considers an "Applicant" to be an organization or legal entity that has an open "Root Inclusion Request" submitted to Google Chrome in the [CCADB](https://ccadb.org/). This policy uses the term "Chrome Root Program Participants" to describe: @@ -185,7 +185,7 @@ The Chrome Root Program considers CA policy documentation disclosed to the CCADB ##### 3.1.1 Root CA Key Material Freshness -The Chrome Root Program only accepts CCADB Root Inclusion Requests from Applicant PKI hierarchies with corresponding root CA key material generated within 5 years of application to the Chrome Root Store. +The Chrome Root Program only accepts CCADB Root Inclusion Requests from Applicant PKI hierarchies with corresponding root CA key material generated within 5 years of application to the Chrome Root Store. Applicants MUST submit written evidence to the CCADB identifying the date(s) of the key generation ceremony and an attestation to the Applicant's adherence to the requirements defined in Sections 6.1.1.1 ("CA Key Pair Generation") and 6.2 ("Private Key Protection and Cryptographic Module Engineering Controls") of the Baseline Requirements from a Qualified Auditor using an approved format, in accordance with the table below. @@ -206,20 +206,20 @@ Within no more than 90 calendar days after an Applicant CA (i.e., replacement) b 1. Issued a cross-certificate from the CA being replaced to the replacement CA; and 2. Transitioned all TLS server authentication certificate issuance from the cross-signing PKI hierarchy to the replacement PKI hierarchy. -The CA certificate being replaced will be removed from the Chrome Root Store upon the absence of unexpired and unrevoked TLS server authentication certificates (excluding test certificates like those disclosed to the CCADB) disclosed to Certificate Transparency (CT) before the date of the Applicant CA (i.e., replacement) being first distributed by the Chrome Root Store. +The CA certificate being replaced will be removed from the Chrome Root Store upon the absence of unexpired and unrevoked TLS server authentication certificates (excluding test certificates like those disclosed to the CCADB) disclosed to Certificate Transparency (CT) before the date of the Applicant CA (i.e., replacement) being first distributed by the Chrome Root Store. -Due to the existence of the cross-certificate, TLS server authentication certificates issued by the replacement PKI hierarchy will be trusted by default in versions of Chrome relying on the Chrome Root Store, regardless of whether they are capable of receiving updates to the Chrome Root Store. +Due to the existence of the cross-certificate, TLS server authentication certificates issued by the replacement PKI hierarchy will be trusted by default in versions of Chrome relying on the Chrome Root Store, regardless of whether they are capable of receiving updates to the Chrome Root Store. ##### 3.1.3 Root CA Term-Limit -Any root CA certificate with corresponding key material generated more than 15 years ago will be removed from the Chrome Root Store on an ongoing basis. +Any root CA certificate with corresponding key material generated more than 15 years ago will be removed from the Chrome Root Store on an ongoing basis. The age of the key material will be determined by the earliest of either: - a key generation report issued by a Qualified Auditor that distinctly represents the corresponding key; or - the validity date of the earliest appearing certificate that contains the corresponding public key. -To phase-in these requirements in a manner that reduces negative impact to the ecosystem, affected root CA certificates included in the Chrome Root Store will be removed according to the schedule in the table below. +To phase-in these requirements in a manner that reduces negative impact to the ecosystem, affected root CA certificates included in the Chrome Root Store will be removed according to the schedule in the table below. | Key Material Created | Approximate Removal Date | |--------------------- |------------------------- | @@ -290,7 +290,7 @@ The automated solution MUST minimize "hands-on" input required from humans durin For each Baseline Requirements certificate policy OID the corresponding hierarchy issues, the Applicant MUST use its automated solution to issue valid test TLS server authentication certificates (i.e., "Automation Test Certificates") intended to demonstrate its automation capabilities to the Chrome Root Program. Valid Automation Test Certificates MUST be renewed at least once every 30 calendar days, however, at any point, the Chrome Root Program may request more frequent renewal. Automation Test Certificates must be served by a publicly accessible website whose URL is disclosed to the CCADB in a Root Inclusion Request. CA Owners are encouraged to issue "Short-lived Subscriber Certificates," as [introduced](https://cabforum.org/2023/07/14/ballot-sc-063-v4make-ocsp-optional-require-crls-and-incentivize-automation/) in Version 2.0.1 of the Baseline Requirements, for the Automation Test Certificates. -If at any point a self-signed root CA certificate is accepted into the Chrome Root Store and the CA Owner intends to issue a Baseline Requirements certificate policy OID not previously disclosed to the Chrome Root Program, the requirements in this section MUST be satisfied before issuing certificates containing the OID to Subscribers from the corresponding hierarchy, with the exception of Automation Test Certificates. +If at any point a self-signed root CA certificate is accepted into the Chrome Root Store and the CA Owner intends to issue a Baseline Requirements certificate policy OID not previously disclosed to the Chrome Root Program, the requirements in this section MUST be satisfied before issuing certificates containing the OID to Subscribers from the corresponding hierarchy, with the exception of Automation Test Certificates. ###### 3.3.1.1 ACME Solutions @@ -305,12 +305,12 @@ Applicant PKI hierarchies SHOULD support the Automatic Certificate Management En - revokeCert - Each Applicant PKI hierarchy's endpoint's corresponding issuing CA(s) MUST support Certification Authority Authorization (CAA) Record Extensions for Account URI and ACME Method Binding, as specified in [RFC 8657](https://www.rfc-editor.org/rfc/rfc8657). - Applicant PKI hierarchies supporting the ACME protocol MUST support ACME Renewal Information (ARI, RFC [TBD](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/)). -- ACME endpoints SHOULD be publicly accessible. +- ACME endpoints SHOULD be publicly accessible. - Each endpoint SHOULD be hosted using an appropriate and readily accessible online means that is available on a 24x7 basis. ###### 3.3.1.2 Non-ACME Solutions -While ACME support is encouraged, Applicant PKI hierarchies MAY support other automated solutions so long as the following characteristics are verifiably demonstrated to the Chrome Root Program. The CA Owner MUST disclose to the CCADB publicly available information that describes the other automated solution capability for each Baseline Requirements certificate policy OID that the corresponding hierarchy issues and how a Subscriber can leverage its benefits. +While ACME support is encouraged, Applicant PKI hierarchies MAY support other automated solutions so long as the following characteristics are verifiably demonstrated to the Chrome Root Program. The CA Owner MUST disclose to the CCADB publicly available information that describes the other automated solution capability for each Baseline Requirements certificate policy OID that the corresponding hierarchy issues and how a Subscriber can leverage its benefits. - The automated solution MUST: - generate a new key pair for each certificate request by default. @@ -334,35 +334,35 @@ For Applicant PKI hierarchies subject of a CCADB Root Inclusion Request submitte - TLS server authentication certificates SHOULD NOT exceed 90 calendar days. - The period for domain control validation data reuse SHOULD NOT exceed 90 calendar days. -- Due to (1) limitations in offering support for automation and (2) these methods offering a weak binding between request authorization and the demonstrated control over the domain(s) appearing in the requested certificate, TLS server authentication certificates SHOULD NOT rely on the following domain validation methods as defined by the Baseline Requirements: - - 3.2.2.4.4 Constructed Email to Domain Contact - - 3.2.2.4.13 Email to DNS CAA Contact - - 3.2.2.4.14 Email to DNS TXT Contact - - 3.2.2.4.16 Phone Contact with DNS TXT Record Phone Contact - - 3.2.2.4.17 Phone Contact with DNS CAA Phone Contact - - 3.2.2.5.2 Email, Fax, SMS, or Postal Mail to IP Address Contact - - 3.2.2.5.5 Phone Contact with IP Address Contact +- Due to (1) limitations in offering support for automation and (2) these methods offering a weak binding between request authorization and the demonstrated control over the domain(s) appearing in the requested certificate, TLS server authentication certificates SHOULD NOT rely on the following domain validation methods as defined by the Baseline Requirements: + - 3.2.2.4.4 Constructed Email to Domain Contact + - 3.2.2.4.13 Email to DNS CAA Contact + - 3.2.2.4.14 Email to DNS TXT Contact + - 3.2.2.4.16 Phone Contact with DNS TXT Record Phone Contact + - 3.2.2.4.17 Phone Contact with DNS CAA Phone Contact + - 3.2.2.5.2 Email, Fax, SMS, or Postal Mail to IP Address Contact + - 3.2.2.5.5 Phone Contact with IP Address Contact In cases where the above requirements cannot be met, CA Owners are encouraged to collect and share the corresponding subscriber use cases and affected technologies with chrome-root-program [at] google [dot] com on a quarterly basis in a format of their choosing to support the Chrome Root Program in better understanding blockers and opportunities for ecosystem improvement. #### 3.4 Promote Increased Transparency in the Web PKI -Within 24 hours of issuance, Chrome Root Program Participants SHOULD log final certificates to at least one CT log [usable](https://googlechrome.github.io/CertificateTransparency/log_list.html) in Chrome at the time of issuance. +Within 24 hours of issuance, Chrome Root Program Participants SHOULD log final certificates to at least one CT log [usable](https://googlechrome.github.io/CertificateTransparency/log_list.html) in Chrome at the time of issuance. Applicants MUST log pre-certificates and final certificates to at least one "Test" CT log disclosed [here](https://www.gstatic.com/ct/log_list/v3/all_logs_list.json) (i.e., log type = "test") until eligible for logging in logs usable in Chrome at the time of issuance (e.g., due to being accepted by a publicly-trusted root store operator or due to the existence of a cross-certificate issued from a publicly-trusted root CA). ### 4. Audits -Chrome Root Program Participant CAs MUST be audited in accordance with the table below. +Chrome Root Program Participant CAs MUST be audited in accordance with the table below. -Audits MUST NOT rely on a version of the accepted audit criteria below if it has been superseded by more than 30 calendar days before the start of the corresponding audit period. +Audits MUST NOT rely on a version of the accepted audit criteria below if it has been superseded by more than 30 calendar days before the start of the corresponding audit period. -| CA Type | EKU Characteristics** | Audit Criteria | -|-------- |---------------------- |--------------- | +| CA Type | EKU Characteristics** | Audit Criteria | +|-------- |---------------------- |--------------- | | Root CA | N/A | **If WebTrust scheme**…

(1) "WebTrust Principles and Criteria for Certification Authorities"; and either…

- (A) "WebTrust Principles and Criteria for Certification Authorities – SSL Baseline with Network Security" or
- (B) "WebTrust Principles and Criteria for Certification Authorities – SSL Baseline" and "WebTrust Principles and Criteria for Certification Authorities – Network Security"

and

(2) "WebTrust for CA - Extended Validation - SSL" (if issuing EV)

**If ETSI scheme*****...

(1) ETSI EN 319 411-1 LCP and [DVCP or OVCP];

or

(2) ETSI EN 319 411-1 [NCP or NCP+] and EVCP (if issuing EV)
| Cross-Certified Subordinate CA | Either: (1) Certificate does not include an EKU; or (2) EKU is present and includes id-kp-serverAuth or anyExtendedKeyUsage | Same as above. | | TLS Subordinate CA or Technically Constrained TLS Subordinate CA | Same as above. | Same as above. | -| Technically Constrained Non-TLS Subordinate CA | EKU is present and does not include id-kp-serverAuth or anyExtendedKeyUsage. | Minimally expected to be audited as defined in Section 8.7 of the BRs (self-audit). | +| Technically Constrained Non-TLS Subordinate CA | EKU is present and does not include id-kp-serverAuth or anyExtendedKeyUsage. | Minimally expected to be audited as defined in Section 8.7 of the BRs (self-audit). | | All others | N/A | Minimally expected to be audited as defined in Section 8.7 of the BRs (self-audit). | \*\* while existing CA certificates trusted by Chrome MAY have EKU values as described in this table, Applicant PKI hierarchies MUST remain [dedicated to only TLS server authentication use cases](#32-promote-use-of-dedicated-tls-server-authentication-pki-hierarchies) @@ -380,7 +380,7 @@ For Applicant PKI hierarchies subject of a CCADB Root Inclusion Request submitte - Except for Externally-operated CAs, when CAs in the hierarchy are assessed against: - **only a single audit scheme** (e.g., all CAs in the hierarchy are only assessed against the WebTrust scheme), they MUST fall under a single audit scope (i.e., represented in a single WebTrust Assurance Report) for the assessed criteria (e.g., (1) WebTrust Principles and Criteria for Certification Authorities, (2) WebTrust Principles and Criteria for Certification Authorities - Network Security, (3) WebTrust Principles and Criteria for Certification Authorities - SSL Baseline, or (4) WebTrust for CA - Extended Validation - SSL). - - **multiple audit schemes** (e.g., some CAs are assessed against the WebTrust scheme and others are assessed against the ETSI scheme), all CAs assessed against each respective scheme MUST fall under a single audit scope for that scheme (i.e., all ETSI-assessed CAs are represented in a single ETSI Audit Attestation Letter, and all WebTrust CAs are represented in a single WebTrust Assurance Report) for the assessed criteria. + - **multiple audit schemes** (e.g., some CAs are assessed against the WebTrust scheme and others are assessed against the ETSI scheme), all CAs assessed against each respective scheme MUST fall under a single audit scope for that scheme (i.e., all ETSI-assessed CAs are represented in a single ETSI Audit Attestation Letter, and all WebTrust CAs are represented in a single WebTrust Assurance Report) for the assessed criteria. #### 4.2 Ad-Hoc Audits @@ -388,10 +388,10 @@ The Chrome Root Program may require Chrome Root Program Participants undergo add #### 4.3 Reporting Requirements -Chrome Root Program Participants MUST provide audit reports that: +Chrome Root Program Participants MUST provide audit reports that: - satisfy the requirements in Section 5.1 ("Audit Statement Content") of the [CCADB Policy](https://www.ccadb.org/policy), and -- are uploaded to the CCADB within 92 calendar days from the audit period ending date specified in the audit letter. +- are uploaded to the CCADB within 92 calendar days from the audit period ending date specified in the audit letter. ### 5. Reporting and Responding to Incidents @@ -399,7 +399,7 @@ The failure of a Chrome Root Program Participant to meet the commitments of this #### 5.1 Incident Reports -Chrome Root Program Participants MUST publicly disclose and/or respond to incident reports in [Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=CA%20Program&component=CA%20Certificate%20Compliance), regardless of perceived impact. Reports MUST be submitted in accordance with the current version of [this](https://www.ccadb.org/cas/incident-report) CCADB incident report format and timelines. +Chrome Root Program Participants MUST publicly disclose and/or respond to incident reports in [Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=CA%20Program&component=CA%20Certificate%20Compliance), regardless of perceived impact. Reports MUST be submitted in accordance with the current version of [this](https://www.ccadb.org/cas/incident-report) CCADB incident report format and timelines. While all Chrome Root Program Participants MAY participate in the incident reporting process, the CA Owner whose corresponding certificate is included in the Chrome Root Store is encouraged to disclose and/or respond to incidents on behalf of the Chrome Root Program Participants included in its PKI hierarchy. @@ -409,11 +409,11 @@ The Chrome Root Program will evaluate every incident on a case-by-case basis, an Chrome Root Program Participants MUST be detailed, candid, timely, and transparent in describing their architecture, implementation, operations, and external dependencies as necessary for the Chrome Root Program and the public to evaluate the nature of the incident and the CA Owner's response. When evaluating an incident response, the Chrome Root Program's primary concern is ensuring that browsers, other CA Owners, users, and website developers have the necessary information to identify improvements, and that the Chrome Root Program Participant is responsive to addressing identified issues. -Factors that are significant to the Chrome Root Program when evaluating incidents include (but are not limited to): +Factors that are significant to the Chrome Root Program when evaluating incidents include (but are not limited to): -- a demonstration of understanding of the [root causes](https://sre.google/sre-book/postmortem-culture/) of an incident, -- a substantive commitment and timeline to changes that clearly and persuasively address the root cause, -- past history by the Chrome Root Program Participant in its incident handling and its follow through on commitments, and, +- a demonstration of understanding of the [root causes](https://sre.google/sre-book/postmortem-culture/) of an incident, +- a substantive commitment and timeline to changes that clearly and persuasively address the root cause, +- past history by the Chrome Root Program Participant in its incident handling and its follow through on commitments, and, - the severity of the security impact of the incident. Due to the incorporation of the Baseline Requirements into CA policy documents, incidents may include a prescribed follow-up action, such as revoking impacted certificates within a certain timeframe. If the Chrome Root Program Participant does not perform the required follow-up actions, or does not perform them in the expected timeframe, the Chrome Root Program Participant MUST file a secondary incident report describing any certificates involved, the expected timeline to complete any follow-up actions, and what changes they are making to ensure they can meet these requirements consistently in the future. @@ -425,7 +425,7 @@ The Chrome Root Program prioritizes and remains committed to promoting public di As standard practice, the Chrome Root Program does not: - discuss ongoing public incident reports privately. We believe using information disclosed to the public as the basis for our response is the most transparent and effective way of upholding the security expectations of Chrome's users, while also ensuring the [factors](#51-incident-reports) that are significant to Chrome are adequately addressed; -- advise on or approve a CA Owner's proposed or planned response to an incident; or +- advise on or approve a CA Owner's proposed or planned response to an incident; or - offer guarantees of specific outcomes in response to the course of action deemed most appropriate by the CA Owner. ### 6. Common CA Database @@ -437,15 +437,15 @@ In some cases, this policy strengthens requirements described in the [CCADB Poli Chrome Root Program Participants MUST: 1. Follow the requirements defined in the [CCADB Policy](https://www.ccadb.org/policy). When a timeline is not defined for a requirement specified within the CCADB Policy, updates MUST be submitted to the CCADB within 14 calendar days of being completed. -2. Disclose all subordinate CA certificates capable of validating to a certificate included in the Chrome Root Store or associated with a Root Inclusion Request to the CCADB. Disclosure MUST take place within 7 calendar days of issuance and before the subject CA represented in the certificate begins issuing publicly-trusted certificates. -3. Disclose revocation of all subordinate CA certificates capable of validating to a certificate included in the Chrome Root Store or associated with a Root Inclusion Request to the CCADB within 7 calendar days of revocation. +2. Disclose all subordinate CA certificates capable of validating to a certificate included in the Chrome Root Store or associated with a Root Inclusion Request to the CCADB. Disclosure MUST take place within 7 calendar days of issuance and before the subject CA represented in the certificate begins issuing publicly-trusted certificates. +3. Disclose revocation of all subordinate CA certificates capable of validating to a certificate included in the Chrome Root Store or associated with a Root Inclusion Request to the CCADB within 7 calendar days of revocation. 4. Disclose either the Certificate Revocation List (CRL) Distribution Point or a JSON Array of Partitioned CRLs on root and subordinate CA certificate records in the CCADB within 7 days of the corresponding CA issuing its first certificate. This applies to each included CA certificate and each CA certificate chaining up to a certificate included in the Chrome Root Store. - + - a. URLs SHOULD be of the "http" scheme (i.e., not "https"). - + - b. When populating the CCADB with multiple CRL URLs for the JSON Array of Partitioned CRLs, Chrome Root Program Participants MUST ensure that each CRL contains a critical Issuing Distribution Point extension and the distributionPoint field of the extension MUST include a UniformResourceIdentifier. - The value of the UniformResourceIdentifier MUST exactly match a URL, from which the CRL was accessed, present in the CCADB record associated with the subordinate CA certificate. - + - c. When providing a single CRL URL in the CCADB, the CRL SHOULD NOT contain an Issuing Distribution Point extension. 6. Complete and submit an annual [self-assessment](https://www.ccadb.org/cas/self-assessment) to the CCADB. Instructions for completing the self-assessment are included in the required assessment template. diff --git a/moving-forward-together.md b/content/moving-forward-together.md similarity index 100% rename from moving-forward-together.md rename to content/moving-forward-together.md diff --git a/policy-archive/policy-version-1-0.md b/content/policy-archive/policy-version-1-0.md similarity index 100% rename from policy-archive/policy-version-1-0.md rename to content/policy-archive/policy-version-1-0.md diff --git a/policy-archive/policy-version-1-1.md b/content/policy-archive/policy-version-1-1.md similarity index 100% rename from policy-archive/policy-version-1-1.md rename to content/policy-archive/policy-version-1-1.md diff --git a/policy-archive/policy-version-1-2.md b/content/policy-archive/policy-version-1-2.md similarity index 100% rename from policy-archive/policy-version-1-2.md rename to content/policy-archive/policy-version-1-2.md diff --git a/policy-archive/policy-version-1-3.md b/content/policy-archive/policy-version-1-3.md similarity index 100% rename from policy-archive/policy-version-1-3.md rename to content/policy-archive/policy-version-1-3.md diff --git a/policy-archive/policy-version-1-4.md b/content/policy-archive/policy-version-1-4.md similarity index 100% rename from policy-archive/policy-version-1-4.md rename to content/policy-archive/policy-version-1-4.md diff --git a/policy-archive/policy-version-1-5.md b/content/policy-archive/policy-version-1-5.md similarity index 100% rename from policy-archive/policy-version-1-5.md rename to content/policy-archive/policy-version-1-5.md diff --git a/content/static/crp.css b/content/static/crp.css new file mode 100644 index 0000000..346a9ba --- /dev/null +++ b/content/static/crp.css @@ -0,0 +1,116 @@ +/* Match the CT site */ +html { + font: 14px / 1.5 "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 14px; +} + +body { + margin: 8px; + display: flex; + flex-direction: row; + + background-color: #fff; + color: #727272; +} + +/* Don't use bullets in Nav */ +nav ul { + list-style: none; + padding: 0; + margin: 0 0 14px 1em; +} +nav li { + list-style: none; +} + +a { + color: #267CB9; +} + +/* Force h1 to be big, even though it's in a
*/ +h1 { + font-size: 2em; +} + +/* Headings can be black. Drop the top margin, so we can align the top of the + * page better. */ +h1, h2, h3, h4 { + color: black; + margin-top: 0; +} + +/* We use h4 in nav, and don't actually want the bottom margin */ +h4 { + margin-bottom: 0; +} + +/* Fixed header at the top left */ +header { + position: fixed; + top: 8px; + left: 25px; + width: 250px; +} + +/* Push section right to avoid overlap */ +section { + max-width: 800px; + margin-left: 300px; +} + +/* Stack column layout on smaller screens, unfix the header */ +@media (max-width: 800px) { + section { + margin-left: 0px; + } + header { + position: unset; + } + body { + flex-direction: column; + } +} + +/* Styling tables. */ +table { + font-size: 12px; + border-collapse: collapse; + border-spacing: 0; + border-color: black; + border-style: solid; + border-width: 1px; + overflow: hidden; + table-layout: auto; + margin-bottom: 14px; +} + +thead { + font-weight: bold; + background-color: #efefef; + border-style: solid; + border-width: 1px; + text-align: center; + vertical-align: top; + word-break: normal; + border-width: 1px; +} + +tbody { + text-align: left; +} + +th { + padding: 0 1em 0 1em; + color: black; + border-style: solid; + border-width: 1px; + min-width: 5em; +} + +td { + padding: 10px 5px; + border-style: solid; + border-width: 1px; + word-break: keep-all; +} diff --git a/content/versions.json.jinja2 b/content/versions.json.jinja2 new file mode 100644 index 0000000..8fd02f4 --- /dev/null +++ b/content/versions.json.jinja2 @@ -0,0 +1,3 @@ +--- +template: versions.json.jinja2 +--- diff --git a/makefile b/makefile new file mode 100644 index 0000000..63c656a --- /dev/null +++ b/makefile @@ -0,0 +1,11 @@ +help: ## List tasks with documentation + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' "$(firstword $(MAKEFILE_LIST))" | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +build: ## build the site to output_html + pipenv run python src/generate.py + +serve: ## serve content in output_html at localhost:8000 + cd output_html && pipenv run python -m http.server 8000 + +clean: ## wipe output_html/ + rm -rf output_html/ diff --git a/src/generate.py b/src/generate.py new file mode 100644 index 0000000..229a03d --- /dev/null +++ b/src/generate.py @@ -0,0 +1,190 @@ +import argparse +import os +import re +import shutil +import pathlib + +import yaml +import markdown + +from collections import namedtuple +import urllib.parse + +from jinja2 import Environment, FileSystemLoader + + +class Filters: + + @classmethod + def join_paths(cls, a, b): + a = pathlib.Path(a) + b = pathlib.Path(b) + return str(a.joinpath(b.relative_to(b.anchor) if b.is_absolute() else b)) + + @classmethod + def absolute_url(cls, base_url, path): + parsed_url = urllib.parse.urlparse(base_url, allow_fragments=False) + new_path = cls.join_paths(parsed_url.path, path) + return urllib.parse.urlunparse(( + parsed_url.scheme or "http", + parsed_url.netloc, + new_path, + parsed_url.params, + parsed_url.query, + parsed_url.fragment, + )) + +def replace_extension(filename, old, new): + parts = filename.rsplit( + f".{old}", 1 + ) # Split from the right, at most once, gives [name, ''] + return f".{new}".join(parts) + + +def title_from_filename(filename): + return replace_extension(filename, "md", "") + + +def render_file(input_path, output_path, env, page_context={}): + filename = os.path.basename(input_path) + + # Create necessary subdirectories in output_path + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(input_path, "r", encoding="utf-8") as f: + content = f.read() + + # Regex to detect YAML front matter + match = re.match(r"^---\n(.*?)\n---\n(.*)", content, re.DOTALL) + + # Parse the front matter + if match: + print("front matter", input_path) + front_matter = yaml.safe_load(match.group(1)) # Parse YAML + md_content = match.group(2) # Extract Markdown part + else: + front_matter = {} + md_content = content + + # Get the template from the front matter + template_name = front_matter.get("template", "base.html") + template = env.get_template(template_name) + + # Convert Markdown to HTML + html_content = markdown.markdown(md_content, extensions=['tables', 'toc', 'attr_list']) + + # Wrap with a template + page_context = page_context.copy() + page_context.update( + { + "content": html_content, + "title": front_matter.get("title", title_from_filename(filename)), + } + ) + final_html = template.render(**page_context) + + # Write to output file + with open(output_path, "w", encoding="utf-8") as f: + f.write(final_html) + + +ConversionResult = namedtuple("ConversionResult", ["converted", "skipped"]) + + +def render_markdown(input_dir, output_dir, env, page_context={}) -> ConversionResult: + converted = 0 + skipped = 0 + for root, _, files in os.walk(input_dir): + for filename in files: + # Only Markdown files will have their extension changed, everything + # else is copied. + input_path = os.path.join(root, filename) + should_render = False + if filename.endswith(".md"): + output_filename = replace_extension(filename, "md", "html") + should_render = True + elif filename.endswith(".jinja2"): + should_render = True + output_filename = filename[:-7] + else: + output_filename = filename + relative_path = os.path.relpath(os.path.dirname(input_path), input_dir) + output_path = os.path.normpath( + os.path.join(output_dir, relative_path, output_filename) + ) + print(input_path, output_path) + + # Only Markdown files are rendered. + if should_render: + render_file(input_path, output_path, env, page_context=page_context) + converted += 1 + else: + os.makedirs(os.path.dirname(output_path), exist_ok=True) + shutil.copy(input_path, output_path) + skipped += 1 + return ConversionResult(converted, skipped) + + +def main(): + # Default paths and context + CONFIG_PATH_DEFAULT = "config.yaml" + INPUT_DIR_DEFAULT = "content" + TEMPLATE_DIR_DEFAULT = "templates" + OUTPUT_DIR_DEFAULT = "output_html" + CONTEXT_DEFAULT = {"base_url": "http://localhost:8000"} + + # Argument parsing + parser = argparse.ArgumentParser(description="Process configuration and override context values.") + parser.add_argument("--config", type=str, default=CONFIG_PATH_DEFAULT, help="Path to the config file.") + parser.add_argument("--input-dir", type=str, default=None, help="Path to the input directory.") + parser.add_argument("--output-dir", type=str, default=None, help="Path to the output directory for rendering.") + parser.add_argument("--template-dir", type=str, default=None, help="Path to the directory containing jinja2 templates.") + parser.add_argument("--context", nargs=2, action="append", metavar=("KEY", "VALUE"), + help="Override context values in config, e.g., --context base_url example.com") + args = parser.parse_args() + + # Load YAML config + print(f"Loading configuration from {args.config}") + with open(args.config, "r", encoding="utf-8") as f: + config = yaml.safe_load(f) + + # Set defaults + config.setdefault("input_dir", INPUT_DIR_DEFAULT) + config.setdefault("template_dir", TEMPLATE_DIR_DEFAULT) + config.setdefault("output_dir", OUTPUT_DIR_DEFAULT) + config.setdefault("context", CONTEXT_DEFAULT.copy()) + + # Override directories from config root with CLI args, if given + if args.input_dir: + config['input_dir'] = args.input_dir + if args.template_dir: + config['template_dir'] = args.template_dir + if args.output_dir: + config['output_dir'] = args.output_dir + + + # Override context values if provided + if args.context: + for key, value in args.context: + config["context"][key] = value + + # Load Jinja2 templates + env = Environment(loader=FileSystemLoader(config["template_dir"])) + env.filters["absolute_url"] = lambda x: Filters.absolute_url( + config["context"]["base_url"], x + ) + + # Ensure output directory exists + os.makedirs(config.get("output_dir"), exist_ok=True) + + # Render the markdow and copy assets + res = render_markdown( + config.get("input_dir"), + config.get("output_dir"), + env, + page_context=config.get("context"), + ) + print(f"Converted {res.converted} files, copied {res.skipped} non-input files") + + +if __name__ == "__main__": + main() diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..e002c9f --- /dev/null +++ b/templates/base.html @@ -0,0 +1,40 @@ + + + + + + {{ title }} + + + + + + +
+

Policy

+ +

Resources

+ +

Policy Archive

+ +
+
+ {{ content | safe }} +
+ + diff --git a/templates/versions.json.jinja2 b/templates/versions.json.jinja2 new file mode 100644 index 0000000..35a1c5b --- /dev/null +++ b/templates/versions.json.jinja2 @@ -0,0 +1,4 @@ +{ + "current_version": {{ current_version }}, + "versions": {{ versions | reverse | list | tojson }} +} From 3e44905da15d4f723724a82ca1b50ba4ad5452ad Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 12:12:41 -0500 Subject: [PATCH 2/9] Install Python on Github actions --- .github/workflows/pages.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 766f01c..4e97cf0 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -23,6 +23,12 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 + # Install Python / Pipenv + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' # Change this to your desired Python version + # Runs a single command using the runners shell - name: Build Site id: build From b727ba12f26c46fe5bc51a376437c9ee79a45b3d Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 17:16:00 -0500 Subject: [PATCH 3/9] install pipenv --- .github/workflows/pages.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 4e97cf0..d6dcbce 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -23,12 +23,17 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - # Install Python / Pipenv + # Install Python - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.13' # Change this to your desired Python version + # Install pipenv + - name: Install pipenv + run: | + python -m pip install --upgrade pipenv wheel + # Runs a single command using the runners shell - name: Build Site id: build From 091c640445a4e143b9b891a58348704fc1ea3fe3 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 17:19:41 -0500 Subject: [PATCH 4/9] pipenv via script --- .github/workflows/pages.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index d6dcbce..02ea472 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -28,11 +28,12 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.13' # Change this to your desired Python version + cache: 'pipenv' # Install pipenv - name: Install pipenv - run: | - python -m pip install --upgrade pipenv wheel + run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python + - run: pipenv install # Runs a single command using the runners shell - name: Build Site From 887dfc841637b3cebe5d62edabd8d92975bf5f75 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 17:22:51 -0500 Subject: [PATCH 5/9] python 3.13.1 --- .github/workflows/pages.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 02ea472..60decb1 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -27,13 +27,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.13' # Change this to your desired Python version + python-version: '3.13.1' # Change this to your desired Python version cache: 'pipenv' # Install pipenv - name: Install pipenv - run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python - - run: pipenv install + run: pip install --upgrade pipenv wheel # Runs a single command using the runners shell - name: Build Site From 1e8e9faf5e1a71b7d52a796e72aae0036261f405 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 17:40:25 -0500 Subject: [PATCH 6/9] Authenticate gh CLI --- .github/workflows/pages.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 60decb1..2da9708 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -37,6 +37,8 @@ jobs: # Runs a single command using the runners shell - name: Build Site id: build + env: + GH_TOKEN: ${{ github.token }} run: | pipenv install url=$(gh api "repos/$GITHUB_REPOSITORY/pages" --jq '.html_url') From b5d25d167eca3c14172b1b0e4da91ebda109d355 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Sat, 15 Feb 2025 17:48:18 -0500 Subject: [PATCH 7/9] Don't display current version in policy archive --- src/generate.py | 2 -- templates/base.html | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/generate.py b/src/generate.py index 229a03d..ccb8b1e 100644 --- a/src/generate.py +++ b/src/generate.py @@ -58,7 +58,6 @@ def render_file(input_path, output_path, env, page_context={}): # Parse the front matter if match: - print("front matter", input_path) front_matter = yaml.safe_load(match.group(1)) # Parse YAML md_content = match.group(2) # Extract Markdown part else: @@ -111,7 +110,6 @@ def render_markdown(input_dir, output_dir, env, page_context={}) -> ConversionRe output_path = os.path.normpath( os.path.join(output_dir, relative_path, output_filename) ) - print(input_path, output_path) # Only Markdown files are rendered. if should_render: diff --git a/templates/base.html b/templates/base.html index e002c9f..670d5c8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -27,7 +27,7 @@

Resources

Policy Archive