aboutsummaryrefslogtreecommitdiff
path: root/content/articles/site-backend.md
blob: 5fb750b0678da285941819512f7835c561b0062e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
---
slug: "site-backend"
title: "Site Backend"

# date (optional for articles)
date: "2024-05-31T03:57:18-04:00"

# draft articles are not visible on live site
draft: true

# show on articles page
show: true

# uncomment to show table of contents
toc: true

pics:
  editing:
    css: "image"
    tip: "Editing this article with Vim and Firefox."
    sources:
      - "/files/articles/site-backend/0-editing-1024.webp"
      - src: "/files/articles/site-backend/0-editing-1024.png"
        width: 1024
        height: 640
  ssl-labs-report:
    css: "image"
    tip: "SSL Labs SSL Test results for this site."
    sources:
      - "/files/articles/site-backend/1-ssl-labs-20240530-1024.webp"
      - src: "/files/articles/site-backend/1-ssl-labs-20240530-1024.png"
        width: 1024
        height: 525
  securityheaders-report:
    css: "image"
    tip: "Security headers report for this site from securityheaders.com"
    sources:
      - "/files/articles/site-backend/2-securityheaders-1024.webp"
      - src: "/files/articles/site-backend/2-securityheaders-1024.png"
        width: 1024
        height: 320
---
## Overview

Site content is managed as [Markdown][] files stored in a [Git][]
repository.  The theme is a modified version of [Bulma][].  The site is
statically generated by [Hugo][].

[{{< pe-figure "editing" >}}][editing-this-article]

## History

From 1998 to 2019 this site was generated by a custom [PHP][] backend,
written by me.  In 2019 I resurrected the site after a several year
hiatus and migrated the site from the custom [PHP][] backend to
[Jekyll][].  In 2021 I switched from [Jekyll][] to [Hugo][].

## Goals

The goals of this site backend are:

- Small
- Fast
- Secure
- Mobile-friendly
- Accessible
- Simple (for me)

Static generation neatly addresses the first three goals:

- The site is small because the [Hugo][] can be configured to
  aggressively pack and minify content and assets.  Images are
  also compressed and served in multiple formats (see [Hugo
  Configuration](#hugo-configurationi "Hugo Configuration") and [Images](#images
  "Images")).
- The site is fast because web servers can serve static content
  extremely quickly, and because static content can be cached.  Caching
  and compression are enabled in [Apache][] (see [Apache
  Configuration](#apache-configuration "Apache Configuration")).
- The site is secure because the content is static; there is no
  web-accessible endpoint which can upload files or modify content.
  Additional security measures are discussed in several of the following
  sections.

Explanations of how I keep the site mobile-friendly and accessible are
available in the [HTML](#html "HTML") and [Hugo
Configuration](#hugo-configuration "Hugo Configuration") sections below.

**TODO:** discuss simplicity

## Content

### HTML

I create and edit site content with [Vim][] in [Markdown][] format and
preview the results locally with `hugo serve -D ...`.  Articles and blog
posts are saved as drafts and committed to the [Git][] repository so I
can work on them as time permits.

The content and layout is designed to be accessible and mobile-friendly:

- links and table components have `title` and `aria-label` attributes
- images have captions, fallback formats, and `title` and `alt` attributes
- images scale gracefully to thumbnails on mobile
- the site supports [dark mode][], checks the [color scheme
  preference][prefers-color-scheme] to determine the default theme,
  and has a [manual theme switcher][post-theme-switcher]
- The menu bar collapes to a hamburger menu on mobile.

### Images

Images are created as follows:

- Mathematical content is created using [Mathy][], saved as [SVG][],
  minified using [minify][], and served cached and compressed by [Apache][].
- Charts are created with [Matplotlib][], saved as [SVG][], minified
  with [minify][], and served cached and compressed by [Apache][]
  Example: [This Python script][bench-chart] was used to generate the
  bar charts in [this blog post][post-fips203ipd].
- Lossless raster images (e.g. screenshots) are scaled and cropped with
  [GraphicsMagick][] and served as a lossless [WebP][] with a [PNG][]
  fallback compressed by [pngquant][].
- Lossy raster images (e.g. pictures) are scaled and cropped with
  [GraphicsMagick][] and served as a lossy [WebP][] with a [JPEG][]
  fallback.

Other notes:

- The animated site logo is an [SVG][] generated by [this Ruby script][gen-logo.rb].
- Menubar icons are borrowed from [Bootstrap Icons][].
- I reviewed several [PNG][] compressors in [this post][post-png-compressors].

### JavaScript

This site has almost no [JavaScript][], by design.  The [JavaScript][]
that is used is minimal, [modern][es2015], [deferred][], and only used
for the [theme switcher][post-theme-switcher] and burger menu.

Below is the unminified `script.js` for this site with some notes
removed.  It is 777 bytes minified and 586 bytes minified and
compressed:

```js
'use strict';

//
// script.js - script which handles:
//
// - set theme
// - theme switcher and burger menu event handlers
//

const D = document,
      C = D.body.parentElement.classList,
      L = localStorage,
      M = window.matchMedia,
      on = (el, id, fn) => el.addEventListener(id, fn);

// use theme if set, otherwise fall back to browser preference
if (L && L.theme && L.theme === 'dark') {
  C.add('dark'); // theme set to "dark"
} else if ((!L || !L.theme) && M && M('(prefers-color-scheme: dark)').matches) {
  C.add('dark'); // prefers dark color scheme
}

document.addEventListener('DOMContentLoaded', () => {
  // theme toggle event handler
  on(D.querySelector('.navbar-item[data-id="theme"]'), 'click', (e) => {
    e.preventDefault(); // stop event
    L.theme = C.toggle('dark') ? 'dark' : 'light'; // toggle
  });

  // iterate through burgers, bind to click events
  D.querySelectorAll('.navbar-burger').forEach(e => on(e, 'click', () => {
    // then toggle is-active on burger and menu
    [e, D.getElementById(e.dataset.target)].forEach(
      e => e.classList.toggle('is-active')
    )
  }));
});
```
[Download][script.js]

## Deployment

Once I am ready to apply any outstanding changes to the public web site,
I push the outstanding commits to a remote [Git][] repository. This
triggers a [`post-receive` Git hook][post-receive], which sends an
[HMAC][]-authenticated `POST` request to a [web hook][] endpoint on the
web server.

The [web hook][] verifies the [HMAC][] and then runs [a deployment
script][deploy.rb] which does the following:

1. Verifies the authenticated timestamp (to prevent [replay attacks][]).
2. Clones the upstream repository.
3. Executes [Hugo][] (`hugo --minify -d ...`) to build the site in an
   isolated output directory.
4. Updates the `htdocs` symlink for the public-facing web site to point
   at the output directory from the previous step.
5. Removes any stale builds.

## Configuration

This section discusses the configuration for [Apache][], [Bulma][],
[Hugo][], and [Webhook][].

### Apache Configuration

This section disusses the [Apache][] configuration for this site.  I
have broken this section into three sub-sections to make them easier to
digest.

The [Apache][] configuration relies on the following modules:

- [mod\_deflate][mod-deflate] (see note below)
- [mod\_http2][mod-http2]
- [mod\_macro][mod-macro]
- [mod\_proxy][mod-proxy]
- [mod\_rewrite][mod-rewrite]

Note: It is safe for this site to enable [mod\_deflate][mod-deflate]
because it does not use [cookies][] and is not vulnerable to [BREACH][].
and does not use [cookies][].

#### Virtual Host Configuration

The [Apache][] virtual host configuration is modified as follows:

- Unconditionally redirect from [HTTP][] to [HTTPS][]
- Unconditionally redirect to strip the `www.` hostname prefix
- Enable [HTTP/2][]
- Set security headers (discussed in [Security Headers](#security-headers))
- Enable aggressive caching of image, script, and style assets

Below is the [Apache][] virtual host configuration for this site with
extraneous comments and configuration for logging, [TLS][], and legacy
redirects removed:

```apache
# unconditionally redirect to https://pablotron.org
<VirtualHost *:80>
  RewriteEngine On
  RewriteRule ^/(.*)$ https://pablotron.org/$1 [R,L]
</VirtualHost>

<VirtualHost *:443>
  # strip "www." prefix and enable mod_deflate
  Use STRIP_WWW https://pablotron.org
  Use MOD_DEFLATE

  # enable http2
  Protocols h2 http/1.1

  # set restrictive content security policy
  Header append "Content-Security-Policy" "default-src 'self'; img-src 'self' https://pmdn.org"

  # set remaining security headers
  Header append "Strict-Transport-Security" "max-age=31536000"
  Header append "X-Frame-Options" "SAMEORIGIN"
  Header append "X-Content-Type-Options" "nosniff"
  Header append "Cross-Origin-Opener-Policy" "same-origin"
  Header append "Cross-Origin-Resource-Policy" "same-origin"
  Header append "Access-Control-Allow-Origin" "https://pablotron.org"
  Header append "Referrer-Policy" "strict-origin-when-cross-origin"

  # set permissions policy
  Header append "Permissions-Policy" "camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), usb=()"

  # POST needed for /hooks
  Header append "Access-Control-Allow-Methods" "POST, GET, HEAD, OPTIONS"

  # cache images, stylesheets, and javascript for 1 year
  <FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|svg|js|json|css)$">
    Header set Cache-Control "max-age=31536000, public"
  </FilesMatch>

  # expose webhook
  <Location /hooks/>
    ProxyPass "http://localhost:9000/"
    ProxyPassReverse "http://localhost:9000/"
  </Location>
</VirtualHost>
```

[Download][apache-vhost.conf]

#### Security Headers

This site uses a strict [Content-Security-Policy][] header; it rejects
links to all external assets with one exception: references to images
hosted on <https://pmdn.org/> are allowed.

The trickiest part of restricting [Content-Security-Policy][] was
`style-src`; many content generation tools (including the [Markdown][]
table generator in [Hugo][]) break without `style-src 'unsafe-inline'`
(e.g., the ability to emit arbitrary `style` attributes).

In order to work around the `style-src` issues I ended up [reconfiguring
the Hugo syntax highlighter](#hugo-configuration) and writing [a custom
Hugo table shortcode][hugo-shortcode-table] which only emits `class`
attributes for inline styling.

The journey to a strict [Content-Security-Policy][] is documented in
this series of blog posts from October 2021:

- [Hugo/Content-Security-Policy Impedance Mismatch (October 19, 2021)][post-headers-1]
- [TLS and Header Fixes (October 21, 2021)][post-headers-2]
- [The Nuclear Option (No More unsafe-inline) (October 25, 2021)][post-headers-3]

The remaining security headers are explained in the following articles:

- [Security headers quick reference][security-headers-quick-ref]
- [Goodby Feature Policy and hello Permissions Policy!][permissions-policy]
- [Permissions Policy Explainer][]
- [Referrer-Policy][]

The security headers on this site earn an A+ rating from
[securityheaders.com][]:

[{{< pe-figure "securityheaders-report" >}}][securityheaders-report]

#### TLS Configuration

The certificate for this site is issued by [Let's Encrypt][] and managed
automatically by [certbot][] with the [certbot-dns-linode][] plugin.

The [Apache][] [TLS][] configuration is based on the intermediate
settings from the [Mozilla SSL Configuration Generator][ssl-config].  In
particular, only [TLS 1.2][] and [TLS 1.3][] are enabled, and the
insecure ciphers from [TLS 1.2][] have been disabled.

```apache
# explicit list of cipher suites (from ssl-config.mozilla.org)
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

# use server priorities for cipher algorithm choice
SSLHonorCipherOrder on

# protocols to enable (only TLS 1.2 and TLS 1.3)
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
```

[Download][apache-tls.conf]

This [TLS][] configuration earns an A+ result from the [SSL Labs SSL
Test][ssl-labs-ssl-test]:

[{{< pe-figure "ssl-labs-report" >}}][ssl-labs-report]

### Bulma Configuration

The site [CSS][] based on the [Bulma][] with the following
modifications:

1. All unused components removed
2. monokai style for [Chroma][] added
3. Styles for navbar icon highlighting and table captions added
4. dark mode styles added

Here is the [SASS][]:

```sass
// style.sass: based on bulma-0.9.3/sass/bulma.sass with the following
// changes:
//
// 1. all unused components removed
// 2. monokai style for chroma added
// 3. styles for navbar icon highlighting and table captions added
// 4. dark mode styles added
@charset "utf-8"

// import chroma style
//
// generated with the following command:
//
//   cd themes/hugo-pt2021/assets
//   hugo gen chromaclasses --style=monokai > chroma.css
//
@import "chroma"

@import "bulma-0.9.3/sass/utilities/_all"
@import "bulma-0.9.3/sass/base/_all"

// elements
@import "bulma-0.9.3/sass/elements/button"
@import "bulma-0.9.3/sass/elements/container"
@import "bulma-0.9.3/sass/elements/content"
@import "bulma-0.9.3/sass/elements/image"
@import "bulma-0.9.3/sass/elements/table"
@import "bulma-0.9.3/sass/elements/title"
@import "bulma-0.9.3/sass/elements/other"

// components
@import "bulma-0.9.3/sass/components/media"
@import "bulma-0.9.3/sass/components/navbar"

// grid (reenabled, used for images)
@import "bulma-0.9.3/sass/grid/_all"

// helpers
@import "bulma-0.9.3/sass/helpers/_all"

// layout
@import "bulma-0.9.3/sass/layout/section"
@import "bulma-0.9.3/sass/layout/footer"

// dim navbar icons by default
.navbar-item .menu-icon
  opacity: 60%

// highlight icons on hover
.navbar-item:hover .menu-icon
  opacity: 100%

// table captions below tables
table.table
  caption-side: bottom

// dark mode (2024-05-27)
@import "dark"
```
[Download][style.sass.txt]

With these changes the generated [CSS][] is 137 kB minified and 16 kB
minified and compressed.

### Hugo Configuration

[Hugo][] is configured to use [Chroma syntax higlighting][] with inline
styles disabled (e.g., `class` attributes only, no `style`) in order to
support the restrictive `Content-Security-Policy` header (see "HTTP
Headers").

The formatted tables in the pages site are generated via [my
`hugo-shortcode-table` shortcode][], because the native table generator
for [Hugo][] uses inline styles.

I have also written a couple of custom [shortcodes][] to generate
`<picture>` and `<figure>` elements in order to support [progressive
enhancement][].

The generated [HTML][] has been modified to:

**TODO:**

- add support `go-import`
- add a [Mastodon][] `<link rel='me' ...>` tag.
- remove all unnecessary tags
- to combine and [minify][] and [JavaScript][] and [CSS][] assets.
- to add `integrity` attributes to `<link>` and `<script>` tags.

### Webhook Configuration

[webhook][] configuration with [HMAC][] key removed:

```json
[{
  "id": "deploy-pablotron-org",
  "execute-command": "/data/www/pablotron.org/git/bin/hook/deploy.rb",

  "pass-arguments-to-command": [{
    "source": "payload",
    "name": "time"
  }],

  "pass-environment-to-command": [{
    "source": "string",
    "envname": "DEPLOY_HTDOCS_PATH",
    "name": "/data/www/pablotron.org/builds/current"
  }, {
    "source": "string",
    "envname": "DEPLOY_REPO_DIR",
    "name": "/data/www/pablotron.org/git"
  }, {
    "source": "string",
    "envname": "DEPLOY_BUILDS_DIR",
    "name": "/data/www/pablotron.org/builds"
  }],

  "trigger-rule": {
    "match": {
      "type": "payload-hmac-sha256",
      "secret": "(omitted)",
      "parameter": {
        "source": "header",
        "name": "X-Hub-Signature"
      }
    }
  }
}]
```

[Download][webhook.conf]

## Validation

I periodically verify the following manually:

- Developer console: Page load time, cached and uncached page size.
- [Lighthouse][]: Accessibility, desktop score, and mobile score.
- security headers
- tls configuration
- manual verification in the desktop and mobile versions of chrome and
  firefox

## Other

**TODO:**

- wireguard
- private ssh, private git
- (more stuff from `TODO.md`)

[hugo]: https://gohugo.io/
  "Hugo static site generator."
[bulma]: https://bulma.io/
  "Bulma minimal CSS framework."
[mathy]: https://mathy.pmdn.org/
  "Online LaTeX editor."
[svg]: https://en.wikipedia.org/wiki/SVG
  "Scalable Vector Graphics vector image format (SVG)"
[png]: https://en.wikipedia.org/wiki/PNG
  "Portable Network Graphics lossless raster image format (PNG)"
[webp]: https://en.wikipedia.org/wiki/WebP
  "WebP raster image format"
[vim]: https://www.vim.org/
  "vim text editor"
[markdown]: https://en.wikipedia.org/wiki/Markdown
  "Markdown lightweight markup language"
[matplotlib]: https://matplotlib.org/
  "Python charting library"
[pngquant]: https://pngquant.org/
  "Command-line tool for \"lossy\" compression of PNG images"
[graphicsmagick]: http://www.graphicsmagick.org/
  "Command-line swiss army knife tool for image processing"
[php]: https://www.php.net/
  "PHP programming language"
[bootstrap icons]: https://icons.getbootstrap.com/
  "Free high quality open source icon library"
[jekyll]: https://jekyllrb.com/
  "Jekyll static site generator"
[git]: https://git-scm.com/
  "Free and open source distributed version control"
[javascript]: https://en.wikipedia.org/wiki/JavaScript
  "JavaScript scripting language"
[minify]: https://github.com/tdewolff/minify
  "Minifier Go library and command-line tool"
[mastodon]: https://en.wikipedia.org/wiki/Mastodon_(social_network)
  "Mastodon distributed social network"
[css]: https://en.wikipedia.org/wiki/CSS
  "Cascading Style Sheets"
[editing-this-article]: /files/articles/site-backend/0-editing.png
  "Editing this article with Vim and Firefox"
[jpeg]: https://en.wikipedia.org/wiki/JPEG
  "Joint Photographic Experts Group (JPEG) lossy raster image format"
[es2015]: https://en.wikipedia.org/wiki/ECMAScript_version_history#6th_Edition_%E2%80%93_ECMAScript_2015
  "Modern JavaScript"
[post-theme-switcher]: {{< relref "posts/2024-05-27-site-updates-dark-mode-and-about-pics" >}}
  "Blog post about dark mode and theme switcher"
[prefers-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
  "prefers-color-scheme media query"
[dark mode]: https://en.wikipedia.org/wiki/Light-on-dark_color_scheme
  "Dark mode"
[webhook]: https://github.com/adnanh/webhook
  "Lightweight webhook server to run shell commands"
[webhook.conf]: /files/articles/site-backend/webhook.conf.txt
  "Download webhook.conf"
[hmac]: https://en.wikipedia.org/wiki/HMAC
  "Hash-based message authentication code (HMAC)"
[apache]: https://apache.org/
  "Apache web server"
[apache-vhost.conf]: /files/articles/site-backend/pablotron.org.conf.txt
  "Download Apache virtual host configuration"
[apache-tls.conf]: /files/articles/site-backend/tls.conf.txt
  "Download Apache TLS configuration"
[web hook]: https://en.wikipedia.org/wiki/Webhook
  "HTTP-accessible callback"
[deferred]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
  "defer attribute of script tag"
[replay attacks]: https://en.wikipedia.org/wiki/Replay_attack
  "Network attack where a valid data transmission is repeated or delayed."
[tls]: https://en.wikipedia.org/wiki/Transport_Layer_Security
  "Transport Layer Security"
[post-receive]: https://git-scm.com/docs/githooks#post-receive
  "Git hook triggered on a remote repository after a push operation"
[deploy.rb]: https://git.pablotron.org/sites/pablotron.org/plain/bin/hook/deploy.rb
  "Deployment script invoked as a web hook."
[gen-logo.rb]: https://git.pablotron.org/sites/pablotron.org/plain/bin/logo-0/gen-logo.rb
  "Ruby script to generate animated SVG logo."
[post-png-compressors]: {{< relref "posts/2023-06-02-end-of-may-miscellany" >}}
  "Blog post with a review of several PNG compressors"
[bench-chart]: https://github.com/pablotron/fips203ipd/tree/main/scripts/bench-chart
  "Scripts to build and run fips203ipd bench utility with several combinations of backend and compiler, generate a summary CSV, and render a bar chart as an SVG."
[post-fips203ipd]: {{< relref "posts/2024-05-15-c11-fips203ipd-v0.6.md" >}}
  "Blog post about release of fips203ipd v0.6."
[script.js]: /files/articles/site-backend/script.js.txt
  "Unminified JavaScript for this site with some comments removed."
[http]: https://en.wikipedia.org/wiki/HTTP
  "Hypertext Transfer Protocol (HTTP)"
[https]: https://en.wikipedia.org/wiki/HTTPS
  "Hypertext Transfer Protocol Secure (HTTPS)"
[http/2]: https://en.wikipedia.org/wiki/HTTP/2
  "Hypertext Transfer Protocol, version 2 (HTTP/2)"
[post-headers-1]: {{< relref "posts/2021-10-19-hugo-csp-impedance-mismatch.md" >}}
  "Hugo/Content-Security-Policy Impedance Mismatch - October 19, 2021"
[post-headers-2]: {{< relref "posts/2021-10-21-tls-and-header-fixes.md" >}}
  "TLS and Header Fixes - October 21, 2021"
[post-headers-3]: {{< relref "posts/2021-10-25-the-nuclear-option-no-more-unsafe-inline.md" >}}
  "The Nuclear Option (No More unsafe-inline) - October 25, 2021"
[mod-http2]: https://httpd.apache.org/docs/current/mod/mod_http2.html
  "Apache module which provides HTTP/2 support."
[mod-proxy]: https://httpd.apache.org/docs/current/mod/mod_proxy.html
  "Apache module which provides proxy and reverse proxy support."
[mod-rewrite]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html
  "Apache module which provides rules-based rewriting and redirecting engine."
[mod-macro]: https://httpd.apache.org/docs/current/mod/mod_macro.html
  "Apache module which provides macros within configuration files."
[mod-deflate]: https://httpd.apache.org/docs/current/mod/mod_deflate.html
  "Apache module which provides DEFLATE output compression."
[ssl-config]: https://ssl-config.mozilla.org/
  "Mozilla SSL Configuration Generator"
[tls 1.2]: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.2
  "TLS version 1.2"
[tls 1.3]: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.3
  "TLS version 1.3"
[ssl-labs-report]: https://www.ssllabs.com/ssltest/analyze.html?d=pablotron.org&hideResults=on
  "SSL Labs SSL Test results for this site."
[ssl-labs-ssl-test]: https://www.ssllabs.com/ssltest/
  "SSL Labs SSL Test"
[security-headers-quick-ref]: https://web.dev/articles/security-headers
  "Security headers quick ref"
[permissions-policy]: https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/
  "Goodbye Feature Policy and hello Permissions Policy!"
[permissions policy explainer]: https://github.com/w3c/webappsec-permissions-policy/blob/main/permissions-policy-explainer.md
  "Permissions Policy Explainer"
[referrer-policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
  "Documentation for \"Referrer-Policy: strict-origin-when-cross-origin\""
[content-security-policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
  "Content-Security-Policy header"
[hugo-shortcode-table]: https://github.com/pablotron/hugo-shortcode-table
  "Powerful Hugo table shortcode which emits HTML using CSS classes rather than inline style attributes."
[breach]: https://en.wikipedia.org/wiki/BREACH
  "Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext (BREACH)"
[cookies]: https://en.wikipedia.org/wiki/HTTP_cookie
  "Small blocks of data created by a web server when a user is browsing a web site."
[let's encrypt]: https://letsencrypt.org/
  "Free, automated, and open certificate authority (CA)"
[certbot]: https://certbot.eff.org/
  "Free, open source software tool for automatically using let's Encrypt certificates."
[certbot-dns-linode]: https://certbot-dns-linode.readthedocs.io/en/stable/
  "Certbot plugin which automates the process of completing an ACME dns-01 challenge by creating TXT records using the Linode API."
[securityheaders-report]: https://securityheaders.com/?q=pablotron.org&hide=on&followRedirects=on
  "Security headers report for this site from securityheaders.com"
[securityheaders.com]: https://securityheaders.com/
  "Free and easy to use tool to check the web site security headers."
[sass]: https://en.wikipedia.org/wiki/Sass_(style_sheet_language)
  "Syntactically Awesome Style Sheet (SASS)"
[style.sass.txt]: /files/articles/site-backend/style.sass.txt
  "Download SASS configuration"
[html]: https://en.wikipedia.org/wiki/HTML
  "HyperText Markup Language (HTML)"