Deploying LDAP / Active Directory Self Service Password Portal

Overview:

There are several choices of platforms to deploy Password Manager: Kubernetes, Docker, Windows, and Linux. This document focuses on the Windows OS.

These instructions are mostly in codes, rather than proper English directions. Many of the chosen options are defaults; hence, this content should only be treated as educational. In computer-speak, please be advised that these scripts are meant for DEV environments. PROD deployments would require decoupling of services (adding dedicated database hosts and restrict permissions of certain service accounts) for purposes of improving security postures and computational performance.

Requirements:

  • OS: Windows 2012R2 or later
  • Hardware: 2 CPU’s, 4GB RAM, 20GB available storage
  • OpenLDAP or Active Directory: 1 proxy account + 1 test account
  • Skills: expert level copy/paste
  • Network: port TCP/443 egress Internet access from the host (ingress TCP/443 from service zones)
  • Public certificates: optionally required or arbitrarily mandatory (half sure and half unsure)
# InstallPasswordManager.ps1
# Source: https://github.com/pwm-project/pwm/wiki/Installation-on-Windows-OS
# This scripted installation is a proximal translation of the semantics provided per vendor documentation
# Additionally, special 'tricks' have been discovered by yours truly to enable hosting of this App on a Windows server seamlessly
# The scope of this script is limited to the deployment of PWM on port 80, without SSL, and no considerations on database placement

#######################################################################################
# Installation of Tomcat & Password Manager
#######################################################################################

$pwmSearchPage='https://www.pwm-project.org/artifacts/pwm/build/'
$pwmExtension='.war'
$tomcatSearchPage='http://apache.mirrors.pair.com/tomcat/tomcat-9/'
$tomcatFileExtension='.exe'
$serviceName='Tomcat9'
$defaultWebappsDirectory='C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps'
$serverXml='C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\server.xml'
$tempDir='C:\Temp'
$pwmData='C:\pwm-data'
$webXml='C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\ROOT\WEB-INF\web.xml'
$webXmlEditPath=@('web-app','context-param','param-value')
$serverPortEditPath=@('Server','Service','Connector','Port')
$serverPortValue='443'
$appUrl="https://localhost:$serverPortValue/"

