Just in time privilege's access to Azure DevOps
Published Jan 18 2022 11:39 AM 4,093 Views
Microsoft

To perform certain actions on Azure DevOps including, process template changes, extension import, build modification etc. requires user to have privilege's scopes at either organization or project level. In large enterprises where Azure DevOps organizations are centrally managed or managed via service accounts, enabling permanent privilege access for group of members posses lot of security risk. In scenario's where organizations are created using service account, require user to login via same to perform privilege actions. This posses a security risk of sharing credentials with multiple users and traceability of action performed.

 

Since there is no out of box solution available to enable just in time access with privilege role alike "Azure PIM", there is a custom approach can be used to enable just in time experience and control.

 

Technology involved:

  • PowerShell
  • Azure DevOps Api's
  • Azure SQL server
  • Azure DevOps pipeline

Approach:

  1. Write a script which contains steps to add user to Azure DevOps organization/project based on input
  2. Create a release pipeline(pipeline1) with pre-deployment gate enabled
  3. Store execution output in database
  4. Create another script which will be used to fetch data from database and remove privilege access enabled by pipeline1.
  5. Create another Azure DevOps pipeline(pipeline2) which will run every 10 min and execute script to remove user.

 

Steps:

1. Login to Azure DevOps using service account having owner access across Azure DevOps organization managed by team.

2. Create a personal access token with below scopes across "All accessible organization"

permissions: vso.graph_manage vso.project.

3. Create addUser.ps1 as below:

This script will add user to respective organization/project with required privilege role and enter the data within Azure Sql server.

 

 

 

Param
(

    [Parameter(Mandatory=$true,HelpMessage = "Org name for which access is requeseted")]
    [string][ValidateNotNullOrEmpty()] $OrgName,

    [Parameter(Mandatory=$true,HelpMessage = "Operation for which request is raised")]
    [string][ValidateNotNullOrEmpty()] $RequestType,

    [Parameter(Mandatory=$true,HelpMessage = "email address for which access should be provded")]
    $UserEmailAddress,

    [Parameter(Mandatory=$true,HelpMessage = "Application Insights details")]
    [string][ValidateNotNullOrEmpty()] $LoggingTelemetryID,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $PAT,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLPassword,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLUserID,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLDataBase,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLServer,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $UserDescriptorOrganization,

    [Parameter(Mandatory=$false,HelpMessage = "Project name for which access is requeseted")]
    [string] $ProjectName

)

#####Creating session id####
try
{
    $sessionId = [guid]::NewGuid().GUID
}
catch
{
    Write-Error "Failed to create session id"
    Write-Output $_.Exception.Message
}

###Initializing telemetry client###
try
{
    $AppInsightsdllPath = "$PSScriptRoot\Microsoft.ApplicationInsights.dll"
    [Reflection.Assembly]::LoadFile($AppInsightsdllPath)   
    $TelClient = New-Object "Microsoft.ApplicationInsights.TelemetryClient"
    $TelClient.InstrumentationKey = $LoggingTelemetryID
    $credentials = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$PAT"))
    $SQLRetryCount = 0 
}
catch
{
    Write-Error "Failed to initialize ApplicationInsights"
    Write-Output $_.Exception.Message
}

####Function to get user descriptor which will be used in adding function####
function Get_User_Descriptor
{
    try
    {
        $ContinuationToken=$null
        do
        {
            ## Set communication protol to TLS1.2
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            $usersList= Invoke-webrequest -Method Get -Uri "https://vssps.dev.azure.com/$($UserDescriptorOrganization)/_apis/graph/users?api-version=6.0-preview.1&subjectTypes=aad&continuationToken=$($ContinuationToken)" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing
            $APIContents = $usersList.Content | ConvertFrom-Json 
            foreach($user in $APIContents.value)
            {
                if($user.principalName -eq $UserEmailAddress)
                {          
                    return $user.descriptor
                }
            }
            $ContinuationToken = $usersList.Headers.'x-ms-continuationtoken'
            } while ($ContinuationToken -ne $null)
        }


    catch
    {

        Write-Error "Failed to get user descriptor"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    }
}

#### Function to add user to Org####

