Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(docker): set standardized headers in NGINX #729

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ the detailed section referring to by linking pull requests or issues.

#### Minor

- Set security headers in NGINX Docker image

#### Patch

#### Deployment Migration Notes
Expand Down
11 changes: 10 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,21 @@
"with": "src/environments/environment.prod.ts"
}
],
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"outputHashing": "all",
"sourceMap": {
"hidden": false,
"scripts": true,
"styles": true
}
},
"subresourceIntegrity": true
},
"development": {
"buildOptimizer": false,
Expand Down
9 changes: 9 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ COPY --from=npm-install /app/node_modules /app/node_modules
COPY ./ /app/
RUN npm run ng build --no-progress --configuration=production

RUN ./docker/generate-csp-hash.mjs dist/edc-demo-client/*.js > script-src.txt

# Stage 3: Serve app with nginx
FROM docker.io/nginxinc/nginx-unprivileged:1.25-alpine3.18

Expand All @@ -28,6 +30,13 @@ COPY docker/default.conf.template etc/nginx/templates/default.conf.template
# so that the automatic envsubst templating is not disabled.
COPY docker/99-generate-app-config.sh /docker-entrypoint.d/99-generate-app-config.sh

# Mount the template from the build context and the hash list from the previous stage
# instead of copying them, as they are not needed in the final image.
RUN --mount=type=bind,from=build,source=/app/script-src.txt,target=/tmp/script-src.txt \
--mount=type=bind,source=/docker/headers.include.template,target=/tmp/headers.include.template \
env SCRIPT_SRC_EXTRA="$(cat /tmp/script-src.txt)" \
envsubst '$$SCRIPT_SRC_EXTRA' < /tmp/headers.include.template > /etc/nginx/headers.include

RUN ln -sf /tmp/app-config.json /usr/share/nginx/html/assets/config/app-config.json \
# Nginx is configured to reject symlinks that point to a file owned by a different user, for security reasons
&& chown --no-dereference nginx:root /usr/share/nginx/html/assets/config/app-config.json
Expand Down
6 changes: 5 additions & 1 deletion docker/default.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ server {
root /usr/share/nginx/html;

location / {
add_header Cache-Control "public, immutable, max-age=604800";
include headers.include;

index do-not-use-me.html;
try_files $uri @index;
}

location @index {
add_header Cache-Control no-cache;
add_header Cache-Control "no-cache";
include headers.include;
expires 0;
index index.html;
try_files /index.html =404;
Expand Down
17 changes: 17 additions & 0 deletions docker/generate-csp-hash.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node
import {createHash} from 'node:crypto';
import {readFile} from 'node:fs/promises';

async function hashFile(file) {
const input = await readFile(file);
// Angular uses sha384 for CSP hashes
const hash = createHash('sha384').update(input).digest('base64');

return `sha384-${hash}`;
}

const files = process.argv.slice(2);
const hashes = await Promise.all(files.map(hashFile));

// CSP hashes must be surrounded by single quotes
console.log(hashes.map((s) => `'${s}'`).join(' '));
47 changes: 47 additions & 0 deletions docker/headers.include.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Documentation of individual directives:
# - default-src: fallback if a more specific directive is not given; 'self' allows resources from the same origin
# - base-uri: restricts the URLs that can be used in a document's <base> element; 'self' allows resources from the same origin
# - script-src: controls <script> tags and inline event handlers
# - 'strict-dynamic' allows propagating trust from a script protected by a hash or nonce to further scripts it loads
# - ${SCRIPT_SRC_EXTRA} should contain the hashes of all scripts present in the initial HTML
# - style-src: controls <style> tags and inline styles
# - 'unsafe-inline' is required for Angular
# - img-src: controls <img> tags
# - https: allows images from any HTTPS source; required to correctly render embedded images in Markdown
# - frame-src: controls <frame> and <iframe> tags; 'none' disallows them
# - object-src: controls legacy <object> and <embed> tags
# - worker-src: controls web workers and service workers
# - form-action: controls <form> tags; disallow all native form submissions, as this is a single-page application
# - frame-ancestors: controls the ancestors of a document that can embed it in an iframe; 'none' disallows embedding
# - require-trusted-types-for and trusted-types: enforces explicit sanitization when assigning to innerHTML and the like
# - upgrade-insecure-requests: forces the browser to use HTTPS for all URL fetches, even if specified as plain HTTP
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; script-src 'self' 'strict-dynamic' ${SCRIPT_SRC_EXTRA}; style-src 'self' 'unsafe-inline'; img-src 'self' https:; frame-src 'none'; object-src 'none'; worker-src 'none'; form-action 'none'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types angular angular#bundler; upgrade-insecure-requests;" always;

# Prevent loading of cross-origin resources unless explicitly permitted by the target origin
# Non-CORS requests are permitted, but have their credentials, e.g., cookies, stripped
# Less strict than require-corp, but allows loading images from servers that do not support CORP, which may happen with Markdown
add_header Cross-Origin-Embedder-Policy "credentialless" always;

# Ensure browsing context isolation from new windows/tabs on different origins created by this origin
add_header Cross-Origin-Opener-Policy "same-origin" always;

# Ensure other origins cannot access resources on this origin
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Ensure browsing context isolation from different origins on the same site
add_header Origin-Agent-Cluster "?1" always;

# Disable Referer [sic] header entirely
add_header Referrer-Policy "no-referrer" always;

# Disable MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# Prevent downloads being interpreted as HTML by legacy browsers
add_header X-Download-Options "noopen" always;

# Prevent embedding in iframes; legacy counterpart to CSP frame-ancestors directive
add_header X-Frame-Options "DENY" always;

# Disable XSS "protection" that only makes things worse
add_header X-XSS-Protection "0" always;
Loading
Loading