Deploying Node.js: Creating FrontNet for the Notes application

We have the back half of our system set up in Docker containers, as well as the private bridge network to connect the backend containers. It’s now time to do the same for the front half of the system: the Notes application (svc-notes) and its associated database (db-notes). Fortunately, the tasks required to build FrontNet are more or less the same as what we did for AuthNet.

The first task is to set up another private bridge network, frontnet. Like authnet, this will be the infrastructure for the front half of the Notes application stack.

Create a directory, frontnet, and in that directory, create a package.json file that will contain the scripts to manage frontnet:

{

“name”: “frontnet”,

“version”: “1.0.0”,

“description”: “Scripts to define and manage FrontNet”, “scripts”: {

“build-frontnet”: “docker network create –driver bridge frontnet”,

},

“license”: “ISC”

}

As with authnet, this is just the starting point as we have several more scripts to add.

Let’s go ahead and create the frontnet bridge network:

$ npm run build-frontnet

$ docker network ls

NETWORK ID   NAME     DRIVER SCOPE

3021e2069278 authnet bridge local

f3df227d4bff frontnet bridge local

 

We have two virtual bridge networks. Over the next few sections, we’ll set up the database and Notes application containers, connect them to frontnet, and then see how to manage everything.

1. MySQL container for the Notes application

As with authnet, the task is to construct a MySQL server container using the mysql/mysql-server image. We must configure the server to be compatible with the SEQUELIZE_CONNECT file that we’ll use in the svc-notes container. For that purpose, we’ll use a database named notes and a notes user ID.

For that purpose, add the following to the scripts section of the package.json file:

“prebuild-db-notes”: “mkdir notes-data”,

“build-db-notes”: “docker run –detach –name db-notes –env

MYSQL_USER=notes –env MYSQL_PASSWORD=notes12345 –env

MYSQL_DATABASE=notes –mount type=bind,src=’pwd’/notes-

data,dst=/var/lib/mysql –network frontnet –env

MYSQL_ROOT_PASSWORD=w0rdw0rd mysql/mysql-server:8.0 —

bind_address=0.0.0.0 –socket=/tmp/mysql.sock”,

“stop-db-notes”: “docker stop db-notes”,

“start-db-notes”: “docker start db-notes”,

This is largely the same as for db-userauth, with the word notes substituted for userauth. Remember that on Windows the –mount option requires a Windows- style absolute pathname.

Let’s now run the script:

$ npm run build-db-notes

> frontnet@1.0.0 prebuild-db-notes /home/david/Chapter10/frontnet

> mkdir -p notes-data

> frontnet@1.0.0 build-db-notes /home/david/Chapter10/frontnet

> docker run –detach –name db-notes –env MYSQL_USER=notes –env MYSQL_PASSWORD=notes12345 –env MYSQL_DATABASE=notes –mount type=bind,src=`pwd`/notes-data,dst=/var/lib/mysql –network frontnet – p 3306:3306 –env MYSQL_ROOT_PASSWORD=w0rdw0rd mysql/mysql-server:8.0

–bind_address=0.0.0.0 af60afab6994095fcbc11c86159bdb0b02924d3ad8bf08506f4c16171959bc2b

This database will be available in the db-notes domain name on frontnet. Because it’s attached to frontnet, it won’t be reachable by containers connected to authnet. To verify this, run the following command:

$ docker exec -it svc-userauth bash

root@ba75699519ef:/userauth# ping db-notes

ping: db-notes: Name or service not known

root@ba75699519ef:/userauth# ping db-userauth

PING db-userauth (172.20.0.3) 56(84) bytes of data.

64 bytes from db-userauth.authnet (172.20.0.3): icmp_seq=1 ttl=64 time=10.5 ms

root@ba75699519ef:/userauth# ping db-notes.frontnet

ping: db-notes.frontnet: Name or service not known 

