Skip to content
Browser-Based RDP via Bridge

Browser-Based RDP via Bridge

Overview

This pattern combines the temporary local user approach with Britive Bridge: the broker creates a temporary Windows local user at checkout, but instead of returning the password, it registers a proxied RDP session with Bridge and returns a browser URL. The user opens the URL and gets an RDP session in the browser — no RDP client, no password handling, and all traffic flows through the Bridge proxy where it can be audited and recorded.

What you’ll accomplish:

  • Create a temporary Windows local user at checkout (over WinRM or SSH)
  • Register a proxied RDP session with Bridge and return a tokenized URL
  • Tear down the session and delete the account at checkin

Before You Begin

  • Bridge is deployed and reachable by user browsers at BRIDGE_URL
  • A provisioning account with admin rights on each target (default Administrator)
  • For WinRM transport: WinRM enabled on the target (5985/5986, NTLM auth), and pywinrm installed on the broker host (pip install pywinrm)
  • For SSH transport: OpenSSH Server + PowerShell 5.1+ on the target, and the broker’s key authorized
  • Broker host has python3 (and ssh for SSH transport) — extend the Bridge image with a custom build if needed
  • bridge.sh present at /opt/britive-broker/scripts/bridge.sh

How It Works

checkout → derive Windows username from BRITIVE_USER_EMAIL (SAM-safe, max 20 chars)
         → generate password meeting Windows complexity rules
         → WinRM or SSH: New-LocalUser (description bridge:<TRX>),
           Add-LocalGroupMember "Remote Desktop Users"
         → bridge.sh checkout-create (payload includes the password)
         → return { token, url: <BRIDGE_URL>/rdp/#token=...&transaction_id=... }

checkin  → bridge.sh checkout-delete <TRX>   ← tunnel closes FIRST
         → WinRM or SSH: Remove-LocalGroupMember, Remove-LocalUser

The checkin order matters: the bridge session is terminated before the account is deleted, so the RDP proxy tunnel closes cleanly.

Environment Variables

VariableRequiredDefaultNotes
BRITIVE_USER_EMAILYesInjected — Windows username derived from the local part
TRXYesInjected — transaction ID; tags the account description
TARGET_HOSTYesRDP target host
BRIDGE_URLYesPublic Bridge base URL
EXPIRATIONYesCheckout duration in seconds
TARGET_PORTNo3389RDP port
PROVISION_TRANSPORTNowinrmwinrm or ssh
PROVISION_USERNoAdministratorProvisioning account
PROVISION_PASSWORDWinRM onlyPassword for the provisioning account
PROVISION_HOSTNoTARGET_HOSTSeparate provisioning host if needed
PROVISION_PORTNo5985/5986/22Auto-selected per transport and WINRM_NO_SSL
PROVISION_KEY / PROVISION_KEY_PEMSSH only/home/bridge/.ssh/id_ed25519Key file path, or PEM content injected as a variable
WINRM_NO_SSLNo11 = HTTP (5985), 0 = HTTPS (5986)
LOCAL_GROUPNoRemote Desktop UsersComma-separated groups to add the user to
BRITIVE_FIRST_NAME / BRITIVE_LAST_NAMENoUsed for the account display name
BROKER_APINo/opt/britive-broker/scripts/bridge.shPath to the bridge helper

Checkout Routine

Full script: Windows/permissions/temp-rdp-bridge/checkout_rdp_bridge.sh

Key sections — create the user, register the session:

# SAM-safe username from the email local part (a-z0-9, max 20 chars)
USERNAME="$(python3 -c "
import re, sys
name = re.sub(r'[^a-z0-9]', '', sys.argv[1].split('@')[0].lower())
if name[0].isdigit(): name = 'brg' + name
print(name[:20])
" "$USER_EMAIL")"

# Password meeting Windows complexity: upper + lower + digit + special
RAND="$(head -c 12 /dev/urandom | base64 | tr -d "+/='\\\\" | head -c 12)"
PASSWORD="${RAND}Aa1@"

# PowerShell run over WinRM (pywinrm) or SSH (-EncodedCommand):
#   New-LocalUser -Description 'bridge:<TRX>' ... ;
#   Add-LocalGroupMember -Group 'Remote Desktop Users' ...

# Register the proxied RDP session — the password goes to Bridge, not the user
TOKEN="$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 43)"
EXPIRES_AT="$(($(date +%s) + EXPIRATION))"

cat > "$PAYLOAD_FILE" <<EOF
{
  "transaction_id":  "${TRANSACTION_ID}",
  "protocol":        "rdp",
  "username":        "${USER_EMAIL}",
  "target_host":     "${TARGET_HOST}",
  "target_port":     ${TARGET_PORT},
  "target_username": "${USERNAME}",
  "target_password": ${PASSWORD_JSON},
  "expires_at":      ${EXPIRES_AT},
  "token":           "${TOKEN}"
}
EOF

"${BROKER_API}" checkout-create --file "$PAYLOAD_FILE" >/dev/null

printf '{"token": "%s", "url": "%s/rdp/#token=%s&transaction_id=%s"}\n' \
  "${TOKEN}" "${BRIDGE_URL}" "${TOKEN}" "${TRANSACTION_ID}"

Checkin Routine

Full script: Windows/permissions/temp-rdp-bridge/checkin_rdp_bridge.sh

# Terminate the Bridge session FIRST so the RDP tunnel closes
"${BROKER_API}" checkout-delete "${TRANSACTION_ID}"

# Then deprovision over WinRM or SSH (best-effort after session revoked):
#   Remove-LocalGroupMember -Group 'Remote Desktop Users' ... ;
#   Remove-LocalUser -Name <username>

Configure in Britive

Create the permission

Resource Manager → Resource Type Permissions → New Permission. Language = Shell. Paste the checkout and checkin routines. Declare TARGET_HOST, BRIDGE_URL, EXPIRATION, and the provisioning variables for your transport (PROVISION_PASSWORD for WinRM, or key variables for SSH); BRITIVE_USER_EMAIL and TRX are system-defined. Attach a response template that surfaces the returned url.

Create a profile and policy

Create a profile (e.g. 1h), add the permission, associate it with the Windows resources, and add a policy assigning members by tag.

Verify

Check out

Check out the profile. The response contains a URL like https://<bridge>/rdp/#token=<token>&transaction_id=<trx>.

Open the session in the browser

The URL opens an RDP session to the target as the derived user. On the target, Get-LocalUser shows the account with description bridge:<TRX>.

Check in

Check in. The URL stops working and the local account is gone (Get-LocalUser no longer lists it).

Troubleshoot

SymptomLikely CauseFix
pywinrm not installedBroker host missing the librarypip install pywinrm or bake it into a custom Bridge image
PROVISION_PASSWORD required for winrm transportWinRM selected without a passwordSet PROVISION_PASSWORD, or switch to PROVISION_TRANSPORT=ssh
WinRM connection refusedWinRM disabled or wrong portEnable WinRM on the target; check WINRM_NO_SSL matches the listener (5985 vs 5986)
Provisioning succeeds but access deniedNon-built-in admin over WinRMSet LocalAccountTokenFilterPolicy=1 on the target
Browser URL rejected immediatelyExpired transaction or wrong BRIDGE_URLCheck EXPIRATION; confirm BRIDGE_URL matches the deployed Bridge endpoint
Account remains after checkinDeprovisioning failed (best-effort)Accounts are tagged bridge:<TRX> in the description — re-run checkin or remove manually

Next Steps

Last updated on