AzurePrincipalWithCert.ps1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Scope="Function", Target="*")]
param()

function Set-KeyVaultCertSecret
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$true)]
        [string] $CertFolderPath,

        [parameter(Mandatory=$true)]
        [string] $CertPassword,

        [parameter(Mandatory=$true)]
        [string] $SecretName,

        [parameter(Mandatory=$true)]
        [string] $VaultName
    )

    $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    $collection.Import($CertFolderPath, $CertPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

    $clearBytes = $collection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12)
    $fileContentEncoded = [System.Convert]::ToBase64String($clearBytes)

    $secret = ConvertTo-SecureString -String $fileContentEncoded -AsPlainText –Force
    $secretContentType = 'application/x-pkcs12'

    Set-AzureKeyVaultSecret -VaultName $VaultName -Name $SecretName -SecretValue $secret -ContentType $secretContentType -Verbose
}

###########################################################
# New-AzurePrincipalWithCert
###########################################################

function New-AzurePrincipalWithCert
{
<#
.SYNOPSIS
Adds AzureRM Active Directory Application and persists a cert to Key Vault for it.
 
.DESCRIPTION
1. Creates a new Azure Active Directory Application
2. Creates a new cert in Azure Key Vault for the AAD Application.
 
.PARAMETER SystemName
The system the application is for.
 
.PARAMETER PrincipalPurpose
The purpose of the principal Authentication or Configuration.
 
.PARAMETER EnvironmentName
The environment the application is for.
 
.PARAMETER CertFolderPath
Local path to where the cert will be created.
 
.PARAMETER CertPassword
The password for the cert.
 
.PARAMETER VaultSubscriptionId
The subscription Id that Key Vault is on.
 
.PARAMETER PrincipalName
The name of the Key Vault principal.
 
.EXAMPLE
New-AzurePrincipalWithCert -SystemName 'sys1' `
                           -PrincipalPurpose 'Authentication' `
                           -EnvironmentName 'test' `
                           -CertFolderPath 'C:\Certificates' `
                           -CertPassword 'something123$' `
                           -VaultSubscriptionId '[ID HERE]' `
                           -PrincipalName 'Keyvault'
 
.NOTES
Currently CmdletBinding doesn't have any internal support built-in.
#>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$true, Position=0)]
        [string] $SystemName,

        [parameter(Mandatory=$true, Position=1)]
        [ValidateSet('Configuration','Authentication')]
        [string] $PrincipalPurpose,

        [parameter(Mandatory=$true, Position=2)]
        [string] $EnvironmentName,

        [parameter(Mandatory=$true, Position=3)]
        [string] $CertFolderPath,

        [parameter(Mandatory=$true, Position=4)]
        [string] $CertPassword,

        [parameter(Mandatory=$false, Position=5)]
        [string] $VaultSubscriptionId,

        [parameter(Mandatory=$false, Position=6)]
        [string] $PrincipalName
    )

    # GUARD: There are no non letter characters on the Cert Name
    if(-not [string]::IsNullOrWhiteSpace($PrincipalName) -and -not ($PrincipalName -match "^([A-Za-z])*$")) {
        throw 'The CertName must be letters only, either lower and upper case. Cannot contain any digits or any non-alpha-numeric characters.'
    }

    # Uniform all the namings
    if([string]::IsNullOrWhiteSpace($PrincipalName)) {
        $principalIdDashed = "$($SystemName)-$($PrincipalPurpose)".ToLower()
        $principalIdDotted = "$($SystemName).$($PrincipalPurpose)".ToLower()
        $identifierUri = "https://$($SystemName).$($PrincipalPurpose).$($EnvironmentName)".ToLower()
    }
    else {
        $principalIdDashed = "$($SystemName)-$($PrincipalPurpose)-$($PrincipalName)".ToLower()
        $principalIdDotted = "$($SystemName).$($PrincipalPurpose).$($PrincipalName)".ToLower()
        $identifierUri = "https://$($SystemName).$($PrincipalPurpose).$($EnvironmentName).$($PrincipalName)".ToLower()
    }

    # GUARD: AD application already exists, return the existing ad application
    $adApplication = Get-AzureRmADApplication -DisplayNameStartWith $principalIdDotted
    if($adApplication -ne $null) {
        Write-Warning 'An AD Application Already exists that looks identical to what you are trying to create'
        return $adApplication
    }

    # GUARD: Certificate system vault exists
    $systemVaultName = "$($SystemName)-$($EnvironmentName)".ToLower()
    if((Get-AzureRmKeyVault -VaultName $systemVaultName) -eq $null) {
        throw "The system vault $systemVaultName doesn't exist in the current subscription. Create it before running this cmdlet!"
    }

    # Create the self signed cert
    $currentDate = Get-Date
    $endDate = $currentDate.AddYears(1)
    $notAfter = $endDate.AddYears(1)

    if($CertFolderPath.EndsWith("\") -eq $false) {
        $CertFolderPath = $CertFolderPath + "\"
    }

    $cert = New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my `
                                      -DnsName $principalIdDotted `
                                      -KeyExportPolicy Exportable `
                                      -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
                                      -NotAfter $notAfter `
                                      -NotBefore $currentDate `
                                      -ErrorAction Stop                                      

    $pwd = ConvertTo-SecureString -String $CertPassword -Force -AsPlainText
    $certPath = "$CertFolderPath$SystemName-$EnvironmentName.pfx"
    $null = Export-PfxCertificate -cert "cert:\localmachine\my\$($cert.Thumbprint)" -FilePath $certPath -Password $pwd -ErrorAction Stop

    # Load the certificate
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, $pwd)
    $KeyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())

    # Aquire the tenant ID on the subscription we are creating the principal on, not on the vault subscription!
    $tenantId = (Get-AzureRmContext).Subscription.TenantId

    # Create the Azure Active Directory Application
    $azureAdApplication = New-AzureRmADApplication -DisplayName $principalIdDotted `
                                                   -HomePage $identifierUri `
                                                   -IdentifierUris $identifierUri `
                                                   -Verbose

    # Create a new Azure Active Directory Application Credential and link it to the previously created Application
    New-AzureRmADAppCredential -ApplicationId $azureAdApplication.ApplicationId -StartDate $cert.NotBefore -EndDate $cert.NotAfter -CertValue ([System.Convert]::ToBase64String($cert.GetRawCertData()))

    Write-Host -ForegroundColor Green  "Application ID: $($azureAdApplication.ApplicationId)"

    # Create the Service Principal and connect it to the Application
    $null = New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId

    # Switch to the KeyVault Techops-Management subscription
    $currentSubId = (Get-AzureRmContext).Subscription.SubscriptionId
    if(($VaultSubscriptionId -ne $null) -and ($currentSubId -ne $VaultSubscriptionId)) {
        Select-AzureRmSubscription -SubscriptionId $VaultSubscriptionId -ErrorAction Stop
    }

    # Upload the cert and cert passwords to the right keyvaults
    $null = Set-KeyVaultCertSecret -VaultName $systemVaultName -CertFolderPath $certPath -CertPassword $CertPassword -SecretName "$principalIdDashed-Cert"
    $null = Set-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$principalIdDashed-CertPwd" -SecretValue (ConvertTo-SecureString -String $CertPassword -AsPlainText –Force)

    # Populate the system keyvault with all relevant principal configuration information
    $null = Set-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$principalIdDashed-TenantId" -SecretValue (ConvertTo-SecureString -String $tenantId -AsPlainText –Force)
    $null = Set-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$principalIdDashed-IdentifierUri" -SecretValue (ConvertTo-SecureString -String $identifierUri -AsPlainText –Force)
    $null = Set-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$principalIdDashed-ApplicationId" -SecretValue (ConvertTo-SecureString -String $($azureAdApplication.ApplicationId) -AsPlainText –Force)

    # Swap back to the subscription the user was in
    if(($VaultSubscriptionId -ne $null) -and ($currentSubId -ne $VaultSubscriptionId)) {
        Select-AzureRmSubscription -SubscriptionId $currentSubId | Out-Null
    }

    return $azureAdApplication
}

###########################################################
# Remove-AzurePrincipalWithCert
###########################################################

function Remove-AzurePrincipalWithCert
{
<#
.SYNOPSIS
Removes the Azure Active Directory Application, Principal and the cert stored for it.
 
.DESCRIPTION
Removes the Azure Active Directory Application, Principal and the cert stored for it.
 
.PARAMETER ADApplicationId
The Id of the Azure Active Directory Application you with to remove.
 
.PARAMETER ADApplication
The Azure Active Directory Application you with to remove.
 
.PARAMETER VaultSubscriptionId
The subscription Id that Key Vault is on.
 
.EXAMPLE
Remove-AzurePrincipalWithCert -ADApplicationId '[ID HERE]' -VaultSubscriptionId '[ID HERE]'
 
.NOTES
Currently CmdletBinding doesn't have any internal support built-in.
#>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false, Position=0)]
        [string] $ADApplicationId,

        [parameter(
            Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true,
            Position=1)]
        [object] $ADApplication,

        [parameter(Mandatory=$false, Position=2)]
        [string] $VaultSubscriptionId
    )

    # GUARD: At least one parameter is supplied
    if((-not $ADApplicationId) -and (-not $ADApplication)) {
        throw 'You must either supply the PSADApplication object in the pipeline or the ApplicationID guid.'
    }

    if(-not $ADApplication) {
        $adApp = Get-AzureRmADApplication -ApplicationId $ADApplicationId

        # GUARD: If ADApplicationId is supplied, make sure it's a valid one that really exists
        if(-not $adApp) {
            throw "The specified ADApplicationID [$ADApplicationId] doesn't exist"
        }

        $identifierUri = $adApp.IdentifierUris[0]
    }
    else {
        $identifierUri = $ADApplication.IdentifierUris[0]
    }

    # Break the Identifier URI of the AD Application into it's individual components so that we can infer everything else.
    if(-not ($identifierUri -match 'https:\/\/(?<system>[^.]*).(?<purpose>[^.]*).(?<environment>[^.]*).(?<principalName>[^.]*)')) {
        throw "Can't infer the correct system information from the identifier URI [$identifierUri] in the AD Application, was this service principal created with this Module?"
    }

    $systemName = $Matches['system']
    $principalPurpose = $Matches['purpose']
    $environmentName = $Matches['environment']
    $principalName = $Matches['principalName']

    # Uniform all the namings
    if([string]::IsNullOrWhiteSpace($principalName)) {
        $dashName = "$systemName-$principalPurpose"
        $dotName = "$systemName.$principalPurpose"
    }
    else {
        $dashName = "$systemName-$principalPurpose-$principalName"
        $dotName = "$systemName.$principalPurpose.$principalName"
    }

    # Switch to the KeyVault Techops-Management subscription
    $currentSubId = (Get-AzureRmContext).Subscription.SubscriptionId
    if(($VaultSubscriptionId -ne $null) -and ($currentSubId -ne $VaultSubscriptionId)) {
        Select-AzureRmSubscription -SubscriptionId $VaultSubscriptionId -ErrorAction Stop
    }

    $systemVaultName = "$($systemName)-$($environmentName)".ToLower()

    # Remove the cert and cert password from the system keyvault
    Remove-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$dashName-Cert" -Force -Confirm:$false
    Remove-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$dashName-CertPwd" -Force -Confirm:$false

    # Remove the principal configuration information from the system keyvault
    Remove-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$dashName-TenantId" -Force -Confirm:$false
    Remove-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$dashName-IdentifierUri" -Force -Confirm:$false
    Remove-AzureKeyVaultSecret -VaultName $systemVaultName -Name "$dashName-ApplicationId" -Force -Confirm:$false

    # Swap back to the subscription the user was in
    if(($VaultSubscriptionId -ne $null) -and ($currentSubId -ne $VaultSubscriptionId)) {
        Select-AzureRmSubscription -SubscriptionId $currentSubId | Out-Null
    }

    # Remove the AD Service Principal
    $servicePrincipal = Get-AzureRmADServicePrincipal -SearchString $dotName -ErrorAction SilentlyContinue
    if($servicePrincipal) {
        Remove-AzureRmADServicePrincipal -ObjectId $servicePrincipal.Id -Force
    }
    else {
        Write-Warning "Couldn't find any Service Principal using the search string [$dotName]"
    }

    # Remove the AD Application
    $adApplication = Get-AzureRmADApplication -DisplayNameStartWith $dotName -ErrorAction SilentlyContinue
    if($adApplication) {
        Remove-AzureRmADApplication -ObjectId $adApplication.ObjectId -Force
    }
}