Quick and secure method of using CDN in Pelican

CDN and SSG

CDN stands for Content Delivery Network. SSG stands for static site generator. SSG is a kind of software application that generates static pages. It usually generates static HTML pages from templates.

In SSG, we make use of template to generate HTML. Inside the templates, we usually use other web frameworks, CSS or JavaScript programs. Usually these web resources are available via CDN. With CDN, the content is served in multiple locations geographically. The websites do not need to host the files by themselves.

For the CDN, usually they provide HTML code blocks that could use the specific CSS or JavaScript, for example:

1
2
3
4
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU"
crossorigin="anonymous">

So, that is the code block for loading the CSS/stylesheet for Bootstrap. It specified the URL and the checksum/SRI (usually in SHA256/384/512) of the file. It would be inconvenient if developers need to manually lookup the CDN website, get the code block and then update their codes. Besides that, the checksum would be different for different version of the applications.

So, what is SRI? SRI stands for Sub-Resource Integrity. It is a security measure. In the above code block, the checksum or hash value of the CSS is specified. When the browser loads the CSS file, it compares the checksum of the CSS file with the value specified in the HTML. If the value does not match, the browser would not load it. This prevents the browser to load a malicious copy of the CSS/JS file.

Major CDN like cdns and JsDelivr have both provided an API for querying metadata like filename, SRI/hashes, file modified timestamp and etc. Users an query the API, the server would return a JSON file. Users can construct the corresponding HTML code blocks.

For cdnjs, the documentation for API is in here. If we query the cdnjs API for Bootstrap, it would return something like below. (I trimmed the result for brevity)

1
2
3
4
5
6
7
8
{
  "sri": {
    "css/bootstrap.css": "sha512-YfFXNd2o6swxA1M0ll6EDdnVdYdE6iz+C6k0Guqf18JW6sVq6Oz9lfbjOso+LMwwNYNxUbp7egkYmC2W/IyeVA==",
    "css/bootstrap.min.css": "sha512-6KY5s6UI5J7SVYuZB4S/CZMyPylqyyNZco376NM2Z8Sb8OxEdp02e1jkKk/wZxIEmjQ6DRCEBhni+gpr9c4tvA==",
    "js/bootstrap.bundle.js": "sha512-sN4+2u8s/0hj3R2fFUJoXVpHip6cXX3/d+K2qy0pSu4+Ke2SU8z4wzW1D7uKTYw1EFDFInMfgZbu33BfS+9xmQ==",
    "js/bootstrap.bundle.min.js": "sha512-trzlduO3EdG7Q0xK4+A+rPbcolVt37ftugUSOvrHhLR5Yw5rsfWXcpB3CPuEBcRBCHpc+j18xtQ6YrtVPKCdsg==",
  }
}

Using a Pelican plugin

For other SSG frameworks, it may already have plugins to perform similar thing. But I cannot find a similar plugin in Pelican, so I write a small plugin in here. The Pelican plugin make use of global settings or global parameters so that the article/pages/templates could reference the JINJA variables. I am not sure if this is the best method. If it relies on Pelican metadata, it would only apply the metadata to articles and pages, but not direct templates. The GitHub repository provides instruction on how to setup and use the plugin.

Parameters to be added for using this plugin

The parameters are:

  • CDN_SRI

  • CDN_SRI_OVERWRITE_INITIAL_CACHE

  • CDN_SRI_UPDATE_INITIAL_CACHE

  • CDN_SRI_CACHE_FILENAME

  • CDN_SRI, example:

CDN_SRI = {
    'bootstrap': {
        'cdn_type': 'cdnjs',
        'version': '5.1.1',
        'css': 'css/bootstrap.min.css',
        'js': 'js/bootstrap.min.js',
    },
    'docs-searchbar.js': {
        'cdn_type': 'jsdelivr',
        'version': '1.3.2',
        'css': 'dist/cdn/docs-searchbar.min.css',
        'js': 'dist/cdn/docs-searchbar.min.js',
    },
}

For CSS/JS, You need to specify the URL in the CDN side, it is different for each web applications.

  • This is the location of the cache file:

    • CDN_SRI_CACHE_FILENAME = './cdn-sri-cache/cache.json'

    • You need to create a directory called 'cdn-sri-cache' in the same directory as your Pelican directory

    • For first time use, you should set 'CDN_SRI_OVERWRITE_INITIAL_CACHE' to true or create an empty cache.json file (content as {}). Not a blank line.

  • You need to set overwrite/update the initial cache to True. If there are any changes in the version of the web applications

    • CDN_SRI_OVERWRITE_INITIAL_CACHE = False

    • CDN_SRI_UPDATE_INITIAL_CACHE = True

Another method by using script

I also developed a script in here. This script does same thing but use a different approach. Instead of using Pelican generators/plugin framework, this script is designed to run before using Pelican to generate static pages. This script reads an INI file and query the CDN, then it generates a Pelican parameter file. Users then import the parameter file in their Pelican config. Then the CDN_SRI parameters could be used. It use a different approach and the output of the script could be used in other SSG or applications. The GitHub repository provides instruction on how to setup and use the script.

Changes need in templates

Either using the plugin or using the script would need changes in the templates. If the setup is done properly, the user should add the variables in the JINJA templates. Here is some examples:

The format is {{ APPLICATION_NAME_CSS }} or {{ APPLICATION_NAME_JS }}. Note you need to replace '-' and '.' with '_' for the application name. This is because parameters or variables in Pelican is in upper case and should not support dashes or dots.

  • Use {{ BOOTSTRAP_CSS }} to generate a code block for Bootstrap css. The generated code block is:

1
2
3
4
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.1/css/bootstrap.min.css"
integrity="sha512-6KY5s6UI5J7SVYuZB4S/CZMyPylqyyNZco376NM2Z8Sb8OxEdp02e1jkKk/wZxIEmjQ6DRCEBhni+gpr9c4tvA=="
crossorigin="anonymous" referrerpolicy="no-referrer">

The format for using the docs-searchbar.js (JS) in the above example would be: {{ DOCS_SEARCHBAR_JS_JS }}. The first '_JS' is there because the application name is called docs-searchbar.js. he last '_JS' is specifying that we want to use the JavaScript code block. If you want to use the CSS of the docs-searchbar.js, then it would be: {{ DOCS_SEARCHBAR_JS_CSS }}.

If there are changes in the version of the CSS/JS, just the the pelicanconf.py or config.ini. If the plugin is used, then the option 'CDN_SRI_UPDATE_INITIAL_CACHE' should also be set to True, so that the updated version would be reflected in the plugin.

Conclusion

We briefly discussed about using a Pelican to generate the code blocks for using CSS and JS resources in two major CDN. By using the plugin or the script, it would save time for editing the source template manually.


Share this article on:

Comments