External IDP and Milestone Federated Architecture
Milestone XProtect 2023 R3 supports a Milestone Federated Architecture setup with an option to use an external IDP to log in from sites within the federated hierarchy.
Current Scope
The current functionality allows users of XProtect Smart Client to log in from a federated site via an external IDP.
Prerequisites
- XProtect Smart Client and the VMS on the main and sub-sites must be version 2024 R2.
- Communication between site management servers still uses Windows AD.
Requirements
Connectivity
To enable login on all levels in the hierarchy, a site needs to be able to connect to all its parents.
Trusted Sites
To log in from a parent site in Milestone Federated Architecture with an external IDP user, the child site must be set up to trust the parent. The trusted sites constitute the collection of sites that the current site trusts for login to the federated hierarchy. See “Set up trusted sites” on how to set up the trust.
Certificate Setup
For a secure VMS setup, the child site must trust the certificate configured on the parent site. For more information about certificates, see the Milestone Certificates Guide.
External Provider Naming
To be able to map between sites, the display name in the configuration of the external IDP must be the same across each site in the federated hierarchy.
User Provisioning
When a user logs in to the federated hierarchy via an external IDP, the user will automatically be provisioned on each site in the hierarchy. To align usernames across sites, the external provider should be set up to resolve usernames in the same manner.
Set Up Trusted Sites
Trusted sites can also be scripted using following PowerShell script.
# Global variables
$server_url = "https://<machine-fqdn>"
$token_url = "$server_url/idp/connect/token"
function Get-AccessToken {
param (
[string]$grant_type,
[string]$scope,
[string]$client_id,
[string]$client_secret = ""
)
$body = @{
grant_type = $grant_type
scope = $scope
client_id = $client_id
}
if ($client_secret) {
$body.client_secret = $client_secret
}
$response = Invoke-WebRequest -Uri $token_url -Method Post -Body $body -UseDefaultCredentials:($grant_type -eq "windows_auth")
$accessToken = ($response.Content | ConvertFrom-Json).access_token
return $accessToken
}
function Invoke-ApiRequest {
param (
[string]$method,
[string]$endpoint,
[string]$accessToken,
[object]$body = $null
)
$headers = @{
Authorization = "Bearer $accessToken"
}
$params = @{
Method = $method
Uri = "$server_url/api/idp/api/$endpoint"
Headers = $headers
ContentType = "application/json"
}
if ($body) {
$params.Body = (ConvertTo-Json $body)
}
$response = Invoke-RestMethod @params
return $response
}
function Get-RegisteredClients {
param ([string]$accessToken)
return Invoke-ApiRequest -method Get -endpoint "clients" -accessToken $accessToken
}
function Get-ClientsWithScope {
param (
[string]$accessToken,
[string]$scope
)
$clients = Get-RegisteredClients -accessToken $accessToken
return $clients | Where-Object { $_.clientScopes -eq $scope }
}
function Add-UpdateClient {
param (
[string]$accessToken,
[string]$clientId,
[string]$clientName,
[string[]]$clientScopes,
[string[]]$clientGrantTypes
)
$clientData = @{
ClientName = $clientName
ClientScopes = $clientScopes
ClientGrantTypes = $clientGrantTypes
}
return Invoke-ApiRequest -method Put -endpoint "clients/$clientId" -accessToken $accessToken -body $clientData
}
function Get-TrustedIssuers {
param ([string]$accessToken)
return Invoke-ApiRequest -method Get -endpoint "trustedIssuers" -accessToken $accessToken
}
function Add-TrustedIssuer {
param (
[string]$accessToken,
[string]$address,
[string]$issuer
)
$data = @{
Address = $address
Issuer = $issuer
}
return Invoke-ApiRequest -method Post -endpoint "trustedIssuers" -accessToken $accessToken -body $data
}
function Delete-TrustedIssuer {
param (
[string]$accessToken,
[string]$id
)
return Invoke-ApiRequest -method Delete -endpoint "trustedIssuers/$id" -accessToken $accessToken
}
function Add-Trust
{
param (
[string]$accessToken
)
$address = Read-Host 'what is the trusted issue address (parent site)? (the address is case sensitive)'
$trustedIssuer = Add-TrustedIssuer -accessToken $accessToken -address $address -issuer $address
""
"Trusted issuer added:"
$trustedIssuer | Format-List
}
function Delete-Trust
{
param (
[string]$accessToken
)
# Get input
$trustedIssuers = Get-TrustedIssuers -accessToken $accessToken
""
"Trusted issuers"
$trustedIssuers | Format-List
$id = Read-Host 'choose which issuer to delete. Specify ID'
Delete-TrustedIssuer -accessToken $accessToken -id $id
}
# Execution
$stop = 1
while($stop -eq 1)
{
# Get input
$operation = Read-Host "Add(0), delete(1) or list(2) trusted issuer(s)"
# Get access token for initial operations
$initialAccessToken = Get-AccessToken -grant_type "windows_auth" -scope "write:client" -client_id "winauthclient"
# Get list of registered clients
$registeredClients = Get-RegisteredClients -accessToken $initialAccessToken
#$registeredClients | Format-List
# Get clients with IDP management scope
$idpManagementClients = Get-ClientsWithScope -accessToken $initialAccessToken -scope "idp_management"
#$idpManagementClients | Format-List
# Add a client with IDP management scopes
$newClient = Add-UpdateClient -accessToken $initialAccessToken -clientId "TrustedIssuerClient" -clientName "TrustedIssuerClient" -clientScopes @("idp_management") -clientGrantTypes @("client_credentials")
#$newClient.data | Format-List
# Get access token for the test client and Add or Delete trust
$accessToken = Get-AccessToken -grant_type "client_credentials" -client_id "TrustedIssuerClient" -client_secret $newClient.data.secret
if($operation -eq 0)
{
Add-Trust -accessToken $accessToken
}
if($operation -eq 1)
{
Delete-Trust -accessToken $accessToken
}
# Get all trusted issuers
$trustedIssuers = Get-TrustedIssuers -accessToken $accessToken
""
"All trusted issuers"
$trustedIssuers | Format-List
$stop = Read-Host "Would you like to do more (0/1)"
}
Trusted Issuer Fields: Issuer and Address
A trusted issuer has two fields: "Issuer" and "Address".
- Issuer: The Issuer field must correlate with the issuer claim set by the VMS IDP when creating an access token. The VMS IDP uses its configured authority string to set the issuer claim. In a standard VMS installation, the IDP Authority is the address of the IDP defined by the FQID appended with "/IDP". To validate the IDP Authority, it can be looked up in appsettings.json in the location of the installed IDP. The Issuer field must match the issuer claim exactly for the trust to be valid.
- Address: The address should be in Uri format and should be the address of the IDP at the trusted site.