This is a quick reflection of the steps I took to establish two IPSec tunnels between AWS' VPG and Azure's Virtual WAN VPN Gateway, propagating routes dynamically via BPG and ensuring High Availability. The design itself is a bit interesting since AWS and Azure differ on how connections are established to remote peers. When everything is said and done, you'll end up with a diagram that conceptually looks something like this:
Note: It is recommended to start with the Virtual WAN side first since you cannot modify the IP address of a Customer Gateway in AWS
Create Azure Virtual WAN and Virtual WAN Hub
On the Azure side, first we need to create a Virtual WAN resource and a Virtual WAN Hub, which will contain our VPN Gateway. If you have already created these, you can skip to the next session.
First, click the "Hamburger" icon and select Create a resource
Search for Virtual WAN and select it from the list in the marketplace.
Select Create
Specify the resource group and region you wish to deploy the Virtual WAN resource to. Specify a name for your Virtual WAN resource and click Review + Create
Click Create to start provisioning the Virtual WAN resource.
Once the resource is created, click Go to resource to navigate to your Virtual WAN resource.
On the Virtual WAN resource, select New Hub from the top menu.
Specify the name of the Hub and an address space that can be used for all the networking components Virtual WAN will deploy into the Virtual Hub. Click Next : Site to Site >
On the Site to Site tab, toggle Yes that you want to provision a VPN Gateway, and specify the scale units you need. Click the Review + create button when done.
Click the Create button to start provisioning the Hub and VPN Gateway. Please note this can take up to 30 minutes to complete.
Configure customer BGP IP Address for Virtual WAN VPN Gateway Instances
Once provisioning is completed, navigate back to the Virtual WAN resource. You can do this by clicking the "Hamburger" icon and searching for Virtual WAN
Select your Virtual WAN resource.
You should now see your Virtual WAN Hub resource you provisioned. Select the Virtual WAN Hub.
On the Virtual WAN Hub, click on the View/Configure link.
On the View/Configure Gateway Configuration blade, specify 169.254.21.2 as the Custom BGP IP address for Instance 0 and 169.254.22.2 as the Custom BGP IP address for Instance 1. Notate the Public IP address uses for Instance 0 and 1 and then click Edit and Confirm to apply the changes.
Create Virtual WAN VPN Site
On the Virtual WAN Hub, click Create new VPN Site
Specify a name for your VPN Site to define the connection connecting to AWS. Click Next : Links >
On the Links tab, add two entries with the following values (to tell VWAN how to connect to each of the AWS Site-to-Site connections). Note: this is very similar to AWS' Customer Gateway section.
Link 1:
Link Name; AWS_Tunnel1
Link Speed: 1000
Link Provider Name: AWS
Link IP address: 1.1.1.1 (this is a placeholder value until we configure the AWS side)
Link BGP address: 169.254.21.1
Link ASN: 64512
Link 2:
Link Name; AWS_Tunnel2
Link Speed: 1000
Link Provider Name: AWS
Link IP address: 1.1.1.2 (this is placeholder value until we configure the AWS side)
Link BGP address: 169.254.22.1
Link ASN: 64512
Click Next: Review + Create >
Click Create
Click Go to resource once the links have finished being created.
Configure Phase 1/2 Proposals
Select your Virtual WAN hub on the Virtual WAN Overview blade.
Check the box for the new VPN Site Name and click the Connect VPN sites button
Specify the following configuration:
Pre-shared key (PSK): YourSecretKeyWithNumb3rs
Must be a 8-64 character string with alphanumeric, underscore(_), and dot(.). It cannot start with 0.
Protocol: IKEv2
IPSec: Custom
IKE Phase 1:
Encryption: GCMAES256
GCM algorithm is more efficient and can improve throughput on the Azure Gateways
Integrity/PRF: SHA256
DH Group: DHGroup14
IKE Phase 2 (ipsec):
IPSec Encryption: AES256
AWS does not support GCM algorithm for IPSec integrity at time of writing this, but if it is available, you may want to opt for that
IPSec Integrity: SHA256
PFS Group: PFS14
Click Connect
Configure AWS
Prerequisites
This guide assumes you have a VPC already (in my case, mine is called AWS-OHIO-VPC), a corresponding set of subnets for your servers, and a route table associated to your VPC.
Note: An AWS VPC is the equivalent of a VNet in Azure. One thing that is different between AWS and Azure is that in AWS you do not need to specify a subnet for your Gateways (i.e. "GatewaySubnet").
Create the Customer Gateways
Customer Gateways in AWS are the equivalent of a local network gateway that you'd associate to a connection for a traditional VPN Gateway in Azure. It is also the equivalent of a defined Site Link for Azure's Virtual WAN VPN configuration.
In this section, you will need to create two Customer Gateways. Specify the corresponding instance value obtained from the Configure Customer BPG IP address section. When creating the Customer Gateways ensure Dynamic routing is enabled and the BGP ASN is specified as 65515.
Configuration for the second Customer Gateway using the Instance 1 Gateway Public IP address.
Create a Virtual Private Gateway
Next we need to create an AWS Virtual Private Gateway. This is the equivalent of Azure's VPN Gateway.
Create VPN Connections
We need to create two VPN Connections, each VPN Connection linked to its corresponding Customer Gateway and VPC.
On the Inside IPv4 CIDR for Tunnel 1 on the first VPN Connection, ensure you use 169.254.21.0/30 as the BGP Peer addresses and 169.254.21.4/30 for the second tunnel. Due to the way that the VPN Connection works, we are using a placeholder value of 169.254.21.4/30 tunnel, which will never be used in practice since we cannot point it to leverage Azure's secondary VPN Gateway instance. This value must be specified as if we define the secondary BGP Peer address that will be created for the secondary instance in VWAN, you will receive an error that overlapping address space exists between this VPN Connection and the secondary VPN connection we create in AWS. Add the pre-shared key value you specified in Azure during this time as well.
When creating the second VPN connection, ensure 169.254.22.0/30 is specified for Inside IPv4 CIDR for Tunnel 1 and 169.254.22.4/30 is specified for Inside IPv4 CIDR for Tunnel 2 (which is again a placeholder value that won't be used).
Configure Route Table to Propagate Routes
To allow the learned routes from BGP propagate to the VPC, you need to enable route propagation on your Route Table.
Navigate to Route Tables and select your Route Table and click the Route Propagation tab and select Edit route propagation
Check the Propagate box and click Save
Update Azure
Update Azure Site Link IP addresses
As per the Configure Phase 1/2 Proposals section for Azure Virtual WAN, you specified 1.1.1.1 and 1.1.1.2 as a placeholder value for the Public IP addresses of the AWS VPN Gateway instances. We will need to update these addresses with the proper values.
Naviate to your Virtual WAN instance and select your Virtual WAN hub
Select VPN (Site to site) and choose click on the Site name you created
Click on the three dots (ellipsis) for AWS_Tunnel1 and click Edit Link.
Specify the proper IP address for Tunnel 1 on AWS Site-to-Site connection 1. Click Confirm.
Click on the three dots (ellipsis) for AWS_Tunnel2 and click Edit Link.
Specify the proper IP address for Tunnel 1 on AWS Site-to-Site connection 2. Click Confirm.
Verify connectivity
On the Azure Side, you should see the VPN Site's Connectivity status change to Connected
You can also select a Virtual Machine that may have it's virtual network attached to the VWAN Hub and validate you see learned routes from the VWAN Hub (and AWS) propagated into the VNet.
Tip: You can see the same route twice as we have both VPN Gateway instance BGP Peers actively connected to AWS. In the event you lose a peer, you would only see one route to one gateway listed.
On the AWS side, you can validate for each Site to Site VPN connection that you see Tunnel 1's status as UP and Tunnel 2's status as DOWN (remember, Tunnel 2 will always be listed as down because a fictitious BGP is specified).
Here you can see the secondary Site-to-Site connection with the same status: UP for Tunnel 1, DOWN for Tunnel 2
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)
In this picture, we show right clicking the zipped folder and clicking Extract All... on the file.In this picture, we are selecting the folder to where the extracted files should go.
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.
Architecture
A high level overview of what resources are deployed
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.