Suricata IDS/IPS on Ubuntu 20.04

No network security setup would be complete without a network-based Intrusion Detection System and Intrusion Prevention System (IPS/IDS). For many businesses, network-based IDS/IPS is a key component of maintaining a PCI-compliant environment. PCI compliance is mandated for any business that handles credit card-holder data in any way. Other businesses handle such sensitive information that literally every packet needs to be inspected for any kind of malicious activity. Financial institutions, healthcare providers and government bodies usually fall into this category.

Whatever the business need, IDS/IPS (from this point forward, I mean network-based. Host-based is another topic entirely) is yet another necessary security tool for keeping a network environment safe. Today we’ll take a look at suricata, an open-source IDS/IPS system that you can install on Ubuntu 20.04. You can even get free, regularly-updated rule lists for staying up-to-date on the latest threats. Some ruleset providers charge for their lists, but many are provided without charge. Let’s get the basics setup and see how it works!

Topology

Topology in GNS3

We’ll install Suricata on Ubuntu20.04-1, which is acting as a router between the hapless workstation and the Internet. We’ll configure Suricata to listen for threats on the “outside”, or Internet-facing interface, ens3.

Installation

Installation requires setting up the Ubuntu PPA for recent, prebuilt packages. We can add it with this command:

add-apt-repository ppa:oisf/suricata-stable

Then update apt, and install:

apt-get update
apt-get install suricata

Configuration

Now that it’s installed, we need to make a couple of configurations in text files. The first is in /etc/default/suricata, which holds the configuration mode and default interface. You’ll want to change the following lines:

LISTENMODE=nfqueue

IFACE=ens3

LISTENMODE sets whether you want suricata to be in IDS or IPS mode. IDS will passively listen and write an alert to a log file. IPS does that too, but with the option to drop detected threats. In order to perform the drop, packets needs to be sent to the nfqueue processing queue. IFACE is whatever network interface you want suricata to listen on.

Next we need to update our rules with the suricata-update command. You can see what lists are enabled by running suricata-update list-sources, but the default ones will do just fine.

suricata-update

Now we should have a set of rules at /var/lib/suricata/rules/suricata.rules. We’ll use the sed tool to mark some of the rules to drop traffic, with these commands:

sed -i -e '/ATTACK_RESPONSE/ s/^alert/drop/' /var/lib/suricata/rules/suricata.rules 
sed -i -e '/USER_AGENTS/ s/^alert/drop/' /var/lib/suricata/rules/suricata.rules 

Finally, we need to send all packets to the NFQUEUE processing queue so they can be dropped if determined to be malicious. These iptables commands will send all packets to that queue with the exception of port 22, which is usually SSH traffic. That way our SSH session to the server isn’t interrupted:

iptables -I INPUT 1 -p tcp --dport 22 -j NFQUEUE --queue-bypass
iptables -I OUTPUT 1 -p tcp --sport 22 -j NFQUEUE --queue-bypass
iptables -I FORWARD -j NFQUEUE
iptables -I INPUT 2 -j NFQUEUE
iptables -I OUTPUT 2 -j NFQUEUE

And finally reload suricata:

systemctl restart suricata

Verification

Quick note: With the way we set suricata up, you can run these tests either from the Ubuntu server where suricata is installed, or from anything behind it. Either will work. Also please rest assured neither of these tests actually does anything close to malicious.

If we send a normal HTTP request to google.com from the hapless workstation at 192.168.0.2, it should work:

curl http://www.google.com
---
<a bunch of web nonsense output here>

But if we change the HTTP user-agent string to “BlackSun”, suricata will detect this as a malware attack. Apparently BlackSun is some kind of ransomware, but just changing the user-agent string to “BlackSun” isn’t going to do anything at all, other than test whether suricata is working or not. The below command will achieve this goal:

curl -A "BlackSun" http://www.google.com
---
<no response>

Suricata is dropping these packets! Our IPS is working. Let’s check if IDS is working too. Activity is logged at /var/log/suricata/fast.log:

cat /var/log/suricata/fast.log
---
01/20/2022-13:40:00.339281  [Drop] [**] [1:2008983:8] ET USER_AGENTS Suspicious User Agent (BlackSun) [**] [Classification: A Network Trojan was detected] [Priority: 1] {TCP} 192.168.122.142:35444 -> 142.250.207.4:80

Success! We’ve been notified of a network attack. Let’s try another one. The website testmynids.com has a nice test we can run with this command:

curl http://testmynids.org/uid/index.html
---
<no response>

This well send an index.html file containing the output of the id root command, a common way for remote command-and-control servers to execute attacks. Let’s check the log:

cat /var/log/suricata/fast.log
---
01/20/2022-13:44:40.778197  [Drop] [**] [1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 13.226.78.68:80 -> 192.168.122.142:37444

Success!

iperf2 vs iperf3: What’s the difference?

At first glance, you might be tempted to use iperf3 simply because it is one more than iperf2 (don’t worry, I’m guilty of this crime as well). It’s not an unfair assumption to think that iperf3 is the most recent version of the software, because of the name. It’s common to have two different versions of software in parallel existence, so the new one can take hold while the older version slowly dies away. Python2 and Python3 come to mind. This is not the case with iperf, however.

I recently wrote a post on how to use iperf3 to test bandwidth. Shortly after that one of the authors of iperf2, Bob McMahon, reached out to me. He pointed out that iperf2 is very much actively developed with some cool new features having been added recently. Under the surface they are very different projects, maintained by different teams with different goals.

Today we’ll take a look at some of the differences between the two.

Topology

Ubuntu 20.04 and Rocky Linux 8.5 VM’s in GNS3

We have a really basic topology here, Ubuntu 20.04 and Rocky Linux 8.5 connected on a single link with IP subnet 10.0.0.0/30. Both VM’s have iperf2 and iperf3 installed.

Bandwidth Test

For a bandwidth test, the two are almost identical. You can perform a bandwidth test using either with the same commands. For this test, the Ubuntu VM will be the client, and Rocky the server. Start the server on Rocky like this:

iperf -s

------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------

And from Ubuntu perform a test like this:

iperf -c 10.0.0.2
------------------------------------------------------------
Client connecting to 10.0.0.2, TCP port 5001
TCP window size:  238 KByte (default)
------------------------------------------------------------
[  3] local 10.0.0.1 port 36528 connected with 10.0.0.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  1.90 GBytes  1.63 Gbits/sec

These commands will work using iperf2 or iperf3, however it should be noted you can’t use an iperf2 client with an iperf3 server, and vice-versa. Also, they use different TCP ports by default. Even if you used an iperf3 client with an iperf2 server and manually set the TCP port to be the same, you will get an error. They are not compatible:

iperf3 -c 10.0.0.2 -p 5001
iperf3: error - received an unknown control message

Supported Operating Systems

iperf2 is the clear winner here, primarily because it has up-to-date Windows packages available for easy download right on the sourceforge page. I avoid Windows when I can, but it has a tendency to be unavoidable due to it’s sheer installation base. iperf3 apparently had some unofficial builds a while back but nothing officially supported. You’ll need to compile it yourself to work on Windows which can be an inconvenience at best.

iperf2 downloads page

For Linux, many operating systems come with iperf2 preinstalled, Ubuntu 20.04 is one such example. iperf3 is just a command away though, with package managers. For macOS, the Homebrew package manager can quickly get you iperf2 or iperf3.

Feature: iperf3 authentication (not encryption)

Description of authentication features in iperf3

iperf3 supports authenticating clients to the server using public key/private key as well as a users file. I decided to try it out. To avoid a hassle I just used the exact commands they provided in the man file. You first generate a public key and private key on the server:

openssl genrsa -des3 -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
openssl rsa -in private.pem -out private_not_protected.pem -outform PEM

Then create a “credentials.csv” file with hashed passwords. The following commands will get a hashed password for you:

S_USER=james S_PASSWD=james
echo -n "{$S_USER}$S_PASSWD" | sha256sum | awk '{ print $1 }'
----
0b0c98028105e9e4d3f100280eac29bba90af614d1c75612729228e4d160c601 #This is the hash of "james"

Then create a “credentials.csv” file that looks like this:

username,sha256
james,0b0c98028105e9e4d3f100280eac29bba90af614d1c75612729228e4d160c601

Now start the server:

iperf3 -s --rsa-private-key-path ./private_not_protected.pem --authorized-users-path ./credentials.csv

Then from the client, copy the public key over:

scp james@10.0.0.1:public.pem .

Then run the client:

iperf3 -c 10.0.0.1 --rsa-public-key-path ./public.pem --username james

You’ll be asked for the password. If you get it right, the server will display a message that authentication succeeded:

-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
Authentication successed for user 'james' ts 1639396545
Accepted connection from 10.0.0.2, port 32784
[  5] local 10.0.0.1 port 5201 connected to 10.0.0.2 port 32786
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec   194 MBytes  1.63 Gbits/sec                  
[  5]   1.00-2.00   sec   204 MBytes  1.71 Gbits/sec

Feature: iperf2 isochronous mode

One of the coolest features of iperf2 is its “isochronous” option. This option is designed to simulate video streaming network traffic. You can hear Bob McMahon explain it himself on his youtube video on this feature.

Using the parameters and commands he describes in his video, we’ll run on a test. The Ubuntu server will be the iperf2 server:

iperf -s -e -i 1

Then on Rocky Linux we’ll run the client test:

[james@localhost ~]$ iperf -c 10.0.0.1 -i 1 --isochronous=60:40m,10m
------------------------------------------------------------
Client connecting to 10.0.0.1, TCP port 5001 with pid 1640
UDP isochronous: 60 frames/sec mean=40.0 Mbit/s, stddev=10.0 Mbit/s, Period/IPG=16.67/0.005 ms
TCP window size:  340 KByte (default)
------------------------------------------------------------
[  3] local 10.0.0.2 port 49150 connected with 10.0.0.1 port 5001 (ct=1.44 ms)
[ ID] Interval        Transfer    Bandwidth       Write/Err  Rtry     Cwnd/RTT        NetPwr
[  3] 0.00-1.00 sec   214 MBytes  1.79 Gbits/sec  1708/0          0       67K/562 us  398346.93
[  3] 1.00-2.00 sec   217 MBytes  1.82 Gbits/sec  1738/0        230      145K/608 us  374676.21
[  3] 2.00-3.00 sec   205 MBytes  1.72 Gbits/sec  1640/0        427      142K/583 us  368710.26
[  3] 3.00-4.00 sec   212 MBytes  1.78 Gbits/sec  1697/0        575      118K/920 us  241770.85
[  3] 4.00-5.00 sec   200 MBytes  1.68 Gbits/sec  1599/0        371      134K/853 us  245702.38
[  3] 5.00-6.00 sec   200 MBytes  1.68 Gbits/sec  1598/0        423      117K/529 us  395941.50

On the server we get our output:

james@u20vm:~$ iperf -s -e -i 1
------------------------------------------------------------
Server listening on TCP port 5001 with pid 3045
Read buffer size:  128 KByte
TCP window size:  128 KByte (default)
------------------------------------------------------------
[  4] local 10.0.0.1 port 5001 connected with 10.0.0.2 port 49150
[ ID] Interval            Transfer    Bandwidth       Reads   Dist(bin=16.0K)
[  4] 0.0000-1.0000 sec   213 MBytes  1.79 Gbits/sec  4631    503:1500:1008:577:276:191:138:438
[  4] 1.0000-2.0000 sec   217 MBytes  1.82 Gbits/sec  4018    570:838:812:502:255:231:164:646
[  4] 2.0000-3.0000 sec   204 MBytes  1.71 Gbits/sec  5074    590:1537:1637:511:316:152:115:216
[  4] 3.0000-4.0000 sec   212 MBytes  1.78 Gbits/sec  3924    599:805:717:464:266:264:246:563
[  4] 4.0000-5.0000 sec   200 MBytes  1.68 Gbits/sec  3876    575:953:672:462:258:242:188:526
[  4] 5.0000-6.0000 sec   200 MBytes  1.68 Gbits/sec  4046    656:1040:687:476:258:242:238:449

Unfortunately the version of iperf that is available in Ubuntu 20.04 repositories (2.0.13) doesn’t support isochronous TCP mode mentioned in the video. You would need to compile from source or use Windows for that. A newer version will be included (probably already has been by the time you’re reading this) in Ubuntu 22.04 LTS.

Various smaller differences

There are many other spots that iperf2 and iperf3 are different.

  • iperf2 supports an “enhanced output mode” using -e that is totally revamped (used it above in the isochronous section).
  • iperf3 supports json output using the -j option.
  • iperf2 supports a bidirectional test which performs tests from the client and server simultaneously using -d
  • iperf2 uses a multi-threaded architecture, while iperf3 uses single-threaded. To be honest, I haven’t seen any way that this actually affects performance of the application. I’d be really curious if anyone has some input on this.

I hope this was helpful, and I hope I did both of these cool programs a small amount of justice. I’m really curious to see if anyone has any other input or differences they know about. Please fee free to comment or reach out directly.

How To Install Free Range Routing (FRR) on Ubuntu 20.04 and Rocky Linux 8.5

The latest version of my favorite routing protocol software, Free Range Routing 8.1 was recently released on November 9th.

Free Range Routing is a fork of the Quagga project that improves upon it and adds lots more features and new protocols. My favorite protocol that is added is EIGRP, which was originally a Cisco proprietary protocol until Cisco released a draft RFC in 2013. Free Range Routing makes it easy to spin up a Linux router and exchange routes via EIGRP. Since Cisco routers speak EIGRP, you can also exchange routes with them too! Today we’ll just exchange routes between Ubuntu 20.04 and Rocky Linux 8.5 via EIGRP.

Topology

Ubuntu and Rocky Linux in GNS3

We have a simple network here with an Ubuntu and Rocky Linux VM’s acting as IP routers. Without adding routes, Ubuntu does not know about 172.16.0.0/24, and Rocky does not know about 192.168.0.0/24. EIGRP can educate them. I should mention – each of the Alpine nodes has a default route pointing to the .1 in their subnet (Ubuntu and Rocky), which is a typical setup in most networks.

Installation

In a previous post, I installed FRR on Ubuntu 18.04 via the snap store. You can still do that, but it looks like the snap version hasn’t been updated with 8.1. I’m sure it will be updated soon, but let’s install it via the binary packages that FRR provides just to do something different.

For Rocky Linux, you can find instructions here. They are RPM packages for CentOS, and in my testing I found them to work fine for Rocky Linux. Per their instructions, we’ll run these commands:

FRRVER="frr-stable"
curl -O https://rpm.frrouting.org/repo/$FRRVER-repo-1-0.el8.noarch.rpm
sudo yum install ./$FRRVER*
sudo yum install frr frr-pythontools

We’ll need to modify /etc/frr/daemons and turn on the protocols we want, in this case EIGRP:

vi /etc/frr/daemons
---

eigrpd=yes #find this line and set to yes

Then you’ll need to restart frr:

systemctl restart frr

The process is similar on Ubuntu. The debian-based instructions are on this page. Following those, we’ll run these commands:

curl -s https://deb.frrouting.org/frr/keys.asc | sudo apt-key add -
FRRVER="frr-stable"
echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) $FRRVER | sudo tee -a /etc/apt/sources.list.d/frr.list
sudo apt update && sudo apt install frr frr-pythontools

We’ll need do modify the daemons file similar to above and run the exact same systemctl command to restart frr.

Installation is complete!

Configure FRR and EIGRP

To setup EIGRP routing, we’ll enter the FRR vtysh configuration tool that should be familiar if you’ve used either Quagga or Cisco IOS routers. On Ubuntu we’ll do this:

vtysh
---

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

u20vm# conf t
u20vm(config)# router eigrp 10
u20vm(config-router)# network 10.0.0.0/30
u20vm(config-router)# network 192.168.0.0/24
u20vm(config-router)# ^Z
u20vm# exit

On Rocky Linux, it’s almost exactly the same but the second network to add is 172.16.0.0/24:

vtysh
---

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

rl85vm# conf t
rl85vm(config)# router eigrp 10
rl85vm(config-router)# network 10.0.0.0/30
rl85vm(config-router)# network 172.16.0.0/24
rl85vm(config-router)# ^Z
rl85vm# exit

Since Rocky is runnning firewalld by default, you’ll need to either stop it with systemctl stop firewalld or go through the process to allow EIGRP-related traffic through the firewall.

We should be able to see that each router has the other’s connected route now installed in its table. On Ubuntu we can see 172.16.0.0/24 is installed from vtysh with show ip route (edited somewhat for brevity):

u20vm# show ip route
---

E   10.0.0.0/30 [90/28160] is directly connected, ens3, weight 1, 00:41:19
C>* 10.0.0.0/30 is directly connected, ens3, 00:41:57
E>* 172.16.0.0/24 [90/30720] via 10.0.0.2, ens3, weight 1, 00:40:55
E   192.168.0.0/24 [90/28160] is directly connected, ens4, weight 1, 00:40:43
C>* 192.168.0.0/24 is directly connected, ens4, 00:41:57

And likewise on Rocky Linux we can see 192.168.0.0/24 is installed:

rl85vm# show ip route
---

E   10.0.0.0/30 [90/28160] is directly connected, ens3, weight 1, 00:43:07
C>* 10.0.0.0/30 is directly connected, ens3, 00:43:45
E   172.16.0.0/24 [90/28160] is directly connected, ens4, weight 1, 00:42:50
C>* 172.16.0.0/24 is directly connected, ens4, 00:43:45
E>* 192.168.0.0/24 [90/30720] via 10.0.0.1, ens3, weight 1, 00:42:38
localhost.localdomain# 

A wireshark (if you’re running GNS3) will show the EIGRP messages flowing. If you catch it right at the start, you can see updates messages and not just hellos:

Wireshark capture of EIGRP traffic between Ubuntu and Rocky Linux

Verify

This should be easy, we’ll just ping between the Alpine Linux nodes. (make sure each has a default route pointing to .1)

/ # ping 172.16.0.1
PING 172.16.0.1 (172.16.0.1): 56 data bytes
64 bytes from 172.16.0.1: seq=0 ttl=63 time=2.662 ms

It works!

Hope you liked it.

How To Test Network Bandwidth With iperf3 in Linux

Testing network bandwidth in multiple flavors in Linux is simple with a tool called iperf. There’s two main versions – iperf2 and iperf3. Project maintainers apparently completely rewrote iperf3 from scratch to make the the tool simpler and to support some new features.

Update 12/12/2021: One of the authors of iperf2 reached out to me. Iperf2 is currently very much actively developed. You can find the most recent code on its sourceforge.net page. Iperf3 was indeed rewritten from scratch as the wikipedia page says, but mostly to meet the U.S. Department of Energy’s use cases. Iperf3’s github page clearly states the the DoE owns the project.

For testing bandwidth properly, you need to be running in server mode on one endpoint and client mode on the other. For this experiement, we will run the server on Rocky Linux 8.5 and the client on Ubuntu 20.04.

Topology

iperf3 test in GNS3

This is about as simple of a topology as I can think of. Two nodes on either end of a single link, Ubuntu at 10.0.0.1/30 running iperf3 client and Rocky at 10.0.0.2/30 running iperf3 server.

Iperf3 installation

On Ubuntu, iperf3 can be installed from distribution sources with apt-get:

apt-get install iperf3

Same on Rocky Linux but with yum:

yum install iperf3

Run iperf3 bandwidth test

First we need to start the server process on Rocky Linux with one command:

iperf3 -s

Then you should see the server listening for incoming tests:

iperf3 server listening on Rocky Linux 8.5

Then from the Ubuntu client, one command will run the test:

iperf3 -c 10.0.0.2

The output will give us our bandwith test results, which can be see on either the client or server:

Connecting to host 10.0.0.2, port 5201
[  5] local 10.0.0.1 port 59628 connected to 10.0.0.2 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   176 MBytes  1.48 Gbits/sec  685    230 KBytes       
[  5]   1.00-2.00   sec   173 MBytes  1.45 Gbits/sec  738    113 KBytes       
[  5]   2.00-3.00   sec   170 MBytes  1.42 Gbits/sec  1004    191 KBytes       
[  5]   3.00-4.00   sec   175 MBytes  1.47 Gbits/sec  714    123 KBytes       
[  5]   4.00-5.00   sec   182 MBytes  1.52 Gbits/sec  458    163 KBytes       
[  5]   5.00-6.00   sec   204 MBytes  1.71 Gbits/sec  443    314 KBytes       
[  5]   6.00-7.00   sec   180 MBytes  1.51 Gbits/sec  910    130 KBytes       
[  5]   7.00-8.00   sec   191 MBytes  1.60 Gbits/sec  849    123 KBytes       
[  5]   8.00-9.00   sec   172 MBytes  1.44 Gbits/sec  564    170 KBytes       
[  5]   9.00-10.00  sec   184 MBytes  1.54 Gbits/sec  412    225 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.76 GBytes  1.52 Gbits/sec  6777             sender
[  5]   0.00-10.04  sec  1.76 GBytes  1.51 Gbits/sec                  receiver

iperf Done.

A wireshark capture in GNS3 between the two hosts (or tcpdump on the links if you’re not in GNS3) will show the packets flying while the test is running:

Wireshark capture from GNS3 of iperf3 test

Hope you liked it!

How To Install (and lab) Keepalived on Ubuntu 20.04 and Rocky Linux 8.5

Keepalived is an open source software project that can do many things related to high availability. One of these many things is the Virtual Router Redundancy Protocol, which provides for high availability for IP networking. In other words, you can have two routers and if one goes down, the second one kicks in automatically.

The way this works is two or more routers exchange VRRP messages on a subnet and based on their configuration, decide who is the master and who is the backup. Once this is decided they will agree on a pre-configured “virtual” IP, or an IP that is not configured on an interface, but a floating one that either router can assume responsibility for should the other one fail for some reason and VRRP messages stop flowing.

Topology

VRRP lab in GNS3

The relevant network here is on the bottom half, where a subnet of 192.168.0.0/24 is configured. The Ubuntu server has 192.168.0.2/24 on its ens3 interface, while Rocky has 192.168.0.3/24 on its ens3 interface. They will both have keepalived installed and through VRRP share virtual IP of 192.168.0.1/24. The Alpine linux “PC” at the bottom which is acting as a workstation or desktop computer will have its default route configured to point to 192.168.0.1, the VRRP address.

Ubuntu 20.04 configuration

On the Ubuntu server we’ll install the keepalived available from the package manager with this:

apt-get install keepalived

Once that’s installed, we’ll write the configuration file which is in /etc/keepalived/keepalived.conf. You’ll need to create the keepalived.conf file:

vrrp_instance VI_1 {
	state MASTER
	interface ens3
	virtual_router_id 51
	priority 100
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass james
	}
	virtual_ipaddress {
		192.168.0.1/24
	}
}

I’ll go through the parameters here:

  • state MASTER: the state that the router will start in.
  • interface ens3: VRRP protocol messages should flow is ens3.
  • virtual_router_id: An integer that both routers should have configured to the same thing.
  • priority: who wins the master/backup election – higher numerical means higher priority.
  • advert_int: backup waits this long (multiplied by 3) after messages from master fail before becoming master
  • authentication: a clear text password authentication.
  • virtual_ipaddress: the agreed-upon virtual IP that the routers will share

Restart keepalived to load the config:

systemctl restart keepalived

Rock Linux configuration

Since Rocky uses yum for package management, we install keepalived like this:

yum install keepalived

And in the /etc/keepalived/keepalived.conf file we’ll write this:

vrrp_instance VI_1 {
	state BACKUP
	interface ens3
	virtual_router_id 51
	priority 99
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass james
	}
	virtual_ipaddress {
		192.168.0.1/24
	}
}

Make sure to restart keepalived to load the config:

systemctl restart keepalived

The only parameters that are different are the state and the priority.

Verify keepalived and VRRP

The first thing you can check on Ubuntu is the /var/log/syslog file to make sure keepalived started and is in the correct state:

tail /var/log/syslog
---
Dec  1 12:23:47 u20vm Keepalived[12349]: Opening file '/etc/keepalived/keepalived.conf'.
Dec  1 12:23:47 u20vm Keepalived[12349]: Starting VRRP child process, pid=12360
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering Kernel netlink reflector
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering Kernel netlink command channel
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Opening file '/etc/keepalived/keepalived.conf'.
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering gratuitous ARP shared channel
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: (VI_1) Entering BACKUP STATE (init)
Dec  1 12:23:48 u20vm Keepalived_vrrp[12360]: (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding
Dec  1 12:23:51 u20vm Keepalived_vrrp[12360]: message repeated 3 times: [ (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding]
Dec  1 12:23:51 u20vm Keepalived_vrrp[12360]: (VI_1) Entering MASTER STATE

On Rocky the journalctl -e command showed me the keepalived logs.

Once you’ve confirmed that keepalived is in the right state, you can prove it further with a wireshark capture. If you’re doing this lab in GNS3 like I am, it’s easy, just right click the link (in this case, between the Ubuntu server and the switch) and capture on it. Otherwise you can use something like tcpdump on the Ubuntu/Rocky routers themselves. Right when you restart the keepalived process you will see packets going back and forth. That’s the master election process and exchange of parameters/neighbor establishment:

VRRP election in wireshark

But once the routers are in agreement and VRRP is working, packets will only flow from the master to the designated (in the RFC) VRRP multicast address at 224.0.0.18:

VRRP keepalives in wireshark

We can also further prove that when we initiate a ping to 8.8.8.8 from the Alpine “PC” (it’s a docker container), we can see that traffic is flowing through the Ubuntu router.

Simulate a failure

We’ll simulate a failure by shutting the ens3 interface on the Ubuntu router, like so:

ip link set ens3 down

The Rocky router will observe that VRRP “hello” messages are no longer going to 224.0.0.18, and quickly assume the role of master and take over for 192.168.0.1. I did a continuous ping on the Alpine PC and it didn’t actually show any failed pings! We can see that traffic is now flowing through the Rocky router:

Now let’s “fail back” to the Ubuntu router by re-enabling the ens3 interface:

ip link set ens3 up
ip addr add 192.168.0.1/24 dev ens3

And we should be able to see keepalived resuming master responsibilities in the Ubuntu /var/log/syslog:

Dec  1 12:44:53 u20vm Keepalived_vrrp[12379]: Netlink reports ens3 up
Dec  1 12:44:53 u20vm systemd-networkd[602]: ens3: Gained carrier
Dec  1 12:44:55 u20vm systemd-networkd[602]: ens3: Gained IPv6LL
Dec  1 12:45:04 u20vm Keepalived_vrrp[12379]: (VI_1) Entering BACKUP STATE
Dec  1 12:45:04 u20vm Keepalived_vrrp[12379]: (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding
Dec  1 12:45:07 u20vm Keepalived_vrrp[12379]: message repeated 3 times: [ (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding]
Dec  1 12:45:08 u20vm Keepalived_vrrp[12379]: (VI_1) Entering MASTER STATE

While the Rocky log will show a similar message about becoming backup. We can see that pings are once again flowing through the Ubuntu router:

Hope you liked it.

How To Check If A UDP Port Is Listening Using Netcat

Netcat is a super cool tool to perform a great number of different testing and troubleshooting tasks. On Ubuntu Linux, netcat comes built-in so you don’t even have to install it. A common question I get from people trying to troubleshoot a UDP-based application is how to check if a UDP port is “listening” or not. Most folks are familiar with the telnet-based method of checking if a TCP port is listening (e.g., telnet google.com 80), but UDP remains elusive.

Today let’s take a look at why UDP is tricky to check and a method using the netcat tool to see if a server is listening on a particular UDP port.

Topology

We’re using a very simple topology here, my PC is at 192.168.122.1 and the server we’re going to check for an open UDP port is at 192.168.122.252. My PC runs an Ubuntu 20.04 Desktop OS, while the server is running the Ubuntu Server version.

TCP and UDP are different protocols

TCP is designed to establish “reliable” connections, meaning packets are either delivered on-time and in order, or the connection fails. To guarantee delivery, it must first establish a “socket”, which is where the TCP handshake comes in. All TCP sockets start like this:

Client –> [SYN] –> Server
Client <– [SYN/ACK] <– Server
Client –> [ACK] –> Server

Once this handshake is done, a socket and connection is established.

A telnet test will succeed if this TCP handshake completes properly, letting you know that the port you’re testing is open.

The problem with UDP is that no such handshake exists, because it was designed specifically to not have connections or reliability. Neither is “better”, they’re just used for different things.

So how do you test for an open UDP connection if there’s not a protocol handshake or guaranteed response? If an application is NOT listening on a particular UDP port, and a UDP packet arrives on that port, the OS will send back an ICMP message of type 3 and code 3, meaning “destination unreachable” and “port unreachable”, respectively. Because UDP lacks a protocol message for this purpose, the ICMP message serves to notify the sender that they’re barking up the wrong tree.

Set up a netcat UDP server

Setting up a server to listen on a UDP port of our choosing is easy using netcat. Just run this command on the Ubuntu server:

netcat -ul 2000 &

The -ul switches specify UDP and “listen” mode, respectively. The ampersand at the end puts the process in the background so we can get our shell back. After running this command, we can do a quick netstat to see that there is indeed a UDP server listening on port 2000:

netstat -an | grep 2000
udp        0      0 0.0.0.0:2000            0.0.0.0:*

Use netcat on the client to test the UDP port

From “James’s PC” on the diagram, I’ll run the following command:

nc -vz -u 192.168.122.252 2000

The -v and -u options specify verbose and UDP mode, respectively. The -z option tells netcat not to send any data with the packet. At the end are the IP address of the server and to UDP port 2000. If the port is open, we should see output like this:

Connection to 192.168.122.252 2000 port [udp/*] succeeded!

The message is well and good, but well all know packets don’t lie. If we take a wireshark capture of the interaction, we can see why this success message is produced:

Five UDP packets were sent by netcat on James’s PC to make sure no response came back from the server. If there’s no ICMP response, we can infer that the port is indeed open.

The netcat UDP server will shut down after hitting it once with UDP packets, so it’s no longer listening on port 2000. If we run our command again, we’ll get no output from netcat and wireshark clearly shows the ICMP type 3 code 3 message being sent back from the server:

UDP port testing is not like TCP at all, since we can only infer the port’s open state, not guarantee it like TCP.

I hope this one was helpful!

Wireguard VPN on Ubuntu 20.04

Wireguard is an attempt to improve VPN tunnels in a number of ways – simpler code, less compute, easier configuration, the list goes on. If we’re comparing it to IPsec, I would say that yes, it’s a bit easier to configure. One of the main differences is that it does not rely on the classic two IPsec options for keys – PSK and X.509 certificates. Instead, it relies on public key/private key similar to SSH.

Today, we’re going to configure a very simple policy-based site-to-site Wireguard VPN. By “policy-based” I mean that tunneled traffic is determined by a pre-written configuration in the Wireguard configuration file, not by static or dynamic routes. By site-to-site, I mean this is not a remote-access (road warrior) VPN, it’s designed to connect the subnets that sit behind the two VPN peers.

Topology

The two Ubuntu20.04 machines are serving as routers and VPN peers. No routing is in place for PC1 and PC2 to talk to each other. While the two Ubuntu machines can connect to each other, there is no network-level encryption. That’s where Wireguard comes in.

Generate key pairs

If you haven’t installed it yet, wireguard can be installed with apt-get:

apt-get install wireguard

We’ll use the wg command to generate keys on Ubuntu20.04-1. These two commands will generate a private and public key pair:

wg genkey > private1.key
wg pubkey < private1.key > public1.key

We’ll do the same on Ubuntu20.04-2

wg genkey > private2.key
wg pubkey < private2.key > public2.key

Tunnel configurations

Then we’ll write a tunnel config file for Ubuntu20.04-1 in /etc/wireguard/wg0.conf:

[Interface]
PrivateKey = uOnrdfW/ANcU+fh+RUjlb3TQlFWIdwbOpDpAA+NkonY=
Address = 10.0.0.1/30
ListenPort = 8888

[Peer]
PublicKey = JjSDqdzjPOX+iIAaWCjxxg1yZ76jAd6jfSfv/1AlojI=
Endpoint = 12.0.0.2:8888
AllowedIPs = 10.0.0.0/30, 172.16.0.0/24

You’ll notice I took the keys out of the key files and pasted them into the config file. Under “Interface” we have Ubuntu20.04-1’s own private key, while the public key of Ubuntu20.04-2 is under “Peer”. The “Address” parameter is the “glue network” of the tunnel. This will be the virtual subnet that exists inside the tunnel encryption. The “AllowedIPs” parameter is where tunneled traffic is specified (interesting traffic). You need to put the destination subnet here. Since this is Ubuntu20.04-1 and 172.16.0.0/24 is the destination on the other side of the tunnel, we put 172.16.0.0/24 as part of the “AllowedIPs” parameter. Also put the glue network in here as well.

We can write a similar one for Ubuntu20.04-2 in /etc/wireguard/wg0.conf. For Ubuntu20.04-2, 192.168.0.0/24 is on the other side of the tunnel so 192.168.0.0/24 will be in “AllowedIPs”. Under “Interface”, we have its own private key, while the public key of Ubuntu20.04-1 is pasted under “Peer”.

[Interface]
PrivateKey = KJgjkPQVhOX5CyYYWr7B6v1AbI7H2kEtBi4wdhAES2g=
Address = 10.0.0.2/30
ListenPort = 8888

[Peer]
PublicKey = +GOlIMgLAnLZujraI8m4F6JyWZOpxWGRAPSUqkwrZyg=
Endpoint = 11.0.0.2:8888
AllowedIPs = 10.0.0.0/30, 192.168.0.0/24

To bring the tunnels up, on each Ubuntu machine run this command:

wg-quick up wg0

To verify the configuration is loaded, use “wg show”. I ran this one from Ubuntu20.04-2:

wg show
---
interface: wg0
  public key: JjSDqdzjPOX+iIAaWCjxxg1yZ76jAd6jfSfv/1AlojI=
  private key: (hidden)
  listening port: 8888

peer: +GOlIMgLAnLZujraI8m4F6JyWZOpxWGRAPSUqkwrZyg=
  endpoint: 11.0.0.2:8888
  allowed ips: 10.0.0.0/30, 192.168.0.0/24
  latest handshake: 28 minutes, 48 seconds ago
  transfer: 1.09 KiB received, 2.63 KiB sent

We should be able to ping the tunnel glue network IPs (here from Ubuntu20.04):

root@u20vm:/home/james# ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=5.22 ms

And PC1 and PC2 can ping each other too:

PC1> ping 172.16.0.2

84 bytes from 172.16.0.2 icmp_seq=1 ttl=62 time=4.942 ms

You’ll notice we did not have to add any routes for 192.168.0.0/24 and 172.16.0.0/24 to be able to reach each other. The Wireguard configuration added routing automatically, which is why I am calling this type of tunnel “policy-based”.

And the most fun part, Wireshark. We can see the traffic going back and forth, and the protocol is labeled “Wireguard”. Pretty cool, right?

Wireshark capture of Wireguard traffic

Hope you liked this one.

Basic Firewall With Iptables on Ubuntu 20.04

Ubuntu comes with iptables, a configuration utility that allows you to manage rules for Netfilter, the Linux Kernel firewall. Using iptables you can manipulate packets as they leave, enter, or are forwarded across network interfaces on a Linux operating system. Today we’ll look at how to block SSH traffic going through an Ubuntu 20.04 system acting as a router.

Topology

I have a basic configuration here with three IP subnets – 192.168.0.0/24 where the SSH client lives, 10.0.0.0/30 is a transit network between routers, and 172.16.0.0/24 where the SSH server lives. We will be configuring Ubuntu20.04-Firewall with iptables to block SSH traffic.

Installation

Iptables requires no external package installation with apt-get or otherwise, it comes stock-and-standard with a fresh Ubuntu 20.04 Server or Desktop OS. You can verify the status of your iptables rules like so:

iptables -S

-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

By default, iptables is configured to pretty much do nothing. No packets are filtered and no NAT (network address translation) is configured.

Iptables syntax

The syntax for iptables can be quite confusing, and since I myself am not configuring them on a daily basis, I always need to reference documentation (or someone else’s blog) for how to configure something specific. It’s a good idea to take a quick look at the basic syntax though. The command structure looks like this, taken straight from the iptables manual page:

iptables [-t table] [mode] [chain] [rulenum] [rule-specification] [options]
  • table is which type of table you want to use. Usually it’s filter for dropping disallowed packets, or nat for translating packets. Filter is default if none is specified.
  • mode is an action, -A (append), -I (insert), -D (delete), -R (replace), -L (list), -P (set policy, is default action) are all valid modes.
  • chain is the part of the routing process to which your rule applies, there are five chains – PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING.
  • rulenum gives the rule a sequence, rules are applied one-by-one and when there is a match, the corresponding action is taken and all subsequent rules are ignored.
  • rule-specification is the actual rule itself. There are many parameters that can be specified here, like protocol, source, destination, etc.
  • options give you some customization – for example, you can add -v for verbose output.

Now that we have a basic idea of what iptables does, let’s drop some SSH packets.

Verifying what we’re blocking

On Ubuntu20.04-Firewall in my topology, I can see that Ubuntu20.04-SSH_Client at 192.168.0.2 has SSH access to Ubuntu20.04-SSH_Server at 172.16.0.2:

james@client$ssh james@172.16.0.2
james@172.16.0.2's password:

james@server$

SSH is an network application protocol that usually uses TCP port 22 to establish an encrypted command-line session between two computers. If we do a quick wireshark betwen the Ubuntu20.04-Firewall and CiscoIOSv15.6(1)T-1 router, we can see this SSH traffic traversing the link:

Wireshark capture of SSH traffic in GNS3

Starting at the top you can see the TCP handshake on port 22 starting with the SYN flag. A few packets later SSHv2 packets begin. All packets use a randomized source TCP port (source is different per session) and a destination TCP port of 22. So we can safely say that in this experiment, if we drop TCP destination port 22, we will effectively block SSH traffic between the client and server.

Configure the iptables rule

A rule to drop SSH packets on TCP 22 can be configured in one line on Ubuntu20.04-Firewall:

iptables -A FORWARD -p tcp --dport 22 -j DROP
  • -A specifies we are appending a rule.
  • FORWARD applies the rule to packets being forwarded from one interface to another
  • -p tcp is a rule-specification to apply the rule to TCP packets
  • --dport 22 applies the rule to destination TCP port 22
  • -j DROP tells iptables to drop the packet if the previous conditions match

Verify

Let’s see if the client can connect now:

james@client$ ssh james@172.16.0.2
ssh: connect to host 172.16.0.2 port 22: Connection timed out

It worked! Packets on TCP port 22 are being dropped at the Ubuntu firewall.

Please keep in mind – this is a simple block on TCP destination port 22. TCP and UDP ports use the concept of “well-known” which means servers running protocols use a port that everyone “knows”. This is so that someone connecting to a server for the first time with no previous knowledge of its configuration will be able to connect, since they both assume that TCP port 22 is used for SSH. However if someone configures SSH on the client and server in this example to use any other port, it will go right around the filter.

Reach out if you have issues or something to share!

Telnet to Ubuntu Server 20.04 in GNS3 Instead of VNC

If you’re using Ubuntu VM’s inside of GNS3, you’re probably sick of using a VNC client to access its command line.

The first big drawback to using VNC is that you can’t (or at least it’s not immediately obvious how to) paste text or commands you’ve found into the terminal. You have to retype everything, which is a real bummer.

The second big drawback is that a VNC session can’t be automated (or at least I don’t know of a good tool to do that). Since VNC is like RDP in that the session is visual, a human being or really advanced AI is required to interact with the session.

Having access to a VM in GNS3 via telnet to its terminal is a real benefit. You can set it up pretty quickly in Ubuntu 20.04. Full disclosure – this method only gets you access after the device has booted and arrived at the login prompt. There is a way to allow access earlier than that so the boot process can be viewed, I just haven’t gotten to it yet.

Set your VM to not be “linked base”

One mistake I often make in GNS3 is forgetting to make my VM not a “linked base” when I want to make permanent changes. A linked base is basically a clone of your VM. Any changes you make, files you download or programs you install will be blown away when you delete the device from the GNS3 canvas. To disable this functionality temporarily to make permanent changes, go to the device in the left pane and click “configure template”. On the advanced tab, uncheck “Use as a linked base VM”:

When you are done configuring the telnet capability, you can recheck this box. All linked base VM’s you drag out afterwards will have the telnet capability.

Create the ttyS0.service

You first need to create a systemd service for serial access. We need to create a file called ttyS0.service in the /lib/systemd/system/ directory:

vi /lib/systemd/system/ttyS0.service

The file contents should look like this:

[Unit]
Description=Serial Console Service

[Service]
ExecStart=/sbin/getty -L 115200 ttyS0 vt102
Restart=always

[Install]
WantedBy=multi-user.target

getty is program that manages tty sessions, physical or virtual terminals. It will run the login prompt when a connection is detected. 115200 is the baud rate, ttyS0 is a device file that points to the current terminal, and vt102 is the terminal emulator.

Load the service in systemd

Just a few commands will load the new service in systemd, and the script will run on boot to activate your serial device and allow telnet. Run these commands:

#Make file executable
chmod 755 ttyS0.service

#Reload systemd
systemctl daemon-reload

#Enable the service
systemctl enable ttyS0

#Start the service
systemctl start ttyS0

Your service is good to go!

Change the console type to telnet

You need to first shut down your VM so you can change the console type. Once it’s shutdown, you can configure the device on the canvas, or the template in the pane to the left, or both. The template will make changes for all VM’s dragged onto the canvas in the figure. Either way, configure the node by right clicking on it, and clicking on “configure” or “configure template”. At the very bottom, you should see a dropdown for “console type”. Change it to “telnet”:

Log in via telnet!

Just double-click on your VM. You won’t see any output on the telnet window while the VM is booting up because the service hasn’t fired yet. But when it does, you should see the login prompt:

Bonus tip – turn off dhcp in netplan

I had to turn off dhcp in Ubuntu’s netplan network configuration tool to get it to stop hanging at boot. There should be a yaml file in /etc/netplan/ (the yaml file name might differ per system) where you can turn it off. My netplan config looks like this:

network:
  ethernets:
    ens3:
      dhcp4: false
      optional: yes
  version: 2

Hope that helps!

Create Your Own Certificate Authority With OpenSSL and NGINX On Ubuntu 20.04

OpenSSL is probably the most widely used cryptographic tool in existence. Deployed on millions of machines, from cloud servers and containers to small embedded devices like network routers, it performs many functions to secure communications between devices. Security in computing and networking is a gigantic topic. So gigantic in fact, that no single blog post can come even close covering everything. To keep things simple, I’ll narrow things to just getting our own environment created, including a “certificate authority” (CA), a web server and a web client. These days, SSL is synonymous with TLS. TLS is just the name of the newer versions of the SSL protocol.

Topology

The network topology in this post isn’t very important, we’re going to be looking at how a certificate authority works with SSL encryption. But I’ve created a basic IP subnet 10.0.0.0/24 so we can watch the SSL traffic flow in Wireshark. All three machines are running Ubuntu 20.04. All SSL-related functions will be performed with OpenSSL.

A (very) basic explanation of a certificate authority

Certificate authorities, SSL, and Public Key Infrastructure (PKI) are a complex topic. To explain the whole thing would take lots of different explanations to break down each piece of the bigger picture and explain the part it plays. So to avoid having a gigantic explanation here, I’m going to sum up the function of a CA with a single sentence:

“A certificate authority provides a way for computers to verify that other computers they communicate with are in fact the domain name (i.e., example.com) they claim to be, and not a fraud.”

I hope that helps. I’ll take a crack at explaining PKI in another post.

Create the CA

A CA can be created with OpenSSL with a couple quick commands. When this process is complete, we will have two files, a private key and a digital certificate. First create a private key on our CA Ubuntu machine (at 10.0.0.3):

openssl genrsa -aes128 -out james_ca.key 2048

Now we’ll create a certificate. The private key’s corresponding public key will be on this certificate. We’ll be asked some questions after issuing the command, most of them are not important. The Common Name field should ideally be unique:

openssl req -x509 -new -nodes -key james_ca.key -sha256 -days 365 -out james_ca.pem
----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:Idaho
Locality Name (eg, city) []:Boise
Organization Name (eg, company) [Internet Widgits Pty Ltd]:James
Organizational Unit Name (eg, section) []:James
Common Name (e.g. server FQDN or YOUR name) []:James_CA
Email Address []:someone@example.com

Our server is now a CA! The world’s CA’s are considered “trusted” because web browsers everywhere come with their certificates pre-installed. The CA we just created is trusted by no one and pre-installed in nothing, but we can get our Ubuntu client machine to trust the CA by manually loading the CA certificate into the client’s certificate store.

Load the CA certificate on the client

We’ll use secure copy (scp) which runs over SSH to copy the CA certificate to the Ubuntu client. The command looks like this:

scp james@10.0.0.3:james_ca.pem .

james_ca.pem                                  100% 1456   605.0KB/s   00:00

The first line above is the command, the second is the output. Now we need to add this to Ubuntu’s trusted certificate store. A couple commands will do the trick.

cp james_ca.pem /usr/local/share/ca-certificates/james_ca.crt
update-ca-certificates 

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

The first two lines are the commands, the rest is output. We’ve copied james_ca.pem (and changed its extension to .crt) to /usr/local/share/ca-certificates, which is where Ubuntu keeps certificates that it trusts. The update-ca-certificates command loads the new certificate for use in applications, like a web client or browser.

We have done manually what the world’s web browsers have built-in. We have loaded the certificate of a CA we trust. Now any other certificate presented to this client will be trusted if it has been signed by the CA.

Create a signing request on the server

Now we need to create what is called a “signing request”, which is actually just another file in a specific format. We take this signing request file to the CA, which it will use to create a signed certificate, which can be returned to the server and loaded into nginx web server for use. We’ll be asked the same set of questions this time since a certificate is going to be created. The important field is the Common Name, which is usually the server’s domain name, but in this simple topology it’s the IP address (since I haven’t set up DNS) that the client will use to access the nginx web server. The signing request is created with SSL like this:

openssl req -newkey rsa:2048 -keyout private.key -out james.csr

Generating a RSA private key
.................+++++
..............................................................................................+++++
writing new private key to 'private.key'
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]:US
State or Province Name (full name) [Some-State]:Idaho
Locality Name (eg, city) []:Boise
Organization Name (eg, company) [Internet Widgits Pty Ltd]:James
Organizational Unit Name (eg, section) []:James
Common Name (e.g. server FQDN or YOUR name) []:10.0.0.2
Email Address []:someone@example.com

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

The first line is the command, the rest is output. The passphrase you use for the private key will have to be removed later, I’ll show you how at that time.

Now we can take the file james.csr to the CA and produce a signed certificate.

Load the signing request on the CA

Let’s again use secure copy to bring the signing request file to the CA. On the CA’s cli:

scp james@10.0.0.2:james.csr .

The signing request file is now on the CA. We can create a certificated (server.pem) signed with the CA’s private key like this:

openssl x509 -req -in james.csr -days 365 -CA james_ca.pem -CAkey james_ca.key -out server.pem

Just a quick recap of the different files in use here:

  • james.csr is the signing request from the server
  • james_ca.pem is the CA’s certificate
  • james_ca.key is the CA’s private key
  • server.pem is the CA-signed certificate that we can now return to the server for use in nginx

Great! We’ve got a freshly signed certificate ready to go. Let’s copy it to the server and load it into nginx.

Load the signed certificate into nginx

Let’s use scp again to copy server.pem to the server. On the server’s cli:

scp james@10.0.0.3:server.pem .

Now we have the signed certificate on our server. By the way, nginx can be quickly installed like this:

apt-get install nginx

And check it’s working with systemctl status nginx:

As I said earlier, we’ll need to remove the passphrase encryption from the server’s private key before it can be used in nginx. It’s one command, like this:

openssl rsa -in private.key -out private_unencrypted.pem

Now that the private key has been unencrypted into a new file called private_unencrypted.pem, it can be used in the nginx config file. The default nginx config is in /etc/nginx/sites-available/default. I added these lines, they’ll be a bit different for yours if the folders and/or files are differently named:

listen 443 ssl default_server;
ssl_certificate /home/james/server.pem;
ssl_certificate_key /home/james/private_unencrypted.pem;
server_name 10.0.0.2;

Restart nginx:

systemctl restart nginx

If you don’t get any errors, you are good to go!

Verify SSL is working from the client

There are a lot of commands to get the certificate presented from the server, view it, verify it, etc. But for now, we can just validate the SSL connection. You can do that with curl really easy from the client:

curl https://10.0.0.2

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

.... omitted for brevity

We can see we properly got an HTML document back from the server. We can also see it in Wireshark if you capture traffic going to and from the client and server. We can see the entire SSL (TLS) handshake go through without any errors:

Ironically, the CA has not had its own certificate installed in its certificate store (unless you did that on your own). So we can actually see using curl from the command line of the CA machine what it would look like to try to access the server without the certificate installed. From the CA:

curl https://10.0.0.2

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

This is the command line equivalent of this screen in your browser:

Since the CA’s certificate hasn’t been installed in it’s certificate store, the connection to the server at 10.0.0.2 can’t be verified and so you get a nasty message.

This was a long one but I hope it helped you understand keys, certificates and certificate authorities! It sure helped me!