function Add_Users
{
    param (
        [string]$UserDescriptor
    )

    try
    {
        $checkUser= Invoke-webrequest -Method Get -Uri "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/users/$($UserDescriptor)?api-version=6.0-preview.1" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing     
    }
    catch
    {
        if($_.Exception.Message -match "(401)")
        {
            Write-Error "gdbldsvc don't have access to ADO $($OrgName)"
            Write-Output $_.Exception.Message
            $TelClient.TrackException($_.Exception)
            $TelClient.Flush()            
        }

        elseif($_.Exception.Message -match "(404)")
        {
            try
            {
                $UserBody = '{"principalName":'+'"'+$UserEmailAddress+'"}'
                $addUser= Invoke-webrequest -Method POST -Body $UserBody -Uri "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/users/$($UserDescriptor)?api-version=6.0-preview.1" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing
                return $UserDescriptor
            }
            catch
            {
                Write-Error "Failed to add user to ADO"
                Write-Output $_.Exception.Message
                $TelClient.TrackException($_.Exception)
                $TelClient.Flush()
            }
        }
        else
        {
            Write-Error "Failed to add users to ADO $($OrgName)"
            Write-Output $_.Exception.Message
            $TelClient.TrackException($_.Exception)
            $TelClient.Flush()
        }
        
    }
}

#### Function to add user to organization####
function Add_UsersTo_Org_PCA
{
    param (
        [string]$OrganizationName
    )

    try
    {
        $userDescriptor = Get_User_Descriptor
        Add_Users -UserDescriptor $userDescriptor
        do
        {

            ## Set communication protol to TLS1.2
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            $grouplist= Invoke-webrequest -Method Get -Uri "https://vssps.dev.azure.com/$($OrganizationName)/_apis/graph/groups?api-version=6.0-preview.1&continuationToken=$($ContinuationToken)" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing
            $ContinuationToken = $grouplist.Headers.'x-ms-continuationtoken'
            $APIContents = $grouplist.Content | ConvertFrom-Json 
            foreach($group in $APIContents.value)
            {
                try
                {
                    if("[$OrgName]\Project Collection Administrators" -eq $group.principalName)
                    {            
                        $groupdescriptor = $group.descriptor
                        $url = "https://vssps.dev.azure.com/$($OrganizationName)/_apis/graph/memberships/$($userDescriptor)/$($groupdescriptor)?api-version=6.0-preview.1"
                        ## Set communication protol to TLS1.2
                        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                        $result= Invoke-RestMethod -Method PUT -Uri $url -ContentType 'application/json' -Headers @{Authorization = $credentials } -ErrorAction Continue -UseBasicParsing
                        Add_Roleassignment_database -Roleassignment $group.principalName
                        $TelClient.TrackEvent("Added $($UserEmailAddress) to $($group.principalName)")
                        $TelClient.Flush()
                        $ContinuationToken=$null 
                        break
                    }
                }
                catch
                {
                    Write-Error "Failed to users as Project Collection Administrators"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()

            }
        }
        } while ($null -ne $ContinuationToken)

        
    }
    catch
    {
        Write-Error "Failed to get group details for the organization"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
        
    }
}

####Function to add user to project####
function Add_UsersTo_Project
{
    param (
        [string]$OrganizationName,
        [string]$ProjectName

    )

    $userDescriptor = Get_User_Descriptor
    Add_Users -UserDescriptor $userDescriptor 
    try
    {
        ## Set communication protol to TLS1.2
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $projectList = Invoke-RestMethod -Uri "https://dev.azure.com/$($OrganizationName)/_apis/projects?`$top=999&api-version=6.0" -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json' 
        foreach($project in $projectList.value)
        {
            if($ProjectName -eq $project.name)
            {
                try
                {
                    ## Set communication protol to TLS1.2
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    $projectDetails = Invoke-RestMethod -Uri "https://vssps.dev.azure.com/$($OrganizationName)/_apis/graph/descriptors/$($project.id)?api-version=5.0-preview.1" -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json' 
                    $GroupsUrl = "https://vssps.dev.azure.com/$($OrganizationName)/_apis/graph/groups?scopeDescriptor=$($projectDetails.value)&api-version=6.0-preview.1" 
                    ## Set communication protol to TLS1.2
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    $grouplist= Invoke-RestMethod -Uri $GroupsUrl -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json'
                }
                catch
                {
                    Write-Error "Failed to get group details  for project"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()
                }
                try
                {
                    foreach($group in $grouplist.value)
                    {
                        if(("[$($project.name)]\Build Administrators" -eq $group.principalName) -or ("[$($project.name)]\Endpoint Administrators" -eq $group.principalName))
                        {
                            $groupdescriptor = $group.descriptor
                            $url = "https://vssps.dev.azure.com/$($OrganizationName)/_apis/graph/memberships/$($userDescriptor)/$($groupdescriptor)?api-version=6.0-preview.1"
                            ## Set communication protol to TLS1.2
                            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                            $result= Invoke-RestMethod -Method PUT -Uri $url -ContentType 'application/json' -Headers @{Authorization = $credentials } -ErrorAction Continue
                            Add_Roleassignment_database -Roleassignment $group.principalName
                            $TelClient.TrackEvent("Added $($UserEmailAddress) to $($group.principalName)")
                            $TelClient.Flush() 
                        }
                    }
                    break
                }
                catch
                {
                    Write-Error "Failed to add user to the project"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()
                }
            }
        }
    }
    catch
    {

            Write-Error "Failed to get project details for organization"
            Write-Output $_.Exception.Message
            $TelClient.TrackException($_.Exception)
            $TelClient.Flush()
    }
}

