Azure DevOps offers a rich set of fine-grained permissions. Unfortunately, the user interface only shows a few of them. When granting permissions through the user interface, it is hard to understand what permissions actually take effect, and how to limit the set to the smallest group possible to enable a task. The Azure DevOps extension for the Azure CLI allows us to open up the hood and take advantage of the full range of fine-grained permissions defined in Azure DevOps.



Azure DevOps Permission System

There are a few concepts to juggle when working with Azure DevOps permissions: subjects, permission namespaces, and security tokens.

  • Subjects are either users or groups, and you can grant permissions directly to either type.
  • Permission namespaces are fundamentally named bit fields that represent individual permissions. These bits can be turned on, off, or unset for a particular subject. Azure DevOps evaluates these to allow or deny a specific action that a user might attempt.
  • Security tokens are arbitrary strings that represent resources in Azure DevOps.

There is no concept of a role or a role assignment as in Azure RBAC. Instead, permissions are applied directly to a group or a user. To create your own pseudo-role with fine-grained permissions, start by creating a group then adding users or existing groups to the new group. To assign permissions to the new group, you need the group’s identifier. Furthermore, you need the security namespace identifier, the security token in that namespace, and which bits in the namespace correspond with the permissions you want. These are quite a few concepts coming together, so let’s step through an example.

Granting permission to manage Service Connections

In this example, we’ll imagine that my team enforces strict segregation of duty policies and wants to minimize the number of users who handle credentials for Service Connections. Simultaneously, the team wants to ensure that the users who manage Service Connection credentials have only that permission and cannot configure Azure DevOps in other ways. We will allow these users to set credentials on a Service Connection but not create or use them.

