Adding and using the Duo-PSModule (PowerShell) in Azure Automation

Decorative image - Duo, PowerShell and Azure Automation logos

All product names, logos, and brands used in this post are property of their respective owners.

As of late, I’ve been spending time in Azure Automation intending to reclaim parts of my day from recurring (sometimes mundane) tasks. The allure for me is PowerShell RunBooks. Since I am a “PowerSheller” already, the learning curve is minimal.

In this post, I will describe the process and best practices for importing and using the Duo-PSModule PowerShell module in Azure Automation. Matt Egan created the Duo PS module. It is a robust Power Shell wrapper for the Duo Admin API. I am a big fan of this module and have used it previously - see this post.

Importing the Duo-PSModule

Adding the Duo PowerShell module to an Azure Automation account is not as easy as downloading the zipped version from GitHub and uploading it into Azure Automation.

Screenshot of incorrect module import attempt using the Duo-PSModule-master.zip archive directly from GitHub

The error message resulting from doing so is:

Error importing the module Duo-PSModule-master.
Import failed with the following error:
Orchestrator.Shared.AsyncModuleImport.ModuleImportException:
Cannot import the module of name Duo-PSModule-master, as the module
structure was invalid.

This is an easy fix - the module files (Duo.psm1 and Duo.psd1) must be at the root of the zip file, and the name of the zip file must match the name of the module. I created a new zip file called Duo.zip and added the module files from the GitHub zip archive to the root accordingly:

Screenshot of properly named zip archive/file with the 2 module files at its root

In the case of the Duo-PSModule, that is not enough. The module (specifically Duo.psd1) has several references to the “Duo_org.ps1” file, which by design, does not exist in the GitHub repository (or downloaded zip file). As a result, importing the correctly structured zip file into an Azure Automation account will fail as follows:

Error importing the module Duo. Import failed with the
following error: Orchestrator.Shared.AsyncModuleImport.ModuleImportException:
While importing the module, an error occurred while processing the module
content. Internal error message: The member 'ScriptsToProcess' in the module
manifest is not valid: Cannot find path 'C:\Users\Client\Temp\XXXXXXXXXX\Duo\Duo_org.ps1'
because it does not exist.. Verify that a valid value is specified for this
field in the 'C:\Users\Client\Temp\XXXXXXXXXX\Duo\Duo.psd1' file.

Luckily, this is another easy fix. For me, the path of least resistance was adding a blank Duo_org.ps1 file to my Duo.zip archive. With the Duo-PSModule installed locally, Duo_org.ps1 provides authentication parameters (Duo integration key, secret key, API hostname, etc.). In Azure Automation, there is a better way (more on that later). The final contents of Duo.zip should reflect the following:

Screenshot of properly named zip archive/file with the 3 required files (2 for the module itself and a blank Duo_org.ps1) at its root

At this point, the module can be successfully added (imported) into an Azure Automation account:

Screenshot of Duo-PSModule / Duo module successfully added and available in an Azure Automation account

Another option (instead of adding a blank Duo_org.ps1 file)

If you prefer not to include a blank Duo_org.ps1, you can also update the source code of Duo.psd1, removing references to Duo_org.ps1 here, here, and here.

Creating the Duo Admin API application and granting permissions

For the Duo-PSModule to connect and interact with a Duo implementation, the Duo Admin API must be exposed by creating a new Application of type “Admin API” in the Duo Console. Select Applications -> Protect an Application -> Type “Admin API” and click Protect. Take note of the Integration key, Secret key, and API hostname - these are needed later to configure the Duo-PSModule in Azure Automation.

Before saving the application, check the boxes for Grant read resource and Grant write resource (or whatever permissions your use case requires):

Screenshot of Duo Admin API permissions selection

Duo authentication settings for Duo-PSModule in Azure Automation

As noted, Duo_org.ps1 usually holds the Duo Admin API connection and authentication information (created above). See Installation, specifically …Create a file called Duo_org.ps1…

In Azure Automation, I opted to use encrypted variables instead and simply constructed the required $DuoOrgs hashtable inline in my Runbook(s) that use the Duo-PSModule and Duo API. More specifically, I created 4 encrypted Automation Variables in my Automation Account as follows:

Screenshot of the 4 encrypted Azure Automation variables being created for the Duo integration

Once done, I used the following code in my RunBook to handle authentication for the Duo-PSModule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Get Duo integration details from Automation Variables
$duoiKey = Get-AutomationVariable -Name MyDuoiKey
$duosKey = Get-AutomationVariable -Name MyDuosKey
$duoApiHost = Get-AutomationVariable -Name MyDuoHostname
$duoDirID = Get-AutomationVariable -Name MyDuoDirectoryID

# Build the $DuoOrgs hashtable from retrieved
# Automation Variables
[string]$DuoDefaultOrg = "Personal"
[Hashtable]$DuoOrgs = @{
Personal = [Hashtable]@{
iKey  = [string]$duoiKey
sKey = [string]$duosKey
apiHost = [string]$duoApiHost
directory_key = [string]$duoDirID
}}

Using Duo-PSModule in Azure Automation

After importing and setting up authentication for the Duo PowerShell module, using it in a RunBook is easy (crude example to synchronize all Duo users from a linked directory):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Get Duo integration details from Automation Variables
$duoiKey = Get-AutomationVariable -Name MyDuoiKey
$duosKey = Get-AutomationVariable -Name MyDuosKey
$duoApiHost = Get-AutomationVariable -Name MyDuoHostname
$duoDirID = Get-AutomationVariable -Name MyDuoDirectoryID

# Build the $DuoOrgs hashtable from retrieved
# Automation Variables
[string]$DuoDefaultOrg = "Personal"
[Hashtable]$DuoOrgs = @{
Personal = [Hashtable]@{
iKey  = [string]$duoiKey
sKey = [string]$duosKey
apiHost = [string]$duoApiHost
directory_key = [string]$duoDirID
}}

# Force a Duo directory synchronization for all
# existing users
duoGetUser | %{ duoSyncUser -username $_.username }

Closing thoughts

I must first reiterate my gratitude and respect for Matt Egan and the Duo-PSModule. This module (coupled with Azure Automation) creates numerous opportunities for automating Duo processes, including but not limited to:

  • Scheduled Duo report generation and distribution (users, devices, authentications, etc.)
  • Automatic Duo enrollment reminder notifications
  • Scheduling more frequent (i.e. hourly vs. daily) Duo directory sync cycles
  • Nearly code-less Duo device management processes with M365 Forms and/or Power Automate front ends

I hope this helps another Duo administrator free up part of their day as well!