Establishing a GCP VPN Tunnel to Azure Virtual WAN; Active/Active BPG Configuration

This is a quick reflection of the steps I took to establish two IPSec tunnels between GCP’s VPC and Azure’s Virtual WAN VPN Gateway, propagating routes dynamically via BPG and ensuring High Availability. The design is fairly straightforward since both GCP and Azure offer the ability to established multiple connections 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 complete the steps in this document in the outlined order to complete the least amount of steps. In this case, provide Azure Virtual WAN first, then configure GCP, then create the Azure Virtual WAN Site Links / Connections to GCP.

Note 2: If you have followed my previous guide on establishing an AWS VPN tunnel to Azure Virtual WAN , this guide will co-exist both connections and can skip the Create Azure Virtual WAN and Virtual WAN Hub sections.

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.

Once the Virtual WAN Hub has been created, click the Menu icon and select All services (note: if you click the Go to resource button after the Virtual WAN Hub resource is created, it’ll take you the properties of the Hub, which isn’t where we want to be).

Search for Virtual WAN and select Virtual WANs.

Select your Virtual WAN resource.

Click on Hubs under Connectivity and select your Virtual WAN Hub.

Select VPN (Site to Site) under Connectivity and then click on the View/Configure link.

Set the Custom BGP IP addresses for each instance. Use the values below:

  • VPN Gateway Instance 0: 169.254.21.2
  • VPN Gateway Instance 1: 169.254.22.2

Click Edit once completed.

Configure GCP

Prerequisites

This guide assumes you have a VPC already (in my case, mine is called GCP-VPC with an address space of 10.60.0.0/16) and corresponding set of subnets for your servers.

Note: A GCP VPC is the equivalent of a VNet in Azure. One thing that is different between GCP and Azure is that in GCP you do not need to specify a subnet for your Gateways (i.e. “GatewaySubnet”).

Within the GCP Console, select Hybrid Connectivity -> VPN

Click Create VPN Connection

Select High-availability (HA) VPN and select Continue

Enter a name, select your VPC, and specify a region. Click Create & Continue.

Write down your Interface public IPs (we’ll use these later) and check On-prem or Non Google Cloud for Peer VPN Gateway. Click Create & continue.

Select two interfaces and enter your Instance 0 and Instance 1 Public IP addresses from your Virtual WAN Hub’s VPN Gateway. Click Create.

Click the dropdown for Cloud Router and select Create a new router

Enter a name and description for your router. For the ASN, enter a unique ASN to use (I used 64700 to differentiate from Azure as well as the ASN I used in the AWS example (which was 64512)). You can specify any supported ASN for this, however I would recommend against using 65515 specifically as this is reserved by Azure’s VPN Gateways.

Note: Google ASN must be an integer between 64512 and 65534 or between 4200000000 and 4294967294 or 16550

Click the pencil icon to modify the first VPN tunnel.

Select the instance 0 VPN Gateway interface, enter a name, set the IKE version to IKEv2, enter a pre-shared key, and click Done.

Repeat the same steps for the second VPN tunnel, specifying instance 1 VPN Gateway interface, enter a name, set the IKE version to IKEv2, enter a pre-shared key, and click Done and then Create & continue.

Click the Configure button for the first BPG session.

Enter a name for the first BGP peer connecting to instance 0 gateway on Virtual WAN.

Specify Peer ASN of 65515 (this is Azure VWAN’s BGP ASN), specify 169.254.21.1 for Cloud Router BGP IP and 169.254.21.2 as BGP peer IP (Azure VWAN’s BGP Peer IP). Click the Save and continue button.

Click the Configure button and enter a name for the first BGP peer connecting to instance 1 gateway on Virtual WAN.

Specify Peer ASN of 65515 (this is Azure VWAN’s BGP ASN), specify 169.254.22.1 for Cloud Router BGP IP and 169.254.22.2 as BGP peer IP (Azure VWAN’s BGP Peer IP).

Click the Save BGP configuration button.

Configure Azure Virtual WAN VPN Site

On the Virtual WAN hub, select VPN (Site to site) and click + Create new VPN site

Specify a name for the VPN connection, enter GCP for vendor, and click Next : Links >

Specify the following values to define each VPN tunnel that should be created to connect to GCP’s VPN interfaces.

Note: I entered 1000 for the link speed as a placeholder, but that doesn’t mean the connection will be throttled down to 1Gbps.

  • First Link:
    • Link Name: gcp-east1-vpc-vpn-int0
    • Link Speed: 1000
    • Link provider name: GCP
    • Link IP address: <GCP VPN Interface 0 Public IP>
    • Link ASN: 64700
  • Second Link:
    • Link Name: gcp-east1-vpc-vpn-int1
    • Link Speed: 1000
    • Link provider name: GCP
    • Link IP address: <GCP VPN Interface 1 Public IP>
    • Link ASN: 64700

Click Create

Configure Virtual WAN VPN Connection

Once the Virtual WAN Hub has been created, click the Menu icon and select All services.

Search for Virtual WAN and select Virtual WANs.

Select your Virtual WAN resource.

Click on Hubs under Connectivity and select your Virtual WAN Hub.

Select VPN (Site to Site) under Connectivity and then click on the X to remove the Hub association filter.

Check the box for your VPN site and click Connect VPN sites

Specify the following information:

  • Pre-shared key (PSK): <use the same one you specified in GCP>
  • Protocol: IKEv2
  • IPsec: Custom
    • SA Lifetime in seconds: 36000
    • Phase 1 (IKE):
      • Encryption: GCMAES256
      • Integrity/PRF: SHA384
      • DH Group: ECP256
    • Phase 2 (IPSec):
      • Encryption: GCMAES256
      • IPSec Integrity: GCMAES256
      • PFS Gropu: ECP384
  • Propagate Default Route: Disable
  • Use policy based traffic selector: Disable

Click Connect.

Note: Here is the link of Supported IKE ciphers for GCP.

Verify Connectivity

From the Azure Side, we will review three different areas to validate connectivity and propagation of routes via BGP.

Note: I connected a virtual network to the Virtual WAN Hub to show further configuration. In this case, you’ll see an additional IP address space of 10.51.0.0/16, which defines my connected VNet.

On the Azure Side, you should see the VPN Site’s Connectivity status change to Connected on the VPN (Site to site) blade of your Virtual WAN hub.

On the Routing blade, the Effective Routes will show you the learned VPC address space from GCP (10.60.0.0/16)

On a virtual machine in a connected VNet to the Virtual WAN Hub, you can pull the Effective Routes. Here I see the 10.60.0.0/16 route learned from both Instance 0 and Instance 1 gateways from the Virtual WAN Hub.

From the GCP Side, we can see the VPN tunnel status as well as Bgp session status now Established and Green on the Hybrid Connectivity -> VPN -> Cloud VPN Tunnels section.

If we switch over to Hybrid Connectivity -> Cloud Routers -> and select View on the logs column

Further, if creating a VM (instance) in GCP, you can view the Firewall and Route details to confirm you see the learned routes from the gateway (in our case, we see 10.51.0.0/16 and 10.50.0.0/24 learned from both BGP Peers):

Huzzah! Traffic! 🙂

Establishing an AWS VPN Tunnel to Azure Virtual WAN; Active/Active BPG Configuration

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:

  • 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.

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

How to upgrade Home Assistant Z-Wave integration to Z-Wave JS for Docker

If you’ve been following my last two tutorials (Home Assistant + Docker + Z-Wave + Raspberry Pi | Jack Stromberg and How to update Home Assistant Docker Container | Jack Stromberg) on running Home Assistant via Docker and how to keep the container updated, you may have noticed that 2021 has been a big year for larger changes, with a surprising change coming to how Home Assistant handles Z-Wave Devices.

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.

So why the change?

As per the Home Assistant v2021.2 announcement:

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)

If you are not running the latest version, you can follow my upgrade steps here: How to update Home Assistant Docker Container | Jack Stromberg

Create a backup

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.

To complete this step, I’d recommend checking out this blog post here which provides several options: Backing up Home Assistant | Tinkering with Home Automation (ceard.tech)

Alternatively, you can be extremely lazy and less cautious by simply copying the configuration folder containing your docker config:

sudo cp /home/docker/home-assistant/ /home/docker/home-assistant-backup/ -R

Update your Operating System

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:


#zwave:
#  usb_path: /dev/ttyACM0
#  network_key: "0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99"

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

sudo docker run --init -d --restart=always --name="home-assistant" -e "TZ=America/Chicago" -v /home/docker/home-assistant:/config --net=host homeassistant/raspberrypi4-homeassistant:stable

Install Z-Wave JS Server

Create a new directory for the zwave-js server configuration files

sudo mkdir /home/docker/zwave-js/

Run the docker container (the first port listed is for the Z-Wave JS Web Interface, the second port is the Z-Wave JS WebSocket listener)

sudo docker run -d --restart=always  -p 8091:8091 -p 3000:3000 --device=/dev/ttyACM0 --name="zwave-js" -e "TZ=America/Chicago" -v /home/docker/zwave-js:/usr/src/app/store zwavejs/zwavejs2mqtt:latest

Configure Z-Wave JS Server

Navigate to the JS Web Server

http://serverIP:8091/settings

On the settings page, enter the following configuration values (ensuring you substitute in the correct values obtained in the previous steps)

  • Serial Port: /dev/ttyACM0
  • Network Key: AABBCCDDEEFF00112233445566778899
    • Take your existing network key you obtained earlier and remove the 0x and “s to only leave one long hex string. For example:
      • Before
        • 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99
      • After
        • AABBCCDDEEFF00112233445566778899
  • Log Enabled: Disabled (toggled should be grayed)
  • Commands Timeout: 30 seconds
  • 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!

How to generate base64 encoded SSL certificates via PowerShell for Azure

Background

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

Generate a self-signed certificate
$selfSignedCert = New-SelfSignedCertificate -DnsName *.azurewebsites.net -NotAfter (Get-Date).AddYears(2)
Export the self-signed certificate into PFX format from Certificate manager
$pwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Export-PfxCertificate -cert $selfSignedCert.PSPath -FilePath "selfSignedCertificate.pfx" -Password $pwd
Convert the certificate to base64 encoding
$pfxBytes = Get-Content "selfSignedCertificate.pfx" -Encoding Byte
[System.Convert]::ToBase64String($pfxBytes) | Out-File "selfSignedCertificate.txt"

Option 2: Encode from a pre-existing pfx file

Convert the certificate to base64 encoding
$pfxBytes = Get-Content "selfSignedCertificate.pfx" -Encoding Byte
[System.Convert]::ToBase64String($pfxBytes) | Out-File "selfSignedCertificate.txt"

Result

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).

MIIKcQIBAzCCCi0GCSqGSIb3DQEHAaCCCh4EggoaMIIKFjCCBg8GCSqGSIb3DQEHAaCCBgAEggX8MIIF+DCCBfQGCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAij81GovXchnAICB9AEggTYvVQbLThNVlLYiivGlD0uSASG3g6OaY9xF+c0BfZ1ZCHGKKQ3705CDkIy4.......jx9lSOAForjR+e1nNaBFfMGy+ONccoS0lnWvFIgggZG8RCZx2jQGMnPQdm4hPdmL3j2pUPMDswHzAHBgUrDgMCGgQUJpp3pnPr5/NXgyhYzi+rGzVkCJMEFBsqGkHSsFZaBXQ/bvR5DnhzgaekAgIH0A==

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).

Cheat sheet on Azure Subnetting

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.

GatewaySubnet – /27 – https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-about-vpn-gateway-settings#gwsub

Point-to-Site (P2S) addressing (VPN or VWAN) – Requires a non-vnet address space – depends on how many P2S clients – https://docs.microsoft.com/en-us/azure/vpn-gateway/point-to-site-about#gwsku

AzureBastionSubnet – /27 – https://docs.microsoft.com/en-us/azure/bastion/bastion-create-host-portal#createhost

Azure Virtual WAN Hub – /24 – https://docs.microsoft.com/en-us/azure/virtual-wan/virtual-wan-site-to-site-portal#hub

AzureFirewallSubnet – /26 – https://docs.microsoft.com/en-us/azure/firewall/tutorial-firewall-deploy-portal#create-a-vnet

RouteServerSubnet – /27 – Quickstart: Create and configure Route Server using Azure PowerShell | Microsoft Docs

Application Gateway – min /27 per deployment – https://docs.microsoft.com/en-us/azure/application-gateway/configuration-overview#size-of-the-subnet

Azure AD Domain Services (AADDS) – min /28 – Network planning and connections for Azure AD Domain Services | Microsoft Docs

Azure SQL Managed Instance (SQL MI) – min /27 – https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-determine-size-vnet-subnet

App Services (Web Apps, Functions, API Apps) – min /27 – https://docs.microsoft.com/en-us/azure/app-service/web-sites-integrate-with-vnet

App Service Environment – /24 – https://docs.microsoft.com/en-us/azure/app-service/environment/network-info

Logic Apps integration service – /27 – https://docs.microsoft.com/en-us/azure/logic-apps/connect-virtual-network-vnet-isolated-environment#set-up-network-ports

