Matteo Mattei

Hello, my name is Matteo Mattei and this is my personal website. I am computer engineer with a long experience in Linux system administration and web software development.

linkedin rss twitter google+ github facebook

Full web server setup with Debian 12 (Bookworm)

Setup bash and update the system

apt update
apt dist-upgrade

Configure hostname correctly

Make sure to have the following two lines (with the same format) at the top of your /etc/hosts file

127.0.0.1       localhost.localdomain localhost
xxx.xxx.xxx.xxx web1.myserver.com web1

Note: xxx.xxx.xxx.xxx is the public IP address assigned to your server.

Add you full hostname (for example web1.myserver.com) to /etc/hostname and apply it:

hostname -F /etc/hostname

Then to validate that the FQDN is valid and correctly applied type this:

hostname -f
// the answer should be web1.myserver.com

Install all needed packages

apt install wget vim git acl screen rsync net-tools pwgen mariadb-server mariadb-client nginx iptables shorewall php php-cli php-curl php-dev php-gd php-imagick php-imap php-memcache php-pspell php-tidy php-xmlrpc php-pear php-fpm php-mbstring php-mysql certbot phpmyadmin python3-pip python3-tld postfix ntp ca-certificates bsd-mailx tree

Postfix:

  • Select Internet Site
  • System mail name: (insert here the FQDN, for example web1.myserver.com)

Web server to reconfigure automatically:

  • Do not select anything and continue

Configure database for phpmyadmin with dbconfig-common?

  • Select Yes

MySQL application password for phpmyadmin:

  • Leave blank and press Ok

Setup Nginx

Stop Nginx web server:

/etc/init.d/nginx stop

Backup Nginx configuration:

cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup

Add the following lines in /etc/nginx/nginx.conf after tcp_nopush on;:

tcp_nodelay on;
keepalive_timeout 65;

And the following lines after gizp on;:

gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# max body size
client_max_body_size 10M;

Copy some nginx configuration files:

mkdir -p /etc/nginx/certs
ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/nginx/certs/server.crt
ln -s /etc/ssl/private/ssl-cert-snakeoil.key /etc/nginx/certs/server.key

mkdir -p /etc/nginx/global
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/codeigniter_production.conf > /etc/nginx/global/codeigniter_production.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/codeigniter_testing.conf > /etc/nginx/global/codeigniter_testing.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/common.conf > /etc/nginx/global/common.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/dokuwiki.conf > /etc/nginx/global/dokuwiki.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/phpmyadmin.conf > /etc/nginx/global/phpmyadmin.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/plainphp.conf > /etc/nginx/global/plainphp.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/ssl.conf > /etc/nginx/global/ssl.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/nginx/global/wordpress.conf > /etc/nginx/global/wordpress.conf

Restart Nginx:

/etc/init.d/nginx restart

Setup let’s encrypt

certbot register --agree-tos -m <your@email>

Setup MariaDB

Secure MariaDB installation:

mysql_secure_installation
  • Enter current password for root (enter for none): [ENTER]
  • Switch to unix_socket authentication [Y/n] Y
  • Change the root password? [Y/n] Y
  • New password: MARIAB_ROOT_PASSWORD
  • Re-enter new password: MARIAB_ROOT_PASSWORD
  • Remove anonymous users? [Y/n] Y
  • Disallow root login remotely? [Y/n] Y
  • Remove test database and access to it? [Y/n] Y
  • Reload privilege tables now? [Y/n] Y

Set MariaDB root password in a configuration file (the same password configured before!)

cat << EOF > /root/.my.cnf
[client]
user = root
password = MARIADB_ROOT_PASSWORD
EOF

Optional enable MySQL slow query logging (often useful during slow page load debugging):

sed -i "{s/^#slow_query_log_file /slow_query_log_file /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#long_query_time /long_query_time /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#log_slow_verbosity /log_slow_verbosity /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#log-queries-not-using-indexes/log-queries-not-using-indexes/g}" /etc/mysql/mariadb.conf.d/50-server.cnf

MySQL is now configured, so restart it:

/etc/init.d/mariadb restart

Configure Shorewall firewall rules

Copy the default configuration for one interface:

cd /usr/share/doc/shorewall/examples/one-interface
cp interfaces /etc/shorewall/
cp policy /etc/shorewall/
cp rules /etc/shorewall/
cp zones /etc/shorewall/