function findDownloadUrl{
    param(
        $parentUrl,
        $fileExtension,
        $maxDepth=2
    )
    if(!$parentUrl){
        write-warning "Cannot start with a blank parent URL"
    }elseif($parentUrl -notmatch '/$'){
        $parentUrl=$parentUrl+'/'
        }
    $page=Invoke-WebRequest $parentUrl   
    $links=$page.links.href|Select -Unique|sort -Descending|%{$parentUrl+$_}
    $knownLinks=$links
 
    function findFile($parentUrl,$extension){
        $ProgressPreference='SilentlyContinue'
        $ErrorActionPreference='stop'
        if($parentUrl -notmatch '/$'){$parentUrl=$parentUrl+'/'}
        try{
            $page=Invoke-WebRequest $parentUrl -TimeoutSec 10
        }catch{
            return @($false,@())
            }
        $newLinks=$page.links.href|?{($_ -notlike "*$(Split-Path $parentUrl -parent)") -and ($_ -notmatch '^http')}| `
            sort -Descending|%{$parentUrl+$(
                                if($_[0] -eq '/'){
                                    $_.Substring(1,$_.length-1)
                                }else{
                                    $_
                                }
                            )}|select -Unique
        $matchedExtension=$newLinks|?{$_ -like "*$extension"}|sort -Descending|select -First 1
        if($matchedExtension){
            return @($true,$matchedExtension)
        }elseif($newLinks){
            return @($false,$newLinks)
        }else{
            return @($false,@())
            } 
    }   
     
    foreach ($link in $links){
        $currentDepth=1
        $newLinks=@($link)
        do{            
            $thisLink=$newLinks|Select -Unique|select -First 1
            $newLinks=$newLinks[1..($newLinks.count-1)]
            $result=findFile $thisLink $fileExtension
            if($result[0]){
                return $result[1]
            }elseif(($currentDepth++ -le $maxDepth) -and ($result[1]|?{$_ -notin $knownLinks})){                
                $addLinks=$result[1]|?{$_ -notin $knownLinks}
                $knownLinks+=$addLinks
                $newLinks=$addLinks+$newLinks
                }
        }until(!$newLinks)
    }
 
    write-host "$linksChecked links have been checked without any matching file extension $extension" -ForegroundColor Red
    return $false
}

function confirmation($content,$testValue="I confirm",$maxAttempts=3){
    $confirmed=$false;
    $attempts=0;        
    $content|write-host
    write-host "Please review this content for accuracy.`r`n"
    while ($attempts -le $maxAttempts){
        if($attempts++ -ge $maxAttempts){
            write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
            break;
            }
        $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm";
        if ($userInput.ToLower() -ne $testValue.ToLower()){
            cls;
            $content|write-host
            write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
            }else{
                $confirmed=$true;
                write-host "Confirmed!`r`n";
                break;
                }
        }
    return $confirmed;
}

function download{
    param(
        $sourceUrl,
        $destinationFile,
        $proxy=$null
        )
    $ErrorActionPreference='stop'
    $proceedToDownload=$true
    if(test-path $sourceUrl){
        write-warning "$sourceUrl is invalid";
        return $false;
        }
    if(test-path $destinationFile){
        write-warning "Destination file already exists. Overwrite $destinationFile`?"
        $proceedToDownload=confirmation "Overwrite $destinationFile`?"        
        }
    if($proceedToDownload){
        if(!$destinationFile){
            $exeName=split-path $sourceUrl -Leaf
            $destinationFile='C:\Temp\'+$exeName
            }
        $proxyEnabled=(Get-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings").ProxyEnable
        $proxyServer=(get-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings').ProxyServer
        if ($proxyEnabled -and !$proxyServer){ 
            if($proxy){$PSDefaultParameterValues = @{"*:Proxy"="$proxy";}}
            else{write-warning "Proxy is detected as enabled, yet no proxy servers are set. Downloads may fail."}
            }
        try{
            if(!(Get-Module BitsTransfer)){Import-Module BitsTransfer}        
            $download=Start-BitsTransfer -Source $sourceUrl -Destination $destinationFile -Asynchronous        
            While( ($download.JobState.ToString() -eq 'Transferring') -or ($download.JobState.ToString() -eq 'Connecting') ){
                $percent = [int](($download.BytesTransferred*100) / $download.BytesTotal)
                Write-Progress -Activity "Downloading..." -CurrentOperation "$percent`% complete"
                }
            Complete-BitsTransfer -BitsJob $download
            
            if(test-path $destinationFile){
                write-host "Download successful: $destinationFile"
                return $true
                }
            else{
                write-warning "Download appears to be successful. However, Test-Path Failed for $destinationFile"
                return $false
                }
            }
        catch{
            write-warning "$($error[0])"
            return $false
            }
        }
    else{
        return $false
        }
}

function installTomcat9{
    param(
        $downloadUrl
        )
    if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
    choco install openjdk -y
    #choco install tomcat -y # This method of install sux; hence, it must be installed 
    #C:\ProgramData\chocolatey\lib\Tomcat\tools
    #C:\ProgramData\Tomcat9
    #Installing the service 'Tomcat9' ...
    #Using CATALINA_HOME:    "C:\ProgramData\chocolatey\lib\Tomcat\tools\apache-tomcat-9.0.36"
    #Using CATALINA_BASE:    "C:\ProgramData\Tomcat9"
    #Using JAVA_HOME:        "C:\Program Files\OpenJDK\jdk-14.0.1"
    #Using JRE_HOME:         "C:\Program Files\OpenJDK\jdk-14.0.1"
    #Using JVM:              "C:\Program Files\OpenJDK\jdk-14.0.1\bin\server\jvm.dll"
    #The service 'Tomcat9' has been installed.
    function setAutoStart($service){
        write-host 'Starting the Tomcat service, and setting its start action to Automatic.'
        get-service|?{$_.name -like "*$service*"}|start-service
        get-service|?{$_.name -like "*$service*"}|set-service -StartupType Automatic
        #WARNING: Waiting for service 'Apache Tomcat 9.0 Tomcat9 (Tomcat9)' to start...

        get-service|?{$_.name -like "*$service*"}| Select-Object -Property Name, StartType, Status
        #Name    StartType  Status
        #----    ---------  ------
        #Tomcat9 Automatic Running
        }
    
    write-host "Installing Tomcat from $downloadUrl"
    $destinationFile="C:\temp\$(split-path $downloadUrl -Leaf)"
    $downloadSuccessful=download $downloadUrl $destinationFile
    if ($downloadSuccessful){
        Start-Process -Wait -FilePath $destinationFile -Argument '/S' -PassThru
        #$installCommand = {"$destinationFile" /S /v/qn } # Alternative install method, which still requires some clicking
        #Invoke-Command -ScriptBlock $installCommand
        setAutoStart 'tomcat9'
        }
    else{
        write-warning "Download was unsuccessful. Installation could not proceed."
        }
    }

function openUrlWithChrome{
    param($url='http://localhost:8080')
    try {
        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
        choco install googlechrome -y
      $chrome = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
      Start-Process "$chrome" $url
    }
    catch {
      write-warning "$($error[0])"
    }
}

function editXml{
    param(
        $xmlFile,
        $navigationPath,
        $setValue
        )
    $ErrorActionPreference='stop'
    try{
        $path=($navigationPath|%{"'$_'"}) -join "."
        $xml = [xml](get-content $xmlFile)
        $before=invoke-expression "`$xml.$path"
        $confirm=confirmation "Change value of $before to $setValue"
        if($confirm){
            invoke-expression "`$xml.$path='$setValue'"            
            $xml.Save($xmlFile)
            write-host 'Done.'
            }
        else{
            write-host "No changes. Please review XML paths by scanning this content:`r`n$($xml.InnerXml|fl *|out-string)"
            }
        }
    catch{
        write-warning "$($error[0])"
        }
}
 
function deployPasswordManager{
    $tomcatDownloadUrl=findDownloadUrl $tomcatSearchPage $tomcatFileExtension
    $pwmDownloadUrl=findDownloadUrl $pwmSearchPage $pwmExtension
    if($tomcatDownloadUrl){
        installTomcat9 $tomcatDownloadUrl
    }else{
        write-warning 'Cannot continue without Tomcat'
        return $false
        }
    if($pwmDownloadUrl){
        download $pwmDownloadUrl "$tempDir\ROOT.war"
    }else{
        write-warning 'Cannot continue without Password Manager .WAR files'
        return $false
        }
    stop-service $serviceName
    rename-item "$defaultWebappsDirectory\ROOT" "$defaultWebappsDirectory\ROOT.OLD"
    move-item "$tempDir\ROOT.war" "$defaultWebappsDirectory\ROOT.war"
    if(!(test-path $pwmData)){$null=mkdir $pwmData}
    editXml $serverXml $serverPortEditPath $serverPortValue
    start-service $serviceName
    while (!(test-path $webXml)){
        sleep 2
        write-host "Waiting for WebXML file to generate"
        }
    stop-service $serviceName
    editXml $webXml $webXmlEditPath $pwmData
    #$env:PWM_APPLICATIONPATH=$pwmData
    #$env:CATALINA_HOME=$pwmData
    #set "PWM_APPLICATIONPATH=$pwmData"
    start-service $serviceName
    if($serverPortValue -eq 443){
        write-host "Tomcat & Password Manager installed with Port $serverPortValue. Keystore needs to be setup next!" -ForegroundColor Yellow
    }else{
        write-host "Now accessing Password Manager..."
        openUrlWithChrome $appUrl
    }    
}

deployPasswordManager

#######################################################################################
# Import public key into Password Manager
#######################################################################################
# User specified variables
$domain='kimconnect.com'
$importKey="$pwmData\wildcard.$domain.pfx" # Be sure to place the .PFX key into this location prior to proceeding
$pwmData='C:\pwm-data'

function importKey{
    param(
        $importKey,
        $domain,
        $pwmData
        )

    $destinationKey="$pwmData\$domain.p12" 
    $certPem="$pwmData\$domain.pem"
    $keyPem="$pwmData\$domain.key"
    $intermediateCertPem="$pwmData\intermediate-$domain.pem"

    function includeKeyTool{
        if(!(get-command keytool.exe -ea SilentlyContinue)){
            if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
            choco install openjdk -y
            $openJdkInstallLocation=(gci 'C:\Program Files\'|?{$_.Name -like '*OpenJDK*'}).FullName # This location has been known to change
            $openJdkBin=(gci $openJdkInstallLocation|sort -Property Name -Descending|select -First 1|gci|?{$_.Name -eq 'bin'}).FullName
            $env:path+=";$openJdkBin"
            }
    }  

    if(!$importkey -or !$domain -or !$pwmData){
        write-warning 'Cannot proceed without necessary inputs.'
        return $false
        }
    
    $ErrorActionPreference='stop'
    try{
        includeKeyTool
        # Convert Key to .p12 format
        $keyExtension=.{[void]((split-path $importKey -Leaf) -match '\.(.{3})$');$matches[1]}
        if($keyExtension -eq 'pfx'){
            keytool -importkeystore -destkeystore $destinationKey -deststoretype pkcs12 -srckeystore $importKey
        }elseif($keyExtension -eq 'pem'){
            openssl pkcs12 -export -in $certPem -inkey $keyPem
               -certfile $intermediateCertPem -name "$domain"
               -out $destinationKey
            }
        
        if(!(test-path $destinationKey)){
            write-warning "Cannot proceed without $destinationKey"
            return $false
            }
        
        # Import key into keystore
        keytool -importkeystore `
           -srckeystore $destinationKey -srcstoretype PKCS12 `
           -destkeystore "$pwmData\$domain.jks"

        return $true
    }catch{
        Write-warning $error[0].Exception.Message
        return $false
        }
}

importKey $importKey $domain $pwmData

# Sample Output:
#
#Importing keystore C:\pwm-data\wildcard.kimconnect.com.pfx to C:\pwm-data\kimconnect.com.p12...
#Enter destination keystore password:
#Enter source keystore password:
#Existing entry alias {aaaa-bbbb-ccccc} exists, overwrite? [no]:  yes
#Entry for alias {aaaa-bbbb-ccccc} successfully imported.
#Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

#######################################################################################
# Configure Tomcat to forward port 80 to 443
#######################################################################################

# Variables to edit web.xml file
$webXml='C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\web.xml'
$matchPattern='</web-app>'
$forceHttps=@"
    <!-- Force HTTPS, required for HTTP redirect! -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected Context</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection> 
     
        <!-- auth-constraint goes here if you require authentication -->
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
"@

# Variables to set Tomcat Connector
$serverXml='C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\server.xml'
$nodeName='Connector'
$parentNodePath="'server'.'service'"
$addComment="Define a non-SSL HTTP/1.1 Connector on port 80 by $($env:USERNAME +' '+ (get-date))"
$keystoreFile='c:\pwm-data\kimconnect.com.jks'
$keystorePassword='PASSWORDHERE'
$port443Config=@{
    'port'='443'
    'protocol'='HTTP/1.1'
    'SSLEngine'='on'
    'maxThreads'='200'
    'clientAuth'='false'
    'scheme'='https'
    'secure'='true'
    'SSLEnabled'='true'
    'SSLProtocol'='TLSv1.2'
    'keystoreFile'=$keystoreFile
    'keystorePass'=$keystorePassword
    }
$port80Config=@{
    'port'='80'
    'acceptCount'='100'
    'enableLookups'='false'
    'maxThreads'='150'
    'redirectPort'='443'
    'URIEncoding'='UTF-8'
    }

function setTomcatConnectors{
    param(
        $xmlFile,
        $port80Config,
        $port443Config,
        $parentNodePath,
        $nodeName,
        $addComment
        )

    try{
        $xml=New-Object System.Xml.XmlDocument
        $xml.Load($xmlFile)

        # Alternate method
        #$xml = [xml](get-content $xmlFile)

        # Manual method
        # view connectors
        #PS C:\> $xml.server.service.connector
        #
        #port protocol connectionTimeout redirectPort
        #---- -------- ----------------- ------------
        #443  HTTP/1.1 20000             8443

        # remove redirection
        #$connectorNode=$xml.server.service.connector
        #$connectorNode.RemoveAttribute('redirectPort')

        # Systematic method
        $parentNode=invoke-expression "`$xml.$parentNodePath"

        # adding comment
        $comment=$xml.CreateComment($addComment)
        $parentNode.AppendChild($comment);

        # Comment out all original connectors
        $parentNode=invoke-expression "`$xml.$parentNodePath"
        $existingConnectors=$parentNode.ChildNodes|?{$_.Name -eq $nodeName}
        $existingConnectorsCount=$existingConnectors.name.count
        write-host "$existingConnectorsCount connector(s) detected."
        $existingConnectors|%{
            $element=$_.Name
            write-host "Commenting out element $element`:"
            $commentOut = $xml.CreateComment($_.OuterXml);
            $_.ParentNode.ReplaceChild($commentOut, $_);
            }
        
        # Alternatively, clear existing connector's values
        # Recording original attributes
        #$port443Connector=$parentNode.Connector
        #$port443OriginalAttributes=@{}
        #$port443Connector.Attributes.GetEnumerator()|%{$port443OriginalAttributes[$_.Name]=$_.Value}
        #
        # Removing extraneous attributes
        #$intendedAttributes=$connectorPort443.GetEnumerator().Name
        #$originalAttributes|%{
        #    if($_ -notin $intendedAttributes){
        #        write-host "Removing $_..."
        #        $connectorNode.RemoveAttribute($_)
        #        }    
        #    }

        # Removing all attributes
        #$port443Connector.RemoveAllAttributes()

        # Creating new connectors
        $port80Connector = $parentNode.AppendChild($xml.CreateElement($nodeName));
        # $newXmlElementTextNode = $newXmlNameElement.AppendChild($xml.CreateTextNode("SomeValue"));
        $port80Config.GetEnumerator()|%{
            $element=$_.Name
            $value=$_.Value
            #write-host "$element`t: $value"
    
            # Set values
            $attribute=$xml.CreateAttribute($element)
            $attribute.Value=$value
            $port80Connector.Attributes.Append($attribute)

            # Alternative method - presented for documentation purposes
            #$setElement=$xml.CreateElement($element)    
            #$port443Connector.AppendChild($setElement)
            #$port443Connector."$element"=$value
            }

        # Adding another connector
        $port443Connector = $parentNode.AppendChild($xml.CreateElement($nodeName));
        $port443Config.GetEnumerator()|%{
            $element=$_.Name
            $value=$_.Value
            $attribute=$xml.CreateAttribute($element)
            $attribute.Value=$value
            $port443Connector.Attributes.Append($attribute)
            }

        cls
        write-host $xml.InnerXml
        write-host 'Please review the contents above for accuracy. Ctrl+C to cancel.' -ForegroundColor Yellow
        pause
        copy $xmlFile "$xmlFile.bak" -Force -Confirm:$false # Making the backup prior to overwriting
        $xml.Save($xmlFile)
        return $true
    }catch{
        Write-Warning $Error[0].Exception.Message
        return $false
        }
}

setTomcatConnectors -xmlFile $serverXml `
                    -port80Config $port80Config `
                    -port443Config $port443Config `
                    -parentNodePath $parentNodePath `
                    -nodeName $nodeName `
                    -addComment $addComment


function insertTextBefore{
    param($sourceFile,$matchPattern,$insertContent)
    function confirmation($content,$testValue="I confirm",$maxAttempts=3){
        $confirmed=$false;
        $attempts=0;        
        $content|write-host
        write-host "Please review this content for accuracy.`r`n"
        while ($attempts -le $maxAttempts){
            if($attempts++ -ge $maxAttempts){
                write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
                break;
                }
            $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm";
            if ($userInput.ToLower() -ne $testValue.ToLower()){
                cls;
                $content|write-host
                write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
                }else{
                    $confirmed=$true;
                    write-host "Confirmed!`r`n";
                    break;
                    }
            }
        return $confirmed;
    }
    function insertBeforeMatchString{
        param(
            $content,
            $searchString,
            $insert
        )
        [int]$matchedIndex=.{$lines=[array]$content
                                for ($i=0;$i -lt $lines.Count;$i++) {if($lines[$i] -like "$searchString*"){return $i}}
                                }
        if($matchedIndex){
            $insertIndex=$matchedIndex-1
            $before=$content[0..$($matchedIndex-1)]
            $after=$content[$($matchedIndex)..$($content.length-1)]                    
             
            $linesBefore=if($matchedIndex-3 -gt 0){
                $content[$($matchedIndex-3)..$($matchedIndex-1)]
                }else{''}
            $linesAfter=if($matchedIndex+2 -lt $content.length){
                $content[$($matchedIndex)..$($matchedIndex+2)]
                }else{$content[$matchedIndex]}
            $sample="$linesBefore`r`n$insert`r`n$linesAfter"
            $content=$before+$insert+$after
            write-host "There was a match at index $matchedIndex"
            return @($content,$sample)
            }
        else{
            write-host "$searchString has not matched anything.";
            $linesBefore=$content[$($content.length-4)..$($content.length-1)]
            $sample="$linesBefore`r`n$insert"
            $content+=$insert
            return @($content,$sample)
        }
 
    }
 
    $fileContent = Get-Content $sourceFile
    $matching=insertBeforeMatchString $fileContent $matchPattern $insertContent
    $newContent=$matching[0]
    $sample=$matching[1]
    $confirmed=confirmation "Sample`t:`r`n----------------------------------`r`n$sample`r`n----------------------------------"
    if ($confirmed){
        $backupFile=$sourceFile+".backup"
        try{
            Rename-Item -Path $sourceFile -NewName $backupFile -ErrorAction Stop
            }
            catch{
                write-host "Unable to rename. Now trying to delete previous backup and retry"
                Remove-item $backupFile -Force
                Rename-Item -Path $sourceFile -NewName $backupFile
                }
        $newContent|set-Content $sourceFile
        write-host "`r`n$insertContent has been added to $sourceFile successfully."
        return $true
        }
    else{
        write-host "`r`nNo changes were made as there were no confirmations."
        return $false;
        }
}
insertTextBefore $webXml $matchPattern $forceHttps

# Sample Output:
#setTomcatConnectors -xmlFile $serverXml `
#                    -port80Config $port80Config `
#                    -port443Config $port443Config `
#                    -parentNodePath $parentNodePath `
#                    -nodeName $nodeName `
#                    -addComment $addComment
#
#    <!-- omitted for brevity -->
#    </Engine>
#    <!--Define a non-SSL HTTP/1.1 Connector on port 80 by kdoan 08/11/2020 22:27:20-->
#    <Connector port="80" acceptCount="100" enableLookups="false" URIEncoding="UTF-8" maxThreads="150" redirectPort="443" />
#    <Connector SSLEnabled="true" secure="true" keystoreFile="c:\pwm-data\kimconnect.com.jks" scheme="https" keystorePass="3qu!n!x" SSLEngine="on" port="443" SSLProtocol="TLSv1.2" clientAuth="false" protocol="HTTP/1.1" maxThreads="200" />
#  </Service>
#</Server>
#
#Please review the contents above for accuracy. Ctrl+C to cancel.
#Press Enter to continue...:
#True
#
#PS C:\> insertTextBefore $sourceFile $matchPattern $forceHttps
#There was a match at index 4736
#Sample  :
#----------------------------------
#        <welcome-file>index.jsp</welcome-file>     </welcome-file-list>
#    <!-- Force HTTPS, required for HTTP redirect! -->
#    <security-constraint>
#        <web-resource-collection>
#            <web-resource-name>Protected Context</web-resource-name>
#            <url-pattern>/*</url-pattern>
#        </web-resource-collection>
#
#        <!-- auth-constraint goes here if you require authentication -->
#        <user-data-constraint>
#            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
#        </user-data-constraint>
#    </security-constraint>
#</web-app>
#----------------------------------
#Please review this content for accuracy.
#
#Please type in this value => I confirm <= to confirm: i confirm
#Confirmed!
#
#
#    <!-- Force HTTPS, required for HTTP redirect! -->
#    <security-constraint>
#        <web-resource-collection>
#            <web-resource-name>Protected Context</web-resource-name>
#            <url-pattern>/*</url-pattern>
#        </web-resource-collection>
#
#        <!-- auth-constraint goes here if you require authentication -->
#        <user-data-constraint>
#            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
#        </user-data-constraint>
#    </security-constraint> has been added to C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\web.xml successfully.
#True
############### Setting Tomcat to use non-public RSA SSL Cert ###############

# generate a keystore file
keytool -genkey -alias keystore -keyalg RSA -keystore "c:\pwm-data\keystore.jks"

# Set this on 'C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\server.xml'
<#

	<!-- Define a non-SSL HTTP/1.1 Connector on port 80 -->  
	<Connector URIEncoding="UTF-8" port="80" acceptCount="100" enableLookups="false" maxThreads="150" redirectPort="443" />
    <Connector port="443" protocol="HTTP/1.1"
		SSLEngine="on" maxThreads="200" clientAuth="false"
		scheme="https" secure="true" SSLEnabled="true" SSLProtocol="TLSv1.2"
		keystoreFile="c:\pwm-data\keystore"
		keystorePass="password"
	/>

#>

# Add this to web.xml, right before </web-app>
$sourceFile='C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\web.xml'
$matchPattern='</web-app>'
$forceHttps=@"
    <!-- Force HTTPS, required for HTTP redirect! -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected Context</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection> 
    
        <!-- auth-constraint goes here if you require authentication -->
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
"@

function insertTextBefore{
    param($sourceFile,$matchPattern,$insertContent)
    function confirmation($content,$testValue="I confirm",$maxAttempts=3){
        $confirmed=$false;
        $attempts=0;        
        $content|write-host
        write-host "Please review this content for accuracy.`r`n"
        while ($attempts -le $maxAttempts){
            if($attempts++ -ge $maxAttempts){
                write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
                break;
                }
            $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm";
            if ($userInput.ToLower() -ne $testValue.ToLower()){
                cls;
                $content|write-host
                write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
                }else{
                    $confirmed=$true;
                    write-host "Confirmed!`r`n";
                    break;
                    }
            }
        return $confirmed;
    }
    function insertBeforeMatchString{
        param(
            $content,
            $searchString,
            $insert
        )
        [int]$matchedIndex=.{$lines=[array]$content
                                for ($i=0;$i -lt $lines.Count;$i++) {if($lines[$i] -like "$searchString*"){return $i}}
                                }
        if($matchedIndex){
            $insertIndex=$matchedIndex-1
            $before=$content[0..$($matchedIndex-1)]
            $after=$content[$($matchedIndex)..$($content.length-1)]                    
            
            $linesBefore=if($matchedIndex-3 -gt 0){
                $content[$($matchedIndex-3)..$($matchedIndex-1)]
                }else{''}
            $linesAfter=if($matchedIndex+2 -lt $content.length){
                $content[$($matchedIndex)..$($matchedIndex+2)]
                }else{$content[$matchedIndex]}
            $sample="$linesBefore`r`n$insert`r`n$linesAfter"
            $content=$before+$insert+$after
            write-host "There was a match at index $matchedIndex"
            return @($content,$sample)
            }
        else{
            write-host "$searchString has not matched anything.";
            $linesBefore=$content[$($content.length-4)..$($content.length-1)]
            $sample="$linesBefore`r`n$insert"
            $content+=$insert
            return @($content,$sample)
        }

    }

    $fileContent = Get-Content $sourceFile
    $matching=insertBeforeMatchString $fileContent $matchPattern $insertContent
    $newContent=$matching[0]
    $sample=$matching[1]
    $confirmed=confirmation "Sample`t:`r`n----------------------------------`r`n$sample`r`n----------------------------------"
    if ($confirmed){
        $backupFile=$sourceFile+".backup"
        try{
            Rename-Item -Path $sourceFile -NewName $backupFile -ErrorAction Stop
            }
            catch{
                write-host "Unable to rename. Now trying to delete previous backup and retry"
                Remove-item $backupFile -Force
                Rename-Item -Path $sourceFile -NewName $backupFile
                }
        $newContent|set-Content $sourceFile
        write-host "`r`n$insertContent has been added to $sourceFile successfully."
        return $true
        }
    else{
        write-host "`r`nNo changes were made as there were no confirmations."
        return $false;
        }
}
insertTextBefore $sourceFile $matchPattern $forceHttps
############### Dealing with CRT and KEY files #####################
$certFile='C:\pwm-data\acme.crt'
$intermediateCertFile='C:\pwm-data\intermediate_acme.crt'
$parentFolder=split-path $certFile -Parent
$certName=.{[void]((split-path $certFile -Leaf) -match '(.*)\.');$matches[1]}
$convertedFile=$parentFolder+'\'+$certName+'.p7b'
$convertedCert=$parentFolder+'\'+$certName+'.cer'

function installOpenSsl{
    if(!(get-command openssl.exe -ea SilentlyContinue)){
        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
        choco install openssl -y
        $env:path+=";C:\Program Files\OpenSSL-Win64\bin" 
        }
}

function installKeyTool{
    if(!(get-command keytool.exe -ea SilentlyContinue)){
        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
        choco install openjdk -y
        $openJdkBin=(get-childitem 'C:\Program Files\AdoptOpenJDK\')[0].FullName+'\bin'
        $env:path+=";$openJdkBin" 
        }
}

keytool -import -alias root -keystore "$parentFolder\$domainName.jks" -trustcacerts -file $certFile
keytool -import -alias intermediate -keystore "$parentFolder\$domainName.jks" -trustcacerts -file $intermediateCertFile
############### Dealing with PFX and PEM files #####################
$domain='acme.com'
$pwmData='C:\pwm-data'
$importKey="$pwmData\acme.com.pfx"
$destinationKey="$pwmData\$domain.p12"

$certPem="$pwmData\acme.com.pem"
$keyPem="$pwmData\acme.com.key"
$intermediateCertPem="$pwmData\intermediate-acme.com.pem"

# Convert Key to .p12 format
$keyExtension=.{[void]((split-path $importKey -Leaf) -match '\.(.{3})$');$matches[1]}
if($keyExtension -eq 'pfx'){
    keytool -importkeystore -destkeystore $destinationKey -deststoretype pkcs12 -srckeystore $importKey
    }
elseif($keyExtension -eq 'pem'){
    openssl pkcs12 -export -in $certPem -inkey $keyPem
       -certfile $intermediateCertPem -name "$domain"
       -out $destinationKey
    }

# Import key into keystore
keytool -importkeystore `
   -srckeystore $destinationKey -srcstoretype PKCS12 `
   -destkeystore "$pwmData\$domain.jks"
############### After Installation Hints #####################
#- Replace favicon.png (16x16) at C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\ROOT\public\resources
#- Add this line to 'style.css' of selected theme:
##header-company-logo {
#        position: relative;
#        float: left;
#        background-image: url('logo50x50.png');
#        top: 10px;
#        left: 10px;
#		width: 50px;		
#        height: 50px;
#        z-index: 1;
#}
#- Change Display Text > Display > Title_Application to reflect the domain name being integrated with this app
###############  Troubleshooting  #####################
#
#LDAP	WARN	
#error connecting to ldap directory (default),
#error: unable to create connection: unable to bind to ldaps://acmedc1:636 as CN=pwm-proxy,CN=Users,DC=acme,DC=com 
#reason: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090446, comment: AcceptSecurityContext error, data 52e, v2580] 
#(FAILED_AUTHENTICATION - The user name or password is not valid. Please try again.)
# Resolution:
# - Ensure that the Proxy DN is correct. For instance, CN must not be confused with OU
# - Trigger AD Sync

# Error:
#An error has occurred. If this error occurs repeatedly please contact your help desk. 
#{ 5015 ERROR_INTERNAL (unexpected error during ldap search (profile=default), 
#error: 5015 ERROR_INTERNAL (ldap error during searchID=0, context=DC=acme,DC=com, error=javax.naming.PartialResultException, 
#cause:javax.naming.CommunicationException: acme.com:636, cause:javax.net.ssl.SSLHandshakeException: server certificate 
#{subject=CN=dc02.acme.com} does not match a certificate in the PWM configuration trust store., 
#cause:java.security.cert.CertificateException: server certificate {subject=CN=dc02.acme.com} does not match a certificate in the PWM configuration trust store.))}
# Resolution: fix LDAP Root Contexts
#    <setting key="ldap.rootContexts" profile="default" syntax="STRING_ARRAY" syntaxVersion="0" modifyTime="2012-02-01T21:41:22Z">
#      <label>LDAP ⇨ LDAP Directories ⇨ default ⇨ Connection ⇨ LDAP Contextless Login Roots</label>
#      <value>OU=Lab OU,DC=acme,DC=com</value>.
#	  <value>OU=Lab OU,DC=acme,DC=com</value>
#    </setting>

# Error:
# The user name is not valid or is not eligible to use this feature { 5006 ERROR_RESPONSES_NORESPONSES }
# Resolution:
# This occurs after error 5015 is fixed. Tomcat must be restarted for PWM to update its connection strings and caches
Some Post Install Configurations
  1. Create a Proxy User Account in Active Directory
    1. Create a security group in AD / LDAP named ‘User Admins’
    2. Create an account named ‘PWM Proxy’ and add it to the ‘User Admins’ group
    3. Delegate user object controls of OUs containing Users:
      Run DSA.msc on a Domain Controller or workstation with Administrator Console plugins installed > Right-click a target OU > select Delegate Control to invoke the Delegation of Control Wizard > Next > click Add > input the User Admins group > click OK to add > Next > toggle option ‘Create a custom task to delegate’ > Next > toggle option ‘Only the following object in the folder’ > scroll down to put a check mark next to ‘User object’ > Next > select ‘Full Control’ > Next > Finish > repeat these steps on any other OU’s until completion


      General Information on PWM Account Access Rights

      PWM Proxy Account Rights User Containers:
      read & write objectClass
      userPassword or equivalent password attributes
      pwmEventLog, pwmLastPwdUpdate, pwmGUID (or other configured attributes)

      Authenticated User Rights
      Browse rights to [Entry Rights]
      Read, Compare, and Write rights to pwmResponseSet
      Write rights, Inherited rights to [This] for pwmLastPwdUpdate
      LDAP rights to execute operations
      All rights to read and write attributes

      Storing challenge-response information in Active Directory
      Apply AD-schema.ldif from https://sspr.server.com/sspr/public/reference/ to add these new attributes:
      pwmEventLog
      pwmResponseSet
      pwmLastPwdUpdate
      pwmToken
      pwmOTPSecret

      Assigning User Rights [to containers]
      User objects
      User containers
      Group policies
      Organizational units
  2. Optional: Extend Active Directory to Add new PWM Attributes
    1. This is out of scope of this article as the illustrated installation pattern precludes storing PWM data in Active Directory.
    2. Perhaps, future iteration of this guide would include this procedure once this avenue has been explored.
  3. Integrate LDAP
    1. Access PWM’s web user interface (UI) Configuration Manager
    2. Include additional domains: Access UI Config Editor > LDAP > LDAP Directories > (Edit List) > Add Profile > type in the profile name to represent a new domain
    3. Config new domain: expand newly generated domain > set LDAP URLs in the format of ‘ldaps://SERVERNAME:636’, without the quotes > Import certificate from server > Add LDAP proxy user (an AD account that has privileges to manage other accounts) > input ‘LDAP Contextless Login Roots in the format of ‘OU=Test OU,DC=subdomain,DC=DOMAIN,DC=TLD’ > set ‘LDAP Profile Enabled’ = True
  4. Set Theme, assuming ‘Blue’ theme
    1. Put favicon.png into C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\ROOT\public\resources
    2. Place logo.png (max height 50px) into C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\ROOT\public\resources\themes\blue
    3. Edit C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\ROOT\public\resources\themes\blue\style.css to show contents similar to this
      #header-company-logo {
      position: relative;
      float: left;
      background-image: url('logo130x50.png');
      top: 10px;
      left: 10px;
      width: 130px;
      height: 50px;
      z-index: 1;
      }
    4. Enable ‘blue’ theme: Access UI config editor > Settings > User Interface > Look & Feel > select ‘blue’ as the Interface Theme value
  5. Forgotten Password Module
    1. Enable feature: Modules > Public > Forgotten Password > Settings > put a check mark next to ‘Enabled (True)’
    2. Change Verification method: Modules > Public > Forgotten Password > Profiles > default > Definition > disable challenge/response answers > enable SMS/Email Token Verification
    3. Setup email:
      • Settings > Email > Email Servers > Default > configure SMTP server address, connection type, port, username, and password
      • Email Settings > input ‘Default From Address’
    4. Disable Setup Responses: Modules > Authenticated > Setup Security Questions > un-check the box next to Enable Setup Responses
  6. Database
    1. Settings > Database (Remote) > Connection > input values for Database driver, connection string, username, and password
  7. Preempt Issues:
    1. Too many redirects

      • Edit this file: C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf
      • Disable session persistence
         <!-- Uncomment this to disable session persistence across Tomcat restarts -->
        <Manager pathname="" />
      • Edit this file: C:\pwm-data\PwmConfiguration.xml

      •  <setting key="enableSessionVerification" syntax="SELECT" syntaxVersion="0">
        <label>Settings ⇨ Security ⇨ Web Security ⇨ Sticky Session Verification</label>
        <value>OFF</value>
        </setting>
  8. Optional: Extending LDAP Schema
    1. Download the correct LDAP schema from: https://github.com/pwm-project/pwm/tree/master/webapp/src/build/ldif
    2. Extend the schema using the LDIF file downloaded from link above using this sample command: ldifde -i -f AD-schema.ldif -c “DC=x” “dc=org,dc=acme”
    3. In ADUC, right click on the OU holding the users you want to allow access into PWM > select Delegate Control > Add > SELF > OK > Next > toggle the option to Create a custom task to delegate > next > choose the option Only the following objects in the folder > Scroll down > select pwmUser objects > next > pick the option labeled Property-specific > select the options for Read pwmResponseSet and Write pwmResponseSet > next > Finish
Troubleshooting

Issue when LDAP certificate has expired:

Error 5017
Directory unavailable. If this error occurs repeatedly please contact your help desk.

5017 ERROR_DIRECTORY_UNAVAILABLE (all ldap profiles are unreachable; errors: ["error connecting as proxy user: unable to create connection: unable to connect to any configured ldap url, last error: unable to bind to ldaps://kimconnect.net:636 as CN=pwm proxy,OU=Service Accounts,DC=kimconnect,DC=com reason: CommunicationException (PHX-DC03.kimconnect.com:636; server certificate {subject=} does not match a certificate in the PWM configuration trust store.)"])
OK

Resolution:

Edit C:\pwm-data\PwmConfiguration.xml

Change:
<property key="configIsEditable">false</property>
To:
<property key="configIsEditable">true</property>

Then, access Configuration Editor:

Navigate to LDAP > LDAP Directories > select the correct ‘default’ or ‘domainname’ node > Connection > click ‘X Clear’ in the LDAP Certificates group > Import From Server > OK > Save

Return to Server > edit C:\pwm-data\PwmConfiguration.xml > revert the ‘true’ back to ‘false’value at “configIsEditable” > Save


LDAP Authentication Issue:

Directory unavailable. If this error occurs repeatedly please contact your help desk.

5017
ERROR_DIRECTORY_UNAVAILABLE (all ldap profiles are unreachable; errors: ["error connecting as proxy user: 5001 ERROR_WRONGPASSWORD (unable to create connection: unable to bind to
ldaps://DC02.intranet.kimconnect.com:636 as CN=pwm proxy,OU=Service
Accounts,DC=intranet,DC=kimconnect,DC=com reason: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090447, comment: AcceptSecurityContext error, data 52e, v3839\u0000])"])

Resolution:

Ensure that the proxy account’s password is valid. Then, verify that the LDAP Proxy User in PWM (e.g. https://pwm.kimconnect.com/private/config/editor) matches this query from AD

PS C:\Users\brucelee> get-aduser pwmproxy|select DistinguishedName

DistinguishedName
-----------------
CN=pwm proxy,OU=Service Accounts,DC=intranet,DC=kimconnect,DC=com

Issue with Importing LDAP Certificate:

Error 5059
A certificate error has been encountered: unable to read server certificates from host=PHX-DC03.kimconnect.com, port=636 error: javax.net.ssl.SSLException: Connection reset, cause:java.net.SocketException: Connection reset.

5059 ERROR_CERTIFICATE_ERROR (unable to read server certificates from host=PHX-DC03.kimconnect.com, port=636 error: javax.net.ssl.SSLException: Connection reset, cause:java.net.SocketException: Connection reset) fields: [unable to read server certificates from host=PHX-DC03.kimconnect.com, port=636 error: javax.net.ssl.SSLException: Connection reset, cause:java.net.SocketException: Connection reset]
OK

Resolution:

This is a rare exception, where the subject DC has an installed Cert that could not be processed by PWM due to various reasons. Follow the Domain Controller Certificates Installation Guide to fix the LDAP server’s certificates so PWM will be able to work with such domain controller(s). Mainly, the ‘Client Authentication’ Cert must be available on the Subject LDAP Connector – ironically, certs being marked with ‘<All>’ purposes would still require one that is designated as ‘Client Authentication’ (although PWM will daisy chain them and grab the cert with ‘<All>’ purposes when both types are present). Below is a sample valid cert

PS C:\Users\kdoan> certutil -verifystore MY
MY "Personal"
================ Certificate 0 ================
Serial Number: SOMESERIAL
Issuer: CN=intranet-CA01-CA, DC=intranet, DC=kimconnect, DC=com
NotBefore: 8/30/1920 4:29 PM
NotAfter: 8/30/1923 4:39 PM
Subject: CN=dc02.intranet.kimconnect.com
Non-root Certificate
Template: DomainController5Years, Domain Controller 5 Years
Cert Hash(sha1): HASH-HASH
Key Container = HASH-HASH
Simple container name: te-DomainController5Years-HASH-HASH
Provider = Microsoft RSA SChannel Cryptographic Provider
Private key is NOT exportable
Encryption test passed
Verified Issuance Policies: None
Verified Application Policies:
1.3.6.1.5.2.3.5 KDC Authentication
1.3.6.1.4.1.311.20.2.2 Smart Card Logon
1.3.6.1.5.5.7.3.1 Server Authentication
1.3.6.1.5.5.7.3.2 Client Authentication
Certificate is valid

Furthermore, content-aware firewalls can sometimes be a problem. Since LDAPS is LDAP over HTTPS (SSL/TLS), firewall may allow 1-way traffic toward the destination via port 636 while simply dropping any HTTP traffic. Hence, consulting with the networking team if this test connection result is gathered:

# Code

$ldapServer='domaincontroller'
$ldapPort=636
openssl s_client -connect "$ldapServer`:$ldapPort"
# Case Failure:

PS C:\Windows\system32> openssl s_client -connect "$ldapServer`:$ldapPort"
CONNECTED(000001A8)
write:errno=10054
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 331 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Issue when PWM is not in Production mode:

PWM is currently in configuration mode. This mode allows updating the configuration without authenticating to an LDAP directory first. End user functionality is not available in this mode.

Resolution:

After you have verified the LDAP directory settings, use the Configuration Manager to restrict the configuration to prevent unauthorized changes. After restricting, the configuration can still be changed but will require LDAP directory authentication prior.


Issue: User Cannot Change Password

New password does not meet rule requirements { 4006 PASSWORD_BADPASSWORD (error setting password for user 'CN=Mickey Mouse,OU=Test-OU,OU=Users,DC=hooli,DC=com (default)'' com.novell.ldapchai.exception.ChaiPasswordPolicyException: javax.naming.directory.InvalidAttributeValueException: [LDAP: error code 19 - 00000005: AtrErr: DSID-03191083, #1:
0: 00000005: DSID-03191083, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9005a (unicodePwd)
]) }

Resolution:

ADUC > right-click Domain icon > Search > input username > double-click on the user account > select ‘Account’ tab > un-check ‘User cannot change password’

Issue:

When user is trying to login, they experience this alert: “5034 ERROR_INVALID_FORMID (form nonce incorrect)”

Resolution:

The problem seems to be related with the lead-time of searching the entire domain via LDAPS. Hence, network, storage, and server lags may cause this issue. Fixing those may not be within the scope of this instruction. Hence, we shall focus on the configs that would be within PWM administration.

a. Increasing the LDAP search timeout from the default of 30 seconds to the maximum of 500 is known to address this error. Modification path is: Configuration Editor > LDAP > LDAP Directories > expand the correct Domain/Realm > Connection > set LDAP Search Timeout = 500 > Save

b. If using GoogleChrome, click on the Settings button represented by the three vertical dots > More Tools > Clear Browsing Data

Click on the Advanced tab > set Time range = All time > scroll toward the bottom > put check marks next to ‘Site Settings’ and ‘Hosted app data’> click Clear data when ready

c. Limiting ldaps searching realms to specific OUs, instead of the entire domain or very large containers will minimize threshold breaches. Modification path is: Configuration Editor > LDAP > LDAP Directories > expand the correct Domain/Realm > Connection > optimize the LDAP Contextless Login Roots > Save.

d. Changing the LDAPS integration node to a faster Domain Controller is recommended. Modification path is: Configuration Editor > LDAP > LDAP Directories > expand the correct Domain/Realm > Connection > click on the edit icon under LDAP URLs > input the path in the format of ldaps://DCNAME.LTD:636 > Save

e. Another item that could help is to change the LDAP Idle time out by accessing Configuration Editor > LDAP > LDAP Settings > Global > change the default LDAP timeout of 30 seconds to 86400 seconds (1 day)

Change session Idle Timeout by accessing Configuration Editor > Settings > Application > Application > Idle Timeout Seconds

Issue:

error connecting to ldap directory (kimconnect), error: unable to create connection: unable to bind to ldaps://DC02.kimconnect.com:636 as CN=proxy pwm,OU=Service Accounts,DC=kimconnect,DC=com reason: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09044E, comment: AcceptSecurityContext error, data 52e, v2580] (FAILED_AUTHENTICATION - The user name or password is not valid. Please try again.)

Resolution:

Access LDAP > LDAP Directories > ${DomainName} > Connection > update LDAP Proxy User with value retrieved by this PoSH command

$username='pwmproxy'
$distinguishedName=(get-aduser $username).DistinguishedName
write-host $distinguishedName

# Sample output:
CN=proxy\, pwm,OU=Service Accounts,DC=kimconnect,DC=com

Issue:

User Permission configuration for setting Modules ⇨ Authenticated ⇨ Administration ⇨ Administrator Permission issue: groupDN: DN 'CN=Domain Admins,CN=Users,DC=hooli,DC=com' is invalid. This may cause unexpected issues.

Resolution:

Navigate to Modules > Authenticated > Administration > Associate the correct Domain Admins Group DN to the correct profile (e.g. default) > Save

Issue:

The cluster system can not operate normally: error writing node service heartbeat: 5079 ERROR_LDAP_DATA_ERROR (error writing node service data user 'CN=pwm test,OU=Service Accounts,DC=hooli,DC=com (default)' attribute 'pwmData', error: javax.naming.directory.NoSuchAttributeException: [LDAP: error code 16 - 00000057: LdapErr: DSID-0C090D77, comment: Error in attribute conversion operation, data 0, v2580])

Resolution:

To add a new domain, gather these pieces of information:

1. Ensure that connectivity toward the ldap node via port 636 is successful
ldaps://DC01.intranet.domain.com:636

2. Prepare a proxy account with the correct domain permissions
CN=proxy\, pwm,OU=Service Accounts,OU=Accounts,DC=intranet,DC=domain,DC=com

3. Determine the scopes of user account search queries:
OU=Accounts,DC=intranet,DC=domain,DC=com

Leave a Reply

Your email address will not be published. Required fields are marked *