From b70db5d7e8610a2de6d6ef299cddb9058fa0154e Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:52:05 -0700 Subject: [PATCH 1/6] Create checkout_with_userExists.ps1 --- .../rdp/checkout_with_userExists.ps1 | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 session-recording/broker-scripts/rdp/checkout_with_userExists.ps1 diff --git a/session-recording/broker-scripts/rdp/checkout_with_userExists.ps1 b/session-recording/broker-scripts/rdp/checkout_with_userExists.ps1 new file mode 100644 index 0000000..38675a4 --- /dev/null +++ b/session-recording/broker-scripts/rdp/checkout_with_userExists.ps1 @@ -0,0 +1,122 @@ +$userEmail = $env:user_email +$resourceName = $env:ResourceName + +$username = $userEmail.Split("@")[0] +$username = $username.Substring(0, [Math]::Min($username.Length, 16)) +$username = "$username-rec" + +$fullName = $userEmail +$description = "Local admin account created for Britive POV" + +$logFile = "logs\${resourceName}.log" +$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + +function GenerateRandomPassword { + $length = 12 + $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&+=_' + $passwordChars = @() + $rand = New-Object System.Random + + for ($i = 0; $i -lt $length; $i++) { + $index = $rand.Next(0, $validChars.Length) + $passwordChars += $validChars[$index] + } + + return -join $passwordChars +} + +function SignData { + param([string]$data, [byte[]]$key) + $hmac = [System.Security.Cryptography.HMACSHA256]::new($key) + $dataBytes = [System.Text.Encoding]::UTF8.GetBytes($data) + $hashBytes = $hmac.ComputeHash($dataBytes) + $hmac.Dispose() + $combined = $hashBytes + $dataBytes + return $combined +} + +function EncryptData { + param([byte[]]$data, [byte[]]$key) + $aes = [System.Security.Cryptography.Aes]::Create() + $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC + $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 + $aes.Key = $key[0..15] + $aes.IV = [byte[]]::new(16) + $encryptor = $aes.CreateEncryptor() + $encryptedBytes = $encryptor.TransformFinalBlock($data, 0, $data.Length) + $encryptor.Dispose() + $aes.Dispose() + return $encryptedBytes +} + +try { + $userPassword = GenerateRandomPassword + $securePassword = ConvertTo-SecureString $userPassword -AsPlainText -Force + + $userExists = Get-LocalUser -Name $username -ErrorAction SilentlyContinue + + if (-not $userExists) { + # Create user + New-LocalUser -Name $username -Password $securePassword -FullName $fullName -Description $description | Out-Null + Add-LocalGroupMember -Group "Administrators" -Member $username | Out-Null + Add-Content -Path $logFile -Value "$timestamp SUCCESS: Created local admin user '$username' with resource '$resourceName'." | Out-Null + } + else { + # Reset password if user already exists + Set-LocalUser -Name $username -Password $securePassword + Add-Content -Path $logFile -Value "$timestamp INFO: Reset password for existing user '$username'." | Out-Null + } + + # Guacamole JSON generation + $connection = @{ + protocol = "rdp" + parameters = @{ + hostname = "$env:hostname" + port = "$env:port" + username = "$username" + password = $userPassword + security = "nla" + "ignore-cert" = "true" + "recording-path" = "/home/guacd/recordings" + "recording-name" = "`${GUAC_DATE}-`${GUAC_TIME}-${userEmail}-${username}-${resourceName}" + } + } + + $expiration = $env:expiration + if (-not $expiration) { $expiration = 3600 } + $epoch = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $expires = ([int64]$epoch + [int64]$expiration) * 1000 + + $jsonObject = @{ + username = "$username-$expires" + expires = $expires + connections = @{ $resourceName = $connection } + } + + $JSON = $jsonObject | ConvertTo-Json -Depth 10 -Compress + + $SECRET_KEY = $env:json_secret_key + + $keyBytes = [byte[]]::new($SECRET_KEY.Length / 2) + for ($i = 0; $i -lt $SECRET_KEY.Length; $i += 2) { + $keyBytes[$i / 2] = [Convert]::ToByte($SECRET_KEY.Substring($i, 2), 16) + } + + $signedData = SignData -data $JSON -key $keyBytes + $encryptedData = EncryptData -data $signedData -key $keyBytes + $base64Token = [Convert]::ToBase64String($encryptedData) + $TOKEN = [System.Web.HttpUtility]::UrlEncode($base64Token) + + $result = @{ + json = $JSON + token = $TOKEN + url = $env:url + } | ConvertTo-Json -Compress + + Write-Output $result +} +catch { + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + Add-Content -Path $logFile -Value "$timestamp ERROR: $_" + Write-Host "ERROR: $_" +} From b15d2872534ae9f65b429d36c2c313a53203804c Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:54:23 -0700 Subject: [PATCH 2/6] Create checkin_kill_rdp_session.ps1 --- .../rdp/checkin_kill_rdp_session.ps1 | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 session-recording/broker-scripts/rdp/checkin_kill_rdp_session.ps1 diff --git a/session-recording/broker-scripts/rdp/checkin_kill_rdp_session.ps1 b/session-recording/broker-scripts/rdp/checkin_kill_rdp_session.ps1 new file mode 100644 index 0000000..bef1437 --- /dev/null +++ b/session-recording/broker-scripts/rdp/checkin_kill_rdp_session.ps1 @@ -0,0 +1,39 @@ +# Origincal code by Will. I added the logic to terminate the RDP session upon checkin. + +$email = $env:user_email +$username = $email.Split('@')[0] +$username = $username.Substring(0, [Math]::Min($username.Length, 16)) +$username = "$username-rec" + +# Function to kill active RDP sessions for a given user +function KillRDPSessions { + param ( + [Parameter(Mandatory = $true)] + [string]$username + ) + + try { + $qwinstaOutput = qwinsta $username + $lines = $qwinstaOutput -split "`r`n" + $userLines = $lines | Where-Object { $_ -match "\b$username\b" } + $sessionIds = $userLines | ForEach-Object { + $fields = $_ -split '\s+' + if ($fields.Count -ge 3) { + $fields[3] + } + } + } + catch { + $sessionIds = @() + } + + foreach ($session in $sessionIds) { + Invoke-RDUserLogoff -HostServer localhost -UnifiedSessionID $session -Force + } +} + +# Only log off the user — do NOT delete! +KillRDPSessions -Username $username + +# Commenting out user deletion to preserve account and profile +# CleanUpLocalUser -Username $username From 83b07f1c531ae774173a400017b8e24746e9037f Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:07:44 -0700 Subject: [PATCH 3/6] Create checkout_ssh.sh --- .../broker-scripts/ssh/checkout_ssh.sh | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 session-recording/broker-scripts/ssh/checkout_ssh.sh diff --git a/session-recording/broker-scripts/ssh/checkout_ssh.sh b/session-recording/broker-scripts/ssh/checkout_ssh.sh new file mode 100644 index 0000000..fae226b --- /dev/null +++ b/session-recording/broker-scripts/ssh/checkout_ssh.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +USER_EMAIL=${firstmac_email:-"test@example.com"} +USERNAME="${USER_EMAIL%%@*}" +USERNAME="${USERNAME//[^a-zA-Z0-9]/}" + +USER=${USERNAME} +GROUP=${USERNAME} + +SUDO=${BRITIVE_SUDO:-"0"} + +useradd -m ${USER} 2>/dev/null + +SSH_PATH=/${BRITIVE_HOME_ROOT:-"home"}/${USER}/.ssh + +finish () { + rm -f $SSH_PATH/britive-id_rsa* + exit $1 +} + +if ! test -d $SSH_PATH; then + mkdir -p $SSH_PATH || finish 1 + chmod 700 $SSH_PATH || finish 1 + chown $USER:$GROUP $SSH_PATH || finish 1 +fi + +ssh-keygen -q -N '' -t rsa -C $USER_EMAIL -f $SSH_PATH/britive-id_rsa || finish 1 + +mapfile -t contents < <(cat $SSH_PATH/authorized_keys 2>/dev/null | sort -u) +mapfile -t -O "${#contents[@]}" contents < <(cat $SSH_PATH/britive-id_rsa.pub 2>/dev/null) +printf "%s\n" "${contents[@]}" > $SSH_PATH/authorized_keys + +chmod 600 $SSH_PATH/authorized_keys || finish 1 +chown $USER:$GROUP $SSH_PATH/authorized_keys || finish 1 + + +if [ "$SUDO" != "0" ]; then + echo "${USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USER} || finish 1 + chmod 440 /etc/sudoers.d/${USER} || finish 1 +fi + +SSH_KEY=$(cat $SSH_PATH/britive-id_rsa || finish 1) + +JSON_STRING='{ + "username": "'${USER_EMAIL}'", + "expires": "'$(date -d "+${expiration} seconds" +%s)'000", + "connections": { + "'${connection_name}'": + { + "protocol": "ssh", + "parameters": { + "hostname": "'${hostname}'", + "port": "'${port}'", + "username": "'${USERNAME}'", + "private-key": "'${SSH_KEY//$'\n'/\\n}'", + "recording-path": "'${recording_path:-/home/guacd/recordings}'", + "recording-name": "${GUAC_DATE}-${GUAC_TIME}-'${USER_EMAIL}'-'${USERNAME}'-'${connection_name}'" + } + } + } +}' + +JSON=$(echo -n $JSON_STRING|jq -r tostring) + +REGION=$(ec2metadata --availability-zone) + +SECRET_KEY=$(aws --region us-west-2 secretsmanager get-secret-value --secret-id ${json_secret_key} --query "SecretString" --output text | jq -r .key) + +sign() { + echo -n "${JSON}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:"${SECRET_KEY}" -binary + echo -n "${JSON}" +} + +encrypt() { + openssl enc -aes-128-cbc -K "${SECRET_KEY}" -iv "00000000000000000000000000000000" -nosalt -a +} + +TOKEN=$(sign | encrypt | tr -d "\n\r" | jq -Rr @uri) + +echo -n '{"token": "'${TOKEN}'", "url": "'${url}'"}' + +finish 0 From af28284298bded320bd6ae1f569e7ce2c50e163b Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:09:02 -0700 Subject: [PATCH 4/6] Rename checkout_ssh.sh to checkout_ssh_will.sh --- .../broker-scripts/ssh/{checkout_ssh.sh => checkout_ssh_will.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename session-recording/broker-scripts/ssh/{checkout_ssh.sh => checkout_ssh_will.sh} (100%) diff --git a/session-recording/broker-scripts/ssh/checkout_ssh.sh b/session-recording/broker-scripts/ssh/checkout_ssh_will.sh similarity index 100% rename from session-recording/broker-scripts/ssh/checkout_ssh.sh rename to session-recording/broker-scripts/ssh/checkout_ssh_will.sh From 794eab362e9aedaa36911f1e28d81451542ea491 Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:09:52 -0700 Subject: [PATCH 5/6] Create checkin_ssh_recording_will.sh --- .../ssh/checkin_ssh_recording_will.sh | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 session-recording/broker-scripts/ssh/checkin_ssh_recording_will.sh diff --git a/session-recording/broker-scripts/ssh/checkin_ssh_recording_will.sh b/session-recording/broker-scripts/ssh/checkin_ssh_recording_will.sh new file mode 100644 index 0000000..4a3fefd --- /dev/null +++ b/session-recording/broker-scripts/ssh/checkin_ssh_recording_will.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +USER_EMAIL=${firstmac_email:-"test@example.com"} +USERNAME="${USER_EMAIL%%@*}" +USERNAME="${USERNAME//[^a-zA-Z0-9]/}" + +USER=${USERNAME} + + +SSH_PATH=/${BRITIVE_HOME_ROOT:-"home"}/${USER}/.ssh + +finish () { + exit $1 +} + +if test -d $SSH_PATH; then + mapfile -t contents < <(cat $SSH_PATH/authorized_keys 2>/dev/null | grep -v "${USER_EMAIL}") + + if (( ${#contents[@]} > 0 )); then + printf "%s\n" "${contents[@]}" > $SSH_PATH/authorized_keys || finish 1 + + chmod 600 $SSH_PATH/authorized_keys || finish 1 + else + rm -f $SSH_PATH/authorized_keys || finish 1 + fi +fi + +if test -f /etc/sudoers.d/${USER}; then + rm -f /etc/sudoers.d/${USER} > /dev/null 2>&1 +fi + +finish 0 From 643fbc56b79cc367b15ff451f59f1d3bb59c8b90 Mon Sep 17 00:00:00 2001 From: Shahzad Ali <52764993+netJoints@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:10:19 -0700 Subject: [PATCH 6/6] Rename checkout_ssh_will.sh to checkout_ssh_recording_will.sh --- .../ssh/{checkout_ssh_will.sh => checkout_ssh_recording_will.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename session-recording/broker-scripts/ssh/{checkout_ssh_will.sh => checkout_ssh_recording_will.sh} (100%) diff --git a/session-recording/broker-scripts/ssh/checkout_ssh_will.sh b/session-recording/broker-scripts/ssh/checkout_ssh_recording_will.sh similarity index 100% rename from session-recording/broker-scripts/ssh/checkout_ssh_will.sh rename to session-recording/broker-scripts/ssh/checkout_ssh_recording_will.sh