DEV Community

Jennifer Davis for Microsoft Azure

Posted on

You gotta keep privileges separated

Ever have an automated process just suddenly stop working even though you've made no changes to the process? There's nothing like the pure adrenaline of debugging why no one's commits are getting deployed to production for a critical fix and yet everything is reporting "green". Of course, there were little things that could get fixed, but those things were just distractions at that moment. Everyone had an opinion of what was going on and just wanted it fixed now! While I had the most seniority within the operations team, some expressed that it was the lack of my expertise causing the problem. That did give me a clue towards finding the problem. Something HAD changed. The engineer who had set up the CD pipeline had recently left. A little sleuthing, and I realized that he had used his credentials to get the automated deploy to work... so when he left, his credentials got disabled and it just failed silently.

Don't have Azure? Grab a free subscription.

When writing scripts for automation or building out a service, don't run under your own credentials. This creates a single point of failure on you for the service. It's also good practice to separate out concerns between environments. This way even if someone accidentally runs a test command against production, it won't have disastrous results.

One recommended approach is to use service principals. An Azure service principal is an identity for use with applications, services, and tools to access Azure resources. Using service principals allows us to assign specific permissions that are limited in scope to precisely what is required so we can minimize the impact if it's compromised!

We can assign different built-in roles to a service principal as well as create custom roles if the built-ins aren't sufficient. There isn't a one-to-one relationship with roles and service principals. Service principals can have multiple roles.

After you've installed the Azure CLI or downloaded the docker image, you can start using the CLI. I'm running these examples in docker. If you've installed directly, you can ignore the docker command.

$ docker run -it microsoft/azure-cli
Enter fullscreen mode Exit fullscreen mode

We get the az command which has a lot of different functionality. We're going to focus on managing a service principal for this article. First, we need to login.

bash-4.4# az login
Enter fullscreen mode Exit fullscreen mode

If the CLI can reach a web browser, it will open it up and have you login. Otherwise, a URL is provided from the command line to visit along with a code to enter into the sign-on page.

Depending on your usage of Azure, you may have different subscriptions. Before running commands in the CLI, it's useful to check out your subscriptions using az account list. I added -o table to get easier for me to read output versus the default JSON output. Here is a modified version of my output showing that I've got 2 accounts, one that is my Pay-As-You-Go account that I upgraded after my Azure Free Account expired, and one that is my work-related subscription.

bash-4.4# az account list -o table
Name                      CloudName    SubscriptionId                        State    IsDefault
------------------------  -----------  ------------------------------------  -------  -----------
my-pay-as-you-go          AzureCloud   00000000-0000-0000-0000-100000000000  Enabled  False
Visual Studio Enterprise  AzureCloud   00000000-0000-0000-0000-100000000001  Enabled  True
Enter fullscreen mode Exit fullscreen mode

I want to make sure that I use my "Visual Studio Enterprise" for this walk-through so I'm going to set my subscription explicitly.

bash-4.4# az account set --subscription="00000000-0000-0000-0000-100000000001"
Enter fullscreen mode Exit fullscreen mode

Next, I want to manage the service principal. I can get more information about an Azure CLI command before running it by adding a -h flag.

bash-4.4# az ad sp -h

Group
    az ad sp : Manage Azure Active Directory service principals for automation authentication.

Subgroups:
    credential      : Manage a service principals credentials.
    owner           : Manage service principal owners.

Commands:
    create          : Create a service principal.
    create-for-rbac : Create a service principal and configure its access to Azure resources.
    delete          : Delete a service principal and its role assignments.
    list            : List service principals.
    show            : Get the details of a service principal.
Enter fullscreen mode Exit fullscreen mode

Breaking this down into its components I'm using the az cli, with the ad option for Active Directory and the sp suboption for service principal.

I'm going to use the create-for-rbac option with a principal name of SigjeServiceDemoPrincipal and grant the Reader role to the principal. If a role isn't specified at creation time, a default role of Contributor is assigned to the principal. The Reader role lets the principal view all resources but make no changes. The Contributor role lets you manage everything except changing access to resources. Role assignments can get pretty complex.

bash-4.4# az ad sp create-for-rbac --name SigjeServiceDemoPrincipal --role Reader
Changing "SigjeServiceDemoPrincipal" to a valid URI of "http://SigjeServiceDemoPrincipal", which is the required format used for service principal names
{
  "appId": "00000000-1111-0000-0000-000000000000",
  "displayName": "SigjeServiceDemoPrincipal",
  "name": "http://SigjeServiceDemoPrincipal",
  "password": "My Sekret Password",
  "tenant": "11111111-0000-0000-0000-000000000000"
}
Enter fullscreen mode Exit fullscreen mode

Without specifying anything extra, Azure will generate and provide a random password for my new service principal.

We can take the appId and use it to examine our just created service principal with az role assignment list --assignee --id "00000000-1111-0000-0000-000000000000" which will respond with a JSON object that contains information about our service principal.

[
  {
    "canDelegate": null,
    "id": "/subscriptions/00000000-0000-0000-0000-100000000001/providers/Microsoft.Authorization/roleAssignments/b681786a",
    "name": "b681786a",
    "principalId": "00000000-1111-0000-0000-000000000000",
    "principalName": "http://SigjeServiceDemoPrincipal",
    "roleDefinitionId": "/subscriptions/00000000-0000-0000-0000-100000000001/providers/Microsoft.Authorization/roleDefinitions/acdd72a7",
    "roleDefinitionName": "Reader",
    "scope": "/subscriptions/00000000-0000-0000-0000-100000000001",
    "type": "Microsoft.Authorization/roleAssignments"
  }
]
Enter fullscreen mode Exit fullscreen mode

The scope of this role assignment is for the entire subscription "scope": "/subscriptions/00000000-0000-0000-0000-100000000001". Scope is the level that the role assignment applies to. In this case, it's every resource within the subscription.

Scope can be within Management group | Subscription | Resource Group | Resource

In this diagram, from the top to the bottom we have the most to least access in terms of scope to resources.

Finally, I clean up the service principal because I don't want to just leave this service principal sitting around in my subscription with az ad sp delete --id "00000000-1111-0000-0000-000000000000" which will respond with Removing role assignments.

What Next

Ideally, I wouldn't set up a service principal for automation to authenticate with a password that I would then embed somewhere. Instead, we can do certificate-based authentication, and even store the certificate in Azure Key Vault. We can also specify the scope of our service principal right at creation time rather than modifying it afterward so we can limit service principals to have access to specific resources.

Share your misadventures with personal credentials or any questions you have below!

Resources:

Top comments (0)