Create Your Own Azure Bastion with Guacamole and Save $100+ a month
Tags: azure, bastion, guacamole, ubuntu
Published: 2021-04-30 15:18:40

Microsoft Azure provides [Azure Bastion](https://azure.microsoft.com/en-us/services/azure-bastion) service which is a jump server so you can securely access your virtual machines via its Azure Portal web interface without exposing SSH or RDP port. Here is its architecture:

![Azure Bastion Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v15wy165uheqdm4ljb6w.jpg)

This service is great except [it costs $0.19 per hour](https://azure.microsoft.com/en-us/pricing/details/azure-bastion/). Combining with other components then this may costs you almost $150 a month. Bad news is you can't stop it unless you delete it.

This post, I will show you how I create my own jump server on  Ubuntu VM to replace Azure Bastion using [Apache Guacamole](https://guacamole.apache.org/) which is an open source tool which provide similar functionalities (I wonder that Azure Bastion may be even built on top of it). The VM can be a small one that may costs only a few tens bucks per month.

---

# Apache Guacamole

From [its website]((https://guacamole.apache.org/)), Apache Guacamole is a clientless remote desktop gateway that supports standard protocols like VNC, RDP, and SSH. Clientless means your clients don't need to install anything but just use a web browser to remotely access your fleet of VMs.

{% vimeo 116207678 %}

The Guacamole comprises of two main components:

- Guacamole Server which provides `guacd` which is like a proxy server for the client to connect to the remote server.
- Guacamole Client which is a servelet container that user will log in and via web browser.

![Guacamole Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdmwjtttqn8va262s79b.png)

For more information about Guacamole, visit its [architecture page](https://guacamole.apache.org/doc/gug/guacamole-architecture.html).

As my disclaimer, installation is not simple as there are several components you need to install and configure before it is _good enough_ to use. There may be simpler ways to deploy e.g. using [Docker image](https://guacamole.apache.org/doc/gug/guacamole-docker.html) or using [this Helm chart](https://github.com/prabhatsharma/apache-guacamole-helm-chart/blob/master/values.yaml) but I haven't tried them yet. Because of cost is my concern so I'd like to deploy on a small VM that may not run a Kubernetes cluster (and may be I just prefer to learn it in hard way :P).

# Network Topology

You use Azure Bastion or a jump server because you want to secure your VMs behind so having the right network design is needed. Here it mine:

![Network Topology](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ve1uz4kmxg65ox3gikj.png)

In my virtual network (VNet), I split into two subnets:

1. `snet-gateway` where I will deploy Guacamole on a Ubuntu VM which has a public IP so its web interface can be reached from the Internet.
2. `snet-default` where I will deploy my backend pool of VM and enable remote access via Guacamole only.

For the sake of security, you should configure Network Security Group (NSG) of your `snet-gateway` to limit inbound and outbound connections to/from Ubuntu VM in the same way as you do for [AzureBastionSubnet](https://docs.microsoft.com/en-us/azure/bastion/bastion-nsg#apply). But in this example, I just associate NSG to the VM directly.

![NSG Inbound Rules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d9jmik21jh988vk99jwo.png)

![NSG Outbound Rules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkncp6amdvvjar3ita6q.png)

Once you setup your network then create the VMs. In this example, I create the following two VMs:

1. `vm-win10` in the `snet-default` which is the machine I will remote access to.
2. `vm-ubuntu1804` in the `snet-gateway` where I will install Guacamole and use it as a jump server. I choose `B2s` size which cost around $39 per month but, of course, you can change its size later.

# Install Guacamole Server

Once your Ubuntu Server 18.04 is created then log in via SSH. You may need to expose SSH port publicly for now. You can change SSH port to something than 22 to make it more secure. See how to do it in my previous blog.

{% medium https://pacroy.medium.com/setting-up-your-own-vpn-server-with-just-5-a-month-c934cf073ea1 %}

From [its documentation](https://guacamole.apache.org/doc/gug/installing-guacamole.html), there's no executable binary available for Guacamole server and you need to build it from source (unless you deploy from Docker image).

First you need to install build tools and all required dependencies for the build process. Missing some of them may result in missing features or build failure.

```sh
# Update & upgrade system
sudo apt-get update && sudo apt-get --yes upgrade

# Install build tools
sudo apt install --yes build-essential

# Install build dependencies
sudo apt install --yes libcairo2-dev libjpeg-turbo8-dev libpng-dev libtool-bin libossp-uuid-dev

# Install optional dependencies
sudo apt install --yes \
    libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \
    freerdp2-dev \
    libpango1.0-dev \
    libssh2-1-dev \
    libtelnet-dev \
    libvncserver-dev \
    libwebsockets-dev \
    libpulse-dev \
    libssl-dev \
    libvorbis-dev \
    libwebp-dev

# Install runtime dependencies
sudo apt install --yes --no-install-recommends \
    netcat-openbsd                \
    ca-certificates               \
    ghostscript                   \
    fonts-liberation              \
    fonts-dejavu                  \
    xfonts-terminus
```

Next, download source codes and extract.

```sh
export GUAC_VERSION="1.3.0"
curl -fLO "https://downloads.apache.org/guacamole/${GUAC_VERSION}/source/guacamole-server-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-server-${GUAC_VERSION}.tar.gz"
```

Configure the build and start the build.

```sh
cd "guacamole-server-${GUAC_VERSION}"
./configure --with-init-dir=/etc/init.d
make
```

Once done, install and start the service.

```sh
sudo make install
sudo ldconfig
sudo systemctl daemon-reload
sudo systemctl start guacd
sudo systemctl enable guacd
```

At this point, the Guacamole server (guacd) service should be up and running. Inspect by executing `systemctl status guacd --no-pager`.

![Guacamole Server Service Status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8absf4ibgp8z964tli9w.png)
 

# Install Guacamole Client

Unlike the server, we don't need to build it (but you can if you want). So we will download the WAR file and install on Tomcat server then expose it through nginx via HTTPS.

## Install Tomcat

First, we need Tomcat to run the WAR file. Use this script to install it.

```sh
export TOMCAT_VERSION="8.5.65"
TOMCAT_MAJOR_VERSION=$(echo ${TOMCAT_VERSION} | awk -F . '{print $1}')
sudo apt-get install --yes default-jdk
sudo groupadd tomcat
sudo useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat
sudo usermod -a -G tomcat "$USER"
curl -LO "https://downloads.apache.org/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
sudo mkdir -p /opt/tomcat
sudo tar -xzf "apache-tomcat-${TOMCAT_VERSION}.tar.gz" -C /opt/tomcat --strip-components=1
sudo chgrp -R tomcat /opt/tomcat
cd /opt/tomcat
sudo chmod -R g+r conf
sudo chmod g+x conf
sudo chown -R tomcat webapps/ work/ temp/ logs/
```

Configure Tomcat and start the service.

```sh
JAVA_ALT_TEXT="$(update-java-alternatives -l || true)"
JAVA_HOME="$(echo "${JAVA_ALT_TEXT}" | awk '{print $3}')"
echo "[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

Environment=JAVA_HOME=${JAVA_HOME}
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/tomcat.service > /dev/null
sudo systemctl daemon-reload
sudo systemctl start tomcat
sudo systemctl enable tomcat
```

Now the Tomcat service should be up and running. Inspect by executing `systemctl status tomcat.service --no-pager`.

![Tomcat Service Status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8c1a8tbd591h0wgbsrt.png)
 
## Add Guacamole Client Servlet

Download and add the WAR file to the Tomcat.

```sh
curl -LO "https://downloads.apache.org/guacamole/${GUAC_VERSION}/binary/guacamole-${GUAC_VERSION}.war"
sudo cp "guacamole-${GUAC_VERSION}.war" "/opt/tomcat/webapps/ROOT.war"
sudo chown tomcat:tomcat "/opt/tomcat/webapps/ROOT.war"
sudo rm -rf /opt/tomcat/webapps/ROOT
```

You may try to access the client at `http://<your-server>:8080/` by either open or forward the port and you should see the Guacamole's login screen (but you can't login now).

![Guacamole Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okirc2h7agxiq27qaste.png)

In case you see Tomcat instead of Guacamole, restart Tomcat using command `sudo systemctl restart tomcat` and try again.

## Install nginx and certbot

We will use [nginx](https://www.nginx.com/) as a proxy and [certbot](https://certbot.eff.org/) to get a certificate from Let's Encrypt.

```sh
sudo apt install --yes nginx-core
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
```

Configure certbot with a domain and an email address and integrate with nginx.

```sh
export DOMAIN_NAME="<Your VM FQDN>"
export EMAIL="<Your Email Address>"
sudo certbot --nginx -d "${DOMAIN_NAME}" -m "${EMAIL}" --agree-tos -n
```

Edit the file `/etc/nginx/sites-enabled/default` and replace the following section:

```
    server_name your.server.fqdn; # managed by Certbot

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
```

with this one:

```
    server_name your.server.fqdn; # managed by Certbot

        location / {


                proxy_pass http://localhost:8080/;
                proxy_buffering off;
                proxy_http_version 1.1;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $http_connection;
                proxy_cookie_path /guacamole/ /;
                access_log off;
        }
```

This will set nginx as a proxy to your servlet. You don't need to configure to redirect HTTP to HTTPS as that is already done by certbot.

Test the configuration and restart nginx.

```sh
sudo nginx -t
sudo systemctl restart nginx
```

Now you should be able to access Guacamole ay `https://<your-server>/`

## Configure User and Connections

We will create a user with two connections in the `user-mapping.xml` file so we can test logging on.

Create the file `/etc/guacamole/user-mapping.xml` an put below content. Create the directory `/etc/guacamole` first if it does not exist.

```xml
<user-mapping>
    <authorize username="guacadmin" password="guacadmin">
        <connection name="this-server-ssh">
             <protocol>ssh</protocol>
             <param name="hostname">localhost</param>
             <param name="port">22</param>
        </connection>
        <connection name="some-win10-rdp">
             <protocol>rdp</protocol>
             <param name="hostname">vm-win10</param>
             <param name="port">3389</param>
             <param name="username">username</param>
             <param name="password">thisisyourpassword</param>
             <param name="ignore-cert">true</param>
        </connection>
    </authorize>
</user-mapping>
```

The first connection is SSH to local server itself while the second one is RDP to Windows 10 VM. Don't forget to update the username and password.

Restart Tomcat and test logging in and making the connections.

```sh
sudo systemctl restart tomcat
```

![Guacamole with Dummy Connections](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzthl1qgsbhacb59oc0p.png) 

If everything is configured properly, you should be able to connect to your VMs via either SSH and RDP. However, you cannot change anything on the web GUI the configuration is static in the file `user-mapping.xml`

![Guacamole Static Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ud6bvfeo2qw0ij2dgost.png) 

To make it editable via the web GUI, we need to install a database. In this example, I will use MySQL but Guacamole also supports other databases e.g. MariaDB, PostgreSQL. You can check for more information in [this documentation page](https://guacamole.apache.org/doc/gug/jdbc-auth.html).

# Install MySQL

Let's install MySQL.

```sh
sudo apt install --yes mysql-server
```

Check MySQL service status by executing `systemctl status mysql --no-pager`.

![MySQL Service Status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whmmn8kqfsr7xx8gn5se.png)

After install the MySQL, we also need to install the Guacamole extension and library so it knows how to talk to the database.

```sh
# Download and install JDBC extensions
curl -fLO "https://downloads.apache.org/guacamole/${GUAC_VERSION}/binary/guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/extensions
sudo cp "guacamole-auth-jdbc-${GUAC_VERSION}/mysql/guacamole-auth-jdbc-mysql-${GUAC_VERSION}.jar" "/etc/guacamole/extensions/"

# Download and install MySQL Connector/J
CONNECTORJ_VERSION="8.0.24"
curl -fLO "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-${CONNECTORJ_VERSION}.tar.gz"
tar -xvf "mysql-connector-java-${CONNECTORJ_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/lib
sudo cp "mysql-connector-java-${CONNECTORJ_VERSION}/mysql-connector-java-${CONNECTORJ_VERSION}.jar" "/etc/guacamole/lib/"
```

## Configure Database

Next, we need to create a database and a user. In this example, I will create the database named `guacamole_db` and the user named `guacamole_user`. Please note the password must meet complexity criteria.

```sh
MYSQL_PASSWORD="<YourPasswordHere>"
sudo mysql --execute='CREATE DATABASE guacamole_db;'
sudo mysql --execute="CREATE USER 'guacamole_user'@'localhost' IDENTIFIED BY '${MYSQL_PASSWORD}';"
sudo mysql --execute="GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'localhost';"
sudo mysql --execute='FLUSH PRIVILEGES;'
```

Then run the provides scripts to create schema and the Guacamole default user `guacadmin`.

```sh
cat guacamole-auth-jdbc-1.3.0/mysql/schema/*.sql | sudo mysql guacamole_db
```

Lastly, you need to configure Guacamole client so it can connect to the database by creating file `/etc/guacamole/guacamole.properties` and put the following content.

```yaml
# MySQL properties
mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole_db
mysql-username: guacamole_user
mysql-password: <YourPasswordHere>
```

If you configure the `user-mapping.xml` file before then you may no longer need that so remove it.

```sh
sudo rm -f /etc/guacamole/user-mapping.xml
```

Restart Tomcat and test logging in again using default username and password i.e. `guacadmin`.

```sh
sudo systemctl restart tomcat
```

Now, you should be able to make changes in the Settings. It is recommended you create a new user and then remove the `guacadmin` as soon as possible.

![Guacamole Dynamic Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ul497mrr8pedfsel6r6v.png)

# Enable Two-Factor Authentication

If you want you can optionally install TOTP module to enable two-factor authentication to add more security to your VM pool. Just download the extension and restart Tomcat.

```sh
# Download and install TOTP extension
curl -fLO "https://downloads.apache.org/guacamole/${GUAC_VERSION}/binary/guacamole-auth-totp-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-auth-totp-${GUAC_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/extensions
sudo cp "guacamole-auth-totp-${GUAC_VERSION}/guacamole-auth-totp-${GUAC_VERSION}.jar" "/etc/guacamole/extensions/"

# Restart tomcat
sudo systemctl restart tomcat
```

Now, when you try logging in again, it will ask you to setup an authenticator and require you to input in every time you log in.

![Multi-factor Authentication Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ogujgoikwc4m2wwp1m99.png)
 

# Scripts

If you don't want to perform all above steps one by one, you may leverage bash scripts I created in this repository.

{% github pacroy/guacamole-setup-script no-readme %}

Use at your own risk!

---

_Cover Image by <a href="https://unsplash.com/@footluz?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Footluz Blog</a> on <a href="https://unsplash.com/s/photos/bastion?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>_