Now open /etc/shorewall/policy file and change the line:

net             all             DROP            info

removing info directive given it fills the system logs:

net             all             DROP

Now open /etc/shorewall/rules and add the following rules at the bottom of the file:

HTTP/ACCEPT     net             $FW
HTTPS/ACCEPT    net             $FW
SSH/ACCEPT      net             $FW

NOTE: in case you want to allow ICMP (Ping) traffic from a specific remote hosts you need to add a rule similar to the following where xxx.xxx.xxx.xxx is the remote IP address, before the Ping(DROP) rule:

Ping(ACCEPT)    net:xxx.xxx.xxx.xxx       $FW

Now edit /etc/default/shorewall and change startup=0 to startup=1 You are now ready to start the firewall:

/etc/init.d/shorewall start

Setup Postfix

Stop postfix server:

/etc/init.d/postfix stop

Edit /etc/mailname and set your server domain name, for example:

server1.mycompany.com

Restart Postfix:

/etc/init.d/postfix start

Select correct timezone

dpkg-reconfigure tzdata
# Select your timezone (for example Europe/Rome)

Log rotation

In order to correctly log files you need to adjust logrotate configuration for Nginx:

cat << EOF >> /etc/logrotate.d/nginx
/home/*/*/logs/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi \
        endscript
        postrotate
                invoke-rc.d nginx rotate >/dev/null 2>&1
        endscript
}
EOF

Install virtualhost manager

wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/lemp_manager.py > /usr/local/sbin/lemp_manager.py
chmod 770 /usr/local/sbin/lemp_manager.py

Download also the tools that will be used with cron:

mkdir /root/cron_scripts
cd /root/cron_scripts
wget https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/cron_scripts/backup_mysql.sh
wget https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian12/cron_scripts/mysql_optimize.sh
chmod 770 *.sh

Configure CRON

Edit /etc/crontab and add the following lines at the bottom:

# mysql optimize tables
3  4  *  *  7   root    /root/cron_scripts/mysql_optimize.sh

# mysql backup
32 4  *  *  *   root    /root/cron_scripts/backup_mysql.sh

How to use virtualhost manager

You can use the virtualhost manager for adding or removing virtualhosts:

# lemp_manager

Usage:
/usr/local/sbin/lemp_manager -a|--action=<action> [-d|--domain=<domain>] [-A|--alias=<alias>] [options]

Parameters:
	-a|--action=ACTION
		it is mandatory
	-d|--domain=domain.tld
		can be used only with [add_domain, remove_domain, add_alias, get_certs, get_info]
	-A|--alias=alias.domain.tld
		can be used only with [add_alias, remove_alias, get_info]

Actions:
	add_domain	Add a new domain
	add_alias	Add a new domain alias to an existent domain
	remove_domain	Remove an existent domain
	remove_alias	Remove an existent domain alias
	get_certs	Obtain SSL certificate and deploy it
	get_info	Get information of a domain or a domain alias (username)

Options:
	-f|--fakessl	Use self signed certificate (only usable with [add_domain, add_alias])

Full web server setup with Debian 11 (Bullseye)

Setup bash and update the system

apt-get update
apt-get dist-upgrade

Configure hostname correctly

Make sure to have the following two lines (with the same format) at the top of your /etc/hosts file

127.0.0.1       localhost.localdomain localhost
xxx.xxx.xxx.xxx web1.myserver.com web1

Note: xxx.xxx.xxx.xxx is the public IP address assigned to your server.

Install all needed packages

apt install wget vim git acl screen rsync net-tools pwgen mariadb-server mariadb-client nginx iptables shorewall php php-cli php-curl php-dev php-gd php-imagick php-imap php-memcache php-pspell php-tidy php-xmlrpc php-pear php-fpm php-mbstring php-mysql certbot phpmyadmin python3-pip postfix ntp ca-certificates bsd-mailx tree

Postfix:

  • Select Internet Site
  • System mail name: (insert here the FQDN, for example web1.myserver.com)

Web server to reconfigure automatically:

  • Do not select anything and continue

Configure database for phpmyadmin with dbconfig-common?

  • Select Yes

MySQL application password for phpmyadmin:

  • Leave blank and press Ok

Setup Nginx

Stop Nginx web server:

/etc/init.d/nginx stop

Backup Nginx configuration:

cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup

Add the following lines in /etc/nginx/nginx.conf after tcp_nopush on;:

tcp_nodelay on;
keepalive_timeout 65;

And the following lines after gizp on;:

gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# max body size
client_max_body_size 10M;

Copy some nginx configuration files:

mkdir -p /etc/nginx/certs
ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/nginx/certs/server.crt
ln -s /etc/ssl/private/ssl-cert-snakeoil.key /etc/nginx/certs/server.key

mkdir -p /etc/nginx/global
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/codeigniter_production.conf > /etc/nginx/global/codeigniter_production.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/codeigniter_testing.conf > /etc/nginx/global/codeigniter_testing.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/common.conf > /etc/nginx/global/common.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/dokuwiki.conf > /etc/nginx/global/dokuwiki.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/phpmyadmin.conf > /etc/nginx/global/phpmyadmin.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/plainphp.conf > /etc/nginx/global/plainphp.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/ssl.conf > /etc/nginx/global/ssl.conf
wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/nginx/global/wordpress.conf > /etc/nginx/global/wordpress.conf

Restart Nginx:

/etc/init.d/nginx restart

Setup let’s encrypt

pip3 install tld
certbot register --agree-tos -m <your@email>

Setup MariaDB

Secure MariaDB installation:

mysql_secure_installation
  • Enter current password for root (enter for none): [ENTER]
  • Switch to unix_socket authentication [Y/n] Y
  • Change the root password? [Y/n] Y
  • New password: MARIAB_ROOT_PASSWORD
  • Re-enter new password: MARIAB_ROOT_PASSWORD
  • Remove anonymous users? [Y/n] Y
  • Disallow root login remotely? [Y/n] Y
  • Remove test database and access to it? [Y/n] Y
  • Reload privilege tables now? [Y/n] Y

Set MariaDB root password in a configuration file (the same password configured before!)

cat << EOF > /root/.my.cnf
[client]
user = root
password = MARIADB_ROOT_PASSWORD
EOF

Optional enable MySQL slow query logging (often useful during slow page load debugging):

sed -i "{s/^#slow_query_log_file /slow_query_log_file /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#long_query_time /long_query_time /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#log_slow_verbosity /log_slow_verbosity /g}" /etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "{s/^#log-queries-not-using-indexes/log-queries-not-using-indexes/g}" /etc/mysql/mariadb.conf.d/50-server.cnf

MySQL is now configured, so restart it:

/etc/init.d/mariadb restart

Configure Shorewall firewall rules

Copy the default configuration for one interface:

cd /usr/share/doc/shorewall/examples/one-interface
cp interfaces /etc/shorewall/
cp policy /etc/shorewall/
cp rules /etc/shorewall/
cp zones /etc/shorewall/

Now open /etc/shorewall/policy file and change the line:

net             all             DROP            info

removing info directive given it fills the system logs:

net             all             DROP

Now open /etc/shorewall/rules and add the following rules at the bottom of the file:

HTTP/ACCEPT     net             $FW
HTTPS/ACCEPT    net             $FW
SSH/ACCEPT      net             $FW

NOTE: in case you want to allow ICMP (Ping) traffic from a specific remote hosts you need to add a rule similar to the following where xxx.xxx.xxx.xxx is the remote IP address, before the Ping(DROP) rule:

Ping(ACCEPT)    net:xxx.xxx.xxx.xxx       $FW

Now edit /etc/default/shorewall and change startup=0 to startup=1 You are now ready to start the firewall:

/etc/init.d/shorewall start

Setup Postfix

Stop postfix server:

/etc/init.d/postfix stop

Edit /etc/mailname and set your server domain name, for example:

server1.mycompany.com

Restart Postfix:

/etc/init.d/postfix start

Select correct timezone

dpkg-reconfigure tzdata
# Select your timezone (for example Europe/Rome)

Log rotation

In order to correctly log files you need to adjust logrotate configuration for Nginx:

cat << EOF >> /etc/logrotate.d/nginx
/home/*/*/logs/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi \
        endscript
        postrotate
                invoke-rc.d nginx rotate >/dev/null 2>&1
        endscript
}
EOF

Install virtualhost manager

wget -q -O - https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/lemp_manager.py > /usr/local/sbin/lemp_manager.py
chmod 770 /usr/local/sbin/lemp_manager.py

Download also the tools that will be used with cron:

cd /root/cron_scripts
wget https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/cron_scripts/backup_mysql.sh
wget https://raw.githubusercontent.com/matteomattei/servermaintenance/master/Debian11/cron_scripts/mysql_optimize.sh
chmod 770 *.sh

Configure CRON

Edit /etc/crontab and add the following lines at the bottom:

# mysql optimize tables
3  4  *  *  7   root    /root/cron_scripts/mysql_optimize.sh

# mysql backup
32 4  *  *  *   root    /root/cron_scripts/backup_mysql.sh

How to use virtualhost manager

You can use the virtualhost manager for adding or removing virtualhosts:

# lemp_manager

Usage:
/usr/local/sbin/lemp_manager -a|--action=<action> [-d|--domain=<domain>] [-A|--alias=<alias>] [options]

Parameters:
	-a|--action=ACTION
		it is mandatory
	-d|--domain=domain.tld
		can be used only with [add_domain, remove_domain, add_alias, get_certs, get_info]
	-A|--alias=alias.domain.tld
		can be used only with [add_alias, remove_alias, get_info]

Actions:
	add_domain	Add a new domain
	add_alias	Add a new domain alias to an existent domain
	remove_domain	Remove an existent domain
	remove_alias	Remove an existent domain alias
	get_certs	Obtain SSL certificate and deploy it
	get_info	Get information of a domain or a domain alias (username)

Options:
	-f|--fakessl	Use self signed certificate (only usable with [add_domain, add_alias])

List of some useful openssl commands

This is a list of some useful openssl commands I used. Just a brief description of what you need to to and the actual command, no more!

  • Verify if a certificate belongs to a CA:
openssl verify -CAfile ca.pem certificate.pem
  • Verify if a certificate and a key matches (hashes must be equal):
openssl x509 -noout -modulus -in certificate.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
  • Print certificate information
openssl x509 -in certificate.pem -noout -text
  • Check a CSR:
openssl req -text -noout -verify -in csr.pem
  • Check a private key:
openssl rsa -in key.pem -check
  • Check a PKCS12:
openssl pkcs12 -info -in key.p12
  • Generate key and certificate (CA)
openssl req -new -x509 -days 365 -keyout ca-key.pem -out ca-cert.pem
  • Generate a randomic private key of 4096 bits
openssl genrsa -out privkey.pem 4096
  • Generate a CSR (certificate signing request):
openssl req -new -sha256 -key privkey.pem -out csr.pem
  • Generate a certificate starting from CSR and sign it with the CA:
openssl x509 -req -days 365 -in csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out certificate.pem
  • Convert pkcs7 certificate to pem:
openssl pkcs7 -inform der -in certificate.p7c -print_certs -out certificate.pem
  • Convert pfx file to pem (certificate + private key):
openssl pkcs12 -in file.pfx -nocerts -out privkey.pem
openssl pkcs12 -in file.pfx -clcerts -nokeys -out cert.pem

// remove password from the private key
openssl rsa -in privkey.pem -out key.pem

Convert old NodeJs applications to ES6 modules

Nowadays every Nodejs application should be converted to ES6 module because it brings several benefits:

  • Modules may be executed any number of times, but are loaded only once, thus improving performance.
  • Module scripts may be shared by multiple applications.
  • Modules help identify and remove naming conflicts.

The biggest difference between standard javascript library and an ES6 module is how we include a library (which can be either within your project or external). The standard classic way is to use const library = require(‘library-name’) but with ES6 modules we have to use import library from ‘library-name’.

In order to support the import keyword you have to add the following line to the package.json of your application:

"type": "module"

In this way you are telling npm that your application is a pure ES6 module.

Then you have to change all require(xxx) statements with import xxx from ‘yyy’. In case you want to use an external library which is not a pure ES6 module you can always include it with this syntax:

import theNameYouWant from 'official-library-name'

In case you want to use a library which is placed inside your application you can use this syntax:

import theNameYouWant from './path/to/mylibrary.js'

And mylibrary.js can export a default object with all functions like this:

import axios from 'axios';

export default {
	run: function(){
		console.log('run');
	},
	sum: function(x, y){
		console.log(`Sum is ${x+y}`);
	},
	get: async function(){
		return await axios.get('https://github.com/matteomattei/matteomattei.github.io/raw/master/public/logo_professtional.jpg');
	}
}

And can be used in this way:

