Securing Azure Pipelines with Azure Identities

Featured image

In the era of DevOps, manual processes are relics of the past. Repetitive tasks are prime candidates for automation, freeing up development teams to focus on strategic innovation. Today, we no longer build and package applications locally for production—infrastructure concerns such as how database schemas and message queues are managed seamlessly through code. But how do we ensure secure access to these protected Azure resources while adhering to the principle of least privilege?

This article delves into leveraging Azure Pipelines to automate the management of protected Azure resources. We’ll explore examples on how to achieve this goal as well as highlighting the “least privilege” approach, ensuring each pipeline has the minimal permissions necessary to complete its specific task.

This article is going to focus on the example of creating an Azure Service Bus (ASB) topic on an existing ASB namespace using Azure Pipelines and will use some of the concepts first touched on in Azure Managed Identities Introduction. I have found the techniques in this article useful when there is a requirement to run custom applications (creating database schemas, Service Bus topics, ect.) and test suites that require access to Azure resources to run.

Options

Basic Authentication

Azure Service Bus, like many other services, has a Basic Authentication option as well a Token Based Authentication option. I understand that a Shared Access Key is more complex than standard Basic authentication and Service Bus allows for multiple keys with restrictions, but given that it is a key that is usually long lived, requires manually copying, and securing to use anywhere I highly recommend staying away if possible.

AsbConfigurationService Code

using Azure.Messaging.ServiceBus.Administration;

var connectionString = "Endpoint=sb://myMessages.servicebus.windows.net/;SharedAccessKeyName=SuperUser;SharedAccessKey=C9JldXro4w14zzTAwm9ValQ6==";
var topicName = "VerryImportantTopic";

var serviceBusAdminClient = new ServiceBusAdministrationClient(connectionString);

//Ensure the Topic already exists
var alreadyExists = await serviceBusAdminClient.TopicExistsAsync(topicName);
if (!alreadyExists)
    await serviceBusAdminClient.CreateTopicAsync(topicName);

Azure Pipelines task that will run the above code in the pipeline.

- task: DotNetCoreCLI@2
  inputs:
    command: 'run'
    projects: '/AsbConfigurationService/AsbConfigurationService.csproj'

It is possible to pass in the connection string as Azure Pipelines Library variable which is significantly better than using storing it in code, however there is still management overhead such as

  • Creating the Shared Access Keys for each bus
  • Ensure the keys are in all Azure Pipelines Libraries
  • Rolling the keys and ensuring they remain in sync

Token Based Authentication

As mentioned in previous articles, Token based authentication has security benefits over basic authentication, in Azure Pipelines the token based authentication approach is cleaner to implement.

using Azure.Identity;
using Azure.Messaging.ServiceBus.Administration;

var serviceBusNamespace = "myMessages.servicebus.windows.net";
var topicName = "VerryImportantTopic";

//Select the Appropriate Credential provider
var credential = new ManagedIdentityCredential();
//Configure the administration client with access to a credential so that it can request its tokens as required
var serviceBusAdminClient = new ServiceBusAdministrationClient(serviceBusNamespace, credential);

//Ensure the Topic already exists
var alreadyExists = await serviceBusAdminClient.TopicExistsAsync(topicName);
if (!alreadyExists)
    await serviceBusAdminClient.CreateTopicAsync(topicName);

The above allows the use of Azure RBAC (Roles Based Authentication Control) to control access to your resources, if this is run inside of an Azure pipeline it will use the Managed Identity of the Pipeline Agent (This will need to be enabled on the self hosted agents).

This benefits of this approach are:

  • No need to copy and protect secrets.
  • No long-lived keys being sent over the wire

The disadvantage are:

  • A Self Hosted Azure Pipelines Agents are required
  • Permissions are granted to the Agent or Agent Pool which does not follow the practices of Least Privilege (anything running on this machine can access this permission)

Least Privilege Option

The security principal of Least Privilege is the practice of ensuring that accounts only have permissions required for their function and nothing beyond this. A good example is that an administrator may require permissions to administer and maintain a SQL Server, but they do not require permissions to query the databases on the SQL server.

My recommend approach would be to use a Azure Pipelines Service Connection, AzureCLI Pipeline task, and the Az CLI Token Credential together. This will allow Service Principals (wrapped in an Azure Pipelines Service Connection) to be seamlessly passed into a Pipeline. For more information on Service Connections Click Here

The below shows passing a Service Connection into an Azure CLI task

- task: AzureCLI@2
  inputs:
    azureSubscription: MyServicePrincipal
    scriptType: 'pscore'
    scriptLocation: 'scriptPath'
    scriptPath: 'dotnet run /AsbConfigurationService/AsbConfigurationService.csproj'

For more information on the Azure CLI task Click Here

The below replaces the machine wide credential with the one that has been passed into the specific task

using Azure.Identity;
using Azure.Messaging.ServiceBus.Administration;

var serviceBusNamespace = "myMessages.servicebus.windows.net";
var topicName = "VerryImportantTopic";

//Select the Appropriate Credential provider
var credential = new AzureCliCredential();
//Configure the administration client with access to a credential so that it can request its tokens as required
var serviceBusAdminClient = new ServiceBusAdministrationClient(serviceBusNamespace, credential);

//Ensure the Topic already exists
var alreadyExists = await serviceBusAdminClient.TopicExistsAsync(topicName);
if (!alreadyExists)
    await serviceBusAdminClient.CreateTopicAsync(topicName);

The benefits of this approach over the Token Based one above are:

  • Respecting the Least Privilege principle (allows access to be granted only where necessary)
  • Removes the requirement for Self Hosted agents