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
pywinrminstalled 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(andsshfor SSH transport) — extend the Bridge image with a custom build if needed bridge.shpresent 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-LocalUserThe checkin order matters: the bridge session is terminated before the account is deleted, so the RDP proxy tunnel closes cleanly.
Environment Variables
| Variable | Required | Default | Notes |
|---|---|---|---|
BRITIVE_USER_EMAIL | Yes | — | Injected — Windows username derived from the local part |
TRX | Yes | — | Injected — transaction ID; tags the account description |
TARGET_HOST | Yes | — | RDP target host |
BRIDGE_URL | Yes | — | Public Bridge base URL |
EXPIRATION | Yes | — | Checkout duration in seconds |
TARGET_PORT | No | 3389 | RDP port |
PROVISION_TRANSPORT | No | winrm | winrm or ssh |
PROVISION_USER | No | Administrator | Provisioning account |
PROVISION_PASSWORD | WinRM only | — | Password for the provisioning account |
PROVISION_HOST | No | TARGET_HOST | Separate provisioning host if needed |
PROVISION_PORT | No | 5985/5986/22 | Auto-selected per transport and WINRM_NO_SSL |
PROVISION_KEY / PROVISION_KEY_PEM | SSH only | /home/bridge/.ssh/id_ed25519 | Key file path, or PEM content injected as a variable |
WINRM_NO_SSL | No | 1 | 1 = HTTP (5985), 0 = HTTPS (5986) |
LOCAL_GROUP | No | Remote Desktop Users | Comma-separated groups to add the user to |
BRITIVE_FIRST_NAME / BRITIVE_LAST_NAME | No | — | Used for the account display name |
BROKER_API | No | /opt/britive-broker/scripts/bridge.sh | Path 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
| Symptom | Likely Cause | Fix |
|---|---|---|
pywinrm not installed | Broker host missing the library | pip install pywinrm or bake it into a custom Bridge image |
PROVISION_PASSWORD required for winrm transport | WinRM selected without a password | Set PROVISION_PASSWORD, or switch to PROVISION_TRANSPORT=ssh |
| WinRM connection refused | WinRM disabled or wrong port | Enable WinRM on the target; check WINRM_NO_SSL matches the listener (5985 vs 5986) |
| Provisioning succeeds but access denied | Non-built-in admin over WinRM | Set LocalAccountTokenFilterPolicy=1 on the target |
| Browser URL rejected immediately | Expired transaction or wrong BRIDGE_URL | Check EXPIRATION; confirm BRIDGE_URL matches the deployed Bridge endpoint |
| Account remains after checkin | Deprovisioning failed (best-effort) | Accounts are tagged bridge:<TRX> in the description — re-run checkin or remove manually |
Next Steps
- What Is Britive Bridge — architecture and concepts
- Temporary Local User Access — the same account lifecycle without Bridge (password returned to the user)
- Source: britive/access-broker-examples — temp-rdp-bridge