import mylib from './path/to/mylibrary.js'

mylib.run(); // prints 'run'
mylib.sum(2,3); // prints 'Sum is 5';
let getResult = await mylib.get();
console.log(Buffer.from(getResult.data).length); // prints the size of the image

You can also export single functions from your library like this:

import axios from 'axios';

export function run() {
  console.log("run");
}
export function sum(x, y) {
  console.log(`Sum is ${x + y}`);
}
export async function get() {
  return await axios.get(
    "https://github.com/matteomattei/matteomattei.github.io/raw/master/public/logo_professtional.jpg"
  );
}

In this case you have to use it in this way:

import * as mylib from './path/to/mylibrary.js'

mylib.run(); // prints 'run'
mylib.sum(2,3); // prints 'Sum is 5';
let getResult = await mylib.get();
console.log(Buffer.from(getResult.data).length); // prints the size of the image

Otherwise you can selectively import only the function you need:

import {run, sum, get} from './path/to/mylibrary.js'

run(); // prints 'run'
sum(2,3); // prints 'Sum is 5';
let getResult = await get();
console.log(Buffer.from(getResult.data).length); // prints the size of the image

Remember:

  • You have to add type: “module” to your package.json file.
  • You can use await directly in your main module without a wrap of an async function.
  • Your code must be implicitly strict.

Client and server SSL mutual authentication with NodeJs

In order to communicate securely between server and client it is important not only to cipher the channel but also trust both endpoints. To do this, a common practice is to do mutual authentication between client and server.

In this post I show you how to implement mutual authentication in Nodejs.

Assume we want to create a mutual authentication channel between a server running on server.aaa.com and a client running on client.bbb.com. Keep in mind the domain names because they are important in the certificates creation.

First of all we need to generate certificates. Obviously you can use certificates released by any certification authority but for the purpose of the article I am going to create self signed certificates and related CA.

Step 1: Create a working folder and setup hosts file

// AS USER
$ mkdir ~/mutual_authentication_example
$ cd ~/mutual_authentication_example

// AS ROOT
# echo '127.0.0.1 server.aaa.com' >> /etc/hosts
# echo '127.0.0.1 client.bbb.com' >> /etc/hosts

Step 2: Generate server certificates

We are going to create a Certification Authority (CA) certificate for the server with 1 year validity and the related key.

$ openssl req -new -x509 -days 365 -keyout server-ca-key.pem -out server-ca-crt.pem
Generating a RSA private key
...........................................................................................+++++
.......................................+++++
writing new private key to 'ca-key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IT
State or Province Name (full name) [Some-State]:Florence
Locality Name (eg, city) []:Campi Bisenzio
Organization Name (eg, company) [Internet Widgits Pty Ltd]:AAA Ltd
Organizational Unit Name (eg, section) []:DevOps
Common Name (e.g. server FQDN or YOUR name) []:aaa.com
Email Address []:info@aaa.com

The PEM pass phrase is optional. The other questions are not mandatory but it’s better if you answer all. The most important question is the Common Name which should be the server main domain (aaa.com).

Now we generate the actual server certificate which will be used in the ssl handshake. First of all we have to generate a random key (4096 bit length in our example):

$ openssl genrsa -out server-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........++++
...................++++
e is 65537 (0x010001)

Then generate a Certificate Signing Request (CSR) with the key we have generated:

$ openssl req -new -sha256 -key server-key.pem -out server-csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IT
State or Province Name (full name) [Some-State]:Florence
Locality Name (eg, city) []:Campi Bisenzio
Organization Name (eg, company) [Internet Widgits Pty Ltd]:AAA Ltd
Organizational Unit Name (eg, section) []:DevOps
Common Name (e.g. server FQDN or YOUR name) []:server.aaa.com
Email Address []:info@aaa.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Pay attention to the Common Name which must have the same name of the host will serve the application (server.aaa.com). As final step, generate the server certificate (validity 1 year) from the CSR previously created and sign it with the CA key:

$ openssl x509 -req -days 365 -in server-csr.pem -CA server-ca-crt.pem -CAkey server-ca-key.pem -CAcreateserial -out server-crt.pem
Signature ok
subject=C = IT, ST = Florence, L = Campi Bisenzio, O = AAA Ltd, OU = DevOps, CN = server.aaa.com, emailAddress = info@aaa.com
Getting CA Private Key
Enter pass phrase for server-ca-key.pem:

The password requested is the one inserted during CA key generation. To verify the certificate signature against the CA you can issue the following command:

$ openssl verify -CAfile server-ca-crt.pem server-crt.pem
server-crt.pem: OK

Now we have all the server certificates we need!

-rw-rw-r--  1 matteo matteo 1440 dic 26 12:52 server-ca-crt.pem
-rw-rw-r--  1 matteo matteo   41 dic 26 17:48 server-ca-crt.srl
-rw-------  1 matteo matteo 1854 dic 26 12:51 server-ca-key.pem
-rw-rw-r--  1 matteo matteo 1671 dic 26 17:48 server-crt.pem
-rw-rw-r--  1 matteo matteo 1785 dic 26 17:34 server-csr.pem
-rw-------  1 matteo matteo 3243 dic 26 17:30 server-key.pem

Step 3: Generate client certificates

Now it’s time to do the same steps for the Client. First of all create a Certification Authority (CA) certificate for the client with 1 year validity and the related key.

$ openssl req -new -x509 -days 365 -keyout client-ca-key.pem -out client-ca-crt.pem
Generating a RSA private key
..........................................................+++++
.............................................+++++
writing new private key to 'client-ca-key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IT
State or Province Name (full name) [Some-State]:Rome
Locality Name (eg, city) []:Rome
Organization Name (eg, company) [Internet Widgits Pty Ltd]:BBB Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:bbb.com
Email Address []:info@bbb.com

The PEM pass phrase is optional. The other questions are not mandatory but it’s better if you answer all. The most important question is the Common Name which should be the client main domain (bbb.com).

Now we generate the actual client certificate which will be used in the ssl handshake. First of all we have to generate a random key (4096 bit length in our example):

$ openssl genrsa -out client-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
..............++++
........................................................................................++++
e is 65537 (0x010001)

Then generate a Certificate Signing Request (CSR) with the key we have generated:

$ openssl req -new -sha256 -key client-key.pem -out client-csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IT
State or Province Name (full name) [Some-State]:Rome
Locality Name (eg, city) []:Rome
Organization Name (eg, company) [Internet Widgits Pty Ltd]:BBB Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:client.bbb.com
Email Address []:info@bbb.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Pay attention to the Common Name which must have the same name of the host will serve the application (client.bbb.com). As final step, generate the client certificate (validity 1 year) from the CSR previously created and sign it with the CA key:

$ openssl x509 -req -days 365 -in client-csr.pem -CA client-ca-crt.pem -CAkey client-ca-key.pem -CAcreateserial -out client-crt.pem
Signature ok
subject=C = IT, ST = Rome, L = Rome, O = BBB Ltd, CN = client.bbb.com, emailAddress = info@bbb.com
Getting CA Private Key
Enter pass phrase for client-ca-key.pem:

The password requested is the one inserted during CA key generation. To verify the certificate signature against the CA you can issue the following command:

$ openssl verify -CAfile client-ca-crt.pem client-crt.pem
client-crt.pem: OK

Now we have all the client certificates we need!

-rw-rw-r--  1 matteo matteo 1350 dic 26 17:59 client-ca-crt.pem
-rw-rw-r--  1 matteo matteo   41 dic 26 18:06 client-ca-crt.srl
-rw-------  1 matteo matteo 1854 dic 26 17:58 client-ca-key.pem
-rw-rw-r--  1 matteo matteo 1586 dic 26 18:06 client-crt.pem
-rw-rw-r--  1 matteo matteo 1712 dic 26 18:04 client-csr.pem
-rw-------  1 matteo matteo 3243 dic 26 18:03 client-key.pem

Step 4: Run the code!

Move all certificates in a folder called certs:

$ mkdir ~/mutual_authentication_example/certs
$ mv ~/mutual_authentication_example/*.pem ~/mutual_authentication_example/certs/
$ mv ~/mutual_authentication_example/*.srl ~/mutual_authentication_example/certs/

Now create a server.js application:

const fs = require("fs");
const https = require("https");
const options = {
  key: fs.readFileSync(`${__dirname}/certs/server-key.pem`),
  cert: fs.readFileSync(`${__dirname}/certs/server-crt.pem`),
  ca: [
    fs.readFileSync(`${__dirname}/certs/client-ca-crt.pem`)
  ],
  // Requesting the client to provide a certificate, to authenticate.
  requestCert: true,
  // As specified as "true", so no unauthenticated traffic
  // will make it to the specified route specified
  rejectUnauthorized: true
};
https
  .createServer(options, function(req, res) {
    console.log(
      new Date() +
        " " +
        req.connection.remoteAddress +
        " " +
        req.method +
        " " +
        req.url
    );
    res.writeHead(200);
    res.end("OK!\n");
  })
  .listen(8888);

Now create a client.js application:

const fs = require("fs");
const https = require("https");
const message = { msg: "Hello!" };

const req = https.request(
  {
    host: "server.aaa.com",
    port: 8888,
    secureProtocol: "TLSv1_2_method",
    key: fs.readFileSync(`${__dirname}/certs/client-key.pem`),
    cert: fs.readFileSync(`${__dirname}/certs/client-crt.pem`),
    ca: [
      fs.readFileSync(`${__dirname}/certs/server-ca-crt.pem`)
    ],
    path: "/",
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(JSON.stringify(message))
    }
  },
  function(response) {
    console.log("Response statusCode: ", response.statusCode);
    console.log("Response headers: ", response.headers);
    console.log(
      "Server Host Name: " + response.connection.getPeerCertificate().subject.CN
    );
    if (response.statusCode !== 200) {
      console.log(`Wrong status code`);
      return;
    }
    let rawData = "";
    response.on("data", function(data) {
      rawData += data;
    });
    response.on("end", function() {
      if (rawData.length > 0) {
        console.log(`Received message: ${rawData}`);
      }
      console.log(`TLS Connection closed!`);
      req.end();
      return;
    });
  }
);
req.on("socket", function(socket) {
  socket.on("secureConnect", function() {
    if (socket.authorized === false) {
      console.log(`SOCKET AUTH FAILED ${socket.authorizationError}`);
    }
    console.log("TLS Connection established successfully!");
  });
  socket.setTimeout(10000);
  socket.on("timeout", function() {
    console.log("TLS Socket Timeout!");
    req.end();
    return;
  });
});
req.on("error", function(err) {
  console.log(`TLS Socket ERROR (${err})`);
  req.end();
  return;
});
req.write(JSON.stringify(message));

It’s now time to test. Open a terminal and start the server:

$ node server.js

Open another terminal and run the client:

$ node client.js
TLS Connection established successfully!
Response statusCode:  200
Response headers:  {
  date: 'Sat, 26 Dec 2020 17:26:13 GMT',
  connection: 'close',
  'transfer-encoding': 'chunked'
}
Server Host Name: server.aaa.com
Received message: OK!

TLS Connection closed!

On the first terminal (the server) will be logged a new connection:

$ node server.js
Sat Dec 26 2020 18:26:13 GMT+0100 (Central European Standard Time) ::ffff:127.0.0.1 GET /

Conclusions

Basically you have to explicitly include the CA of the server in the connection object of the client and vice versa. In this way the server will be able to verify the client certificate and the client will be able to verify the server certificate.

I hope this example can help you implementing a mutual authentication between your endpoints in your applications. You can download demo files from this GitHub repository.


Show Git branch name on shell prompt

Maintaining multiple git repositories might became a mess specially if you often switch from one folder to another and if you are used to work on different branches. Basically you have to type every time git branch on the terminal to make sure to work on the correct working copy.

If your OS is Linux (or MacOSx) and you have Bash installed, you can customize the Bash prompt to always show the current branch name in your working directory.

First of all type ensure your default shell is /bin/bash

matteo@barracuda ~ $ echo $SHELL
/bin/bash

Then edit ~/.bashrc file and add the following lines at the bottom:

parse_git_branch() {
     git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}

PS1="\u@\h \[\e[32m\]\w \[\e[91m\]\$(parse_git_branch)\[\e[00m\]$ "

Basically we added the parse_git_branch() function which prints out the current git branch (if you are in a git project) and then reset the PS1 variable (the Bash prompt) calling the previously function.

Now you have to enable the new configuration just typing the following or doing a new login:

matteo@barracuda ~ $ . ~/.bashrc

Let’s try and see how it looks like:

matteo@barracuda ~ $ cd src/myproject
matteo@barracuda ~/src/myproject (master)$

That’s all! If you like to change colors or other PS1 parameters you can refer to the following resources: