Skip to content

Commit

Permalink
Merge pull request d365collaborative#809 from FH-Inway/improve-entra-…
Browse files Browse the repository at this point in the history
…integration

Improve entra integration
  • Loading branch information
Splaxi authored Feb 12, 2024
2 parents 8eaf295 + e1dfa11 commit 13cf34d
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 78 deletions.
1 change: 1 addition & 0 deletions d365fo.tools/d365fo.tools.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
'Backup-D365MetaDataDir',
'Backup-D365Runbook',
'Backup-D365WebConfig',
'Backup-D365WifConfig',

'Clear-D365ActiveBroadcastMessageConfig',
'Clear-D365BacpacObject',
Expand Down
13 changes: 1 addition & 12 deletions d365fo.tools/functions/backup-d365devconfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,7 @@ function Backup-D365DevConfig {
if (-not (Test-PathExists -Path $File -Type Leaf)) { return }

if (Test-PSFFunctionInterrupt) { return }

$fileName = Split-Path -Path $File -Leaf
$destinationFile = $(Join-Path -Path $OutputPath -ChildPath $fileName)

if (-not $Force) {
if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) {
Write-PSFMessage -Level Host -Message "The <c='em'>$destinationFile</c> already exists. Consider changing the <c='em'>destination</c> path or set the <c='em'>Force</c> parameter to overwrite the file."
return
}
}

Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item
Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File"
Backup-File -File $File -DestinationPath $OutputPath -Force:$Force
}
}
13 changes: 1 addition & 12 deletions d365fo.tools/functions/backup-d365runbook.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,7 @@ function Backup-D365Runbook {
if (-not (Test-PathExists -Path $File -Type Leaf)) { return }

if (Test-PSFFunctionInterrupt) { return }

$fileName = Split-Path -Path $File -Leaf
$destinationFile = $(Join-Path $DestinationPath $fileName)

if (-not $Force) {
if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) {
Write-PSFMessage -Level Host -Message "The <c='em'>$destinationFile</c> already exists. Consider changing the <c='em'>destination</c> path or set the <c='em'>Force</c> parameter to overwrite the file."
return
}
}

Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item
Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File"
Backup-File -File $File -DestinationPath $DestinationPath -Force:$Force
}
}
16 changes: 3 additions & 13 deletions d365fo.tools/functions/backup-d365webconfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,10 @@ function Backup-D365WebConfig {
process {

if (-not (Test-PathExists -Path $File -Type Leaf)) { return }

if (Test-PSFFunctionInterrupt) { return }

$fileName = Split-Path -Path $File -Leaf
$destinationFile = $(Join-Path -Path $OutputPath -ChildPath $fileName)

if (-not $Force) {
if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) {
Write-PSFMessage -Level Host -Message "The <c='em'>$destinationFile</c> already exists. Consider changing the <c='em'>destination</c> path or set the <c='em'>Force</c> parameter to overwrite the file."
return
}
}

Backup-File -File $File -DestinationPath $OutputPath -Force:$Force

Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item
Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File"
}
}
72 changes: 72 additions & 0 deletions d365fo.tools/functions/backup-d365wifconfig.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

<#
.SYNOPSIS
Backup the wif.config file
.DESCRIPTION
Will backup the wif.config file located in the AOS / IIS folder
.PARAMETER OutputPath
Path to the folder where you want the web.config file to be persisted
Default is: "C:\Temp\d365fo.tools\WifConfigBackup"
.PARAMETER Force
Instructs the cmdlet to overwrite the destination file if it already exists
.EXAMPLE
PS C:\> Backup-D365WifConfig
Will locate the wif.config file, and back it up.
It will look for the file in the AOS / IIS folder. E.g. K:\AosService\WebRoot\wif.config.
It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup".
A result set example:
Filename LastModified File
-------- ------------ ----
wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config
.EXAMPLE
PS C:\> Backup-D365WifConfig -Force
Will locate the wif.config file, back it up, and overwrite if a previous backup file exists.
It will look for the file in the AOS / IIS folder. E.g. K:\AosService\WebRoot\wif.config.
It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup".
It will overwrite any file named wif.config in the destination folder.
A result set example:
Filename LastModified File
-------- ------------ ----
wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config
.NOTES
Author: Florian Hopfner (@FH-Inway)
#>
function Backup-D365WifConfig {
[CmdletBinding()]
[OutputType()]
param (
[string] $OutputPath = $(Join-Path $Script:DefaultTempPath "WifConfigBackup"),

[switch] $Force
)

begin {
if (-not (Test-PathExists -Path $OutputPath -Type Container -Create)) { return }

$File = $(Join-Path -Path $Script:AOSPath -ChildPath $Script:WifConfig)
}

process {

if (-not (Test-PathExists -Path $File -Type Leaf)) { return }

if (Test-PSFFunctionInterrupt) { return }

Backup-File -File $File -DestinationPath $OutputPath -Force:$Force

}
}
57 changes: 49 additions & 8 deletions d365fo.tools/functions/new-d365entraintegration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
The steps executed are:
1. Create a self-signed certificate and save it to Desktop or use a provided certificate.
2. Install the certificate to the "LocalMachine" certificate store.
3. Grant NetworkService READ permission to the certificate (only on cloud-hosted environments).
4. Update the web.config with the application ID and the thumbprint of the certificate.
- 1) Create a self-signed certificate and save it to Desktop or use a provided certificate.
- 2) Install the certificate to the "LocalMachine" certificate store.
- 3) Grant NetworkService READ permission to the certificate (only on cloud-hosted environments).
- 4) Update the web.config with the application ID and the thumbprint of the certificate.
- 5) (Optional) Add the application registration to the WIF config.
To execute the steps, the id of an Azure application must be provided. The application must have the following API permissions:
a. Dynamics ERP - This permission is required to access finance and operations environments.
b. Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type).
- Dynamics ERP - This permission is required to access finance and operations environments.
- Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type).
The URL of the finance and operations environment must also be added to the RedirectURI in the Authentication section of the Azure application.
Finally, after running the cmdlet, if a new certificate was created, it must be uploaded to the Azure application.
Expand Down Expand Up @@ -54,6 +55,9 @@
.PARAMETER Force
Forces the execution of some of the steps. For example, if a certificate with the same name already exists, it will be deleted and recreated.
.Parameter AddAppRegistrationToWifConfig
Adds the application registration to the WIF config. This is not part of the official Microsoft documentation to enable the Entra ID integration. It is however highly recommended to fix additional issues with the missing entry integration.
.Parameter WhatIf
Executes the cmdlet until the first operation that would change the state of the system, without executing that operation.
Subsequent operations are likely to fail.
Expand All @@ -72,7 +76,12 @@
Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years.
.EXAMPLE
PS c:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert"
PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -AddAppRegistrationToWifConfig
Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years and adds the application registration to the wif.config.
.EXAMPLE
PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert"
Enables the Entra ID integration with a new self-signed certificate with the name "Selfsignedcert" that expires after 2 years.
Expand All @@ -95,6 +104,8 @@
Enables the Entra ID integration with the certificate file "C:\Temp\SelfsignedCert.cer", the private key file "C:\Temp\SelfsignedCert.pfx" and the provided password to install it.
.NOTES
Test-D365EntraIntegration can be used to validate an entra integration.
Author: Øystein Brenna (@oysbre)
Author: Florian Hopfner (@FH-Inway)
#>
Expand Down Expand Up @@ -129,7 +140,9 @@ function New-D365EntraIntegration {

[Security.SecureString] $CertificatePassword,

[switch] $Force
[switch] $Force,

[switch] $AddAppRegistrationToWifConfig
)