First, create a group for the users that will have this role. This task can be performed in the UI, but I’ll show you how to do it with the Azure DevOps extension for the Azure CLI. (You will need to install this extension to follow these steps. See instructions here: https://docs.microsoft.com/en-us/azure/devops/cli/?view=azure-devops). After making sure to login with “az login,” I created the “Service Connection Managers” group with the following command:

az devops security group create \
--organization https://dev.azure.com/olive-steel \
--project scheduling \
--scope project \
--name "Service Connection Managers" \
--description "Manage Service Connection Credentials" \
--query "{displayName:displayName,descriptor:descriptor}" \
--output table

To successfully run this command, we need to provide the organization URL, the project name, and whether to define the group at the project level or the organization level. In this case, I wanted to create the group at the project level. Besides the group’s name, I also provided a description for the group. On success, we can see that DevOps created the group I requested and assigned it a descriptor. This descriptor will be necessary for later commands:

DisplayNameDescriptor
Service Connection Managersvssgp.Uy0xLTktMTU1MTM3NDI0NS0x…

Next, I need to find the security namespace with the permissions I am interested in. A few namespaces are documented online, but the best way to get the full set is to query them with the CLI. The command is simple enough, but like all commands, it requires the organization to query. Entering the organization and project for every command is tedious, so the CLI provides an option to set defaults:

az devops configure \
--defaults organization=https://dev.azure.com/olive-steel project=scheduling

Now I can query the security namespaces without adding the organization to the command:

az devops security permission namespace list \
	--output table

The output is very long and returned 58 namespaces for my example organization. A search through the list finds one likely namespace: ServiceEndpoints with the namespace identifier: 49b48001-ca20-4adc-8111-5b60c903a50c. “Service Endpoints” is an alternate name for the feature now labeled “Service Connections” in the user interface. We can use this namespace GUID to pull up the details for the namespace.

az devops security permission namespace show \
--id 49b48001-ca20-4adc-8111-5b60c903a50c \
--query "[0].actions[].{bit:bit,displayName:displayName}" \
--output table

The results show us the individual permission bits, summarized below:

BitDisplayName
1Use Service Connection
2Administer Service Connection
4Create Service Connection
8View Authorization
16View Service Connection

This information will be handy when we update permissions. Before we can update permissions, we need to figure out the security token. A security token in Azure DevOps is an arbitrary string representing a resource in Azure DevOps. After some experimentation, I learned that Azure DevOps would create security tokens after making its first Service Connection. As far as I know, security tokens or their patterns aren’t documented anywhere, nor do I know of a CLI command to list them.

After creating a service connection, the CLI shows us some security tokens, and we can learn about their patterns.

az devops security permission list \
--subject ${GROUP_DESCRIPTOR} \
--id ${NAMESPACE_ID} \
--query "[].{token:token}" \
--output table

Note that I have replaced some of the longer values with variables for brevity. This command lists all tokens and related permissions in a specific namespace for the requested group. We provide it with two of the values we found earlier: the group descriptor and the security namespace id. The CLI shows the following security tokens:

Token
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71
endpoints/Collection/ba349990-dc9c-4bf8-9340-70845950fd71

Security tokens vary by security namespace, so what we learn about values in the ServiceEnpoints namespace may not apply to other namespaces. The GUID values represent the project ID and the Service Connection ID, respectively. The Azure CLI can confirm these values for us by listing the project ID:

az devops project list \
	--query "value[].{name:name,ID:id}" \
	--output table

This confirms the project ID is the GUID starting with “80ca”:

NameID
scheduling80cad8fd-1891-4491-95d8-cc68f0f8b72e

And next, the Service Connection ID.

az devops service-endpoint list \
	--query "[].{name:name,ID:id}" \
	--output table

This confirms the Service Connection ID is the GUID starting with “ba34”:

NameID
Service Connection Oneba349990-dc9c-4bf8-9340-70845950fd71

So, interpret each token according to the following table:

Token Scope
 endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e All Service Connections in the “scheduling” project
 endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71 “Service Connection One” in the “scheduling” project
 endpoints/Collection/ba349990-dc9c-4bf8-9340-70845950fd71 “Service Connection One” in any(?) project

The first token covers all Service Connections in the project. The second token covers just one Service Connection in the project. Because these two tokens share a common prefix, we might infer that tokens are matched by a prefix. In earlier experiments, I found that permissions for the token “endpoints” would apply to any Service Connection, supporting that hypothesis. The token “endpoints/Collection/ba34…” might grant permissions on the Service Connection anywhere in the Azure DevOps project collection. Still, I have not experimented with permissions across several projects yet, so I cannot be sure.

In any case, we can continue our example with the tokens we have available by granting permissions to our group using the narrowest scope. The second security token grants permission on “Service Connection One” in the “scheduling” project, and I’ll use that one.

To build the permission update command, combine the security namespace identifier for the ServiceEndpoints namespace, the group descriptor for the Service Connection Managers group, and the selected security token. Also, calculate the bitmasks for the permissions to allow and deny. To calculate the bitmask, refer back to the permission bits table for the ServiceEnpoints namespace:

BitDisplayName
1Use Service Connection
2Administer Service Connection
4Create Service Connection
8View Authorization
16View Service Connection

In this table, each permission is represented by a number. To create the mask for the permissions you are interested in, add the corresponding numbers together. For example, I decided to give my new group permission to Administer and View Service Connections. The corresponding permission values are 16+8+2=26. This becomes the argument for the “allow-bit” parameter on the CLI. In this example, I decided to deny the “Use” and “Create” permissions. The corresponding value for the “deny-bit” is 4+1=5.

az devops security permission update \
--deny-bit 5 \
--allow-bit 26 \
--namespace-id ${NAMESPACE_ID} \
--subject ${GROUP_DESCRIPTOR} \
--token "endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71" \
--output table

The output from the command shows the resulting permissions:

NameBitPermission DescriptionPermission Value
Use1Use Service ConnectionDeny
Administer2Administer Service ConnectionAllow
Create4Create Service ConnectionDeny
ViewAuthorization8View AuthorizationAllow
ViewEndpoint16View Service ConnectionAllow

Testing the Permissions

I want to try these permissions to be sure that they work as I expect them to. My default user owns the organization and all projects in it. I am already allowed Use, Administer, and Create:

NameBitPermission DescriptionPermission Value
Use1Use Service ConnectionAllow (inherited)
Administer2Administer Service ConnectionAllow (inherited)
Create4Create Service ConnectionAllow (inherited)
ViewAuthorization8View AuthorizationNot set
ViewEndpoint16View Service ConnectionNot set

Since that includes two of the permissions I want to deny in the new group, I need to invite another user into the project. Fortunately, I have more than one Microsoft/Office365 user, so I simply added my alternate user to the organization and the scheduling project as a reader. As expected, my alternate user has no permissions related to Service Connections and cannot create new ones or view existing configurations.

Get Started with Azure DevOps Permissions On The CLI 1

Next, I added this secondary user to the Service Connection Managers group:

az devops security group membership add \
--member-id ${USER_EMAIL} \
--group-id ${GROUP_DESCRIPTOR} \
--output table

After this, I checked the new user’s permissions in the ServiceEndpoints namespace:

az devops security permission show \
--subject ${USER_EMAIL} \
--id ${NAMESPACE_ID} \
--token ${SECURITY_TOKEN} \
--output table

I saw the expected results:

NameBitPermission DescriptionPermission Value
Use1Use Service ConnectionDeny (inherited)
Administer2Administer Service ConnectionAllow (inherited)
Create4Create Service ConnectionDeny (inherited)
ViewAuthorization8View AuthorizationAllow (inherited)
ViewEndpoint16View Service ConnectionAllow (inherited)

As expected, the user can view the existing Service Connection.

Get Started with Azure DevOps Permissions On The CLI 2

As expected, the user fails when trying to create a Service Connection.

Get Started with Azure DevOps Permissions On The CLI 3

However, editing the existing service connection works!

Get Started with Azure DevOps Permissions On The CLI 4
Get Started with Azure DevOps Permissions On The CLI 5

Wrap Up

In this example, we could use the Azure DevOps CLI to find the permissions we needed. Once I determined the security namespace that interested me, I could use it to check for permissions assigned to an Azure DevOps group for specific scopes. However, if I had not found an example to work backward from, determining the correct Security Token may have been impossible. Security Tokens in Azure DevOps are very sparsely documented. Finding and figuring out the right pattern for a token is likely to be the most challenging task facing a team trying to implement fine-grained permissions in Azure DevOps. With the Azure DevOps CLI, some experimentation, and some luck, it is possible to deploy limited fine-grained permissions in Azure DevOps.


Cloud / DevOps Architect at Solliance