####Function to log activity to sql database####
function Add_Roleassignment_database
{
    param(

        [string] $Roleassignment
    )

        try
        {              
            $SQLConnectionString = "Server=tcp:$($SQLServer),1433;Initial Catalog=$($SQLDataBase);Persist Security Info=False;User ID=$($SQLUserID);Password=$($SQLPassword);MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
            $sqlCommand = New-Object System.Data.SqlClient.SqlCommand
            $sqlCommand.Connection = $SQLConnectionString
            [DateTime] $date= Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            ## SQL Query
            $sqlCommand.CommandText = "SET NOCOUNT ON; " +
                "INSERT INTO dbo.ADOAccessLogs (SessionID,RequesterEmailAddress,OrganizationName,ProjectName,RequestType,AccessInitiationTime,IsActive, RoleType) " +
                "VALUES ('$sessionId','$UserEmailAddress','$OrgName','$ProjectName','$RequestType','$($date.ToUniversalTime())',1,'$Roleassignment')";
            # Run the query and get the scope ID back into $InsertedID
            $sqlCommand.Connection.Open()
            $sqlCommand.ExecuteScalar()
            $sqlCommand.Connection.Close()
        }
        catch
        {
            Write-Error "Failed to insert access logs to Database"
            Write-Output $_.Exception.Message
            $TelClient.TrackException($_.Exception)
            $TelClient.Flush()
            $SQLRetryCount++
            if($SQLRetryCount -lt 6)
            {
                Add_Roleassignment_database -Roleassignment $Roleassignment
            }
        }        
    
}


####Request type validation####
if( ($RequestType -eq "Process_Template_Change") -or ($RequestType -eq "Project_Creation"))
{
    try
    {
        ###Adding user to organization###
        Add_UsersTo_Org_PCA -OrganizationName $OrgName
        $TelClient.TrackEvent("Added $($UserEmailAddress) to $($OrgName) as Project Collection Administrators")
        $TelClient.Flush()
    }
    catch
    {
        Write-Error "Failed to users as Project Collection Administrators"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    }
}
elseif( ($RequestType -ieq "Build_troubleshooting") -or ($RequestType -eq "Build_creation" ))
{
    if($ProjectName -eq $null)
    {
        Write-Error "Please provide Project details"
        $TelClient.TrackException("Project details is missing")
        $TelClient.Flush()
        break
    }
    try
    {
        ###Adding user to project###
        Add_UsersTo_Project -OrganizationName $OrgName -ProjectName $ProjectName
        $TelClient.TrackEvent("Added $($UserEmailAddress) to $($ProjectName)")
        $TelClient.Flush() 
    }
    catch
    {
        Write-Error "Failed while provding Build Administrator Permission"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    }
}
else
{
    Write-Output "Please provide valid justification for ADO access"
    $TelClient.TrackException("Invalid justification provided for ADO access, email address : $($UserEmailAddress)" )
    $TelClient.Flush()
}

 

 

 

 

 

4. Create a Azure Sql server and database. Reference: https://docs.microsoft.com/en-us/azure/azure-sql/database/single-database-create-quickstart?tabs=azu...

5. Create a application insight resource. Reference: https://docs.microsoft.com/en-us/azure/azure-monitor/app/create-new-resource 

6. Store below credentials in Azure key vault. Reference: https://docs.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal

Secret: serviceAccountPAT, Value: Personal access token created at step 1

Secret: applicationInsightKey, Value: Instrumentation key created at step 5

Secret: SqlDatabaseName, Value: Sql details as created at step 4

Secret: SqlServerName, Value: Sql details as created at step 4

Secret: SqlAdminUserName, Value: Sql details as created at step 4

Secret: SqlAdminPassword, , Value: Sql details as created at step 4

 

7. Create a variable group in Azure DevOps and link all the secrets created at step 6 as variables. Reference: https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups?view=azure-devops&ta...

8. Create a release pipeline on Azure DevOps with pre-deployment enabled. This pre-deployment will be used to approve or deny request by administrator. Reference: https://docs.microsoft.com/en-us/learn/modules/create-release-pipeline/

9. Use below task in release pipeline to execute addUser.ps1 script

 

 

 

variables:
  UserDescriptorOrganization: '$(OrganizationName)'

steps:
- task: PowerShell@2
  displayName: 'PowerShell Script'
  inputs:
    targetType: filePath
    filePath: './$(System.DefaultWorkingDirectory)/_azureDevOpsJitAccess/addUser.ps1'
    arguments: '-OrgName $(OrganizationName) -RequestType $(RequestType) -UserEmailAddress $(EmailAddress) -LoggingTelemetryID $(applicationInsightKey) -PAT $(serviceAccountPAT) -ProjectName $(Project) -SQLPassword $(SqlAdminPassword) -SQLUserID $(SqlAdminUserName) -SQLDataBase $(SqlDatabaseName) -SQLServer $(SqlServerName) -UserDescriptorOrganization $(UserDescriptorOrganization)'

 

 

 

8. Add below variables as pipeline variable which will be used by user or executor to pass runtime variable. 

EmailAddress - This contain email id of user  whom just in time privilege access need to be enabled.

OrganizationName - Organization name where user is seeking privilege role.

Project - Project name where user is seeking access.

RequestType - This contain activity type which user want to perform and for which access is requested. Script as part of step 3, will check request type and based on that provide user with required permission.

UserDescriptorOrganization - This contain organization name where user is currently part of to extract user descriptor and accordingly add in destination/requested organization.

 

9. Create a script removeUser.ps1 to remove user after certain time. Below script will remove all users having permission more than 30min.

Note: Below script will read the database and fetch all users having access longer than 30 min and remove the same. To validate if user is removed, it will update IsActive column as 0 from 1.

 

 

Param
(

    [Parameter(Mandatory=$true,HelpMessage = "Application Insights details")]
    [string][ValidateNotNullOrEmpty()] $LoggingTelemetryID,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $PAT,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLPassword,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLUserID,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLDataBase,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $SQLServer,

    [Parameter(Mandatory=$true)]
    [string][ValidateNotNullOrEmpty()] $UserDescriptorOrganization

)

