I’ve been doing a few Azure tenant assessments lately. This usually covers overall governance, security, SDLC and sometimes even application architecture reviews. Anyone feeling this need can certainly reach out, but a good start is having a look at the Cloud Adoption Framework.
During one of these assessments, I quickly noticed that the company was using the default Azure DevOps service connections. I asked them if they had a particular reason for this approach and what their desired environment would look like. As it is an official supported way to connect your Azure DevOps pipelines to your Azure tenant resources, it is certainly a valid approach for some. But since there are multiple options, we should look into each one of them and weigh the pros and cons before deciding what to use.
Why do we need a Service Connection?
If you want to deploy from Azure DevOps, but also from another CI/CD platform, you will need a connection to your Azure tenant. Since this is a machine executing the deployments, it is not smart to use your own credentials and you should use some sort of machine credentials to make the connection. In Azure DevOps this setup is called a service connection. ‘Azure’ is only one of the many possible connection types:
A full description of each type is available in the official documentation. Important to note is that the Azure Classic one is there mainly for backward compatibility and you’ll normally use Azure Resource Manager for any deployments to Azure. This option has a few more sub-options:
As mentioned in the introduction, my customer was using the first option, which I challenged. More info on each below:
Service Principal (automatic)
In my humble opinion, this option should not have the recommended label. It is very handy because it allows you to select a subscription from all subscriptions available to the current logged-in user. This also highlights a large problem: these connections are typically created by a personal user account, with probably too much access to enable this setup.
Next to the actual user having too much permissions, the service connection is also created at subscription or management group level. For both these reasons this approach is, in my opinion, a security risk as you should always follow the least-privilege principle.
Service Principal (manual)
Instead of using security principals with too much access, you should create a service principal for each Azure DevOps project (and each environment) directly in Azure Active Directory. Here you have guides to create one in the portal, using Azure CLI and using Azure PowerShell and a certificate.
Even the official docs acknowledge this is more secure:
Use this approach when you need to connect to an Azure account using different credentials from the credentials you’re currently signed in with in Azure Pipelines. It’s useful way to maximize security and limit access. Service principals are valid for two years.
Creating the service principal is only the first step, also make sure to limit access to the actual Azure resources that this specific pipeline has to deploy to with the well-known RBAC assignments.
This option is available for secured access with a managed identity, in short: a more fine-grained service principal linked to a resource). This is typically linked to your private cloud-hosted build service, which also will need access to the resources it deploys to.
Publish profiles contain the deployment configuration directly in the profile and are part of your source repository rather than your build/deploy pipeline. This allows for developers to update the publish configuration (pro), but immediately opens up a whole new spectrum of possible security issues (con).
Security should always be part of your process and for that reason I prefer using manual service principals or managed identities.