23 Jul 2024 Matteo Mattei linux server mariadb debian nginx php iptables postfix ssl letsencrypt sftp Setup bash and update the system
apt update
apt dist-upgrade
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?
MySQL application password for phpmyadmin:
Setup Nginx
Stop Nginx web server:
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
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:
removing info directive given it fills the system logs:
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:
Edit /etc/mailname and set your server domain name, for example:
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
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])
05 Nov 2022 Matteo Mattei linux server mariadb debian nginx php iptables postfix ssl letsencrypt sftp Setup bash and update the system
apt-get update
apt-get dist-upgrade
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?
MySQL application password for phpmyadmin:
Setup Nginx
Stop Nginx web server:
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
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:
removing info directive given it fills the system logs:
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:
Edit /etc/mailname and set your server domain name, for example:
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
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])
23 Dec 2021 Matteo Mattei nodejs 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:
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.
26 Dec 2020 Matteo Mattei security nodejs server tcp certificates openssl 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:
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.
24 Dec 2020 Matteo Mattei git bash shell linux 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: