From b342d796a40841069fa384a5b7a1d92b0e75c774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 7 Nov 2025 13:26:30 +0100 Subject: [PATCH 1/2] add script to generate enrollment tokens for Entra --- .../GenerateEnrollmentTokensAD.ps1 | 5 +- .../GenerateEnrollmentTokensEntraID.ps1 | 287 ++++++++++++++++++ 2 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 diff --git a/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensAD.ps1 b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensAD.ps1 index 184e5ae8..8d8f64c7 100644 --- a/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensAD.ps1 +++ b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensAD.ps1 @@ -18,7 +18,7 @@ param( [string]$DomainController ) -# Function to make authenticated API calls +# Function to make authenticated API calls to Defguard function Invoke-AuthenticatedRestMethod { param( [string]$Method, @@ -44,7 +44,7 @@ function Invoke-AuthenticatedRestMethod { return $response } catch { - Write-Error "API call failed: $($_.Exception.Message)" + Write-Error "Defguard API call failed: $($_.Exception.Message)" return $null } } @@ -130,6 +130,7 @@ if ($ADUsername) { } # Get group members +Write-Host "Fetching group members from Defguard..." -ForegroundColor Yellow $groupEndpoint = "api/v1/group/$GroupName" $groupResponse = Invoke-AuthenticatedRestMethod -Method "GET" -Endpoint $groupEndpoint diff --git a/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 new file mode 100644 index 00000000..42ca3386 --- /dev/null +++ b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 @@ -0,0 +1,287 @@ +param( + [Parameter(Mandatory=$true)] + [string]$Url, + + [Parameter(Mandatory=$true)] + [string]$ApiToken, + + [Parameter(Mandatory=$true)] + [string]$GroupName, + + [Parameter(Mandatory=$false)] + [string]$AttributeSetName = "Defguard" +) + +# Function to make authenticated API calls to Defguard +function Invoke-AuthenticatedRestMethod { + param( + [string]$Method, + [string]$Endpoint, + [object]$Body = $null + ) + + $headers = @{ + "Authorization" = "Bearer $ApiToken" + "Content-Type" = "application/json" + "Accept" = "application/json" + } + + $uri = "$Url/$Endpoint" + + try { + if ($Body) { + $jsonBody = $Body | ConvertTo-Json + $response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers -Body $jsonBody + } else { + $response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers + } + return $response + } + catch { + Write-Error "Defguard API call failed: $($_.Exception.Message)" + return $null + } +} + + +# Function to find user in Entra ID by username using Microsoft.Graph module +function Get-EntraIDUser { + param( + [string]$Username + ) + + # Try different user properties to find the user + try { + # Try by UserPrincipalName first + $user = Get-MgUser -Filter "userPrincipalName eq '$Username'" -ErrorAction Stop + if ($user) { return $user } + } catch { } + + try { + # Try by mail + $user = Get-MgUser -Filter "mail eq '$Username'" -ErrorAction Stop + if ($user) { return $user } + } catch { } + + try { + # Try by display name + $user = Get-MgUser -Filter "displayName eq '$Username'" -ErrorAction Stop + if ($user) { return $user } + } catch { } + + try { + # Try starts with userPrincipalName + $user = Get-MgUser -Filter "startswith(userPrincipalName,'$Username@')" -ErrorAction Stop + if ($user) { return $user } + } catch { } + + return $null +} + +# Function to update Entra ID user custom security attributes using Microsoft.Graph module +function Set-EntraIDUserEnrollmentToken { + param( + [string]$UserId, + [string]$EnrollmentToken, + [string]$EnrollmentUrl, + [string]$AttributeSetName + ) + + try { + # Build the custom security attributes payload + $attributes = @{ + "$AttributeSetName" = @{ + "@odata.type" = "#microsoft.graph.customSecurityAttributeValue" + "enrollmentToken" = $EnrollmentToken + "enrollmentUrl" = $EnrollmentUrl + } + } + + # Update user with custom security attributes + Update-MgUser -UserId $UserId -CustomSecurityAttributes $attributes -ErrorAction Stop + + Write-Host " Successfully updated Entra ID custom security attributes for user ID: $UserId" -ForegroundColor Green + return $true + } + catch { + Write-Host " Failed to update Entra ID custom security attributes for user ID: $UserId" -ForegroundColor Red + Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red + + # Check if it's a permissions error + if ($_.Exception.Message -like "*403*" -or $_.Exception.Message -like "*Forbidden*") { + Write-Host " This is likely a permissions issue. Ensure the current user has:" -ForegroundColor Yellow + Write-Host " - CustomSecurityAttributes.ReadWrite.All permission" -ForegroundColor Yellow + Write-Host " - User.ReadWrite.All permission" -ForegroundColor Yellow + } + return $false + } +} + +# Main script execution +Write-Host "Starting enrollment token generation for Entra ID users in group: $GroupName" -ForegroundColor Green + +# Check for Microsoft.Graph module and install/import if needed +$requiredModules = @( + "Microsoft.Graph.Authentication", + "Microsoft.Graph.Users", + "Microsoft.Graph.Users.Actions" +) + +foreach ($module in $requiredModules) { + if (-not (Get-Module -ListAvailable -Name $module)) { + Write-Host "$module module is required but not installed. Attempting to install..." -ForegroundColor Yellow + try { + Install-Module $module -Scope CurrentUser -Force -ErrorAction Stop + Write-Host "$module module installed successfully" -ForegroundColor Green + } + catch { + Write-Error "Failed to install $module module: $($_.Exception.Message)" + Write-Host "Please install it manually using: Install-Module $module -Scope CurrentUser" -ForegroundColor Red + exit 1 + } + } + + # Import the module + try { + Import-Module $module -Force -ErrorAction Stop + Write-Host "$module module imported successfully" -ForegroundColor Green + } + catch { + Write-Error "Failed to import $module module: $($_.Exception.Message)" + exit 1 + } +} + +# Connect to Microsoft Graph +Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Yellow +try { + # Check if we're already connected + $context = Get-MgContext -ErrorAction SilentlyContinue + if ($context -and $context.Scopes -contains "CustomSecAttributeAssignment.ReadWrite.All" -and $context.Scopes -contains +"User.ReadWrite.All") { + Write-Host "Already connected to Microsoft Graph with required permissions" -ForegroundColor Green + } else { + # Disconnect from any existing Graph sessions first + Disconnect-MgGraph -ErrorAction SilentlyContinue + + # Try authentication methods in order of preference + Write-Host "Attempting authentication methods..." -ForegroundColor Cyan + + # Method 1: Try integrated Windows authentication (best for domain-joined) + try { + Write-Host "Trying Integrated Windows Authentication..." -ForegroundColor Yellow + Connect-MgGraph -Scopes @('CustomSecAttributeAssignment.ReadWrite.All', 'User.ReadWrite.All') -UseDeviceCode -ErrorAction Stop + Write-Host "Successfully connected using device code flow" -ForegroundColor Green + } + catch { + # Method 2: Fall back to interactive browser + Write-Host "Device code flow failed, trying interactive browser..." -ForegroundColor Yellow + Connect-MgGraph -Scopes @('CustomSecAttributeAssignment.ReadWrite.All', 'User.ReadWrite.All') -ErrorAction Stop + Write-Host "Successfully connected using interactive authentication" -ForegroundColor Green + } + } +} +catch { + Write-Error "Failed to connect to Microsoft Graph: $($_.Exception.Message)" + Write-Host "Authentication troubleshooting:" -ForegroundColor Red + Write-Host " - Ensure you're on a domain-joined machine and connected to corporate network" -ForegroundColor Yellow + Write-Host " - Verify you have the required Entra ID permissions" -ForegroundColor Yellow + Write-Host " - Check that custom security attributes are enabled in your tenant" -ForegroundColor Yellow + Write-Host " - Complete any MFA prompts if required" -ForegroundColor Yellow + Write-Host "Required permissions: CustomSecAttributeAssignment.ReadWrite.All, User.ReadWrite.All" -ForegroundColor Cyan + exit 1 +} + +# Get group members from Defguard +Write-Host "Fetching group members from Defguard..." -ForegroundColor Yellow +$groupEndpoint = "api/v1/group/$GroupName" +$groupResponse = Invoke-AuthenticatedRestMethod -Method "GET" -Endpoint $groupEndpoint + +if (-not $groupResponse) { + Write-Error "Failed to fetch group members from Defguard" + exit 1 +} + +# Extract usernames from the response +$usernames = $groupResponse.members + +if (-not $usernames -or $usernames.Count -eq 0) { + Write-Host "No members found in group: $GroupName" -ForegroundColor Yellow + exit 0 +} + +Write-Host "Found $($usernames.Count) members in the group" -ForegroundColor Green + +# Arrays to store results +$enrollmentTokens = @() +$entraUpdateResults = @() + +# Loop through each user and generate enrollment token, then update Entra ID +foreach ($username in $usernames) { + Write-Host "Processing user: $username" -ForegroundColor Cyan + + # Generate enrollment token from Defguard + $enrollmentEndpoint = "api/v1/user/$username/start_enrollment" + $requestBody = @{ + email = $null + send_enrollment_notification = $false + } + + $enrollmentResponse = Invoke-AuthenticatedRestMethod -Method "POST" -Endpoint $enrollmentEndpoint -Body $requestBody + + if ($enrollmentResponse) { + Write-Host " Enrollment token generated for $username" -ForegroundColor Green + + # Find user in Entra ID + Write-Host " Searching for user in Entra ID..." -ForegroundColor Yellow + $entraUser = Get-EntraIDUser -Username $username + + if ($entraUser) { + Write-Host " Found user in Entra ID: $($entraUser.UserPrincipalName) (ID: $($entraUser.Id))" -ForegroundColor Green + + # Update Entra ID custom security attributes + $updateResult = Set-EntraIDUserEnrollmentToken -UserId $entraUser.Id -EnrollmentToken $enrollmentResponse.enrollment_token -EnrollmentUrl $enrollmentResponse.enrollment_url -AttributeSetName $AttributeSetName + + $entraUpdateResults += @{ + username = $username + entra_id = $entraUser.Id + user_principal_name = $entraUser.UserPrincipalName + success = $updateResult + enrollment_token = $enrollmentResponse.enrollment_token + enrollment_url = $enrollmentResponse.enrollment_url + } + } + else { + Write-Host " User $username not found in Entra ID" -ForegroundColor Red + $entraUpdateResults += @{ + username = $username + entra_id = $null + user_principal_name = $null + success = $false + enrollment_token = $enrollmentResponse.enrollment_token + enrollment_url = $enrollmentResponse.enrollment_url + } + } + } + else { + Write-Host " Failed to generate enrollment token for $username" -ForegroundColor Red + $entraUpdateResults += @{ + username = $username + entra_id = $null + user_principal_name = $null + success = $false + enrollment_token = $null + enrollment_url = $null + } + } +} + +# Output summary +Write-Host "`nEnrollment token generation and Entra ID update completed!" -ForegroundColor Green +$successfulEntraUpdates = ($entraUpdateResults | Where-Object { $_.success }).Count +Write-Host "Successfully updated Entra ID custom security attributes: $successfulEntraUpdates/$($usernames.Count)" -ForegroundColor $(if ($successfulEntraUpdates -eq $usernames.Count) { "Green" } else { "Yellow" }) + +# Disconnect from Microsoft Graph +Write-Host "`nDisconnecting from Microsoft Graph..." -ForegroundColor Yellow +Disconnect-MgGraph -ErrorAction SilentlyContinue +Write-Host "Disconnected successfully" -ForegroundColor Green From 01d55744d91afdc3a9383bc59a5135b0cfd48224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 7 Nov 2025 16:33:34 +0100 Subject: [PATCH 2/2] update script to use email for matching users --- .../GenerateEnrollmentTokensEntraID.ps1 | 169 ++++++++++++------ 1 file changed, 113 insertions(+), 56 deletions(-) diff --git a/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 index 42ca3386..dfb00399 100644 --- a/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 +++ b/src-tauri/resources-windows/admin-scripts/GenerateEnrollmentTokensEntraID.ps1 @@ -44,37 +44,74 @@ function Invoke-AuthenticatedRestMethod { } -# Function to find user in Entra ID by username using Microsoft.Graph module +# Function to find user in Entra ID by email address using Microsoft.Graph module function Get-EntraIDUser { param( - [string]$Username + [string]$Email ) - # Try different user properties to find the user + Write-Host " Searching for user with email: $Email" -ForegroundColor Cyan + + # Try to find user by mail (primary email) try { - # Try by UserPrincipalName first - $user = Get-MgUser -Filter "userPrincipalName eq '$Username'" -ErrorAction Stop - if ($user) { return $user } - } catch { } + Write-Host " Trying search by 'mail' property..." -ForegroundColor Gray + $user = Get-MgUser -Filter "mail eq '$Email'" -ErrorAction Stop + if ($user) { + Write-Host " Found user by mail: $($user.UserPrincipalName)" -ForegroundColor Green + return $user + } + } + catch { + Write-Host " Error searching by mail: $($_.Exception.Message)" -ForegroundColor Red + Write-Host " Full error details:" -ForegroundColor Red + Write-Host " Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red + Write-Host " Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red + } + # Try to find user by userPrincipalName (often matches email) try { - # Try by mail - $user = Get-MgUser -Filter "mail eq '$Username'" -ErrorAction Stop - if ($user) { return $user } - } catch { } + Write-Host " Trying search by 'userPrincipalName' property..." -ForegroundColor Gray + $user = Get-MgUser -Filter "userPrincipalName eq '$Email'" -ErrorAction Stop + if ($user) { + Write-Host " Found user by userPrincipalName: $($user.UserPrincipalName)" -ForegroundColor Green + return $user + } + } + catch { + Write-Host " Error searching by userPrincipalName: $($_.Exception.Message)" -ForegroundColor Red + Write-Host " Full error details:" -ForegroundColor Red + Write-Host " Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red + Write-Host " Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red + } + # Try other mail properties if the above don't work try { - # Try by display name - $user = Get-MgUser -Filter "displayName eq '$Username'" -ErrorAction Stop - if ($user) { return $user } - } catch { } + Write-Host " Trying search by 'otherMails' property..." -ForegroundColor Gray + $user = Get-MgUser -Filter "otherMails/any(m:m eq '$Email')" -ErrorAction Stop + if ($user) { + Write-Host " Found user by otherMails: $($user.UserPrincipalName)" -ForegroundColor Green + return $user + } + } + catch { + Write-Host " Error searching by otherMails: $($_.Exception.Message)" -ForegroundColor Red + Write-Host " Full error details:" -ForegroundColor Red + Write-Host " Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red + Write-Host " Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red + } + # Try a broader search to see if we can find any users at all try { - # Try starts with userPrincipalName - $user = Get-MgUser -Filter "startswith(userPrincipalName,'$Username@')" -ErrorAction Stop - if ($user) { return $user } - } catch { } + Write-Host " Testing basic user query..." -ForegroundColor Gray + $testUser = Get-MgUser -Top 1 -ErrorAction Stop + Write-Host " Basic user query successful - permissions appear valid" -ForegroundColor Green + } + catch { + Write-Host " Basic user query failed: $($_.Exception.Message)" -ForegroundColor Red + Write-Host " This suggests a permissions issue with User.ReadWrite.All" -ForegroundColor Red + } + Write-Host " User not found in Entra ID for email: $Email" -ForegroundColor Yellow return $null } @@ -92,8 +129,8 @@ function Set-EntraIDUserEnrollmentToken { $attributes = @{ "$AttributeSetName" = @{ "@odata.type" = "#microsoft.graph.customSecurityAttributeValue" - "enrollmentToken" = $EnrollmentToken - "enrollmentUrl" = $EnrollmentUrl + "EnrollmentToken" = $EnrollmentToken + "EnrollmentUrl" = $EnrollmentUrl } } @@ -107,12 +144,6 @@ function Set-EntraIDUserEnrollmentToken { Write-Host " Failed to update Entra ID custom security attributes for user ID: $UserId" -ForegroundColor Red Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red - # Check if it's a permissions error - if ($_.Exception.Message -like "*403*" -or $_.Exception.Message -like "*Forbidden*") { - Write-Host " This is likely a permissions issue. Ensure the current user has:" -ForegroundColor Yellow - Write-Host " - CustomSecurityAttributes.ReadWrite.All permission" -ForegroundColor Yellow - Write-Host " - User.ReadWrite.All permission" -ForegroundColor Yellow - } return $false } } @@ -121,33 +152,35 @@ function Set-EntraIDUserEnrollmentToken { Write-Host "Starting enrollment token generation for Entra ID users in group: $GroupName" -ForegroundColor Green # Check for Microsoft.Graph module and install/import if needed +Write-Host "Setting up Microsoft.Graph modules..." -ForegroundColor Yellow + +# Install only the specific modules we need to avoid function capacity issues $requiredModules = @( "Microsoft.Graph.Authentication", - "Microsoft.Graph.Users", - "Microsoft.Graph.Users.Actions" + "Microsoft.Graph.Users" ) foreach ($module in $requiredModules) { - if (-not (Get-Module -ListAvailable -Name $module)) { - Write-Host "$module module is required but not installed. Attempting to install..." -ForegroundColor Yellow - try { + try { + # Check if module is installed + if (-not (Get-Module -ListAvailable -Name $module)) { + Write-Host "$module module is required but not installed. Attempting to install..." -ForegroundColor Yellow Install-Module $module -Scope CurrentUser -Force -ErrorAction Stop Write-Host "$module module installed successfully" -ForegroundColor Green + } else { + # Update to latest version to avoid dependency issues + Write-Host "Updating $module module to latest version..." -ForegroundColor Yellow + Update-Module $module -Force -ErrorAction SilentlyContinue + Write-Host "$module module is up to date" -ForegroundColor Green } - catch { - Write-Error "Failed to install $module module: $($_.Exception.Message)" - Write-Host "Please install it manually using: Install-Module $module -Scope CurrentUser" -ForegroundColor Red - exit 1 - } - } - - # Import the module - try { + + # Import the module Import-Module $module -Force -ErrorAction Stop Write-Host "$module module imported successfully" -ForegroundColor Green } catch { - Write-Error "Failed to import $module module: $($_.Exception.Message)" + Write-Error "Failed to setup $module module: $($_.Exception.Message)" + Write-Host "Please try installing manually: Install-Module $module -Scope CurrentUser -Force" -ForegroundColor Red exit 1 } } @@ -167,18 +200,18 @@ try { # Try authentication methods in order of preference Write-Host "Attempting authentication methods..." -ForegroundColor Cyan - # Method 1: Try integrated Windows authentication (best for domain-joined) + # Method 1: Try interactive browser authentication try { - Write-Host "Trying Integrated Windows Authentication..." -ForegroundColor Yellow - Connect-MgGraph -Scopes @('CustomSecAttributeAssignment.ReadWrite.All', 'User.ReadWrite.All') -UseDeviceCode -ErrorAction Stop - Write-Host "Successfully connected using device code flow" -ForegroundColor Green - } - catch { - # Method 2: Fall back to interactive browser - Write-Host "Device code flow failed, trying interactive browser..." -ForegroundColor Yellow + Write-Host "Trying interactive browser authentication" -ForegroundColor Yellow Connect-MgGraph -Scopes @('CustomSecAttributeAssignment.ReadWrite.All', 'User.ReadWrite.All') -ErrorAction Stop Write-Host "Successfully connected using interactive authentication" -ForegroundColor Green } + catch { + # Method 2: Fall back to device code authentication + Write-Host "Interactive browser authentication failed, trying device code flow..." -ForegroundColor Yellow + Connect-MgGraph -Scopes @('CustomSecAttributeAssignment.ReadWrite.All', 'User.ReadWrite.All') -UseDeviceCode -ErrorAction Stop + Write-Host "Successfully connected using device code flow" -ForegroundColor Green + } } } catch { @@ -216,11 +249,32 @@ Write-Host "Found $($usernames.Count) members in the group" -ForegroundColor Gre $enrollmentTokens = @() $entraUpdateResults = @() -# Loop through each user and generate enrollment token, then update Entra ID +# Loop through each username, fetch user details to get email, then generate enrollment token and update Entra ID foreach ($username in $usernames) { Write-Host "Processing user: $username" -ForegroundColor Cyan - # Generate enrollment token from Defguard + # Get user details from Defguard to fetch email + $userDetailsEndpoint = "api/v1/user/$username" + $userDetailsResponse = Invoke-AuthenticatedRestMethod -Method "GET" -Endpoint $userDetailsEndpoint + + if (-not $userDetailsResponse -or -not $userDetailsResponse.user -or -not $userDetailsResponse.user.email) { + Write-Host " Failed to fetch user details or email not found for user: $username" -ForegroundColor Red + $entraUpdateResults += @{ + username = $username + email = $null + entra_id = $null + user_principal_name = $null + success = $false + enrollment_token = $null + enrollment_url = $null + } + continue + } + + $userEmail = $userDetailsResponse.user.email + Write-Host " Found email: $userEmail" -ForegroundColor Green + + # Generate enrollment token from Defguard using username $enrollmentEndpoint = "api/v1/user/$username/start_enrollment" $requestBody = @{ email = $null @@ -232,9 +286,9 @@ foreach ($username in $usernames) { if ($enrollmentResponse) { Write-Host " Enrollment token generated for $username" -ForegroundColor Green - # Find user in Entra ID - Write-Host " Searching for user in Entra ID..." -ForegroundColor Yellow - $entraUser = Get-EntraIDUser -Username $username + # Find user in Entra ID by email + Write-Host " Searching for user in Entra ID by email..." -ForegroundColor Yellow + $entraUser = Get-EntraIDUser -Email $userEmail if ($entraUser) { Write-Host " Found user in Entra ID: $($entraUser.UserPrincipalName) (ID: $($entraUser.Id))" -ForegroundColor Green @@ -244,6 +298,7 @@ foreach ($username in $usernames) { $entraUpdateResults += @{ username = $username + email = $userEmail entra_id = $entraUser.Id user_principal_name = $entraUser.UserPrincipalName success = $updateResult @@ -252,9 +307,10 @@ foreach ($username in $usernames) { } } else { - Write-Host " User $username not found in Entra ID" -ForegroundColor Red + Write-Host " User with email $userEmail not found in Entra ID" -ForegroundColor Red $entraUpdateResults += @{ username = $username + email = $userEmail entra_id = $null user_principal_name = $null success = $false @@ -267,6 +323,7 @@ foreach ($username in $usernames) { Write-Host " Failed to generate enrollment token for $username" -ForegroundColor Red $entraUpdateResults += @{ username = $username + email = $userEmail entra_id = $null user_principal_name = $null success = $false