Functions/Private/CertDeployment/Deploy-UserCertificate.ps1
function Deploy-UserCertificate { [CmdletBinding()] param ( # Input from users.json [Parameter(HelpMessage = 'An individual or array of user objects from users.json', Mandatory)] [System.Object[]] $userObject, # when specified will force newly generated commands to be invoked on systems [Parameter(HelpMessage = 'When specified, this parameter will invoke commands on systems associated to the user from the "userObject" parameter')] [bool] $forceInvokeCommands, # when specified will generated a new command for the given user [Parameter(HelpMessage = 'When specified, this parameter will generate new commands for the user from the "userObject" parameter')] [bool] $forceGenerateCommands, # prompt replace existing certificate [Parameter(HelpMessage = 'When specified, this parameter will prompt for user input and ask if generated commands should be invoked on associated systems')] [switch] $prompt ) begin { $workToBeDone = [PSCustomObject]@{ remainingMacOSDevices = $null remainingWindowsSDevices = $null removeQueuedCommands = $null removeCommands = $null forceGenerateMacOSCommands = $false forceGenerateWindowsCommands = $false macOSCommandID = $null windowsCommandID = $null commandIDsToRemove = New-Object System.Collections.ArrayList commandQueueIDsToRemove = New-Object System.Collections.ArrayList commandQueueIDsDuplicates = New-Object System.Collections.ArrayList } $status_commandGenerated = $false $result_deployed = $false switch ($forceInvokeCommands) { $true { $invokeCommands = $true } $false { $invokeCommands = $false } } switch ($forceGenerateCommands) { $true { $workToBeDone.forceGenerateMacOSCommands = $true $workToBeDone.forceGenerateWindowsCommands = $true } $false { $workToBeDone.forceGenerateMacOSCommands = $false $workToBeDone.forceGenerateWindowsCommands = $false } } switch ($prompt) { $true { $invokeCommandsChoice = Get-ResponsePrompt -message "Would you like to invoke commands after they've been generated?" switch ($invokeCommandsChoice) { $true { $invokeCommands = $true } $false { $invokeCommands = $false } } } } } process { foreach ($user in $userObject) { ## first determine if the local cert sha is the same as the cert sha in the admin console: # Write-Warning "processing user: $($user.username)" # Write-Warning "user: $user" # Get the commands for this user: $radiusCommandsByUser = Get-CommandByUsername -username $user.username if ($radiusCommandsByUser) { $macOSCommands = ($radiusCommandsByUser | Where-Object { $_.Name -match "MacOSX" }) $windowsOSCommands = ($radiusCommandsByUser | Where-Object { $_.Name -match "Windows" }) } # Get the queued commands for the user $queuedRadiusCommandsByUser = Get-queuedCommandByUser -username $user.username if ($queuedRadiusCommandsByUser) { $windowsQueuedCommands = $queuedRadiusCommandsByUser | Where-Object { $_.name -match "Windows" } $macOSQueuedCommands = $queuedRadiusCommandsByUser | Where-Object { $_.name -match "macOS" } } else { $windowsQueuedCommands = $null $macOSQueuedCommands = $null } # Get the users certificate Details: # Get certificate and zip to upload to Commands $userCertFiles = Get-ChildItem -Path (Resolve-Path -Path "$($global:JCRConfig.radiusDirectory.value)/UserCerts") -Filter "$($user.userName)-*" # set crt and pfx filepaths $userCrt = ($userCertFiles | Where-Object { $_.Name -match "crt" }).FullName $userPfx = ($userCertFiles | Where-Object { $_.Name -match "pfx" }).FullName # define .zip name $userPfxZip = "$($global:JCRConfig.radiusDirectory.value)/UserCerts/$($user.userName)-client-signed.zip" # get certInfo for commands: $certInfo = Get-CertInfo -UserCerts -username $user.username # Determine if the commands have matching SHA1 values: if ($macOSCommands) { # verify the certs match for macOS systems: foreach ($maOSRadiusCommand in $macOSCommands) { # if we want to forceGenerate new commands, add all commands to the remove list switch ($workToBeDone.forceGenerateMacOSCommands) { $true { # add the command to the list to remove $workToBeDone.commandIDsToRemove.Add($maOSRadiusCommand.Id) | Out-Null } $false { if ((-Not $workToBeDone.macOSCommandID) -AND ($maOSRadiusCommand.trigger -eq $certInfo.sha1)) { # set the existing command IDs: # There is a potential for this to be a bug, if duplicate commands with the same SHA1 trigger exist, this code just selects the first one and removes the second in the next iteration $workToBeDone.macOSCommandID = ($radiusCommandsByUser | Where-Object { $_.Name -match "MacOSX" }).Id | Select-Object -First 1 } elseif (($workToBeDone.macOSCommandID) -AND ($maOSRadiusCommand.trigger -eq $certInfo.sha1)) { # add the duplicate command to the list to remove $workToBeDone.commandIDsToRemove.Add($maOSRadiusCommand.Id) | Out-Null } else { # add the command to the list to remove $workToBeDone.commandIDsToRemove.Add($maOSRadiusCommand.Id) | Out-Null } } } } } else { $workToBeDone.macOSCommandID = $null } if ($windowsOSCommands) { # verify the certs match for windows systems: foreach ($windowsOSRadiusCommands in $windowsOSCommands) { switch ($workToBeDone.forceGenerateWindowsCommands) { $true { # add the command to the list to remove $workToBeDone.commandIDsToRemove.Add($windowsOSRadiusCommands.Id) | Out-Null } $false { if ((-Not $workToBeDone.windowsCommandID) -AND ($windowsOSRadiusCommands.trigger -eq $certInfo.sha1)) { # set the existing command IDs: # There is a potential for this to be a bug, if duplicate commands with the same SHA1 trigger exist, this code just selects the first one and removes the second in the next iteration $workToBeDone.windowsCommandID = ($radiusCommandsByUser | Where-Object { $_.Name -match "Windows" }).Id | Select-Object -First 1 } elseif (($workToBeDone.windowsCommandID) -AND ($windowsOSRadiusCommands.trigger -eq $certInfo.sha1)) { # add the duplicate command to the list to remove $workToBeDone.commandIDsToRemove.Add($windowsOSRadiusCommands.Id) | Out-Null } else { # add the command to the list to remove $workToBeDone.commandIDsToRemove.Add($windowsOSRadiusCommands.Id) | Out-Null } } } } } else { $workToBeDone.windowsCommandID = $null } if ($windowsQueuedCommands) { if ($workToBeDone.windowsCommandID) { # Get queued commands that are from a previous command; delete those foreach ($queuedCommand in $windowsQueuedCommands) { if ($queuedCommand.command -eq $workToBeDone.windowsCommandID) { # TODO: nothing to do here? $workToBeDone.commandQueueIDsDuplicates.Add($queuedCommand.Id) | Out-Null } else { # if the queued commands does not match the cert command, remove all these queued commands $workToBeDone.commandQueueIDsToRemove.Add($queuedCommand.Id) | Out-Null } } } else { foreach ($queuedCommand in $windowsQueuedCommands) { # if there are commands in queue for the user but no matching cert command, remove all the items in the queue $workToBeDone.commandQueueIDsToRemove.Add($queuedCommand.Id) | Out-Null } } } if ($macOSQueuedCommands) { if ($workToBeDone.windowsCommandID) { foreach ($queuedCommand in $macOSQueuedCommands) { if ($queuedCommand.command -eq $workToBeDone.macOSCommandID) { # TODO: nothing to do here? $workToBeDone.commandQueueIDsDuplicates.Add($queuedCommand.Id) | Out-Null } else { # if the queued commands does not match the cert command, remove all these queued commands $workToBeDone.commandQueueIDsToRemove.Add($queuedCommand.Id) | Out-Null } } } else { foreach ($queuedCommand in $macOSQueuedCommands) { # if there are commands in queue for the user but no matching cert command, remove all the items in the queue $workToBeDone.commandQueueIDsToRemove.Add($queuedCommand.Id) | Out-Null } } } # remove the commands: if ($workToBeDone.commandIDsToRemove) { foreach ($commandIDToRemove in $workToBeDone.commandIDsToRemove) { Remove-JcSdkCommand -Id $commandIDToRemove | Out-Null } } # remove queued commands: if ($workToBeDone.commandQueueIDsToRemove) { foreach ($commandQueueIDToRemove in $workToBeDone.commandQueueIDsToRemove) { Clear-JCQueuedCommand -workflowId $commandQueueIDToRemove | Out-Null } } # remove queued commands: if ($workToBeDone.commandQueueIDsDuplicates) { foreach ($commandQueueIDToRemove in $workToBeDone.commandQueueIDsDuplicates) { Clear-JCQueuedCommand -workflowId $commandQueueIDToRemove | Out-Null } } ## determine the systems that need the cert try { $userCertHashData = $Global:JCRCertHash["$($user.certInfo.sha1)"] } catch { $userCertHashData = $null } if ($userCertHashData) { # set the remaining systems that don't have the cert: # macOS $workToBeDone.remainingMacOSDevices = $user.systemAssociations | Where-Object { ($_.systemID -notin $userCertHashData.systemId ) -And ($_.osFamily) -eq "macOS" } # windows $workToBeDone.remainingWindowsSDevices = $user.systemAssociations | Where-Object { ($_.systemID -notin $userCertHashData.systemId ) -And ($_.osFamily) -eq "Windows" } # if there's work to be done but not a valid cert command, generate the command if (($workToBeDone.remainingMacOSDevices) -AND (-Not $workToBeDone.macOSCommandID)) { $workToBeDone.forceGenerateMacOSCommands = $true } # if there's work to be done but not a valid cert command, generate the command if (($workToBeDone.remainingWindowsSDevices) -AND (-Not $workToBeDone.windowsCommandID)) { $workToBeDone.forceGenerateWindowsCommands = $true } # regenerate the cert to keep it on the console: if ((-Not $workToBeDone.remainingMacOSDevices) -AND (-Not $workToBeDone.macOSCommandID)) { $workToBeDone.forceGenerateMacOSCommands = $true } if ((-Not $workToBeDone.remainingWindowsSDevices) -AND (-Not $workToBeDone.windowsCommandID)) { $workToBeDone.forceGenerateWindowsCommands = $true } } else { # set the remaining devices to the association list from users.json # macOS $workToBeDone.remainingMacOSDevices = $user.systemAssociations | Where-Object { ($_.osFamily) -eq "macOS" } # Windows $workToBeDone.remainingWindowsSDevices = $user.systemAssociations | Where-Object { ($_.osFamily) -eq "Windows" } # if there's work to be done but not a valid cert command, generate the command if (($workToBeDone.remainingMacOSDevices) -AND (-Not $workToBeDone.macOSCommandID)) { $workToBeDone.forceGenerateMacOSCommands = $true } # if there's work to be done but not a valid cert command, generate the command if (($workToBeDone.remainingWindowsSDevices) -AND (-Not $workToBeDone.windowsCommandID)) { $workToBeDone.forceGenerateWindowsCommands = $true } # regenerate the cert to keep it on the console: if ((-Not $workToBeDone.remainingMacOSDevices) -AND (-Not $workToBeDone.macOSCommandID)) { $workToBeDone.forceGenerateMacOSCommands = $true } if ((-Not $workToBeDone.remainingWindowsSDevices) -AND (-Not $workToBeDone.windowsCommandID)) { $workToBeDone.forceGenerateWindowsCommands = $true } } # now clear out the user command associations from users.json $user.commandAssociations = @() If (-Not $certInfo) { Write-host "$($user.username) did not have a certificate generated" $status_commandGenerated = $false $result_deployed = $false continue } else { # explicitly validate that the subject header exists if (-Not $certInfo.subject) { Write-host "$($user.username) has a certificate file but no subject was found" $status_commandGenerated = $false $result_deployed = $false continue } # explicitly validate that the serial number exists if (-Not $certInfo.serial) { Write-host "$($user.username) has a certificate file but no serial number was found" $status_commandGenerated = $false $result_deployed = $false continue } } if (($workToBeDone.forcegenerateMacOSCommands) -OR ($workToBeDone.forceGenerateWindowsCommands)) { # determine certType switch ($($global:JCRConfig.certType.value)) { 'EmailSAN' { # set cert identifier to SAN email of cert $sanID = Invoke-Expression "$($global:JCRConfig.openSSLBinary.value) x509 -in $($userCrt) -ext subjectAltName -noout" $regex = 'email:(.*?)$' $JCR_SUBJECT_HEADERSMatch = Select-String -InputObject "$($sanID)" -Pattern $regex $certIdentifier = $JCR_SUBJECT_HEADERSMatch.matches.Groups[1].value # in macOS search user certs by email $macCertSearch = 'e' # On windows devices, search certs by Subject Alternative Name this is required to remove previously generated certs $windowsCertHereString = @" `$SANMatch = `$cert.Extensions | Where-Object { `$_.Oid.FriendlyName -eq "Subject Alternative Name" } if (`$SANMatch){ if ((`$SANMatch).format(`$true) -match "$($certIdentifier)") { if (`$(`$cert.serialNumber) -eq "$($certInfo.serial)"){ write-host "Found Cert:``nCert SN: `$(`$cert.serialNumber)" } else { write-host "Removing Cert:``nCert SN: `$(`$cert.serialNumber)" Get-ChildItem "Cert:\CurrentUser\My\`$(`$cert.thumbprint)" | remove-item } } } "@ } 'EmailDN' { # Else set cert identifier to email of cert subject $regex = 'emailAddress\s*=\s*(.*?)$' $JCR_SUBJECT_HEADERSMatch = Select-String -InputObject "$($certInfo.Subject)" -Pattern $regex $certIdentifier = $JCR_SUBJECT_HEADERSMatch.matches.Groups[1].value # in macOS search user certs by email $macCertSearch = 'e' # On windows devices, search certs by Subject $windowsCertHereString = @" if (`$cert.subject -match "$($certIdentifier)") { if (`$(`$cert.serialNumber) -eq "$($certInfo.serial)"){ write-host "Found Cert:``nCert SN: `$(`$cert.serialNumber)" } else { write-host "Removing Cert:``nCert SN: `$(`$cert.serialNumber)" Get-ChildItem "Cert:\CurrentUser\My\`$(`$cert.thumbprint)" | remove-item } } "@ } 'UsernameCn' { # if username just set cert identifier to username $certIdentifier = $($user.userName) # in macOS search user certs by common name (username) $macCertSearch = 'c' # On windows devices, search certs by Subject $windowsCertHereString = @" if (`$cert.subject -match "$($certIdentifier)") { if (`$(`$cert.serialNumber) -eq "$($certInfo.serial)"){ write-host "Found Cert:``nCert SN: `$(`$cert.serialNumber)" } else { write-host "Removing Cert:``nCert SN: `$(`$cert.serialNumber)" Get-ChildItem "Cert:\CurrentUser\My\`$(`$cert.thumbprint)" | remove-item } } "@ } } # Create the zip Compress-Archive -Path $userPfx -DestinationPath $userPfxZip -CompressionLevel NoCompression -Force } if ($workToBeDone.forcegenerateMacOSCommands) { # Get the macOS system ids $systemIds = (Get-SystemsThatNeedCertWork -userData $user -osType "macOS") if ($systemIds.count -gt 0) { # Create new Command and upload the signed pfx try { $CommandBody = @{ Name = "RadiusCert-Install:$($user.userName):MacOSX" Command = @" unzip -o /tmp/$($user.userName)-client-signed.zip -d /tmp chmod 755 /tmp/$($user.userName)-client-signed.pfx currentUser=`$(/usr/bin/stat -f%Su /dev/console) currentUserUID=`$(id -u "`$currentUser") currentCertSN="$($certInfo.serial)" networkSsid="$($global:JCRConfig.networkSSID.value)" # store orig case match value caseMatchOrigValue=`$(shopt -p nocasematch; true) # set to case-insensitive shopt -s nocasematch userCompare="$($user.localUsername)" if [[ "`$currentUser" == "`$userCompare" ]]; then # restore case match type `$caseMatchOrigValue certs=`$(security find-certificate -a -$($macCertSearch) "$($certIdentifier)" -Z /Users/$($user.localUsername)/Library/Keychains/login.keychain) regexSHA='SHA-1 hash: ([0-9A-F]{5,40})' regexSN='"snbr"<blob>=0x([0-9A-F]{5,40})' global_rematch() { # Set local variables local s=`$1 regex=`$2 # While string matches regex expression while [[ `$s =~ `$regex ]]; do # Echo out the match echo "`${BASH_REMATCH[1]}" # Remove the string s=`${s#*"`${BASH_REMATCH[1]}"} done } # Save results # Get Text Results textSHA=`$(global_rematch "`$certs" "`$regexSHA") # Set as array for SHA results arraySHA=(`$textSHA) # Get Text Results textSN=`$(global_rematch "`$certs" "`$regexSN") # Set as array for SN results arraySN=(`$textSN) # set import var import=true if [[ `${#arraySN[@]} == `${#arraySHA[@]} ]]; then len=`${#arraySN[@]} for (( i=0; i<`$len; i++ )); do if [[ `$currentCertSN == `${arraySN[`$i]} ]]; then echo "Found Cert: SN: `${arraySN[`$i]} SHA: `${arraySHA[`$i]}" installedCertSN=`${arraySN[`$i]} installedCertSHA=`${arraySHA[`$i]} # if cert is installed, no need to update import=false else echo "Removing previously installed radius cert:" echo "SN: `${arraySN[`$i]} SHA: `${arraySHA[`$i]}" security delete-certificate -Z "`${arraySHA[`$i]}" /Users/$($user.localUsername)/Library/Keychains/login.keychain fi done else echo "array length mismatch, will not delete old certs" fi if [[ `$import == true ]]; then /bin/launchctl asuser "`$currentUserUID" sudo -iu "`$currentUser" /usr/bin/security import /tmp/$($user.userName)-client-signed.pfx -x -k /Users/$($user.localUsername)/Library/Keychains/login.keychain -P $($global:JCRConfig.certSecretPass.value) -T "/System/Library/SystemConfiguration/EAPOLController.bundle/Contents/Resources/eapolclient" if [[ `$? -eq 0 ]]; then echo "Import Success" # get the SHA hash of the newly imported cert installedCertSN=`$(/bin/launchctl asuser "`$currentUserUID" sudo -iu "`$currentUser" /usr/bin/security find-certificate -$($macCertSearch) "$($certIdentifier)" -Z /Users/$($user.localUsername)/Library/Keychains/login.keychain | grep snbr | awk '{print `$1}' | sed 's/"snbr"<blob>=0x//g') if [[ `$installedCertSN == `$currentCertSN ]]; then installedCertSHA=`$(/bin/launchctl asuser "`$currentUserUID" sudo -iu "`$currentUser" /usr/bin/security find-certificate -$($macCertSearch) "$($certIdentifier)" -Z /Users/$($user.localUsername)/Library/Keychains/login.keychain | grep SHA-1 | awk '{print `$3}') fi else echo "import failed" exit 4 fi else echo "cert already imported" fi # check if the cert security preference is set: IFS=';' read -ra network <<< "`$(`$global:JCRConfig.networkSSID.value)" for i in "`${network[@]}"; do echo "begin setting network SSID: `$i" if /bin/launchctl asuser "`$currentUserUID" sudo -iu "`$currentUser" /usr/bin/security get-identity-preference -s "com.apple.network.eap.user.identity.wlan.ssid.`$i" -Z "`$installedCertSHA"; then echo "it was already set" else echo "certificate not linked from SSID: `$i to certSN: `$currentCertSN, setting now" /bin/launchctl asuser "`$currentUserUID" sudo -iu "`$currentUser" /usr/bin/security set-identity-preference -s "com.apple.network.eap.user.identity.wlan.ssid.`$i" -Z "`$installedCertSHA" if [[ `$? -eq 0 ]]; then echo "SSID: `$i and certificate linked" else echo "Could not associate SSID: `$i and certifiacte" fi fi done # print results echo "################## Cert Install Results ##################" echo "Installed Cert SN: `$installedCertSN" echo "Installed Cert SHA1: `$installedCertSHA" echo "##########################################################" # Finally clean up files if [[ -f "/tmp/$($user.userName)-client-signed.zip" ]]; then echo "Removing Temp Zip" rm "/tmp/$($user.userName)-client-signed.zip" fi if [[ -f "/tmp/$($user.userName)-client-signed.pfx" ]]; then echo "Removing Temp Pfx" rm "/tmp/$($user.userName)-client-signed.pfx" fi # update si table # The conf file is JSON and can be parsed using JSON.parse() in a supported language. conf="`$(cat /opt/jc/jcagent.conf)" regex='\"systemKey\":\"([a-zA-Z0-9_]+)\"' if [[ `${conf} =~ `$regex ]] ; then systemKey="`${BASH_REMATCH[1]}" fi # get the certUUID regex='\"certuuid\":\"([a-zA-Z0-9_]+)\"' if [[ `${conf} =~ `$regex ]] ; then certUUID="`${BASH_REMATCH[1]}" fi # get the key /cert locations keyLocation="/opt/jc/client.key" certLocation="/opt/jc/client.crt" caCertLocation="/opt/jc/ca.crt" # Get json certificate data from osquery and add missing windows certificate columns certJson=`$(/opt/jc/bin/jcosqueryi --json "select *, '' AS sid, '' AS store, '' AS store_id, '' AS store_location, '' AS username from certificates;") # post curl --cert `$certLocation --key `$keyLocation --cacert `$caCertLocation \ -X POST 'https://agent.jumpcloud.com/systeminsights/snapshots/certificates' \ -H "x-system-id: `$systemKey" \ -H "x-ssl-client-dn: /CN=`$certUUID/O=JumpCloud" \ -H 'Content-Type: application/json' \ --data-raw '{ "data": '"`$certJson"' }' else # restore case match type `$caseMatchOrigValue echo "Current logged in user, `$currentUser, does not match expected certificate user. Please ensure $($user.localUsername) is signed in and retry" # Finally clean up files if [[ -f "/tmp/$($user.userName)-client-signed.zip" ]]; then echo "Removing Temp Zip" rm "/tmp/$($user.userName)-client-signed.zip" fi if [[ -f "/tmp/$($user.userName)-client-signed.pfx" ]]; then echo "Removing Temp Pfx" rm "/tmp/$($user.userName)-client-signed.pfx" fi exit 4 fi "@ launchType = "trigger" User = "000000000000000000000000" trigger = "$($certInfo.sha1)" commandType = "mac" timeout = 600 TimeToLiveSeconds = 864000 files = (New-JCCommandFile -certFilePath $userPfxZip -FileName "$($user.userName)-client-signed.zip" -FileDestination "/tmp/$($user.userName)-client-signed.zip") } $NewCommand = New-JcSdkCommand @CommandBody } catch { $status_commandGenerated = $false # throw $_ } # Find newly created command and add system as target $Command = Get-JcSdkCommand -Filter @("trigger:eq:$($certInfo.sha1)", "commandType:eq:mac") $workToBeDone.macOSCommandID = $Command.Id $CommandTable = [PSCustomObject]@{ commandId = $workToBeDone.macOSCommandID commandName = $command.name commandPreviouslyRun = $false commandQueued = $false systems = $systemIds } $user.commandAssociations += $CommandTable # Write-Host "[status] Successfully created $($Command.name): User - $($user.userName); OS - Mac OS X" $status_commandGenerated = $true } } if (-Not ($workToBeDone.forceGenerateMacOSCommands) -And ($workToBeDone.macOSCommandID)) { $systemIds = (Get-SystemsThatNeedCertWork -userData $user -osType "macOS") $Command = Get-JcSdkCommand -Filter @("trigger:eq:$($certInfo.sha1)", "commandType:eq:mac") $CommandTable = [PSCustomObject]@{ commandId = $workToBeDone.macOSCommandID commandName = $command.name commandPreviouslyRun = $false commandQueued = $false systems = $systemIds } $user.commandAssociations += $CommandTable } if (($invokeCommands) -And ($workToBeDone.remainingMacOSDevices)) { try { $commandStart = Start-JcSdkCommand -Id $workToBeDone.macOSCommandID -SystemIds $workToBeDone.remainingMacOSDevices.systemId | Out-Null $result_deployed = $true } catch { $result_deployed = $false } } # set the command associations if (($workToBeDone.remainingMacOSDevices) -And ($workToBeDone.macOSCommandID)) { $workToBeDone.remainingMacOSDevices | ForEach-Object { try { $commandAssociation = Set-JcSdkCommandAssociation -CommandId:("$($workToBeDone.macOSCommandID)") -Op 'add' -Type:('system') -Id:("$($_.systemId)") | Out-Null } catch { "already exists/ couldn't add" | Out-Null } } } if ($workToBeDone.forceGenerateWindowsCommands) { # Get the Windows system ids $systemIds = (Get-SystemsThatNeedCertWork -userData $user -osType "windows") # If there are no systemIds to process, skip generating the command: if ($systemIds.count -gt 0) { # Create new Command and upload the signed pfx try { $CommandBody = @{ Name = "RadiusCert-Install:$($user.userName):Windows" Command = @" `$ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 `$PkgProvider = Get-PackageProvider If ("Nuget" -notin `$PkgProvider.Name){ Install-PackageProvider -Name NuGet -Force } `$CurrentUser = (Get-WMIObject -ClassName Win32_ComputerSystem).Username if ( -Not [string]::isNullOrEmpty(`$CurrentUser) ){ `$CurrentUser = `$CurrentUser.Split('\')[1] } else { `$CurrentUser = `$null } if (`$CurrentUser -eq "$($user.localUsername)") { if (-not(Get-InstalledModule -Name RunAsUser -errorAction "SilentlyContinue")) { Write-Host "RunAsUser Module not installed, Installing..." Install-Module RunAsUser -Force Import-Module RunAsUser -Force } else { Write-Host "RunAsUser Module installed, importing into session..." Import-Module RunAsUser -Force } # create temp new radius directory If (Test-Path "C:\RadiusCert"){ Write-Host "Radius Temp Cert Directory Exists" } else { New-Item "C:\RadiusCert" -itemType Directory } # expand archive as root and copy to temp location Expand-Archive -LiteralPath C:\Windows\Temp\$($user.userName)-client-signed.zip -DestinationPath C:\RadiusCert -Force `$password = ConvertTo-SecureString -String $($global:JCRConfig.certSecretPass.value) -AsPlainText -Force `$ScriptBlockInstall = { `$password = ConvertTo-SecureString -String $($global:JCRConfig.certSecretPass.value) -AsPlainText -Force Import-PfxCertificate -Password `$password -FilePath "C:\RadiusCert\$($user.userName)-client-signed.pfx" -CertStoreLocation Cert:\CurrentUser\My } `$imported = Get-PfxData -Password `$password -FilePath "C:\RadiusCert\$($user.userName)-client-signed.pfx" # Get Current Certs As User `$ScriptBlockCleanup = { `$certs = Get-ChildItem Cert:\CurrentUser\My\ foreach (`$cert in `$certs){ $windowsCertHereString } } `$scriptBlockValidate = { if (Get-ChildItem Cert:\CurrentUser\My\`$(`$imported.thumbrprint)){ return `$true } else { return `$false } } Write-Host "Importing Pfx Certificate for $($user.userName)" `$certInstall = Invoke-AsCurrentUser -ScriptBlock `$ScriptBlockInstall -CaptureOutput `$certInstall Write-Host "Cleaning Up Previously Installed Certs for $($user.userName)" `$certCleanup = Invoke-AsCurrentUser -ScriptBlock `$ScriptBlockCleanup -CaptureOutput `$certCleanup Write-Host "Validating Installed Certs for $($user.userName)" `$certValidate = Invoke-AsCurrentUser -ScriptBlock `$scriptBlockValidate -CaptureOutput write-host `$certValidate # finally clean up temp files: If (Test-Path "C:\Windows\Temp\$($user.userName)-client-signed.zip"){ Remove-Item "C:\Windows\Temp\$($user.userName)-client-signed.zip" } If (Test-Path "C:\RadiusCert\$($user.userName)-client-signed.pfx"){ Remove-Item "C:\RadiusCert\$($user.userName)-client-signed.pfx" } # Lastly validate if the cert was installed if (`$certValidate.Trim() -eq "True"){ Write-Host "Cert was installed" } else { Throw "Cert was not installed" } # update si table # define curl path: `$curlPath = "C:\ProgramData\chocolatey\bin\curl.exe" if (Test-Path -Path `$curlPath) { # Parse the systemKey from the cong file. `$config = get-content 'C:\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf' `$regex = 'systemKey\":\"(\w+)\"' `$systemKey = [regex]::Match(`$config, `$regex).Groups[1].Value # get the certUUID `$regex = 'certuuid\":\"(\w+)\"' `$certUUID = [regex]::Match(`$config, `$regex).Groups[1].Value # Get the key/ cert location `$keyLocation = "C:\Program Files\JumpCloud\Plugins\Contrib\client.key" `$certLocation = "C:\Program Files\JumpCloud\Plugins\Contrib\client.crt" `$caCertLocation = "C:\Program Files\JumpCloud\Plugins\Contrib\ca.crt" # Get json certificate data from osquery `$certs = . "C:\Program Files\JumpCloud\jcosqueryi" --json "select * from certificates" # format the data `$certsText = "{``"data``":`$certs}" # save the data to a temp file `$certJsonFile = "C:\Windows\Temp\jsonFile.txt" `$certsText | Out-File -FilePath `$certJsonFile -Force -Encoding utf8 # submit the request C:\ProgramData\chocolatey\bin\curl.exe --cert "`$certLocation" --key "`$keyLocation" --cacert "`$caCertLocation" --header "x-system-id: `$systemKey" --header "x-ssl-client-dn: /CN=`$certUUID/O=JumpCloud" --header "Content-type:application/json " -d @C:\Windows\Temp\jsonFile.txt --url 'https://agent.jumpcloud.com/systeminsights/snapshots/certificates' # remove the temp file Remove-Item -Path `$certJsonFile } else { Write-Host "Curl might not be installed, system insights will update certificate information on this device within an hour" } } else { if (`$CurrentUser -eq `$null){ Write-Host "No users are signed into the system. Please ensure $($user.userName) is signed in and retry." } else { Write-Host "Current logged in user, `$CurrentUser, does not match expected certificate user. Please ensure $($user.localUsername) is signed in and retry." } # finally clean up temp files: If (Test-Path "C:\Windows\Temp\$($user.userName)-client-signed.zip"){ Remove-Item "C:\Windows\Temp\$($user.userName)-client-signed.zip" } If (Test-Path "C:\RadiusCert\$($user.userName)-client-signed.pfx"){ Remove-Item "C:\RadiusCert\$($user.userName)-client-signed.pfx" } exit 4 } "@ launchType = "trigger" trigger = "$($certInfo.sha1)" commandType = "windows" shell = "powershell" timeout = 600 TimeToLiveSeconds = 864000 files = (New-JCCommandFile -certFilePath $userPfxZip -FileName "$($user.userName)-client-signed.zip" -FileDestination "C:\Windows\Temp\$($user.userName)-client-signed.zip") } $NewCommand = New-JcSdkCommand @CommandBody } catch { $status_commandGenerated = $false } # Find newly created command and add system as target $Command = Get-JcSdkCommand -Filter @("trigger:eq:$($certInfo.sha1)", "commandType:eq:windows") $workToBeDone.windowsCommandID = $command.Id $CommandTable = [PSCustomObject]@{ commandId = $workToBeDone.windowsCommandID commandName = $command.name commandPreviouslyRun = $false commandQueued = $false systems = $systemIds } $user.commandAssociations += $CommandTable # Write-Host "[status] Successfully created $($Command.name): User - $($user.userName); OS - Windows" $status_commandGenerated = $true } } if (-Not ($workToBeDone.forceGenerateWindowsCommands) -And ($workToBeDone.windowsCommandID)) { $systemIds = (Get-SystemsThatNeedCertWork -userData $user -osType "windows") $Command = Get-JcSdkCommand -Filter @("trigger:eq:$($certInfo.sha1)", "commandType:eq:windows") $CommandTable = [PSCustomObject]@{ commandId = $workToBeDone.windowsCommandID commandName = $command.name commandPreviouslyRun = $false commandQueued = $false systems = $systemIds } $user.commandAssociations += $CommandTable } if (($invokeCommands) -AND ($workToBeDone.remainingWindowsSDevices)) { try { $commandStart = Start-JcSdkCommand -Id $workToBeDone.windowsCommandID -SystemIds $workToBeDone.remainingWindowsSDevices.systemId | Out-Null $result_deployed = $true } catch { $result_deployed = $false } } # set the command associations if (($workToBeDone.remainingWindowsSDevices) -And ($workToBeDone.windowsCommandID)) { $workToBeDone.remainingWindowsSDevices | ForEach-Object { try { $commandAssociation = Set-JcSdkCommandAssociation -CommandId:("$($workToBeDone.windowsCommandID)") -Op 'add' -Type:('system') -Id:("$($_.systemId)") | Out-Null } catch { "already exists/ couldn't add" | Out-Null } } } } # TODO: get the userIndex only? $userObjectFromTable, $userIndex = Get-UserFromTable -userid $user.userid switch ($invokeCommands) { $true { if ($user.commandAssociations) { $user.commandAssociations | ForEach-Object { $_.commandPreviouslyRun = $true } # set the deployed status to true, set the date if (Get-Member -inputObject $user.certInfo -name "deployed" -MemberType Properties) { # if ($userObjectFromTable.certInfo.deployed) { $user.certInfo.deployed = $true } else { $user.certInfo | Add-Member -Name 'deployed' -Type NoteProperty -Value $false } if (Get-Member -inputObject $user.certInfo -name "deploymentDate" -MemberType Properties) { # if ($userObjectFromTable.certInfo.deploymentDate) { $user.certInfo.deploymentDate = (Get-Date -Format "o") } else { $user.certInfo | Add-Member -Name 'deploymentDate' -Type NoteProperty -Value (Get-Date -Format "o") } } } $false { $result_deployed = $false } Default { } } } end { $resultTable = [ordered]@{ 'Username' = $user.username; 'Command Generated' = $status_commandGenerated; 'Command Deployed' = $result_deployed } $workDone = [PSCustomObject]@{ userIndex = $userIndex commandAssociationsObject = $user.commandAssociations certInfoObject = $user.certInfo } return $resultTable, $workDone } } |