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

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".