Since db-notes is on a different network segment, we’ve achieved separation. But we can notice something interesting. The ping command tells us that the full domain name for db-userauth is db-userauth.authnet. Therefore, it stands to reason that db-notes is also known as db-notes.frontnet. But either way, we cannot reach containers on frontnet from a container on authnet, and so we have achieved the desired separation.

We’re able to move more quickly to construct FrontNet because it’s so much like AuthNet. We just have to do what we did before and tweak the names.

In this section, we created a database container. In the next section, we will create the Dockerfile for the Notes application.

2. Dockerizing the Notes application

Our next step is, of course, to Dockerize the Notes application. This starts by creating a Dockerfile, and then adding another Sequelize configuration file, before finishing up by adding more scripts to the frontnet/package.json file.

In the notes directory, create a file named Dockerfile containing the following:

FROM node:14

RUN apt-get update -y \

&& apt-get -y install curl python build-essential git ca

-certificates

ENV DEBUG=”notes:*,messages:*”

ENV SEQUELIZE_CONNECT=”models/sequelize-docker-mysql.yaml”

ENV NOTES_MODEL=”sequelize”

ENV USER_SERVICE_URL=”http://svc-userauth:5858″

ENV PORT=”3000″

RUN mkdir -p /notesapp /notesapp/minty /notesapp/partials

/notesapp/public /notesapp/routes /notesapp/theme /notesapp/theme/dist

/notesapp/views

COPY minty/ /notesapp/minty/