if (-not ($Script:IsAdminRuntime)) {
Expand Down Expand Up @@ -325,4 +338,32 @@ function New-D365EntraIntegration {
if ($PSCmdlet.ParameterSetName -eq "NewCertificate") {
Write-PSFMessage -Level Host -Message "The certificate file <c='em'>$NewCertificateFile</c> must be uploaded to the Azure application, see https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app#add-a-certificate."
}

# Step 5: Add app registration to Wif.config
if ($AddAppRegistrationToWifConfig) {
Write-PSFMessage -Level Verbose -Message "Step 5: Starting adding app registration to Wif.config"
$wifConfigBackup = Join-Path $Script:DefaultTempPath "WifConfigBackup"
$wifConfigFileBackup = Join-Path $wifConfigBackup $Script:WifConfig
if (Test-PathExists -Path $wifConfigFileBackup -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) {
Write-PSFMessage -Level Warning -Message "Backup of Wif.config already exists."
if (-not $Force) {
Stop-PSFFunction -Message "Stopping because a backup of Wif.config already exists"
return
}
Write-PSFMessage -Level Host -Message "Backup of Wif.config will be overwritten."
}
$null = Backup-D365WifConfig -Force:$Force
$wifConfigFile = Join-Path -Path $Script:AOSPath $Script:WifConfig
if (-not (Test-PathExists -Path $wifConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
Write-PSFMessage -Level Host -Message "Unable to find the Wif.config file."
Stop-PSFFunction -Message "Stopping because the Wif.config file could not be found"
}
[xml]$xml = Get-Content $wifConfigFile
$audienceUris = $xml.'system.identityModel'.identityConfiguration.securityTokenHandlers.securityTokenHandlerConfiguration.audienceUris
$audienceUriElement = $xml.CreateElement('add')
$audienceUriElement.SetAttribute('value', "spn:$ClientId")
$audienceUris.PrependChild($audienceUriElement)
$xml.Save($wifConfigFile)
Write-PSFMessage -Level Host -Message "Wif.config was updated with the audience URIs."
}
}
2 changes: 1 addition & 1 deletion d365fo.tools/functions/rename-d365instance.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function Rename-D365Instance {
# Backup files
if ($null -ne $BackupExtension -and $BackupExtension -ne '') {
foreach ($item in $Files) {
Backup-File $item $BackupExtension
Backup-File -File $item -Suffix $BackupExtension
}
}

Expand Down
28 changes: 26 additions & 2 deletions d365fo.tools/functions/test-d365entraintegration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function Test-D365EntraIntegration {
return
}

# Check web.config
$webConfigFile = Join-Path -Path $Script:AOSPath $Script:WebConfig

if (-not (Test-PathExists -Path $webConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
Expand Down Expand Up @@ -68,12 +69,35 @@ function Test-D365EntraIntegration {
}

if ((-not [System.String]::IsNullOrWhiteSpace($config.S2SCertThumbprint)) -and $config.S2SCertThumbprint -ne $config.GraphAPIServicePrincipalCert) {
Write-PSFMessage -Level Host -Message "The <c='em'>'Infrastructure.S2SCertThumbprint'</c> and the <c='em'>'GraphApi.GraphAPIServicePrincipalCert'</c> value doesn't match each other. This indicates that you a <c='em'>corrupted</c> configuration. Running the <c='em'>'New-D365EntraIntegration'</c> cmdlet could assist with fixing the configuration."
Stop-PSFFunction -Message "Stopping because the 'Infrastructure.S2SCertThumbprint' and 'GraphApi.GraphAPIServicePrincipalCert' values doesn't match"
Write-PSFMessage -Level Host -Message "The <c='em'>'Infrastructure.S2SCertThumbprint'</c> and the <c='em'>'GraphApi.GraphAPIServicePrincipalCert'</c> value do not match each other. This indicates that you have a <c='em'>corrupted</c> configuration. Running the <c='em'>'New-D365EntraIntegration'</c> cmdlet could assist with fixing the configuration."
Stop-PSFFunction -Message "Stopping because the 'Infrastructure.S2SCertThumbprint' and 'GraphApi.GraphAPIServicePrincipalCert' values do not match"
}

if (Test-PSFFunctionInterrupt) { return }

# Check wif.config
$wifConfigFile = Join-Path -Path $Script:AOSPath $Script:WifConfig

if (-not (Test-PathExists -Path $wifConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
Write-PSFMessage -Level Host -Message "Unable to find the wif.config file."
Stop-PSFFunction -Message "Stopping because the wif.config file could not be found"
}

[xml]$xml = Get-Content $wifConfigFile
$nodes = ($xml.'system.identityModel'.identityConfiguration.securityTokenHandlers.securityTokenHandlerConfiguration.audienceUris).ChildNodes
$config.AudienceUri = $nodes | Where-Object { $_.value -like "*$($config.AadRealm)*" } | Select-Object -First 1 -ExpandProperty value

if ([System.String]::IsNullOrWhiteSpace($config.AudienceUri)) {
Write-PSFMessage -Level Host -Message "The <c='em'>'AudienceUri'</c> value is empty. This may not be needed, but in case of issues, try running the <c='em'>'New-D365EntraIntegration'</c> cmdlet with the -AddAppRegistrationToWifConfig switch."
}
elseif ($config.AadRealm -ne $config.AudienceUri) {
Write-PSFMessage -Level Host -Message "The <c='em'>'Aad.Realm'</c> and the <c='em'>'AudienceUri'</c> value do not match each other. This indicates that you have a <c='em'>corrupted</c> configuration. Running the <c='em'>'New-D365EntraIntegration'</c> cmdlet with the -AddAppRegistrationToWifConfig switch could assist with fixing the configuration."
Stop-PSFFunction -Message "Stopping because the 'Aad.Realm' and 'AudienceUri' values do not match"
}

if (Test-PSFFunctionInterrupt) { return }

# Check certificate
$certStoreLocation = "Cert:\LocalMachine\My"

$certEntra = Get-ChildItem -Path $certStoreLocation -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $config.S2SCertThumbprint } | Select-Object -First 1
Expand Down
Loading

0 comments on commit 13cf34d

Please sign in to comment.