Practical Introduction to Web-Security with Angular and Nginx (Part 1)

Philip Lysenko
8 min readAug 5, 2018

In this tutorial you will learn how to make your website pass a security audit with MDN’s Observatory by setting a few configuration options in Nginx and Angular. Along the way you will learn how to setup Angular in a Docker container and run it on Nginx. For this tutorial no framework- or web-server-specific knowledge is required; we will start from scratch!

Before setting up a testable environment, let’s take a look at Observatory. If your website is already deployed on the Internet, you can just go to https://observatory.mozilla.org/ and make a security audit for your domain. Here is an example Observatory audit for medium.com:

If you are not in production yet, don’t worry — I will show you how to use the offline version of the Observatory, which you can even integrate in a CI-pipeline.

Prerequisite: Running Angular from Docker

Starting from scratch, we need to install the Angular-Cli globally via NPM and create a new Angular-project (I give it the name angular-nginx). Execute this in the terminal:

npm install -g @angular/cli
ng new angular-nginx

Now you can could simply build the project with ng build —-prod and put the output in Nginx’s html-directory from where it would be served. But to make things a bit more realistic, I want to do all of this in a dockerized container. For this you need to create three files in your Angular project’s root folder:

Dockerfile

# Stage 0, for downloading project’s npm dependencies, building and compiling the app.
# Angular 6 requires node:8.9+
FROM node:10 as node
WORKDIR /app
COPY package.json /app/
RUN npm install
COPY ./ /app/
RUN npm run build --prod
# Stage 1, for copying the compiled app from the previous step and making it ready for production with NginxFROM nginx:alpine
COPY --from=node /app/dist/angular-nginx /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

.dockerignore

node_modules

nginx.conf

#user  nginx;
worker_processes 1;

#error_log logs/error.log;
#pid /run/nginx.pid;

events {
worker_connections 1024;
}

http {
# Compression
gzip on;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain text/css
application/x-javascript text/xml
application/xml application/xml+rss
text/javascript;
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
}
}

You can read what these files do in this tutorial if you want to dig in further:

For now it is enough to know that the server {...} -block is the configuration for one server and Nginx can configure multiple servers by surrounding server {...} -blocks inside anhttp {...} -block. The single server in our nginx.conf is only serving a static index.html for now.

Now you can build your container. If you didn’t do it already, install Docker and start the daemon. Then execute this in the terminal:

docker build -t angular-nginx .

The -t parameter assigns the docker image a tag and the . at the end specifies a location of the Dockerfile. Now run the built image:

docker run -p 80:80 -it angular-nginx

The -p parameter sets a port-mapping and -it makes the running container interactive, so you can jump inside from your console for debugging. Note that you can exit an interactive terminal session with Control+C. The last parameter in the command is the name of the image which we have built before. Now if you open your browser on http://localhost:80 you should see this default Angular-page, served from NGINX in Docker:

Prerequisite: Running the audit locally

Unfortunately you cannot run the Observatory Web-audit on a website hosted on your private network. You can, however, download the Observatory-CLI and run that on your localhost-server with Python 3:

git clone https://github.com/mozilla/http-observatory.git
cd http-observatory
pip3 install --upgrade .
pip3 install --upgrade -r requirements.txt

If you run into an error with some PostgreSQL-package like me, check out this thread on stack overflow. For me it helped to install that one package manually. After installing everything you can run the check:

httpobs-local-scan --http-port 80 --no-verify localhost

It should print something like this in your console, telling you that these checks fail:

  • HTTPS (-redirection)
  • HPKP
  • HSTS
  • CSP
  • Referer Policy
  • SRI
  • x-content-type-options
  • x-frame-options
  • x-xss-protection

As you can see from their attribution to the overall FAIL score, some of them are considered more and some less critical. I will go through the checks one by one, starting with the HTTPS-related ones as they are the most important.

HTTPS

