functions/Posh-ACME/Invoke-SetupPoshACME.ps1

# based on https://github.com/rmbolger/Posh-ACME/blob/main/docs/Plugins/Azure.md as of 2025-06-10
function Invoke-SetupPoshACME {
    [CmdletBinding()]
    param (
        [string]$TenantId,
        [string]$SubscriptionId,
        [string]$ServicePrincipalName,
        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName,
        [string]$DnsTxtContributorRoleName = 'DNS TXT Contributor',
        [string]$PoshACMEServicePrincipalDisplayName = 'PoshACME',
        [int]$ValidYears = 5
    )
    
    begin {
        $doConnectWithStoredServicePrincipalCredentials = 
            (-not ([string]::IsNullOrWhiteSpace($TenantId) -or
             [string]::IsNullOrWhiteSpace($SubscriptionId) -or
             [string]::IsNullOrWhiteSpace($ServicePrincipalName)))
    }
    
    process {
        # Connect to Azure
        if ($doConnectWithStoredServicePrincipalCredentials) {
            $connected = Test-AzContextAndConnect -TenantId $TenantId -SubscriptionId $SubscriptionId -ServicePrincipalName $ServicePrincipalName -Endpoint https://graph.microsoft.com
        }

        if (-not $connected) {
            $connectParams = @{}
            if (-not [string]::IsNullOrWhiteSpace($TenantId)) {
                $connectParams += @{ Tenant = $TenantId }
            }
            if (-not [string]::IsNullOrWhiteSpace($SubscriptionId)) {
                $connectParams += @{ Subscription = $SubscriptionId }
            }
            $az = Connect-AzAccount @connectParams
            $tenantID = $az.Context.Subscription.TenantId
            $subscriptionID = $az.Context.Subscription.Id
        } else {
            $tenantId = $TenantId
            $subscriptionId = $SubscriptionId
        }

        # Create a Custom Role
        if (-not ($roleDef = Get-AzRoleDefinition -Name $DnsTxtContributorRoleName)) {
            $roleDef = Get-AzRoleDefinition -Name "DNS Zone Contributor"
            $roleDef.Id = $null
            $roleDef.Name = $DnsTxtContributorRoleName
            $roleDef.Description = "Manage DNS TXT records only."
            $roleDef.Actions.RemoveRange(0,$roleDef.Actions.Count)
            $roleDef.Actions.Add("Microsoft.Network/dnsZones/TXT/*")
            $roleDef.Actions.Add("Microsoft.Network/dnsZones/read")
            $roleDef.Actions.Add("Microsoft.Authorization/*/read")
            $roleDef.Actions.Add("Microsoft.Insights/alertRules/*")
            $roleDef.Actions.Add("Microsoft.ResourceHealth/availabilityStatuses/read")
            $roleDef.Actions.Add("Microsoft.Resources/deployments/read")
            $roleDef.Actions.Add("Microsoft.Resources/subscriptions/resourceGroups/read")
            $roleDef.AssignableScopes.Clear()
            $roleDef.AssignableScopes.Add("/subscriptions/$($subscriptionId)")

            $null = New-AzRoleDefinition $roleDef
        } else {
            Write-Warning "Role definition $DnsTxtContributorRoleName already exists!"
        }

        #Create a Service Principal / App Registration
        $existingSp = Get-AzADServicePrincipal -DisplayName $PoshACMEServicePrincipalDisplayName
        if ($existingSp) {
            Write-Warning "Service principal $PoshACMEServicePrincipalDisplayName already exists!"
            $sp = $existingSp
        } else {
            $notBefore = Get-Date
            $notAfter = $notBefore.AddYears($ValidYears)

            $spParams = @{
                DisplayName = $PoshACMEServicePrincipalDisplayName
                StartDate = $notBefore
                EndDate = $notAfter
            }
            try {
                $sp = New-AzADServicePrincipal @spParams
            } catch {
                # TODO test error for 'Insufficient privileges to complete the operation'
                throw $_
            }
        }

        # TODO add support for certificate based service principal
        $spPass = $sp.PasswordCredentials.SecretText | ConvertTo-SecureString -AsPlainText -Force
        $appCred = [pscredential]::new($sp.AppId,$spPass)

        # Assign Permissions to the Service Principal
        $raParams = @{
            ApplicationId = $sp.AppId
            ResourceGroupName = $ResourceGroupName
            RoleDefinitionName = $DnsTxtContributorRoleName
        }
        $null = New-AzRoleAssignment @raParams

        $pArgs = @{
            AZSubscriptionId = $subscriptionID
            AZTenantId = $tenantID
            AZAppCred = $appCred
        }

        $null = Set-AzStoredServicePrincipalCredential -TenantId $TenantId -SubscriptionId $SubscriptionId -ServicePrincipalName $PoshACMEServicePrincipalDisplayName -ApplicationId $sp.AppId -ClientSecret $spPass
    }
    
    end {
        return $pArgs
    }
}