API Management – min /29 – https://docs.microsoft.com/en-us/azure/api-management/api-management-using-with-vnet#–subnet-size-requirement

Azure Kubernetes Service (AKS) – depends on node count –  https://docs.microsoft.com/en-us/azure/aks/configure-azure-cni#plan-ip-addressing-for-your-cluster

Azure Container Instances (ACI) – /29 – https://docs.microsoft.com/en-us/azure/container-instances/container-instances-vnet

Azure Databricks – Requires 2 subnets (Public/Private) – min of two /26 – https://docs.azuredatabricks.net/administration-guide/cloud-configurations/azure/vnet-inject.html#virtual-network-requirements

Azure NetApp Files – /28 – https://docs.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-delegate-subnet

Azure Dedicated HSM – /28 – https://docs.microsoft.com/en-us/azure/dedicated-hsm/networking#subnets

Azure VMware Solutions – /22 – https://docs.microsoft.com/en-us/azure/azure-vmware/tutorial-network-checklist#routing-and-subnet-considerations

How to update Home Assistant Docker Container

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.

sudo docker stop home-assistant
sudo docker rm home-assistant

Pull the latest container from Docker Hub

Replace the value below with your IMAGE value you documented in the previous steps.

sudo docker pull homeassistant/raspberrypi4-homeassistant:stable

Deploy the container

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.

sudo docker run --init -d --restart=always --name="home-assistant" -e "TZ=America/Chicago" --device=/dev/ttyACM0 -v /home/docker/home-assistant:/config --net=host homeassistant/raspberrypi4-homeassistant:stable

Validate your version number

After a few minutes, navigate back to the Developers Tools page. Upon load, you should now be on the latest version of Home Assistant.

Notes:

You can find the latest, stable, and development builds out on docker hub here: https://hub.docker.com/u/homeassistant

For example, for raspberrypi4 builds, here you can validate the versions of all the different containers offered: https://hub.docker.com/r/homeassistant/raspberrypi4-homeassistant/tags

Home Assistant + Docker + Z-Wave + Raspberry Pi

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.

Hardware