HTTPS is the basis of security on the Internet. It protects against one of the most common kind of attacks on the Internet, the Man In The Middle (MITM) attack. In MITM, an attacker places himself between two communicating parties (for example the user and a bank-server), thus gaining the ability to read and possibly alter the communication between them. With HTTPS the communication between those two parties is encrypted on the SSL/TLS layer of the OSGI model (e.g. below the application layer and thus hardware-optimised and fast). This way possible attackers sitting in between the communicating parties (like bad WiFi hosts) can only read encrypted messages which they cannot understand or alter, unless they break the encryption or gain access to cryptographic secrets.

You can read more about public-key cryptography on Wikipedia. For now you just need to know that each communication party needs a pair of private/public keys for establishing a secure connection. Normally a publicly trusted third party called certification authority would generate those keys. For our testing environment we will impersonate that certification authority and use self-signed certificates.

Warning: As you can see HTTPS is pretty important. The following configuration is intended for testing purposes only. In production you should use a service like Let’s Encrypt instead of self-signed certificates. And depending on your architecture you might even consider doing encryption and redirection on a load-balancer like F5 or some external service instead of the web-server. This way you would have only one entry-point to manage and keep secure.

You might have noticed, our Dockerfile uses Alpine Linux as the base-image. Alpine Linux has a primer on security and ships only with the basic packages needed for running a server in production. So for creating cryptographic keys ourselves (which you should not do in production), you need to install two additional packages. Add this at the end of your Dockerfile:

RUN apk update
RUN apk upgrade
RUN apk add bash
RUN apk add openssl
RUN /bin/bash -c "openssl req -x509 -out etc/ssl/localhost.crt -keyout etc/ssl/localhost.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf '[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth')"

Now Docker installs bash and openssl and generates the private localhost.key and the public localhost.crt (.crt for public certificate). If you want, you can check that everything worked correctly by building the image and running it with the -it parameter like I have shown you above; after navigating to /etc/ssl, you should see both key-files.

Now we want to create two web-servers: one for all the calls asking for not-secure and one for secure connections. For passing the Observatory audit, the server for non-secure connections should never serve the website and always redirect to the secure server using TLS. To create both servers, replace the one existing server {...}-block in the nginx.conf with these two:

server {
listen 80;

server_name localhost;

return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl;

ssl_certificate /etc/ssl/localhost.crt;
ssl_certificate_key /etc/ssl/localhost.key;

server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
}

Now we have one server listening on port :80 and responding with a redirect-status-code 301, telling the browser to try the same address with the https://-prefix. And we have the server which the unsecured one points to, listening on port :443. It is the default port for communication over SSL/TLS and https-calls automatically resolve to it. Now after rebuilding the docker-image you have to run it exposing the second port too:

docker run -p 80:80 -p 443:443 -it angular-nginx

When you call http://localhost:80 in the browser, you should be automatically redirected to https://localhost and prompted with a warning like this because you use self-signed certificates, which do not have a signature of a publicly-known and trusted certificate authority:

If you click on proceed you should connect to your web-server over TLS now, although your browser will still probably mark the website as insecure in the address bar.

If you run the audit now, it should not complain about missing HTTPS and redirection anymore.

Bonus: Hiding Server Version

If you inspect the audit in detail, you might notice this line:

“Server”: “nginx/1.13.12”,

As you can see, this simple scan that anyone could execute on a public website shows the server’s internals (lib and version number). With this information attackers can simply look up vulnerabilities of that specific configuration. So while this is not affecting the Observatory’s score, one thing you definitely should set in your nginx.conf for production is this (add it to the http-block):

server_tokens off;

This prevents Nginx from submitting tokens with version information. After running the audit again, the Server-field will only show that you are running nginx and attackers would need to do a more in depth search to find clues what version you are using.

Off-topic: You might have heard the phrase “no security by obscurity”, which means hiding a broken lock is not as good as fixing the lock. But while obscurity shouldn’t replace fixing, it is valid for obscurity to be an addition to a functioning system.

Would you use it to lock your stuff?

End of Part 1

That’s it for the first part! Here you can see all the project files as a repo: https://github.com/philly-vanilly/angular-nginx/tree/fbf04e9acdc60da57d4d24d9b748bea091a123e7

Coming up next…

In the second part of this tutorial we will strengthen our HTTPS settings and in the third part we will take a look at Cross-Site-Scripting attacks and their countermeasures. Don’t miss it!

--

--