Generating CSP and HPKP headers in Ansible template

2016-12-18 00:00:00 +0000


Content Security Policy headers can grow very long and as such are error prone if edited manually. One way to resolve that is generating them using a template language from a clean YAML structure, for example using Ansible. The Ansible setup for CSP and HPKP headers is relatively simple, especially if you compare it with the nightmarish IPSec configuration generator I wrote (but which works in production nonetheless). The whole CSP configuration sits in group variable file in YAML format (group_vars/all). Note how special CSP origins (‘none’) are placed in double quotes to ensure proper rendering by Ansible, and how the single keyword option (upgrade-insecure-requests) is assigned an empty list to render as the target keyword only:

csp: "default-src": - "'none'" "img-src": - https://webcookies-20c4.kxcdn.com - https://*.facebook.com - https://*.twitter.com - https://pagead2.googlesyndication.com "script-src": - https://cdnjs.cloudflare.com - https://maxcdn.bootstrapcdn.com - https://pagead2.googlesyndication.com - https://connect.facebook.net - https://*.google.com - https://*.twitter.com - "'unsafe-inline'" "style-src": - https://maxcdn.bootstrapcdn.com - https://webcookies-20c4.kxcdn.com - "'unsafe-inline'" "font-src": - https://maxcdn.bootstrapcdn.com "frame-src": - https://*.google.com - https://*.facebook.com - https://*.twitter.com - https://*.doubleclick.net "upgrade-insecure-requests": -

Then the template to output the Nginx header is quite simple:

add_header Content-Security-Policy "";

Same story for HTTP Public Key Pins (HPKP) header. First, the YAML configuration in the same group_vars/all file:

pkp_hashes:

And the template (actually, the same file as above, just split for clarity):

add_header Public-Key-Pins '; max-age=3600';

The final output after Ansible task is run will be these machine-but-not-human-readable headers:

add_header Content-Security-Policy " script-src https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com https://pagead2.googlesyndication.com https://connect.facebook.net https://*.google.com https://*.twitter.com 'unsafe-inline'; img-src https://webcookies-20c4.kxcdn.com https://*.facebook.com https://*.twitter.com https://pagead2.googlesyndication.com; default-src 'none'; frame-src https://*.google.com https://*.facebook.com https://*.twitter.com https://*.doubleclick.net; style-src https://maxcdn.bootstrapcdn.com https://webcookies-20c4.kxcdn.com 'unsafe-inline'; upgrade-insecure-requests ; font-src https://maxcdn.bootstrapcdn.com;";

add_header Public-Key-Pins ‘pin-sha256=”G5Yh5Mo/24pSh64SB3fhj0L5FZpnp4xjEg/INNDt9t8=”; pin-sha256=”TRi1sP2dt38aFrLNgr+zmllBN3tlzm0B/Hb4JZxrGvk=”; pin-sha256=”YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=”; pin-sha256=”sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=”; pin-sha256=”C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=”; pin-sha256=”Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=”; ; max-age=3600’; </code>