COPY models/*.mjs models/*.yaml /notesapp/models/

COPY partials/ /notesapp/partials/

COPY public/ /notesapp/public/

COPY routes/ /notesapp/routes/

COPY theme/dist/ /notesapp/theme/dist/

COPY views/ /notesapp/views/

COPY *.mjs package.json /notesapp/ WORKDIR /notesapp

RUN npm install –unsafe-perm

VOLUME /sessions EXPOSE 3000

CMD [ “node”, “./app.mjs” ]

This is similar to the Dockerfile we used for the authentication service. We’re using the environment variables from notes/package.json, plus a new one: NOTES_SESSION_DIR.

The most obvious change is the number of COPY commands. The Notes application is a lot more involved, given the number of sub-directories full of files that must be installed. We start by creating the top-level directories of the Notes application deployment tree. Then, one by one, we copy each sub-directory into its corresponding sub-directory in the container filesystem.

In a COPY command, the trailing slash on the destination directory is important. Why? Because the Docker documentation says that the trailing slash is important, that’s why.

The big question is why use multiple COPY commands like this? This would have been incredibly simple:

COPY . /notesapp 

However, the multiple COPY commands let us control exactly what’s copied. It’s most important to avoid copying the node_modules directory into the container. Not only is the node_modules file on the host large, which would bloat the container if copied, but it is set up for the host OS and not the container OS. The node_modules directory must be built inside the container, with the installation happening on the container’s OS. That constraint led to the choice to explicitly copy specific files to the destination.

We also have a new SEQUELIZE_CONNECT file. Create models/sequelize-docker- mysql.yaml containing the following:

dbname: notes

username: notes

password: notes12345

params:

host: db-notes

port: 3306

dialect: mysql

This will access a database server on the db-notes domain name using the named database, username, and password.

Notice that the USER_SERVICE_URL variable no longer accesses the authentication service at localhost, but at svc-userauth. The svc-userauth domain name is currently only advertised by the DNS server on AuthNet, but the Notes service is on FrontNet. Therefore, this will cause a failure for us when we get to running the Notes application, and we’ll have to make some connections so that the svc-userauth container can be accessed from svc-notes.

In Chapter 8, Authenticating Users with a Microservice, we discussed the need to protect the API keys supplied by Twitter. We could copy the .env file to the Dockerfile, but this may not be the best choice, and so we’ve left it out of the Dockerfile.

The value of TWITTER_CALLBACK_HOST needs to reflect where Notes is deployed. Right now, it is still on your laptop, but if it is deployed to a server, this variable will require the IP address or domain name of the server.

In notes/package.json, add the following scripts entry:

“scripts”: {

“docker-build”: “docker build -t svc-notes .”

}

As with the authentication server, this lets us build the container image for the Notes application service.

Then, in frontnet/package.json, add these scripts:

“build-notes”: “cd ../notes && npm run docker-build”,

“postbuild-notes”: “docker run –detach –name svc-notes –network

frontnet -p 80:3000 svc-notes”,

“start-notes”: “docker start svc-notes”, “stop-notes”: “docker stop svc-notes”,

“start-notes-service”: “npm run start-db-notes && npm run start

-notes”,

“stop-notes-service”: “npm run stop-db-notes && npm run stop-notes”

Now, we can build the container image:

$ npm run build-notes

> frontnet@1.0.0 build-notes /home/david/Chapter10/frontnet

> cd ../notes && npm run docker-build 

> notes@0.0.0 docker-build /home/david/Chapter10/notes

> docker build -t svc-notes . 

Sending build context to Docker daemon 223.9MB Step 1/22 : FROM node:13.8 

—> 07e774543bdf

> frontnet@1.0.0 postbuild-notes /home/david/Chapter10/frontnet

> docker run –detach –name svc-notes –network frontnet -p 80:3000 svc-notes

01bffcf4818aedeb082760c9d39087c08f2ba167d601413f1a745fdf305cdc3d 

This creates the container image and then launches the container.

Notice that the exposed port 3000 is mapped with -p 80:3000 onto the normal HTTP port. Since we’re getting ready for deployment on a real service, we can stop using port 3000.

At this point, we can connect our browser to http://localhost and start using the Notes application. However, we’ll quickly run into a problem:

The user experience team is going to scream about this ugly error message, so put it on your backlog to generate a prettier error screen. For example, a flock of birds pulling a whale out of the ocean is popular.

This error means that Notes cannot access anything at the host named svc- userauth. That host does exist because the container is running, but it’s not on frontnet, and is not reachable from the notes container. Instead, it is on authnet, which is currently not reachable by svc-notes:

$ docker exec -it svc-notes bash

root@9318fa2ecbb6:/notesapp# ping svc-userauth

ping: svc-userauth: Name or service not known

root@9318fa2ecbb6:/notesapp# ping svc-userauth.authnet

ping: svc-userauth.authnet: Name or service not known 

root@9318fa2ecbb6:/notesapp# ping db-notes

PING db-notes (172.21.0.2) 56(84) bytes of data.

64 bytes from db-notes.frontnet (172.21.0.2): icmp_seq=1 ttl=64 time=1.33 ms 

We can reach db-notes from svc-notes but not svc-userauth. This is as expected since we have attached these containers to different networks.

If you inspect FrontNet and AuthNet, you’ll see that the containers attached to each do not overlap:

$ docker network inspect frontnet

$ docker network inspect authnet 

In the architecture diagram presented in Chapter 10, Deploying Node.js Applications to Linux Servers, we showed a connection between the svc-notes and svc- userauth containers. This connection is required so that Notes can authenticate its users. But that connection does not yet exist.

Docker requires you to take a second step to attach the container to a second network:

$ docker network connect authnet svc-notes 

With no other change, the Notes application will now allow you to log in and start adding and editing notes. Furthermore, start a shell in svc-notes and you’ll be able to ping both svc-userauth and db-userauth.

There is a glaring architecture question staring at us. Do we connect the svc- userauth service to frontnet, or do we connect the svc-notes service to authnet? We just connected svc-notes to authnet, but maybe that’s not the best choice. To verify which network setup solves the problem, run the following commands:

$ docker network disconnect authnet svc-notes

$ docker network connect frontnet svc-userauth 

The first time around, we connected svc-notes to authnet, then we disconnected it from authnet, and then connected svc-userauth to frontnet. That means we tried both combinations and, as expected, in both cases, svc-notes and svc- userauth were able to communicate.

This is a question for security experts since the consideration is the attack vectors available to any intruders. Suppose Notes has a security hole allowing an invader to gain access. How do we limit what is reachable via that hole?

The primary observation is that by connecting svc-notes to authnet, svc- notes not only has access to svc-userauth but also to db-userauth. To see this, run these commands:

$ docker network disconnect frontnet svc-userauth

$ docker network connect authnet svc-notes

$ docker exec -it svc-notes bash

root@9318fa2ecbb6:/notesapp#

root@9318fa2ecbb6:/notesapp# ping svc-userauth

PING svc-userauth (172.20.0.2) 56(84) bytes of data.

64 bytes from svc-userauth.authnet (172.20.0.2): icmp_seq=1 ttl=64 time=0.151 ms

root@9318fa2ecbb6:/notesapp# ping db-userauth

PING db-userauth (172.20.0.3) 56(84) bytes of data.

64 bytes from db-userauth.authnet (172.20.0.3): icmp_seq=1 ttl=64 time=0.379 ms 

This sequence reconnects svc-notes to authnet and demonstrates the ability to access both the svc-userauth and db-userauth containers. Therefore, a successful invader could access the db-userauth database, a result we wanted to prevent. Our diagram in Chapter 10, Deploying Node.js Applications to Linux Servers, showed no such connection between svc-notes and db-userauth.

Given that our goal for using Docker was to limit the attack vectors, we have a clear distinction between the two container/network connection setups. Attaching svc- userauth to frontnet limits the number of containers that can access db- userauth. For an intruder to access the user information database, they must first break into svc-notes, and then break into svc-userauth; unless, that is, our amateur attempt at a security audit is flawed.

For this and a number of other reasons, we arrive at this final set of scripts for frontnet/package.json:

“build-frontnet”: “docker network create –driver bridge frontnet”,

“connect-userauth”: “docker network connect frontnet svc-userauth”,

“prebuild-db-notes”: “mkdir -p notes-data”,

“build-db-notes”: “docker run –detach –name db-notes –env

MYSQL_USER=notes –env MYSQL_PASSWORD=notes12345 –env

MYSQL_DATABASE=notes –mount type=bind,src=’pwd’/notes-

data,dst=/var/lib/mysql –network frontnet –env

MYSQL_ROOT_PASSWORD=w0rdw0rd mysql/mysql-server:8.0

–bind_address=0.0.0.0″,

“stop-db-notes”: “docker stop db-notes”,

“start-db-notes”: “docker start db-notes”,

“build-notes”: “cd ../notes && npm run docker-build”,

“postbuild-notes”: “npm run launch-notes”,

“launch-notes”: “docker run –detach –name svc-notes –network

frontnet -p 80:3000 node-web-development/notes”,

“start-notes”: “docker start svc-notes”, “stop-notes”: “docker stop svc-notes”,

“start-notes-service”: “npm run start-db-notes && npm run start-notes”,

“stop-notes-service”: “npm run stop-db-notes && npm run stop-notes”

Primarily, this adds a command, connect-userauth, to connect svc- userauth to frontnet. That helps us remember our decision on how to join the containers. We also took the opportunity to do a little reorganization.

We’ve learned a lot in this section about Docker—using Docker images, creating Docker containers from images, and configuring a group of Docker containers with some security constraints in mind. We came out of this section having implemented our initial architecture idea. We have two private networks with the containers connected to their appropriate network. The only exposed TCP port is the Notes application, visible on port 80. The other containers connect with one another using TCP/IP connections that are not available from outside the containers.

Before proceeding to the next section, you may want to shut down the services we’ve launched. Simply execute the following command:

$ cd frontnet

$ npm run stop-notes-service

$ cd ../authnet

$ npm run stop-user-service 

Because we’ve automated many things, it is this simple to administer the system. However, it is not as automated as we want it to be. To address that, let’s learn how to make the Notes stack more easily deployable by using Docker Compose to describe the infrastructure.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *