In Home Assistant v2021.2, Home Assistant announced the Z-Wave integration as deprecated in favor of a new integration called Z-Wave JS. In Home Assistant v2021.3, many fixes were implemented, with the notable limitation of Door Sensors being removed.
More and more people were concerned about the future of Z-Wave with Home Assistant; meanwhile the Z-Wave JS project was rapidly growing and gathering a large community around it. Long story short: Home Assistant and Z-Wave JS teamed up! And a lot of contributors jumped on the train!
This new integration is based on the same base principles as the OpenZWave integration: It is decoupled from Home Assistant. Instead of MQTT, the Z-Wave JS integration uses a WebSocket connection to a Z-Wave JS server.
This means, in order to use this new integration, you’ll need to run the Z-Wave JS server that sits in between your Z-Wave USB stick and Home Assistant. There are multiple options available for running the Z-Wave JS server, via Docker or manually, and there is also a Home Assistant add-on available.
So how do I upgrade?
This article reflects the steps I took to update my Z-Wave implementation.
Ensure you are running Home Assistant v2021.3.2 or greater
This will ensure you have support for most all sensors. You can find your Home Assistant version by selecting the Configuration gear on the left menu, and then selecting Info
Here you should see the version of Home Assistant (in my case 2021.3.2)
It's rather critical to create a backup, especially in this case if you need to roll back to the older OpenZWave integration if you find many of your devices not being compatible. One downside in not using Home Assistant's OS is you don't have the "Supervisor" option to create a full backup.
Execute the following commands against your machine:
sudo sh -c 'apt update && apt upgrade'
Make sure you restart your machine to ensure your kernel updates to the latest version:
sudo shutdown -r -t now
Document Z-Wave entity IDs
The easiest way to do this is to navigate to Developer Tools (hammer icon on the left menu) and then type node_id into the Attributes column's filter.
In this case, you'll want to write down the node_id and the name of the entity it maps to. If you want to do this quickly, you can single click on the table, press Control + A to select all contents, or cmd+a on a Mac, and copy the contents into Word or Excel (Excel works remarkably well).
Document & Comment Z-Wave Stick Hardware ID and Network Key
SSH to your server and find your configuration.yaml file (if using my tutorial it should be /home/docker/home-assistant/configuration.yaml). Open the file in vi
sudo vi configuration.yaml
Find the section of code labeled zwave: and copy the information (we'll need it later) as well as comment out the following lines like so:
Type :wq to write the changes to the file and quit vi
Uninstall Z-Wave integration
Navigate to Configuration -> Integrations
Click the three dots on the Z-Wave integration and select Delete
Click OK when prompted
Click OK on the prompt that you should restart home assistant (it won't restart home assistant at this point)
Restart Home Assistant
While we can restart Home Assistant from the web UI, we need to ensure that the Docker container running home assistant no longer needs access to your Z-Wave stick directly (Z-Wave JS Server will be what interfaces with the device directly). In this case, you will need to SSH into your Home Assistant server and stop / remove / start the container accordingly.
Stop the Docker container
sudo docker stop home-assistant
Remove the container
sudo docker rm home-assistant
Deploy the new container configuration, which removes any device mappings to your Z-Wave stick/device
Disable MQTT Gateway: Can be enabled if you have no use for MQTT
Click the Home Assistant menu and set the following:
WS Server: Enabled
Server Port: 3000
Click SAVE
Verify you see devices
Click on the Control Panel icon on the top left of the Z-Wave JS Web UI. Verify that you see the amount of devices you previously had.
At this point, I would recommend waiting a few minutes / possibly hours to let the table populate with all the device information.
Install Z-Wave JS Integration
I would recommend a full refresh of the web page for Home Assistant and then navigate back to Configuration -> Integrations
Click the Add Integration button and search for Z-Wave JS
Click Submit to accept the URL as-is (assuming you are running the container on the same server running the Home Assistant container; if not, you can specify the IP address of the server hosting the Z-Wave JS Server container as well).
If all went well, you should see your Z-Wave devices and you can click Finish (Note: I wouldn't worry about specifying Areas since it's likely you have no idea what device is what at this point)
Update your Z-Wave Device Names in Home Assistant
The last step is to update your device names to match your existing device names. To do this, on the Configuration -> Integrations page, select the devices link on the Z-Wave JS integration tile
Next, select one of the items in your list. In my case, I'm going to select the first 1000W Dimmer I have.
On the device, you should see Node ID. This can be looked up on your list of devices you exported in the previous steps.
Click the Device Name (in my case 1000W Dimmer) and specify the correct information for the device. Once done, click Update
Rinse and repeat
Go through each of the devices you have and update their corresponding names. If you click Advanced settings, you can specify the area for the device as well.
Congrats!
If you've made it this far, you have successfully migrated to the latest Z-Wave integration for Home Assistant!
Many Azure services allow you to bring your own SSL Certificate to the cloud. While Azure provides an easy way to create and deploy resources through ARM templates, specification of what SSL certificate is a little less trivial since it's not as easy to specify an exported PEM or PFX file. In this case, Azure may look for the certificate in a base64 encoded format, so the certificate can be passed as a string (or list of characters) into the template.
Goal of this tutorial
This tutorial will walk through the commands needed to generate a self-signed certificate that is base64 encoded via PowerShell (Option 1) or base64 encode an existing PFX (Option 2), so that the certificate can be passed as a parameter into ARM templates in Azure.
Option 1: Generate and encode a self-signed certificate
At this point, if you open selfSignedCertificate.txt, you should see a long list of characters compromised of letters, numbers, and a few symbols, which is your base64 version of your certificate. See example below (...s denote I removed a large portion of the text, you won't see that in your file).
This text can be used-as within your templates now (although, in general, try to never code these values into your templates, these values should be passed as parameters into the template).
Here's a quick cheat sheet on recommended subnet sizing for Azure. Items in bold are subnet names reserved by the platform for their corresponding service.
Continuing from my previous guide on how to setup Home Assistant + Docker + Z-Wave + Raspberry Pi, this tutorial will show you how to update Home Assistant to the latest version. Updating Home Assistant to the latest version is critical to ensure you have the latest bug fixes, integrations, and security patches.
Note: during the update your devices will continue to work fine, but please note any automations or access to the application will not be available, so it's recommended to do this during a time that you know no automations will be running.
Validate your current version
Navigate to the Developer Tools section of Home Assistant. Here you can validate the latest version you currently have deployed.
Get the current name of your container and version
sudo docker ps
In running this command, note the NAME of your container as well as the IMAGE.
Stop and delete the container
Replace the name of the container in the command below with the value you had.
Make sure your replace the name and value of the image with the values in the previous step. In addition, ensure you specify the correct path to where you existing configuration files exist to have the container load your existing configurations.
Note: If you are now using the Z-Wave JS docker container, you will not want to attach the Z-Wave stick to the Home-Assistant container. In this case, run the following command:
In newer versions of the docker container --init should not be specified in the docker run command. Specifying --init will result with the following error: "s6-overlay-suexec: fatal: can only run as pid 1". This was mentioned as a breaking change in: 2022-06-01 update: 2022.6: Gaining new insights! - Home Assistant (home-assistant.io)
Notice: Home Assistant has released a new integration called Z-Wave JS. You should be using that integration vs the older Z-Wave integration that this article covers. I will be updating this guide soon.
A few years back I had a SmartThings Hub and for the most part it worked great. It was simple to setup, can be accessed anywhere, and for the most part automatically updated itself. Unfortunately, with the acquisition of it by Samsung, it seems to have turned into bloatware with poor responsiveness, the mobile application's UI is horrific, and they have a less than desirable security/privacy policy.
Luckily, the open source community has thrown together Home Assistant, an open source home automation project backed by hundreds/thousands of individuals. Over the years, they have now brought native support for mobile devices, at time of writing this there are 1500+ integrations for dang near any device, and the software puts you in control of who has access to and where your data is accessible.
The one trade-off though is while Home Assistant works well and is very extensible, the documentation and usability of the application can be overwhelming to understand for someone new to home automation, unfamiliar with Linux/Open Source technologies, or new to debugging/command line interfaces.
In this case, I've tried to document a crash course in getting Home Assistant up and running as quickly as possible for those that want to get started with Z-Wave devices and Home Assistant.
Home Assistant will run on any version of Raspberry Pi, but it is recommended to use version 3 or 4 for best performance. In this guide, I use a Raspberry Pi 4 for reference. Below is a link to the Raspberry Pi kit, which contains everything you need to get started.
First things first, update your Raspberry Pi with the latest updates. Open up Terminal or SSH to your Raspberry Pi and execute the following command:
sudo apt-get update && sudo apt-get upgrade
Prepare your Z-Wave USB Stick
Plug in your Z-Wave USB stick. Once plugged in, we need to find the device path so that we can reference it for Home Assistant. Execute the lsusb command to find your device ID. In this case, you can see my device ID begins with 0658.
root@raspberrypi:/dev# lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 0658:0200 Sigma Designs, Inc. Aeotec Z-Stick Gen5 (ZW090) - UZB
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Next, let's find what the device path is for the USB stick. You can do this by executing the following command: dmesg | egrep '0658|acm' Please note, if you purchased a difference device, 0658 may be a different number. In this case, you can see my device is presented on ttyACM0.
root@raspberrypi:/dev# dmesg | egrep '0658|acm'
[ 1.405327] usb 1-1.2: New USB device found, idVendor=0658, idProduct=0200, bcdDevice= 0.00
[ 3.468875] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device
[ 3.471348] usbcore: registered new interface driver cdc_acm
[ 3.471359] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
Install Docker
Home Assistant doesn't require Docker, but by leveraging Docker you can easily copy/backup your configuration and simply redeploy the container if something goes wrong. As updates are made, you can simply remove your container and redeploy. To install Docker, execute the following command:
curl -sSL https://get.docker.com | sh
Deploy Home Assistant Docker Container
Once Docker is installed, you can deploy the container from Docker Hub. Docker Hub is a public repository that has tons of different prebuilt containers to deploy. Here you can find the official homeassistant containers: https://hub.docker.com/u/homeassistant
To deploy the container, execute the following line, replacing the following variables with your desired configuration:
This allows the container to leverage the Z-Wave USB device. Make sure you specify the path to your device found in the previous step
-v /home/docker/home-assistant:/config
This is the path that the home assistant configuration files should be stored to. You can specify a fileshare or other path to place your configuration files.
The first half of this is the container you wish to deploy and the second half is the version. You can find all of Home Assistant's official containers here: https://hub.docker.com/u/homeassistant
Note: In newer versions of the docker container --init should not be specified in the docker run command. Specifying --init will result with the following error: "s6-overlay-suexec: fatal: can only run as pid 1". This was mentioned as a breaking change in: 2022-06-01 update: 2022.6: Gaining new insights! - Home Assistant (home-assistant.io)
Setup Home Assistant
Give the container a few minutes to deploy and configure itself for the first time. After a few minutes, try opening your web browser and navigating to the IP address assigned to your machine, using port number 8123: http://192.168.1.2:8123/
When the page loads, it should first ask for your Name, Username, and Password. This is the username and password you will use to login to Home Assistant.
Next, specify the location of where your Home Assistant deployment is located. Oddly enough, you cannot type in a location, but you can place the pin near your location by dragging the map around and clicking once to set the pin.
Once you click Next, Home Assistant may have already found a few devices connected to your network. You can add them now or skip and add them later.
Tell Home Assistant to use your Z-Wave USB Stick
Although we granted access to the container to use the Z-Wave USB Stick, you need to tell Home Assistant how to leverage the device. To do so, you will need to open up Terminal or SSH to your machine and edit the configuration.yaml file to point to the device. Before we get into modifying the configuration.yaml file, first execute the following command to generate a Z-Wave Security Key. This key may be required by Z-Wave security devices (Door Locks, Keypads, etc), as an extra layer of security. More information on this can be found here: https://www.home-assistant.io/docs/z-wave/adding#network-key
Execute the following command via Terminal or SSH:
Next, we need to edit the configuration.yaml file, which can be found in the path specified when the Docker container was deployed (using the -v parameter). For the purpose of this article, /home/docker/home-assistant/configuration.yaml is where the file is located. Using your favorite text editor, add the following lines of code:
Once saved, go back to Home Assistant and click the Gear icon and then select Server Controls
Select the Restart button to restart Home Assistant. Any time you make a change to the configuration.yaml file, you will need to restart Home Assistant to pickup the configuration changes.
Click OK to Restart
Upon restart, navigate back to the Gear icon and you should see a new entry in the Config portal for Z-Wave. If you do not see the "Z-Wave" section, scroll down to the troubleshooting step at the end of this article.
Add a Z-Wave device
Once you see that your Z-Wave network has started, adding a device is a piece of cake. First click the Add Node button. When you click the button, nothing will happen, but go ahead and put your device in inclusion mode. Once the device is in inclusion mode, Home Assistant should automatically add the device.
At this point, if you navigate back to Configuration (Gear icon) and select Devices
You should see your newly added Z-Wave device!
At this point, you can select the Device to give it a friendly name or start to work on building your own home automation actions.
Hope this helped! If you have any comments or suggestions on how to improve this guide, please drop it below.
Troubleshooting Missing Z-Wave Configuration
The first time I ran through this, I noticed I was missing the Z-Wave configuration tile after making changes to the configuration.yaml file. It turned out I specified the wrong device path in the configuration file. To verify, you can check the logs from your Docker container by executing the following command in your Terminal or via SSH. (Replace home-assistant with the name of your container if you specified something else)
sudo docker logs home-assistant
In my case, I had the following error:
2020-02-16 21:08:01 INFO (MainThread) [homeassistant.components.scene] Setting up scene.homeassistant
2020-02-16 21:08:02 INFO (MainThread) [homeassistant.components.zwave] Z-Wave USB path is /dev/ttyACM01
2020-02-16 21:08:02 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Z-Wave (import from configuration.yaml) for zwave
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 78, in __init__
raise ZWaveException(u"Can't find device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))
openzwave.object.ZWaveException: "Zwave Generic Exception : Can't find device /dev/ttyACM01 : ['NoneType: None\\n']"
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 215, in async_setup
hass, self
File "/usr/src/homeassistant/homeassistant/components/zwave/__init__.py", line 369, in async_setup_entry
config_path=config.get(CONF_CONFIG_PATH),
File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 81, in __init__
raise ZWaveException(u"Error when retrieving device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))
openzwave.object.ZWaveException: 'Zwave Generic Exception : Error when retrieving device /dev/ttyACM01 : [\'Traceback (most recent call last):\\n\', \' File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 78, in __init__\\n raise ZWaveException(u"Can\\\'t find device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))\\n\', \'openzwave.object.ZWaveException: "Zwave Generic Exception : Can\\\'t find device /dev/ttyACM01 : [\\\'NoneType: None\\\\\\\\n\\\']"\\n\']'
Here you can see I accidentally specified /dev/ttyACM01 vs /dev/ttyACM0. Simply updating the configuration.yaml file with the correct device path solved the issue.
This tutorial will review how to create a bootable USB drive to flash the fimrware/bios on your Lenovo device.
Before we begin, Lenovo offers three different downloads for Firmware today:
Windows installer/flash utility (.exe)
CD ISO version (.iso) to burn to a disk
USB Flash Package (.zip)
While the USB Flash Package (.zip) is exactly what we are looking for, by default if you just drag the files onto your USB drive, it won't boot to the flash utility. In this case, the instructions below will show you have to make the drive bootable and then launch the USB Flash Package.
Make a bootable drive
First, you will want to download a copy of the Rufus utility. This utility is an open source utility for Windows only, but will allow you to make a bootable USB drive. You can obtain a copy of the utility here. Rufus' website can officially be found here: https://rufus.ie/
Once installed, open the application. Select your USB device you wish to flash (note this will erase all data on your device) and set the Boot selection to FreeDOS. Once your Device and Boot selection has been set, go ahead and click Start to flash the device.
You will be prompted to confirm you are OK with erasing the device. Go ahead and click OK if you are sure you have selected the correct device in the prior step.
Once completed, you should see a green bar that says READY. This is kinda misleading, wish it would say completed, but your device should be flashed at this point.
Download the right firmware from Lenovo
As mentioned earlier, Lenovo offers 3 different types of downloads on their website. You will want a copy of the zipped installer as shown in the screenshot below.
Once downloaded, navigate to where you downloaded the zipped file, right click, and select Extract All... If you don't see Extract All... then try downloading a copy of 7-Zip, which is a fantastic free archiver solution that can open all types of compressed files (zip, 7zip, tar.gz, etc)
Copy the extracted files to your bootable USB drive
Once you have extracted the files from the zipped folder from Lenovo, you will want to copy and paste the files from the extracted directory to the bootable USB drive. To show visually, I opened two file explorer windows, one in the directory of the extracted firmware and the other on the bootable USB drive. I simply dragged and dropped the files from the firmware directory to the bootable USB drive.
When you try to copy the files from the firmware directory to the bootable USB drive, you will be prompted to replace AUTOEXEC.BAT. Make sure to Replace the file in the destination as this will execute the command to launch the flash2 utility, which actually writes the firmware to the device.
Plug in the drive and set the device to boot to it
At this point, you should have a bootable USB device that you can now plugin to your Lenovo device. You can unplug it from your client machine and plug it in to your Lenovo device. Make sure you set your Lenovo device to boot from the USB drive (this can usually be set by pressing the F1 or F2 keys during the post screen).
What to expect
Upon boot, you should be greeted by the Lenovo flash utility, which will ask if you want to update your device. Please note, that in my experience, once I select yes the device needed to reboot several times and may boot into the BIOS. The utility will tell you when everything is completed, so make sure you don't power down your device or unplug your USB drive after the first or second reboot, make sure you wait things out. As with updating any firmware, make sure you don't do this in a storm or on a device with low battery as you ensuring little chance of disruption as possible is absolutely critical.
Summary
At this point, you should have a bootable USB drive created by Rufus and FreeDOS that can be paired with Lenovo's firmware to go around and flash your devices. Hope this helps!
In my career in doing IT, handling email is one of the most tedious tasks to setup/maintain due to so many moving pieces; many of which may be out of your control. Dealing with spam, blacklisting, having emails non-deliverable for several reasons, handling dns records, certificates, etc.... it's sometimes worth paying a few extra bucks to have someone else host your email and have peace of mind the message will be delivered. That being said, if you have the extra time on your hands and like the challenge of solving problems, here's a quick way to get started.
Preamble
This guide took me several hours to compile through trial and error. If you have any thoughts, notice any errors/typos, or have ideas on how to further secure/optimize, please leave feedback below to further improve this guide. Thank you and good luck on the deployment of your mail server!
You are running Ubuntu or Debian as per the above guide (you can still follow this guide, you may have to slightly change which commands you use for your distribution -- configuration should remain the same though)
DNS
Let's first start at getting your DNS records configured properly. This guide will talk about configuring MX, SPF, and PTR records. We won't be covering Domain Keys in this article, maybe in a separate article if someone donates to my paypal on the right side of the website 😉
MX Record
Via your nameservers, add a new mx record for your domain name. Here's a list of tutorials for some of the major domain registrars:
Contrary to many websites that say you need to create a "SPF" record type, the SPF record type was never ratified by RFC standards. In this case, the proper way to create a SPF record is via a TXT record with the SPF value (as per RFC 7208).
You can leverage my SPF generator to create a new TXT record in the root of your domain.
PTR Record
To help decrease the odds of your emails being labeled as spam, I'd recommend creating a PTR record that will resolve your IP address to a DNS name (we call this a reverse lookup). For example, if my mail server's domain name was mail.mydomain.com and it resolved to 123.123.123.123, I would create a PTR record for 123.123.123.123 that points to mail.mydomain.com.
In many cases, you will need to either work with your ISP (Internet Service Provider) or domain registrar if you own your own IP block to make changes to the record for your IP address block.
When you are ready, you can leverage the nslookup command on Windows to validate the name from the IP address.
nslookup 123.123.123.123
Or on linux you can leverage the host command to verify the reverse lookup as well:
host 123.123.123.123
Get the OS ready
Download the latest packages and actually perform any updates.
sudo sh -c 'apt update && apt upgrade'
Prepare MariaDB for virtual users/aliases
One of the primary reasons we need to configure a database is it is what will contain the information about all of our users and their corresponding email addresses (aliases). To do so, we need to create 3 new tables inside of a new database.
Login to the database
sudo mariadb -u root -p
Create the database, database user, and tables
Create a new database for our users (in this case, I'm calling the database mailserver). Note: This command must be run in the context of mariadb, this is not a bash command.
create database mailserver;
Create a new user called mailuser, grant them access to the entire database, require the user to only create connections from 127.0.0.1 (localhost), and specify a password for the user.
GRANT SELECT ON mailserver.* TO 'mailuser'@'127.0.0.1' IDENTIFIED BY 'mysupersecretpassword';
Execute the following command to apply the changes
FLUSH PRIVILEGES;
Create a table for each of the domain names we will leverage for our email addresses.
CREATE TABLE `mailserver`.`virtual_domains` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create a table that will hold each of the users that will need mailboxes.
CREATE TABLE `mailserver`.`virtual_users` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`password` varchar(106) NOT NULL,
`email` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create a table that will hold aliases (additional email addresses) for a particular user.
CREATE TABLE `mailserver`.`virtual_aliases` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Insert a new user into the database
First, we need to add our first domain name into the domains table
INSERT INTO `mailserver`.`virtual_domains`
(`name`)
VALUES
('mydomain.com');
Second, we need to create the user. Replace mysupersecretpassword with your password.
Type exit once you are done to leave the context of MariaDB.
Install Packages for Postfix and Dovecot
Postfix is what we call a Mail Transport Agent (MTA) and is responsible for actually sending/receive the messages from the internet. Later, we will talk about Dovecot which will be our MDA (Mail Delivery Agent) (what actually interacts with the mailbox).
The following command will install postfix, dovecot, and pull the packages to interact with MySQL. Although these are labeled MySQL, they should interact fine with MariaDB.
During the installation of Postfix, you will be prompted to configure the connection type to the mail server. In this case, select Internet Site for the mail configuration.
On the second installation prompt, it will ask for the domain name used in receiving email. In this prompt, specify one of the domain names you will be using for your users. For example, if your email addresses are going to be [email protected] you would specify mydomain.com for this prompt. Don't worry if you have multiple email addresses, we will cover that later on.
Configure Postfix to leverage MariaDB
First, let's create a backup of the Postfix configuration, so we have a baseline to refer back to.
Copy the following configuration and replace the domain name example.com with yours. Credit to linode for sharing their configuration as it not only defines integration into a database, but also hardens the Postfix deployment.
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/mydomain.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mydomain.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Authentication
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
# Restrictions
smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
reject_unlisted_recipient,
reject_unauth_destination
smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_unknown_sender_domain
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
defer_unauth_destination
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
myhostname = example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = mydomain.com
myorigin = $mydomain
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Virtual domains, users, and aliases
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-users.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-mailbox-aliases.cf,
mysql:/etc/postfix/mysql-virtual-mailbox-users.cf
# Even more Restrictions and MTA params
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
#smtpd_etrn_restrictions = reject
#smtpd_reject_unlisted_sender = yes
#smtpd_reject_unlisted_recipient = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtp_always_send_ehlo = yes
#smtpd_hard_error_limit = 1
smtpd_timeout = 30s
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s
smtpd_recipient_limit = 40
minimal_backoff_time = 180s
maximal_backoff_time = 3h
# Reply Rejection Codes
invalid_hostname_reject_code = 550
non_fqdn_reject_code = 550
unknown_address_reject_code = 550
unknown_client_reject_code = 550
unknown_hostname_reject_code = 550
unverified_recipient_reject_code = 550
unverified_sender_reject_code = 550
Next, we need to create the mappings of domain names, users, and aliases. In the same directory as the main.cf (/etc/postfix) we need to first create a file that will tell postfix how to lookup what domain names exist. You can open the documents with your favorite text editor; I use vi since it's universally installed.
sudo vi /etc/postfix/mysql-virtual-mailbox-domains.cf
Press i to get vi into insert mode and paste the following, replacing the password with the mailuser we specified earlier in this tutorial.
user = mailuser
password = mysupersecretpassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Next, we will create another file that is used to lookup each user's mailbox.
sudo vi /etc/postfix/mysql-virtual-mailbox-users.cf
Press i to get vi into insert mode and paste the following, replacing the password with the mailuser we specified earlier in this tutorial.
user = mailuser
password = mysupersecretpassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT email FROM virtual_users WHERE email='%s'
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Last, we will create another file that is used to map an alias to a user's mailbox.
sudo vi /etc/postfix/mysql-virtual-mailbox-aliases.cf
Press i to get vi into insert mode and paste the following, replacing the password with the mailuser we specified earlier in this tutorial.
user = mailuser
password = mysupersecretpassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Restart the Postfix service for the changes to take effect
sudo service postfix restart
Next, to enable port 587 and 465 to connect securely with email clients, we need to modify /etc/postfix/master.cf. First, let's create a backup of the master.cf file.
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Restart the Postfix service for the changes to take effect
sudo service postfix restart
Configure Dovecot
Now that we have our MTA configured, we now need to configure our MDA. You can think of Postfix as a shipping center and Dovecot as the courier, who interfaces directly with your mailbox. Roundcube will be our MUA (mail user agent) that interfaces with Dovecot to display your mail. The goal for this section is to ensure Dovecot requires SSL.
First, we'll create backups of each of the Dovecot configuration files
Next we need to edit the user authentication file (/etc/dovecot/conf.d/10-auth.conf) to tell Dovecat to leverage MariaDB for our users. Execute the following commands:
sudo sed -i 's/^#disable_plaintext_auth = yes/disable_plaintext_auth = yes/g' /etc/dovecot/conf.d/10-auth.conf
sudo sed -i 's/^#auth_mechanisms = plain login/auth_mechanisms = plain login/g' /etc/dovecot/conf.d/10-auth.conf
sudo sed -i 's/^!include auth-system.conf.ext/#!include auth-system.conf.ext/g' /etc/dovecot/conf.d/10-auth.conf
sudo sed -i 's/^#!include auth-sql.conf.ext/!include auth-sql.conf.ext/g' /etc/dovecot/conf.d/10-auth.conf
Once we have the authentication file configured, we need to update the sql driver (/etc/dovecot/conf.d/auth-sql.conf.ext) to point to our mailboxes. You will need to uncomment the passdb section and uncomment the userdb driver that is static.
sudo vi /etc/dovecot/conf.d/auth-sql.conf.ext
Press i to get vi into insert mode and paste the following configuration
# Authentication for SQL users. Included from 10-auth.conf.
#
# <doc/wiki/AuthDatabase.SQL.txt>
passdb {
driver = sql
# Path for SQL configuration file, see example-config/dovecot-sql.conf.ext
args = /etc/dovecot/dovecot-sql.conf.ext
}
# "prefetch" user database means that the passdb already provided the
# needed information and there's no need to do a separate userdb lookup.
# <doc/wiki/UserDatabase.Prefetch.txt>
#userdb {
# driver = prefetch
#}
#userdb {
# driver = sql
# args = /etc/dovecot/dovecot-sql.conf.ext
#}
# If you don't have any user-specific settings, you can avoid the user_query
# by using userdb static instead of userdb sql, for example:
# <doc/wiki/UserDatabase.Static.txt>
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
Press : and then type wq and press enter to write the changes to the file and quit in vi.
The final Dovecot file we need to modify will set our database settings (/etc/dovecot/dovecot-sql.conf.ext). Execute the following commands to uncomment the correct settings. Note: be sure to replace the password with the database password we configured earlier.
sudo sed -i 's/^#driver = /driver = mysql/g' /etc/dovecot/dovecot-sql.conf.ext
sudo sed -i 's/^#connect =/connect = host=127.0.0.1 dbname=mailserver user=mailuser password=mysupersecretpassword/g' /etc/dovecot/dovecot-sql.conf.ext
sudo sed -i 's/^#default_pass_scheme = MD5/default_pass_scheme = SHA512-CRYPT/g' /etc/dovecot/dovecot-sql.conf.ext
sudo sed -i '/^#password_query = \\/i password_query = SELECT email as user, password FROM virtual_users WHERE email=\x27%u\x27;' /etc/dovecot/dovecot-sql.conf.ext
After making the changes to the dovecot-sql.conf.ext file, next we need to change the owner and the group of the dovecot folder to the vmail user:
Next, we need to disable the unencrypted versions of IMAP and SMTP.
sudo vi /etc/dovecot/conf.d/10-master.conf
We need to edit the /etc/dovecot/conf.d/10-master.conf file and set ports to 0 to disable non-encrypted imap/pop3. Find service imap-login { and make it look like the following.
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
# Number of connections to handle before starting a new process. Typically
# the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
# is faster. <doc/wiki/LoginProcess.txt>
#service_count = 1
# Number of processes to always keep waiting for more connections.
#process_min_avail = 0
# If you set service_count=0, you probably need to grow this.
#vsz_limit = $default_vsz_limit
}
service pop3-login {
inet_listener pop3 {
port = 0
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
In the same file, find service lmtp { and replace the whole block down to the third } with the following:
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
# Create inet listener only if you can't use the above UNIX socket
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}
In the same file, find service auth { and replace the whole block down to the third } with the following:
service auth {
# auth_socket_path points to this userdb socket by default. It's typically
# used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
# full permissions to this socket are able to get a list of all usernames and
# get the results of everyone's userdb lookups.
#
# The default 0666 mode allows anyone to connect to the socket, but the
# userdb lookups will succeed only if the userdb returns an "uid" field that
# matches the caller process's UID. Also if caller's uid or gid matches the
# socket's uid or gid the lookup succeeds. Anything else causes a failure.
#
# To give the caller full permissions to lookup all users, set the mode to
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
mode = 0600
user = vmail
#group =
}
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0600
user = postfix
group = postfix
}
# Auth process is run as this user.
user = dovecot
}
In the same file, find service auth-worker { and replace the whole block down to the } with the following:
service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $default_internal_user.
user = vmail
}
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Last, we need to tell dovecot where our SSL certificate is for encryption. We will modify the /etc/dovecot/conf.d/10-ssl.conf file. Make sure to update the directory with the correct path for your certificates.
Execute the following commands, replacing
sudo sed -i 's/^ssl = yes/ssl = required/g' /etc/dovecot/conf.d/10-ssl.conf
sudo sed -i 's/^ssl_cert = .*/ssl_cert = <\/etc\/letsencrypt\/live\/mydomain.com\/fullchain.pem/g' /etc/dovecot/conf.d/10-ssl.conf
sudo sed -i 's/^ssl_key = .*/ssl_key = <\/etc\/letsencrypt\/live\/mydomain.com\/privkey.pem/g' /etc/dovecot/conf.d/10-ssl.conf
Last, restart devocot to enable all of our changes.
sudo service dovecot restart
Configure Roundcube
Install dependencies for Roundcube
Roundcube requires several PHP PEAR modules. To install the bare minimum featureset, execute the following command:
First, we need to create a new database and user for Roundcube. We can do this by logging into MariaDB and executing the create and grant commands.
sudo mariadb -u myusername -p
CREATE DATABASE roundcubemail CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost IDENTIFIED BY 'myreallyreallysecretpassword';
FLUSH PRIVILEGES;
exit
Request SSL Certificates for Roundcube
We will want to ensure all traffic to and from the client is encrypted in transit when trying to access Roundcube. To do this, I leverage Let's Encrypt, which will allow you to request a free SSL certificate. If you have your own SSL certificate, go ahead and copy it to a location on the server so we can reference it later.
We will need to create a directory that will hold Roundcube's files to serve to the web. Let's create a new directory to serve these files and limit permissions to www-data.
We will need to grab the latest copy of Roundcube's code to run the website. Note: please ensure you substitute the correct version for Roundcube when executing the commands below as the version listed in the guide will likely be out of date as time goes on:
cd /tmp
wget https://github.com/roundcube/roundcubemail/releases/download/1.4.1/roundcubemail-1.4.1.tar.gz
tar -xf roundcubemail-1.4.1.tar.gz
mv roundcubemail-1.4.1 /var/www/webmail.mydomain.com
Populate the SQL Database
You will need to execute the following SQL command to populate your Roundcube database with the tables needed to run Roundcube. To do so, execute the following commands.
Roundcube doesn't ship with several javascript dependencies. To ensure the Roundcube pages load properly, you will need to execute the following command to pull down the javascript dependencies.
Let's configure NGINX to point to our web directory for the website. When doing so, it is very important you protect your installation by preventing access to some sensitive files from the web.
First, create a virtual-host file within the nginx sites-available folder:
sudo vi /etc/nginx/sites-available/webmail.mydomain.com
Press i to get vi into insert mode and paste the following. Note: Please replace the values with the path to your SSL Certificate we generated earlier.
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or WordPress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name webmail.mydomain.com;
ssl_certificate /etc/letsencrypt/live/webmail.mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webmail.mydomain.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/webmail.mydomain.com/chain.pem;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/webmail.mydomain.com;
# Add index.php to the list if you are using PHP
index index.php index.html index.htm;
# Revoke access to sensitive files and directories
location ~ ^/(README|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ {
deny all;
}
location ~ ^/(config|temp|bin|SQL|logs)/ {
deny all;
}
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
fastcgi_pass unix:/run/php/php7.3-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Last, we need to create a link of the virtual host file to /etc/nginx/sites-enabled. You will need to execute the following commands to create the link as well as restart nginx to apply the changes.
sudo ln -s /etc/nginx/sites-available/webmail.mydomain.com /etc/nginx/sites-enabled/webmail.mydomain.com
sudo service nginx restart
Run the Roundcube installer
At this point, if you navigate to https://webmail.mydomain.com/installer, you should see the Roundcube Webmail Installer page. You should see a series of items show OK, NOT AVAILABLE, or NOT OK. You will need to remediate any items that show NOT OK for Roundcube to successfully run.
In this installer, I primarily focused on Step 1 (Checking the environment) and Step 2 (Checking the database). Once both show OK (don't worry about if email is successful or fails (likely it is failing still), move the installer directory to your home drive to secure the environment (IT IS VERY DANGEROUS TO LEAVE THIS PAGE!!! DON'T SKIP THIS STEP).
sudo mv /var/www/webmail.mydomain.com/installer ~
Update Roundcube configuration
I couldn't get Roundcube to actually work during the installation with this setup until I manually specified a few items via the Roundcube configuration file. Within the /var/www/webmail.mydomain.com/config/config.inc.php file, ensure you have the following code snippets to allow Roundcube to properly authenticate to your mailserver.
sudo vi /var/www/webmail.mydomain.com/config/config.inc.php
Ensure you have the following code snippets (typically there is a section under // IMAP that has the config we can start with). To do so, press i to get vi into insert mode and paste the following.
Press : and then type wq and press enter to write the changes to the file and quit in vi.
Verify
At this point, you should be able to login to https://webmail.mydomain.com and send/receive email!
As with all technology, ensure you keep up-to-date with all the latest security patches to keep your environment stable and secure.
If you made it to this point, were able to successfully send/receive mail via Roundcube, pat yourself on the back and grab a fine beverage!
Troubleshooting
Here are some useful commands to help troubleshoot your deployment.
sudo postqueue -p can be used to check if any pending emails are queued.
sudo postmap -q mydomain.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf can be used to validate what domain names are accepted. You should receive the value of 1 if it exists.
sudo postmap -q [email protected] mysql:/etc/postfix/mysql-virtual-mailbox-users.cf will validate if a user account exists with the specified email address. You should receive the value of the email address of the user if it exists.
sudo postmap -q [email protected] mysql:/etc/postfix/mysql-virtual-mailbox-aliases.cf can be used to validate the alias of an email address. You should receive the email address of the user account if it does map back to another user.
tail -f /var/log/mail.log can be useful watching how emails are handled by postfix/dovecot to troubleshoot how messages are being handled
One of the hidden gems of Azure is HCM (Hybrid Connection Manager), which addresses the issue of Azure's App Services (Web App, API App, Functions) having the ability to connect to resources hosted in other Azure environments, clouds, or on-premises. In many cases, VPN or ExpressRoute connectivity may be overkill or not a possibility in establishing connectivity to the requested service. The great thing is Hybrid Connections is all the traffic will be egress TCP 443 traffic to Azure via TLS 1.2, which can easily attest to the needs of many secured environments and not require ports to be opened inbound into the environment.
There are two ways to leverage Hybrid Connections for App Services in Azure:
For the purposes of this article, we are going to cover how to connect to a web service "on-premises" via the HCM Agent. While we are using a Web App as an example, keep in mind that this concept can be applied to all App Services such as Web Apps, API Apps, Logic Apps, and Azure Functions. In addition, this article will make a call to a web service on-premises, however keep in mind that HCM is able to connect to any TCP service such as MSSQL, MySQL, Oracle, Web Services, custom TCP service, mainframes, etc.
Tutorial
To begin, we will first deploy a Web App from the Azure Portal to give us access to the Hybrid Connection Manager blade. Note: You can leverage any App Service to create the hybrid connection manager instance, but you must be on a paid tier (Free tier will not work).
Select All services -> App Services -> click + Add
Fill out the required information, ensuring you are on a plan greater than Free. Select Review + create and Create
Once deployed, navigate to your Web App, select Networking, and click on Configure your hybrid connection endpoints
On the Hybrid connections screen, click on Download connection manager.
Note: This is the agent you will need to install in the environment that contains the service you are trying to access. The agent itself can be deployed on any machine as long as the machine can access the service you are trying to reach.
Installation of the agent is very straightforward. Complete the steps below.
Select HybridConnectionManager.msi
Read the EULA, select I accept the terms in the License Agreement, and click Install
Click Finish
Once installed, navigate back to the Azure Portal (portal.azure.com), click All services -> App Services -> Select your webapp, click Networking, select Configure your hybrid connection endpoints, and click Add hybrid connection.
Click Create new hybrid connection and enter the following:
Hybrid connection Name
MyService
Endpoint Host
IPAddress or DNSNameOfTheService
Endpoint Port
PortNumberofYourService
Servicebus namepsace
Create new
Location
Pick the location of the Azure region you want to go to
Name
Enter a unique name for the service bus resource that will be created. This is a globally unique name accross all of Azure and must only consist of lowercase letters, numbers, and hyphens.
Click OK once you have filled out the information above. Once Azure has created the connection, navigate back to the machine you installed the agent on. On the machine, click Start, HybridConnectionManager, and select Hybrid Connection Manager UI.
Once the agent has launched, select Add a new Hybrid Connection.
This will prompt you to enter your Azure credentials. Enter your credentials in the prompt.
Note: if the machine is locked down and cannot leverage javascript, you can close out of the sign-in window and select Enter Manually on the previous step. Back in the Azure Portal, you can select your connection and copy the "Gateway Connection String" to connect this agent to Azure.
Once you have authenticated click the Subscription dropdown to select your Azure Subscription, select the connection you created via the portal, and click Save.
Once Saved, you should see the connection we created via the Azure Portal with the Azure Status of "Connected". If you don't see "Connected", double check you don't have a proxy blocking outbound TCP 443 requests to the Service Bus instance we created earlier (azurehcmdemo.servicebus.windows.net).
Note: To help with resiliency, you can deploy multiple agents on different machines to ensure resiliency/availability/scalability. When you select the same connection endpoint, HCM will automatically begin to load balance traffic between the agents.
Once you see the agent connected on-premises, you can validate from the Azure Portal we see the agent is connected as well. Via All services -> App Services -> your app service -> Networking -> Configure your hybrid connection endpoints, you should see "Connected" via the Status column on your Hybrid connections blade.
At this point, within your application, you should be able to reference the contents of the on-premises machine via the same connection string you may have used before. Below I've added an example showing an on-premises IIS server that displays the text "Moo" when you browse to the web page. Via my Web App in Azure, I created a quick PHP script that will request the on-premises server, in which HCM on the App Service will place the request on a Service Bus queue, the HCM agent on-premises will pull down the request, forward the request to the Web App on-premises, place the response back on the queue, and the web app will display the result "Moo".
Hope this helps! If you have any questions or comments feel free to reach out below.
Here is a recap of some of the reflections I have with deploying Fortinet's FortiGate appliance on Azure. This is more of a reflection of the steps I took rather than a guide, but you can use the information below as you see fit. At a high level, you will need to deploy the device on Azure and then configure the internal “guts” of the device to allow it to route traffic properly on your Virtual Network (VNet) in Azure. While Fortinet does have some documentation on deploying their appliance, I found it very confusing, so I hope this helps walk through deployment. At the time of writing this, v6.2 was the latest version; however I recommend using at least version 6.0 or greater as it provides support for auto-scaling, which is what we will be looking at for this guide.
First, just want to provide a quick overview of the different options you can take and a rough overview of each architecture:
Single FortiGate (One VM, easiest to deploy, but is not highly available)
HA FortiGate in Active/Passive mode (Two VMs with a public IP that gets manually attached to a given instance and updates to route tables)
Notes: Fortinet in active/passive deployment requires the modification of UDRs and Public IPs. Please note, any manipulation of UDRs or public IPs for Active/Passive solutions can take about 30 seconds to be applied after the failover is initiated. This deployment typically contains 4 IPs on each appliance, one used for external traffic, another for internal traffic, a third for heartbeat traffic, and a fourth for management traffic.
HA FortiGate in Active/Active mode (Two VMs load balanced by Azure Load Balancer for high availability; a little more complex to manage; sometimes called the "load balancer sandwhich")
Note: As of 8/20/2019 - the only downside to this deployment method is BYOL isn't officially supported yet (you must use Pay as you go (PAYG) licensing) and this mode will not let you easily establish VPN connections to the appliance vs Azure VPN Gateway. If using this deployment strategy, I would recommend pairing it with Azure's VPN Gateway to handle VPN connectivity.
Note: As of 8/20/2019 - I don't believe this deployment works for Azure's sovereign clouds. The image for the FortiGate appliance is only up to v6.1.0 in Azure Government Cloud and I don't see a way to specify within the FortiGate that it needs to use the Government Cloud APIs. You would need to manually modify the templates and work with Fortinet to ensure the images work for Azure's sovereign clouds. In this case, I would recommend deploying the HA FortiGate in Active/Active mode listed above.
Deploy the Appliance in Azure
As part of this tutorial, we will look at FortiGate's Autoscaling deployment as this will allow us to dynamically scale up or down depending on load. In addition, this deployment will provide us high availability, so in the event we lose a VM, network traffic will automatically failover to another appliance.
Navigate to All services -> Azure Active Directory
Select App registrations
Click New Registration
Name: Fortigate-NVA
Supported account types: Accounts in this organizational directory only
Redirect URI: leave blank
Click Register
Write down the Application (client) ID, Directory (tenant) ID, and Object ID.
Click on Certificates & secrets
Click on the New client secret button and set the description to Fortigate-NVA, set the password expiry to your preference and click Add
Write down the value of your client secret
Note: once you navigate away from the blade you won't be able to retrieve it again
Delegate the Service Principal
Navigate to All services -> Subscriptions -> select your subscription -> and select Access control (IAM)
Click Add, Add role assignment, and use the following configuration
Role: Owner
Assign access to: Azure AD user, group, or service principal
Select: Search for Fortigate-NVA and select it
Click Save
Note: I didn't have a chance to test, but I think these permissions could likely be delegated down at the resource group level vs subscription. If someone could confirm, please leave a comment below.
Deploy the Fortigate Handler (CosmosDB and Function App)
Once you click the button above to deploy the template, use the following configuration
Function App Name
This is the name of the Azure Function resource that gets created. This must be globally unique across all customers within Azure.
Cosmos DB Name
Name of the Cosmos DB that will be created. This field must be between 3 and 31 characters and can contain only lowercase letters, numbers and -. This value should be globally unique across all customers within Azure.
Storage Account Type: Standard_LRS
Tenant ID
Use the Directory (tenant) ID from the Service Principal we created earlier.
Subscription ID
Enter the subscription ID to the Azure Subscription you wish to deploy to. You can find your subscription ID by navigating to All services -> Subscriptions and selecting your subscription.
Rest App ID
Use the Application (client) ID from the Service Principal we created earlier.
Use the value you wrote down when generating the Client Secret when creating the Service Principal.
Heart Beat Loss Count: 3
Number of consecutively lost heartbeats. When the heartbeat loss count has been reached, the VM is deemed unhealthy and failover activities commence.
Scaling Group Resource Group Name: Fortigate-VMSS-RG
This is the value of the secret Resource Group you created at the beginning of this guide. This Resource Group will contain the VM Scale Set and it's corresponding resources.
Script Timeout: 230
This is the timeout for the Function App script to run. By default this is 230 seconds.
Election Wait Time: 90
This is the maximum time (in seconds) to wait for a master election for the FortiGate's to complete.
PSK Secret: mysupersecretpassphrase
This is a random string of characters used by the FortiGates in the scale set to synchronize configuration items.
Once you click the button above to deploy the template, use the following configuration
Instance Type: Standard_F2
FOS Version: 6.2.1
VNet New Or Existing: new
Select whether you wish to use an existing or new Virtual Network
VNet Name: AzureHubVNet
The name of the VNet to be used or created.
Subnet Address Prefix: 10.0.0.0/16
The address space of the VNet to be used or created.
Subnet1Name: Untrust
The name of the subnet that will be public facing to the internet.
Subnet1Prefix: 10.0.1.0/24
The address space of the subnet to be created for the public facing zone.
Subnet2Name: Trust
The name of the subnet that will contain the private NICs of the FortiGate's.
Subnet2Prefix: 10.0.2.0/24
The address space of the subnet to be created for the private facing zone.
Subnet2Load Balancer IP: 10.0.2.10
The IP address of the load balancer in the private zone.
Subnet3Name: Private
The name of the subnet that will contain the private machines that are behind the FortiGate appliance.
Subnet3Prefix: 10.0.3.0/24
The address space of the subnet that will contain the private machines that are behind the FortiGate. Note: this is more of a place holder in FortiGate's template, you can create additional subnets later on/use a different subnet for your private resources.
Public IP New or Existing: new
The Public IP address to be associated as the VIP of the Azure Load Balancer for incoming traffic.
Scaling Group Name Prefix: fgtasg
The prefix each VMSS Name is given when deploying the FortiGate autoscale template. The value of this parameter should be the same as for deploy_funcapp.json. The prefix cannot contain special characters \/""[]:|<>+=;,?*@& or begin with '_' or end with '.' or '-'.
Initial Capacity: 2
How many FortiGate's should be deployed. Default value is 1, however I recommend at least 2 for high availability.
Min Capacity: 2
The smallest amount of FortiGate's that should be running. Default value is 1, however I recommend at least 2 for high availability.
Max Capacity: 3
The max amount of FortiGate's that should be deployed.
Scale Out Threshold: 80
Percentage of CPU utilization at which scale-out should occur.
Scale In Threshold: 20
Percentage of CPU utilization at which scale-in should occur.
Admin Username: azureadmin
FortiGate administrator username on all VMs.
Admin Password: azurepassword
FortiGate administrator password on all VMs. This field must be between 11 and 26 characters and must include at least one uppercase letter, one lowercase letter, one digit, and one special character such as (! @ # $ %).
This can be found by navigating to All services -> Function App -> YourFunctionApp-> URL on the overview blade.
At this point, your FortiGate deployment should be completed. When a FortiGate appliance comes up, it will reach out to the Azure Function to pull down its base configuration. Any changes to the primary FortiGate will be synchronized to any additional FortiGates deployed as well.
For those using a hub/spoke network, you will want to associate a UDR to each of your subnets to force traffic back to the internal load balancer's VIP. You can do this by creating a new Route Table, add a Route, set the next hop type to Virtual Appliance, and set the IP address to the IP address you specified for the "Subnet2Load Balancer IP".
You can connect to the primary FortiGate for management via web console on Port 8443 (https://IP.AD.DR.ESS:8443) or via SSH on Port 22.
Here is a recap of some of the reflections I have with deploying Cisco NGFWv (Next Generation Firewall Virtual) on Azure. This is more of a reflection of the steps I took rather than a guide, but you can use the information below as you see fit. At a high level, you will need to deploy the device on Azure and then configure the internal "guts" of the Cisco device to allow it to route traffic properly on your Virtual Network (VNet) in Azure. While Cisco does have decent documentation on deploying a single appliance, the primary purpose of this document is to look at HA/Scale out deployments.
First, just want to provide a quick overview of some of Cisco's offerings today for Azure:
Cisco CSR
In Cisco’s words:
The Cisco Cloud Services Router (CSR) 1000v is a full-featured Cisco IOS XE router, enabling IT departments to deploy enterprise-class networking services in the Microsoft Azure cloud. Most Cisco IOS XE features are also available on the virtual Cisco CSR 1000v.
Virtual MX is a virtual instance of a Meraki security & SD-WAN appliance, dedicated specifically to providing the simple configuration benefits of site-to-site Auto VPN for customers running or migrating IT services to an Amazon Web Services or Microsoft Azure Virtual Private Cloud (VPC).
The ASAv is a virtualized network security solution that provides policy enforcement and threat inspection across heterogeneous, multisite environments.
ASA firewall and VPN capabilities help safeguard traffic and multitenant architectures. Available in most hypervisor environments, the Cisco ASAv can be deployed exactly where it is needed to protect users and workloads on-premises or in the cloud.
The Cisco Firepower® NGFW (next-generation firewall) is the industry’s first fully integrated, threat-focused next-gen firewall with unified management. It uniquely provides advanced threat protection before, during, and after attacks.
The Firepower Threat Defense Virtual (FTDv) is the virtualized component of the Cisco NGFW solution. Organizations employing SDN can rapidly provision and orchestrate flexible network protection with Firepower NGFWv. As well, organizations using NFV can further lower costs utilizing Firepower FTDv.
In deploying the Cisco appliances, you'll notice you can deploy from the Azure Marketplace: https://azuremarketplace.microsoft.com/en-us/marketplace/apps/cisco.cisco-firepower-threat-defense-appliance?tab=Overview). Personally, I'm not a big fan of deploying the appliance this way as I don't have as much control over naming conventions, don't have the ability to deploy more than one appliance for scale, cannot specify my availability set, etc. While Cisco does offer an ARM template, it doesn't allow flexibility for more than two devices, nor configures anything from a load balancer perspective. In this case, I've written a custom ARM template that leverages managed disks, availability sets, consistent naming nomenclature, proper VM sizing, and most importantly, let you define how many virtual instances you'd like to deploy for scaling.
Note: this article doesn't cover deployment of Cisco's Firepower Management Center, which is what is used to centrally manage each of the scale-out instances in a "single pane of glass".
With the above said, this article will cover what Cisco calls their "scalable design" model. Here is an example of what this visually looks like (taken from one of their slide decks listed in the notes section at the bottom of this article):
Deployment of this template can be done by navigating to the Azure Portal (portal.azure.com), select Create a resource, type Template Deployment in the Azure Marketplace, click Create, select Build your own template in the editor, and paste the code into the editor.
Alternatively, you can click this button here:
Here are some notes on what the parameters mean in the template:
VMsize: Per Cisco, the recommend VM sizes should be D3v2, D4v2, or D5v2. Interestingly, they don't call out the use of Premium storage anywhere, which I would highly recommend using if this was a single instance machine (to get at least some sort of SLA by Azure).
CiscoVersion: The version of the Cisco appliance to deploy.
CiscoCount: This defines how many virtual instances you want deployed and placed behind load balancers.
VNetName: The name of your virtual network you have created.
VNetRG: The name of the resource group your virtual network is in. This may be the same as the Resource Group you are placing the Cisco devices in, but this is a needed configurable option to prevent errors referencing a VNet in a different resource group.
envPrefix: All of the resources that get created (load balancer, virtual machines, public IPs, NICs, etc.) will use this naming nomenclature.
manPrivateIPPrefix, diagPrivateIPPrefix, trustPrivateIPPrefix, untrustPrivateIPPrefix: Corresponding subnet address range. These should be the first 3 octets of the range followed by a period. For example, 10.5.6. would be a valid value.
manPrivateIPFirst, diagPrivateIPFirst, trustPrivateIPFirst, untrustPrivateIPFirst: The first usable IP address on the subnet specified. For example, if my subnet is 10.4.255.0/24, I would need to specify 4 as my first usable address.
Username: this is the name of the privileged account that should be used to ssh and login to the PanOS web portal.
NewStorageAccountName: this is the name of the storage account that will store boot diagnostics for the Cisco appliances. This will give you the ability to see what the serial console shows. This value should be alphanumeric and 3-24 characters.
Password: Password to the privileged account used to ssh and login to the device.
Configure the Appliance
Complete these steps for both devices.
SSH to the device via it's public or private IP address of the management interface
Please note, SSH may not come up for another 10+ minutes after deployment has finished, even though the VMs show running. There are several tasks within the Cisco appliance that run post-provisioning which take awhile to complete before the ability to SSH works.
Login using the following credentials
Note: Even though we specified credentials within our template, cisco has a default set of admin credentials "baked" into the image and they should be specified during first login (which prompts you to immediately change). Please login using the default admin credentials.
Username: admin
Password: Admin123
The password is case sensitive, you should use a capital A on Admin123.
Change your password once prompted
Enter y to configure IPv4
Enter n to not configure IPv6
As of 6/1/2019, Azure only has preview support for IPv6, so this article won't cover any IPv6 specific items
Enter dhcp to configure IPv4 with DHCP
All addresses in Azure should be DHCP, static addresses are set within Azure, which essentially give the appliance a DHCP reservation
Important Note: Once you configure this option, you'll get an awkward "If your networking information has changed, you will need to reconnect" message and things will appears to be stuck. Be patient, it appears a script runs in the background, you'll see it eventually prompt for the next question.
Leave your SSH connection open for the next step
Configure NGFWv to use FirePower Management Center
Once you have gone through the initial configuration on both devices, you will need to register the sensor to a Firepower Management Center instance. To do this, you will need to run the configure manager command on both appliances. Please note I've listed the command below with the parameters it will accept, you will need to use the applicable values for your environment.
The registration key is a user-defined one-time use key that must not exceed 37 characters. Valid characters include alphanumeric characters (A–Z, a–z, 0–9) and the hyphen (-). You will need to remember this registration key when you add the device to the Firepower Management Center.
If the Firepower Management Center is not directly addressable, use DONTRESOLVE.
The NAT ID is an optional user-defined alphanumeric string that follows the same conventions as the registration key described above. It is required if the hostname is set to DONTRESOLVE. You will need to remember this NAT ID when you add the device to the Firepower Management Center
Add the appliances into FirePower Management Center
Repeat the following steps for each of the appliances you deployed
Login to FirePower Management Center
Select the Devices tab, click Device Management, and then click the Add button
If you used NAT, configure NAT and specify the NAT ID
Click Register
Initialize the interfaces on your appliances
Repeat the following steps for each of the appliances you deployed
Select the Devices tab, click Device Management, and select the edit button (Pencil Icon) for your appliance
Click the edit button (Pencil Icon) for GigabitEthernet 0/0
Name: Untrust
Check the Enabled checkbox
Security Zone: Create a new zone called Untrusted
Click the IPv4 tab
IP Type: Use Static
IP Address: IPAddressOfYourAppliance/SubnetSize
Click OK
Click the edit button (Pencil Icon) for GigabitEthernet 0/1
Name: Trust
Check the Enabled checkbox
Security Zone: Create a new zone called Trusted
Click the IPv4 tab
P Type: Use Static
IP Address: IPAddressOfYourAppliance/SubnetSize
Click OK
Click the Save button
Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device
Configure static routes on your device
In this section, we will create several routes to handle the flow of traffic to and to/from your trusted subnets, traffic destined towards the internal, traffic destined towards the management interface (we'll need this to help handle the health probes from the azure load balancer later on), and a specific route to define the Azure Health Probes themselves.
Repeat the following steps for each of the appliances you deployed.
Select the Devices tab, click Device Management, and select the edit button (Pencil Icon) for your appliance
Select the Routing tab and click Static Route
Click the Add Route button
Type: IPv4
Interface: Trust
Create new network objects
Add network objects that represent each of the subnets you have in Azure that the device will need to return traffic to
For example, you'd repeat these steps for each private subnet
Name: DBServers
Network: 10.3.5.0/24
Click Save
Add network object for the appliance's management interface
Name: YourAppliance-mgmt
Network: IPAddressOfManagementInterface
Use the private IP of your management interface
Click Save
Add network object for Azure Health Probes
Name: Azure-LB-Probe
Network: 168.63.129.16
Click Save
Add the defined network objects above to Selected Network box
Gateway: Use the IP address of the default gateway of your subnet the Trust interface is deployed on
Note: To find this, navigate to the Azure Portal (portal.azure.com) and select All Services -> Virtual Networks -> Your Virtual Network -> Subnets and use the first IP address of your subnet the trusted interface is on. For example, if the address range of my subnet is 10.5.15.0/24, I would use 10.5.15.1 as my IP address. If my subnet was 10.5.15.128/25, I would use 129 10.5.15.129 as my IP address
Metric: 3
Click OK
Click the Add Route button
Type: IPv4
Interface: Untrust
Add the any-ipv4 object to Selected Network box
This will allow us to force all internet bound traffic through our Untrust interface
Add the Azure-LB-Probe object to the Selected Network box
This will allow health probes from the external azure load balancer probes to flow properly
Add the YourAppliance-mgmt object to the Selected Network box
Gateway: Use the IP address of the default gateway of your subnet the Untrust interface is deployed on
Note: To find this, navigate to the Azure Portal (portal.azure.com) and select All Services -> Virtual Networks -> Your Virtual Network -> Subnets and use the first IP address of your subnet the untrusted interface is on. For example, is the address range of my subnet is 10.5.15.0/24, I would use 10.5.15.1 as my IP address. If my subnet was 10.5.15.128/25, I would use 129 10.5.15.129 as my IP address
Metric: 2
Click OK
Click the Save button
Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device
Configure NAT Policies
First create a NAT rule that will SNAT any traffic from our trusted zone to the Untrust interface. This is needed so Azure understands to return traffic through the external interface of your device for inspection.
Select the Devices tab, click NAT, and select the Threat Defense NAT Policy link (or New Policy button)
Select your first appliance, click the Add to Policy button, and click Save
Click the Add Rule button
NAT Rule: Auto NAT Rule
Type: Dynamic
Interface Objects Tab
Select the Trusted Interface Object and click the Add to Source button
Select the Untrusted Interface object and click the Add to Destination button
Translation Tab
Click the green button to add a new network object under Original Packet
Name: any-ipv4
Network: 0.0.0.0/0
Click Save
Original Source: any-ipv4
Translated Source: Destination Interface IP
Click OK
Next, we need to create a new NAT statement to handle traffic for our load balancer probes. We will need to configure two statements since we will receive health probes from the same IP address (168.63.129.16) to both NICs. On the same appliance, continue the following steps.
Click the Add Rule button
NAT Rule: Manual NAT Rule
Type: Static
Interface Objects Tab
Select the Trusted Interface Object and click the Add to Source button
Select the Untrusted Interface object and click the Add to Destination button
Translation Tab
Original Source: Azure-LB-Probe
Original Destination: Source Interface IP
Original Destination Port: SSH
Translated Source: Destination Interface IP
Translated Destination: YourAppliance-mgmt
Translated Destination Port: SSH
Click OK
Click the Add Rule button
NAT Rule: Manual NAT Rule
Type: Static
Interface Objects Tab
Select the Untrusted Interface Object and click the Add to Source button
Select the Trusted Interface object and click the Add to Destination button
Translation Tab
Original Source: Azure-LB-Probe
Original Destination: Source Interface IP
Original Destination Port: SSH
Translated Source: Destination Interface IP
Translated Destination: YourAppliance-mgmt
Translated Destination Port: SSH
Click OK
Optional Step: If you are using the appliances to front applications to the internet, you will also need to configure a NAT rule for ingress traffic. This is an optional step, but will show you how to configure traffic to let's say a web server (which the ALB is configured to listen for per the template). If you do complete this step, make sure you add an access policy (Policies -> Access Control -> Select your policy -> click Add Rule).
Click the Add Rule button
NAT Rule: Manual NAT Rule
Type: Static
Interface Objects Tab
Select the Untrusted Interface Object and click the Add to Source button
Select the Trusted Interface object and click the Add to Destination button
Translation Tab
Original Source: any-ipv4
Original Destination: Source Interface IP
Original Destination Port: HTTP
Translated Source: Destination Interface IP
Translated Destination: webserver
Click the green add button to create a new network object to define the private IP address of your web server.
Translated Destination Port: HTTP
Click OK
Click Save once you have finished adding the rules.
At this point, you will need to repeat the same steps above. The reason why we cannot apply the policy to both devices is when you configure the rule for the Azure Health Probes, you'll need to specify the correct Translated Destination (I.e. Appliance1 should use the network object that resolves to appliance 1; Appliance2 should use the network object that resolves to appliance 2)
Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device
Finalize the environment
Now that the environment is configured, there are two steps you will want to check back on.
Add Route Tables to each subnet to force traffic to the Cisco appliances
Remove the public IP from your management interface
Considering at this point you've configured the device and have private connectivity via VPN or ExpressRoute, I'd remove the public IP from your management interface to prevent the public internet from accessing this interface
Adjust NSG rules
Similar to above, I'd scope down who/what network segments can pass traffic to the device. Go back and modify the NSG on the management interfaces to only allow traffic from specific source addresses.