###Initializing telemetry client and Varibales###
try
{
    $AppInsightsdllPath = "$PSScriptRoot\Microsoft.ApplicationInsights.dll"
    [Reflection.Assembly]::LoadFile($AppInsightsdllPath)   
    $TelClient = New-Object "Microsoft.ApplicationInsights.TelemetryClient"
    $TelClient.InstrumentationKey = $LoggingTelemetryID
    $credentials = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$PAT"))
    $SQLConnectionString = "Server=tcp:$($SQLServer),1433;Initial Catalog=$($SQLDataBase);Persist Security Info=False;User ID=$($SQLUserID);Password=$($SQLPassword);MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
catch
{
    Write-Error "Failed to initialize ApplicationInsights"
    Write-Output $_.Exception.Message
}

####Get past 30 min active role data####
function Get-ActiveAssignment-Database
{
    try
    {   
        [DateTime] $date= Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $query = "SELECT * FROM ADOAccessLogs where IsActive = 1 and AccessInitiationTime < '$($date.AddMinutes(-30).ToUniversalTime())'"
        $Datatable = New-Object System.Data.DataTable
        $command = New-Object System.Data.SqlClient.SqlCommand
        $command.Connection = $SQLConnectionString
        $command.CommandText = $query
        $command.Connection.Open()
        $results = $command.ExecuteReader()
        $Datatable.Load($results)
        $command.Connection.Close()
        return $Datatable;

    }

    ####Catch SQL Exception####
    catch
    {
        Write-Error "Failed to get access logs from Database"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()   
    }
}

####Function to get user descriptor which will be removed####
function Get-User-Descriptor
{
    param(
        
        [string] $UserEmailAddress
    )
    try
    {
        $ContinuationToken=$null
        do{

            ## Set communication protol to TLS1.2
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            $usersList= Invoke-webrequest -Method Get -Uri "https://vssps.dev.azure.com/$($UserDescriptorOrganization)/_apis/graph/users?api-version=6.0-preview.1&subjectTypes=aad&continuationToken=$($ContinuationToken)" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing
            $APIContents = $usersList.Content | ConvertFrom-Json 
            foreach($user in $APIContents.value)
            {
                if($user.principalName -eq $UserEmailAddress)
                {                
                    return $user.descriptor
                }
            }
            $ContinuationToken = $usersList.Headers.'x-ms-continuationtoken'
            } while ($null -ne $ContinuationToken)
        }
    catch
    {
        Write-Error "Failed to get group details for the organization"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    }
}


function Remove-AccessFor-Users
{
    param (
        [object]$ActiveAssignments
    )

    try
    {
        if($null -eq $ActiveAssignments)
        {
            Write-Output "No Active Role Assignments Found"
            $TelClient.TrackEvent("No Active Role Assignments Found")
            $TelClient.Flush()
            break            
        }

        foreach($ActiveAssignment in $ActiveAssignments)
        {
            if($ActiveAssignment.RoleType -match "Project Collection Administrators")
            {
                Remove-Access-From-Org -EmailAddress $ActiveAssignment.RequesterEmailAddress -OrgName $ActiveAssignment.OrganizationName -SessionID $ActiveAssignment.SessionID -RoleType $ActiveAssignment.RoleType
            }

            elseif(($ActiveAssignment.RoleType -match "Build Administrators") -or ($ActiveAssignment.RoleType -match "Endpoint Administrators") )
            {
                Remove-Access-From-Project -EmailAddress $ActiveAssignment.RequesterEmailAddress -OrgName $ActiveAssignment.OrganizationName -SessionID $ActiveAssignment.SessionID -RoleType $ActiveAssignment.RoleType -ProjectName $ActiveAssignment.ProjectName
            }

            else
            {
                Write-Error "Invaid RoleType : $($ActiveAssignment.RoleType)"
                $TelClient.TrackException("Invaid RoleType : $($ActiveAssignment.RoleType)")
                $TelClient.Flush()
                
            }
        }
    }

    catch
    {
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
        
    }


}
function Remove-Access-From-Org 
{
    param (
        [string]$EmailAddress,
        [String]$OrgName,
        [String]$SessionID,
        [String]$RoleType
    )

    try
    {
        $userDescriptor= Get-User-Descriptor -UserEmailAddress $EmailAddress
        do
        {

            ## Set communication protol to TLS1.2
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            $grouplist= Invoke-webrequest -Method Get -Uri "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/groups?api-version=6.0-preview.1&continuationToken=$($ContinuationToken)" -Headers @{Authorization = $credentials } -ContentType 'application/json' -UseBasicParsing
            $ContinuationToken = $grouplist.Headers.'x-ms-continuationtoken'
            $APIContents = $grouplist.Content | ConvertFrom-Json 
            foreach($group in $APIContents.value)
            {
                try
                {
                    if("[$OrgName]\Project Collection Administrators" -eq $group.principalName)
                    {            
                        $groupdescriptor = $group.descriptor
                        $url = "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/memberships/$($userDescriptor)/$($groupdescriptor)?api-version=6.0-preview.1"

                        ## Set communication protol to TLS1.2
                        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                        Invoke-RestMethod -Method DELETE -Uri $url -ContentType 'application/json' -Headers @{Authorization = $credentials } -ErrorAction Continue -UseBasicParsing
                        Update_Roleassignment_database -Email $EmailAddress -SessionID $SessionID -RoleType $RoleType
                        $TelClient.TrackEvent("Added $($UserEmailAddress) to $($group.principalName)")
                        $TelClient.Flush()
                        $ContinuationToken=$null 
                        break
                    }
                }
                catch
                {
                    Write-Error "Failed to remove user $($EmailAddress) from   Project Collection Administrators"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()
            }
        }
        } while ($null -ne $ContinuationToken)

    }
    catch
    {
        Write-Error "Failed to get group details from Org : $(OrgName)"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    
    }
} 

function Remove-Access-From-Project
{
    param (
        [string]$EmailAddress,
        [String]$OrgName,
        [String]$ProjectName,
        [String]$SessionID,
        [String]$RoleType
    )

    $userDescriptor = $userDescriptor= Get-User-Descriptor -UserEmailAddress $EmailAddress
    try
    {
        ## Set communication protol to TLS1.2
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $projectList = Invoke-RestMethod -Uri "https://dev.azure.com/$($OrgName)/_apis/projects?`$top=999&api-version=6.0" -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json'  
        foreach($project in $projectList.value)
        {
            if($ProjectName -eq $project.name)
            {
                try
                {
                    ## Set communication protol to TLS1.2
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    $projectDetails = Invoke-RestMethod -Uri "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/descriptors/$($project.id)?api-version=5.0-preview.1" -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json' 
                    $GroupsUrl = "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/groups?scopeDescriptor=$($projectDetails.value)&api-version=6.0-preview.1" 

                    ## Set communication protol to TLS1.2
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    $grouplist= Invoke-RestMethod -Uri $GroupsUrl -Headers @{Authorization = $credentials } -Method Get -ContentType 'application/json'
                }
                catch
                {
                    Write-Error "Failed to get group details  for project"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()
                }
                try
                {
                    foreach($group in $grouplist.value)
                    {
                        if(($RoleType -eq $group.principalName) -or ($RoleType -eq $group.principalName))
                        {
                            $groupdescriptor = $group.descriptor
                            $url = "https://vssps.dev.azure.com/$($OrgName)/_apis/graph/memberships/$($userDescriptor)/$($groupdescriptor)?api-version=6.0-preview.1"

                            ## Set communication protol to TLS1.2
                            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                            Invoke-RestMethod -Method DELETE -Uri $url -ContentType 'application/json' -Headers @{Authorization = $credentials } -ErrorAction Continue
                            $TelClient.TrackEvent("Removed $($UserEmailAddress) from  $($group.principalName)")
                            $TelClient.Flush() 

                            ## Updated status in DataBase
                            Update_Roleassignment_database -Email $EmailAddress -SessionID $SessionID -RoleType $RoleType

                        }
                    }
                    break
                }
                catch
                {
                    Write-Error "Failed to Delete user $($EmailAddress) from  the project : $($ProjectName) Group : $($RoleType)"
                    Write-Output $_.Exception.Message
                    $TelClient.TrackException($_.Exception)
                    $TelClient.Flush()
                }
            }
        }
    }
    catch
    {
        Write-Error "Failed to get project details for organization"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
    }
}

function Update_Roleassignment_database
{
    param (
        [string]$Email,
        [String]$SessionID,
        [String]$RoleType
    )

    try
    {
        $query = "UPDATE  ADOAccessLogs SET IsActive = 0 where SessionID='$($SessionID)' AND RoleType='$($RoleType)'"
        $command = New-Object System.Data.SqlClient.SqlCommand
        $command.Connection = $SQLConnectionString
        $command.CommandText = $query
        $command.Connection.Open()
        $command.ExecuteReader()
        $command.Connection.Close()
    }

    catch
    {
        Write-Error "Failed to update active status for User : $($Email) SessionID : $($SessionID)"
        Write-Output $_.Exception.Message
        $TelClient.TrackException($_.Exception)
        $TelClient.Flush()
        
    }

}
$ActiveUsers = $null
$ActiveUsers = Get-ActiveAssignment-Database | Sort-Object ID -Unique
$ActiveUsers
Remove-AccessFor-Users -ActiveAssignments $ActiveUsers

 

 

 

10. Create a build pipeline using below YAML. This will require variable group to be linked, created as part of step 7

 

 

schedules:
- cron: "*/10 * * * *"
  displayName: Daily build for every 30 min
  branches:
    include:
    - master
  always: true


pool:
  Image: 'windows-2019'


steps:
- task: PowerShell@2
  displayName: 'PowerShell Script'
  inputs:
    filePath: './removeUser.ps1'
    arguments: '-LoggingTelemetryID $(applicationInsightKey) -PAT $(serviceAccountPAT) -SQLPassword $(SqlAdminPassword) -SQLUserID $(SqlAdminUserName) -SQLDataBase $(SqlDatabaseName) -SQLServer $(SqlServerName) -UserDescriptorOrganization $(UserDescriptorOrganization)'

 

 

 

 

 

Version history
Last update:
‎Jan 17 2022 05:14 AM
Updated by: