Create Your Own Azure Bastion with Guacamole and Save $100+ a month
Chairat Onyaem (Par)
Posted on April 30, 2021
Microsoft Azure provides 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:
This service is great except it costs $0.19 per hour. 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 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, 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.
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.
For more information about Guacamole, visit its architecture page.
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 or using this Helm chart 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:
In my virtual network (VNet), I split into two subnets:
-
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. -
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. But in this example, I just associate NSG to the VM directly.
Once you setup your network then create the VMs. In this example, I create the following two VMs:
-
vm-win10
in thesnet-default
which is the machine I will remote access to. -
vm-ubuntu1804
in thesnet-gateway
where I will install Guacamole and use it as a jump server. I chooseB2s
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.
Setting Up Your Own VPN Server with just $5 a month | by Chairat Onyaem (Par) | Medium
Chairat Onyaem (Par) ・ ・
pacroy.Medium
From its documentation, 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.
# 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.
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.
cd "guacamole-server-${GUAC_VERSION}"
./configure --with-init-dir=/etc/init.d
make
Once done, install and start the service.
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
.
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.
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.
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
.
Add Guacamole Client Servlet
Download and add the WAR file to the Tomcat.
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).
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 as a proxy and certbot to get a certificate from Let's Encrypt.
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.
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.
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.
<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.
sudo systemctl restart tomcat
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
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.
Install MySQL
Let's install MySQL.
sudo apt install --yes mysql-server
Check MySQL service status by executing systemctl status mysql --no-pager
.
After install the MySQL, we also need to install the Guacamole extension and library so it knows how to talk to the database.
# 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.
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
.
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.
# 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.
sudo rm -f /etc/guacamole/user-mapping.xml
Restart Tomcat and test logging in again using default username and password i.e. guacadmin
.
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.
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.
# 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.
Scripts
If you don't want to perform all above steps one by one, you may leverage bash scripts I created in this repository.
pacroy / guacamole-setup-script
Guacamole Setup Script for Ubuntu Linux Server
Use at your own risk!
Cover Image by Footluz Blog on Unsplash
Posted on April 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.