You can leverage pretty much any hardware with Home Assistant, but here are the two items I used in my venture. Home Assistant has a full list of recommendations for what hardware to use for Home Assistant (https://www.home-assistant.io/getting-started/#suggested-hardware) as well as what Z-Wave controllers are supported (https://www.home-assistant.io/docs/z-wave/controllers/).

Update your Raspberry Pi

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.

[email protected]:/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.

[email protected]:/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:

  • –name=”the name of your container
  • -e “TM=YourTimezone
  • –device=/dev/ttyACM0
    • 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.
  • –net=host homeassistant/raspberrypi4-homeassistant:stable
    • 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
sudo docker run --init -d --restart=always --name="home-assistant" -e "TZ=America/Chicago" --device=/dev/ttyACM0 -v /home/docker/home-assistant:/config --net=host homeassistant/raspberrypi4-homeassistant:stable

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:

cat /dev/urandom | tr -dc '0-9A-F' | fold -w 32 | head -n 1 | sed -e 's/\(..\)/0x\1, /g' -e 's/, $//'

Once you execute the command, it should give you a string of characters that look something like:

“0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10”

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:

zwave:
  usb_path: /dev/ttyACM0
  network_key: "0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10"
configuration.yaml file with Z-Wave configuration

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.

[Tutorial] How to create a bootable USB Drive to flash a Lenovo device’s BIOS

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:

  1. Windows installer/flash utility (.exe)
  2. CD ISO version (.iso) to burn to a disk
  3. 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!

Setting up an email server on a RaspberryPI (Postfix+Dovecot+MariaDB+Roundcube)

There’s a few things in this journey that you should be aware of when running your own mailserver before we begin.

  1. Invest in a static public IP
  2. Don’t open mail relay
  3. Leverage proper DNS records to help mitigate your email from being marked spam
  4. Leverage something to scrub your email
  5. Don’t open mail relay
  6. Don’t open mail relay
  7. Verify your domain or IP address hasn’t been placed on a blacklist from a previous owner

In my career in doing IT, handling email is one of the most tidiest 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!

Assumptions

  • You have previously followed my guide on building a LEMP stack
  • 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:

SPF Record

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.

INSERT INTO `mailserver`.`virtual_users`
  (`domain_id`, `password` , `email`)
VALUES
  ('1', ENCRYPT('mysupersecretpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), '[email protected]');

Third, we can optionally specify an alias (secondary email address) for the user.

INSERT INTO `mailserver`.`virtual_aliases`
  (`domain_id`, `source`, `destination`)
VALUES
  ('1', '[email protected]', '[email protected]');

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.

sudo apt-get install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql

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.

sudo cp /etc/postfix/main.cf /etc/postfix/main.cf.bak

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.

sudo cp /etc/postfix/master.cf /etc/postfix/master.cf.bak

Next, we need to modify the master.cf file. Modify the document (mostly uncomment many of the lines) to look similar to the code below.

sudo vi /etc/postfix/master.cf
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

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

sudo cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
sudo cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
sudo cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
sudo cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
sudo cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
sudo cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig

Execute the following command to enable support for imap and lmtp (pop3 can be added, but ensure you install the dovecot-pop3d package).

sudo sed -i '/^\!include_try \/usr\/share\/dovecot\/protocols.d\/\*.protocol/a protocols=imap lmtp' /etc/dovecot/dovecot.conf

Next, we need to edit /etc/dovecot/conf.d/10-mail.conf to define where mailboxes are stored. Execute the following commands:

sudo sed -i 's/mail_location = mbox.*/mail_location = maildir:\/var\/mail\/vhosts\/%d\/%n\//g' /etc/dovecot/conf.d/10-mail.conf
sudo sed -i 's/^#mail_privileged_group = mail/mail_privileged_group = mail/g' /etc/dovecot/conf.d/10-mail.conf

Next, we need to make directories for each of your domain names. Execute the following command for each of your domain names.

sudo mkdir -p /var/mail/vhosts/example.com

Now we need to create a user and group called vmail, assigned with an id of 5000, and set the directory with the owner of vmail

sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /var/mail
sudo chown -R vmail:vmail /var/mail

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:

sudo chown -R vmail:dovecot /etc/dovecot
sudo chmod -R o-rwx /etc/dovecot 

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:

sudo apt-get install php7.3-mbstring php-pear php-net-idna2 php-net-smtp  php-mail-mime

Create a database for Roundcube

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 [email protected] 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.

sudo apt-get install certbot
sudo certbot certonly --authenticator standalone -d webmail.mydomain.com --pre-hook "service nginx stop" --post-hook "service nginx start"

Create a directory for Roundcube

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.

sudo mkdir /var/www/webmail.mydomain.com
sudo chown -R www-data:www-data /var/www/webmail.mydomain.com

Copy Roundcube Files to the web directory

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.

sudo mariadb roundcubemail < /var/www/webmail.mydomain.com/SQL/mysql.initial.sql

Install Roundcube dependencies

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.

sudo php /var/www/webmail.mydomain.com/bin/install-jsdeps.sh

Configure NGINX

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.

$config['default_port'] = 993;
$config['default_host'] = 'imaps://localhost';
$config['mail_domain'] = '%d';
$config['imap_conn_options'] = array(
 'ssl'         => array(
     'verify_peer'       => true,
     'verify_peer_name' => false,
  ),
);

// SMTP
$config['smtp_server'] = 'ssl://localhost';
$config['smtp_port'] = 465;
$config['smtp_auth_type'] = 'LOGIN';
// Required if you're running PHP 5.6 or later
$config['smtp_conn_options'] = array(
    'ssl' => array(
        'verify_peer'      => true,
        'verify_peer_name' => false,
    ),
);

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

Roundcube installation instructions (documentation): https://github.com/roundcube/roundcubemail/wiki/Installation

[Tutorial] Using Azure Hybrid Connection Manager to reach resources on-premises without VPN Connections

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:

  1. Via WCF Hybrid Relays
  2. Via Hybrid Connections

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).

  1. Login to the Azure Portal (portal.azure.com)
  2. Select All services -> App Services -> click + Add
  3. 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.

    1. Select HybridConnectionManager.msi
    2. Read the EULA, select I accept the terms in the License Agreement, and click Install
    3. 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.

Helpful Links/Sources

Azure Friday Video showing an example of this: https://www.youtube.com/watch?v=y_zAJZC_8Yk

Azure documentation on Hybrid Connections: https://docs.microsoft.com/en-us/azure/app-service/app-service-hybrid-connections

How to enable logging/debug HCM: https://blogs.msdn.microsoft.com/waws/2017/06/26/troubleshooting-hybrid-connections-with-logging/