Azure DevOps, Tips & Tricks

Enterprise-level Azure DevOps permissions from the trenches

Or how to implement role-based access control (RBAC) in Azure DevOps in enterprise environments and still keep it maintainable. 4 Antipatterns and an approach on how to implement this yourself!

Introduction and key values

Assigning permissions to users and groups of users in Azure DevOps in small companies, maybe up to about 25-50 employees is easy and straightforward. However, at a large scale one needs to think carefully about how to approach this. At a medium-sized customer (about 250 users) I had to redesign the permissions structure in Azure DevOps. Their most important requirement was to be able to manage access control through Microsoft Identity Manager (MIM – https://docs.microsoft.com/en-us/microsoft-identity-manager/). Microsoft MIM is put in place to implement role-based access control (RBAC – https://en.wikipedia.org/wiki/Role-based_access_control). The idea behind their implementation is that team leads can approve access to systems (self-service), instead of a support team. This access was predefined by the system administrators by setting up MIM roles and (Azure) Active Directory (AAD) groups. By using MIM roles that are linked to AAD groups, it is no longer necessary to assign permissions to individual users, as adding them to a group is all that is needed.

This article is not about how to setup RBAC itself, but rather how to facilitate RBAC in Azure DevOps.

Important note: I assume you have sufficient knowledge and experience to make choices about changes in permissions in Azure DevOps. A useful reference to understand permissions is this one: https://docs.microsoft.com/en-us/azure/devops/organizations/security/permissions-access?view=azure-devops

Approach

Now let’s continue with the approach;

I set up the following key values as a basis for the implementation:

  • Maintainability – Frequent changes should be as effortless as possible
  • Visibility – One should be able to easily see what specific permissions a person has
  • Integration – The setup should integrate with MIM
  • Transparency – People have access to all resources within Azure DevOps, unless there is a strong reason to restrict access. This applies mainly to engineers (this is against the principle of least amount of privileges, but if possible, I value transparency over least amount of privileges if possible)

The key values are implemented by using the following guidelines:

  • Do not use “Deny” permissions
  • Permissions are always applied using (A)AD or Azure DevOps groups, never on an individual user. This also applies to membership of Team Project level groups
  • Membership of any “Administrators” group (Project Administrators, Endpoint Administrators, Build Administrators, Release Administrators etc) is restricted to the team that manages Azure DevOps. The issue is that these groups give permissions to change permissions 
  • Keep it organized and clean; if a team ceases to exist, discuss with the team about what can be:
    • removed (backlogs, areas, iterations, pipelines, repos)
    • Archived (backlogs)
    • Transferred to another team
  • If the organization uses separate accounts for system administration, users should not be able to access Azure DevOps using these accounts. It’s not what they’re made for. Also, it would result in additional license cost. The only exception should/can be the team that manages Azure DevOps, so they can separate concerns
  • Unified names for Azure DevOps and (A)AD groups throughout the system
  • Default Azure DevOps groups maintain out-of-the box permissions, these should not be changed in any way. If custom permissions are required, then create another group

Implementation

Implementing security structures can be cumbersome and can be part of lengthy processes. Therefor I think it is good to approach this as agile as possible, not to slow down the progress any more than necessary.

Building the structure of groups in AD and MIM takes time, so I figured I would (1) create the authorization groups (that actually reflect a future role in MIM) in Azure DevOps at Collection/Organization level. Then (2) put users in those groups and once the AD/MIM groups are ready, I would be able to (3) replace the individual users by the groups.

It also helps to first (1) create the new permissions group, then (2) hand out the permissions to that group and then (3) fill the group with other groups or individuals (which is an intermediate step before the actual MIM roles are available). After that, (4) the old permissions can be removed.

Before we move to the actual steps and examples, let’s look at some antipatterns.

Antipattern 1 – Permissions assigned directly to individuals

Permission assigned directly to an individual

When very specific permissions are assigned to an individual in a large environment, it’s very hard to trace. There is no central place where one can see these permissions. An example is the Force push permission in a Git repo. It is a permission often requested and implemented by giving an individual the permission on a specific repo. This permission can only be traced back by going into the Repositories settings, navigating to the specific repo and then checking the security settings. Not an easy task in an environment where a project contains 100+ repos…

Instead, create a group at Team Project level or Organization level, assign permissions to that group and make other groups/people member of this group.

Step 1: create an Organization-level group and add members;

Create an Organization-level group

Step 2: Create Project-level group and make Organization-level group member of it;

Create a Project-level group

Step 3: Assign permissions to Project-level group (the group will not appear by default; search for it using the search field);

Assign permissions to group

Antipattern 2 – Using teams in cases where groups can be used

Team used instead of group

Generally, when creating a team, an Area is created as well as Boards, Team-specific settings and Dashboard(s). If one is creating a Team to host a group of people that only share (a) specific permission(s), then that is quite some overhead. Use a Group instead of a Team. A Team is only required when a group of people work together using Azure Boards. If no Azure Boards are required, create a Group instead.

Antipattern 3 – Changing the permissions of default groups

Changing default group permissions

Every Azure DevOps Organization has default groups (see reference at the bottom of this post), every Team Project created has default groups as well. Changing the permissions of these groups causes confusion for co-workers. People expect default groups to behave in a default way. If you need different behavior, create a new group specifically for that behavior. Check the solution to the first antipattern “Permissions assigned directly to individuals”, which also solves this antipattern.

Antipattern 4 – Abuse of administrator groups at project level

Membership of an Administrators group

To create an Azure Pipeline that uses a certain Agent Pool, one needs permissions to use that Agent Pool, otherwise the Pipeline cannot be used. To give the permissions, don’t put people in the Build or Release Administrators Group! Why not? Because those people will get permission to change permissions, which might potentially mess up your RBAC model.

Administrative group membership gives permissions modification grant

Instead, create a separate group to which the User role is assigned in that specific Agent Pool, or multiple Agent Pools. This can only be done at Team Project level, not at Organization-level (the User role is not available there).

Step 1: Create Project-level group and add the Team Project Contributors group to this permissions group

Create new group

Step 2: Give the User role to the custom permissions group for all Agent Pools

Make member of the user role

Implementation strategy

Before starting the implementation, it’s important to think about the strategy. The strategy depends completely on the situation the company is in. In my case, I was able to implement it in small phases, enabling myself to change course/direction when solutions I chose were not working as expected. Also I was able to do the process in two steps:

  1. Put users in (Organization-level) Azure DevOps Groups
  2. Replace those users with the new (A)AD/MIM groups when they are ready

When setting up your own strategy, it helped me to keep these items in mind:

  • Communicate to the users about changes you are making, it enables them to get in touch whenever permission changes impact their work
  • Short improvement cycles over long projects with big-bang implementations
  • Stay in touch regularly with the team responsible for MIM and (A)AD and check with them what their requirements are
  • Make changes in such a way, that they are easy to rollback

Naming conventions

It helps when the Groups in Azure DevOps conform to a naming convention. It makes them easy to recognize. Of course everyone is free to determine their own conventions, but these are the conventions I used:

LevelConventionExampleExplanation
Organization[Companyname] – [Group]Contoso – EngineersThese groups replicate the MIM groups, they describe a role, e.g. Engineer, or Scrum Master
Organization[Companyname] Serviceaccount [Servicename]Contoso Serviceaccount SonarQubeServiceaccounts sometimes have cryptic names, putting them in groups clarifies the use
Team Project or Organization[Companyname] Teamleads [Team name]Contoso Teamleads JedisTeamleads have more permissions (e.g. sprint administration), they are member of the Team Admins role
Team Project or OrganizationCustom Release Approval Group – [Teamname or other spec]Custom Release Approval Group – Jedis CD to ProdApprovals can be setup in pipelines. When adding individual approvers, maintenance is decentralized. Using a group is much easier.
Team ProjectCustom Permissions – [Area] – [Permission description]Custom Permissions – Repos – Force PushCertain actions (in this case Git Force Push) require special permissions not available in the default groups except for the Admin groups.
Team ProjectCustom Permissions – Queries – [Teamname] – Contribute & DeleteCustom Permissions – Queries – Jedis – Contribute & DeleteTo have a Shared  Queries folder per team, create a  Queries folder(s) and assign these Group(s) to them.
Team ProjectCustom Permissions – Areas – [Team/areaname] – View Work ItemsCustom Permissions – Areas – Jedis – View Work ItemsTo give people access to read/view Work Items only, put the Group/Role in this Group.

Determine and create Organization-level groups

Organization-level groups are generally the groups that would reflect the roles. Roles can be (lead) engineer, product owner, scrum master, admin or anything you need. At my customer I have gradually built up these rules according to my needs. I have been scouring the entire Azure DevOps environment for any custom permissions and tried to check if they were still necessary and to which role they belong. Based on that, I have created the roles at Organization-level and cleaned up the original custom permissions.

Determine and create Project-level groups

First of all, always check if the permission/group can be made generic at Organization-level. If there is really a need to setup permissions at project-level, then create groups there. Some situations in which this can be useful:

  • Strategic repositories, only accessible to some senior engineers
  • One project, multiple teams that have their own Shared Queries
  • Customer-specific projects where the customer has access to their backlog

Making use of Azure DevOps Group Rules

Some time ago, Microsoft introduced Group Rules in Azure DevOps (Services only!). With Group Rules, one can assign Team Project Access and Licenses automatically to AAD groups. This functionality is specifically helpful (but not limited to) for managing licenses (Access Levels) by using AAD Groups. Although I have not used it at my customer, we do use it at our own company. Our engineers get the Visual Studio Subscriber Access Level by default by using Group Rules. We have a Group Rule that assigns this Access Level and gives our engineers access to all except a few Team Projects.

Group rules

Handling Team and Team Admin memberships

Teams and Team Administrators are a difficult subject in RBAC, because having a MIM role not necessarily reflects being member of a certain team. When somebody has Team Administrator membership, that person can actually add and remove team members, breaking the RBAC principle where everything is controlled through roles instead of permissions.

Team Administrators

So effectively, the Team Administrators is a “group” you’ll want to avoid.

Regarding Team Members, there is some very useful functionality attached to it. When people are in a team, they are automatically notified of important events happening within their Azure DevOps Team, they have their own backlog that is immediately visible when heading to Azure Boards and there are more benefits. There is no ideal approach here. One could define all teams available in the organization at Organization-level and make these groups member of the Engineers group, so people do not have to be member of two roles (Engineers role and their Team role). That would result in a structure like the one displayed in the diagram below:

In the diagram, the arrows follow the memberships, so the Contoso Engineers group has three members:

  • Team Jedis
  • Team Starlords
  • Team Guardians
Group structure

The following groups then either contain users or (A)AD/MIM groups:

  • Team Jedis
  • Team Starlords
  • Team Guardians

Notice that the default Team Project level group Contributors has the Contoso Engineers group as a member in each project in order for all engineers to access and contribute to all Team Projects (or at least the majority of them).

Reference

This reference describes some default groups that are available at different levels in Azure DevOps. For a full reference, head over to: https://docs.microsoft.com/en-us/azure/devops/organizations/security/permissions-access?view=azure-devops

Default groups at Organization level

Azure DevOps Server (on prem):

  • Project Collection Administrators
  • Project Collection Build Administrators
  • Project Collection Build Service Accounts
  • Project Collection Proxy Service Accounts
  • Project Collection Service Accounts
  • Project Collection Test Service Accounts
  • Project Collection Valid Users
  • Security Service Group
  • Team Foundation Licensed Users (only available when ever in history the Workgroup Edition of TFS has been used)

Azure DevOps Services (cloud):

  • Project Collection Administrators
  • Project Collection Build Administrators
  • Project Collection Build Service Accounts
  • Project Collection Proxy Service Accounts
  • Project Collection Service Accounts
  • Project Collection Test Service Accounts
  • Project Collection Valid Users
  • Project-Scoped Users
  • Security Service Group

Default groups at Team Project level

Note: some groups are created, when the functionality related to it is first used.

  • Build Administrators
  • Contributors
  • Deployment Group Administrators
  • Endpoint Administrators
  • Endpoint Creators
  • Project Administrators
  • Project Valid Users
  • Readers
  • Release Administrators

2 thoughts on “Enterprise-level Azure DevOps permissions from the trenches

  1. Do all accounts need to have Basic access level license to use this process? Or, is that also controlled thru the MIM process?

    1. It depends on what you mean by “this process”. If you mean that all accounts should have the Basic Access Level in order to be managed through MIM then that is not true. Also users that have the Stakeholder Access Level or any other Access Level can be managed through MIM using this approach.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s