Using hardcoded security credentials in automation scripts is bad practice, yet some IT administrators still do. If you’re one of the few that does it with Azure, perhaps it’s time you switch to using Azure service principals.
But what’s an Azure service principal anyway? In a gist, it is a form of security identity that can be assigned fine-grained access and permission to resources on different scope levels. When you create a service principal, you can assign a built-in role, such as Owner, Contributor, or Reader.
An Azure service principal is similar to a user account because it has an application ID (username) and secret (password). The secret can also have an expiration date, like a password that expires. A service principal can use a certificate instead of a secret, which is more secure.
Requirements
- A computer with the Azure PowerShell module installed.
- An active Azure tenant.
Create a New Azure Service Principal
The cmdlet for creating a new Azure service principal is the New-AzAdServicePrincipal. When creating the service principal, you must decide its authentication type. There are two types: password-based and certificate-based. The service principal can have both password-based and certificate-based authentication types.
Before anything else, this tutorial assumes that you’ve already connected to Az PowerShell. If you haven’t, do so now.
Connect-AzAccount -Tenant <tenant id>
With Password
The password-based authentication uses the application ID and secret text, similar to a username and password. This means that you must treat the secret, well, a secret. This authentication type is convenient and familiar but less secure.
Run the below command to create a new service principal with an autogenerated password.
$azsp = New-AzADServicePrincipal -DisplayName 'Blue Turtle'
This command creates a new Azure service principal called ‘Blue Turtle’. The resulting object will be stored in the $azsp variable.
After creating the service principal, you can get the AppID and SecretText values.
$azsp | Format-List AppDisplayName, AppId
$azsp.PasswordCredentials | Format-List SecretText, EndDateTime
As you can see, the secret text expiration is one year from the time you created the service principal.
With Certificate
Certificate-based authentication also uses the application ID. But instead of a password, it uses a digital certificate to prove its identity during authentication. The certificate can be inside the certificate store or as a PFX file on the disk.
First, create a self-signed certificate. The certificate will be created in the personal certificate Cert:\CurrentUser\My store with the subject CN=Blue Turtle and will be valid for five (5) years.
# What's the display name of the service principal?
# This name will be the subject of the certificate.
$spDisplayName = 'Blue Turtle'
# How many years will the certificate be valid?
$yearsValid = 5
$cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" `
-Subject "CN=$($spDisplayName)" `
-KeySpec KeyExchange `
-NotBefore ((Get-Date).AddDays(-1)) `
-NotAfter ((Get-Date).AddYears($yearsValid))
$cert
You can see the new certificate when you open the certificate management console (certmgr.msc).
Next, convert the certificate to a base64-encoded string:
$certData = [System.Convert]::ToBase64String($cert.RawData)
$certData
The resulting data is stored in the $certData variable.
Lastly, let’s create the Azure service principal and attach the certificate:
$azsp = New-AzADServicePrincipal -DisplayName $spDisplayName `
-CertValue $certData `
-EndDate $cert.NotAfter `
-StartDate $cert.NotBefore
$azsp
Once the service principal is created, run the below commands to get the application ID and certificate details.
# Get the display name and application id
$azsp | Format-List AppDisplayName, AppId
# Get the certificate expiration and thumbprint
Get-AzADAppCredential -ApplicationId $azsp.AppId | Select-Object `
DisplayName, EndDateTime, @{
n = 'ThumbPrint'; e = { $([System.Convert]::ToBase64String($_.CustomKeyIdentifier)) }
}
As expected, the attached certificate to the service principal is valid for five years since its creation time.
Assign a Role and Scope
Before an Azure service principal can be used to manage Azure resources, it must be assigned a role and scope.
There are multiple Azure built-in roles under each Azure service category (Compute, DevOps, Analytics, etc.). Below are the most common roles under the General category.
- Owner — Has full access to manage resources and can assign roles to others.
- Contributor — Has full access to manage resources but cannot assign roles to others.
- Reader — Can view resources but cannot make any changes.
- User Access Administrator — Can manage user access to Azure resources.
Refer to Azure AD built-in roles to learn about all available Azure roles.
As for the scope, there are four levels to which the service principal role applies. From the most specific to the broadest, scopes can be applied to the following:
- Resource
- Resource Group
- Subscription
- Management Group
The best practice is to apply just enough privilege to the most specific scope whenever possible. But how you design your access hierarchy in your Azure tenant is another topic.
How do we assign the Azure role to the service principal and define its scope?
Which Azure Service Principal Needs Access?
First, get the details of the service principal who needs access. In this example, we’ll use the Blue Turtle service principal that we previously created.
$sp = Get-AzADServicePrincipal -SearchString 'Blue Turtle'
What’s the Scope of Access?
The next step is to determine the scope of access. Suppose a target scope is a virtual machine named GRDC01 under the GADGETREVO resource group. Let’s find the ID of this resource.
$vm = Get-AzResource -Name 'GRDC01' -ResourceGroupName 'GADGETREVO'
$vm.ResourceID
As you can see below, the VM’s resource ID is now stored in the $vm.ResourceID variable.
Assign the Azure Role
The last part is to assign the Azure role. In this example, let’s make the Azure service principal an ‘Owner’ of the virtual machine.
New-AzRoleAssignment -ApplicationId $sp.AppId`
-Scope $vm.ResourceID `
-RoleDefinitionName 'Owner'
If the operation were successful, you’d get a result similar to the below screenshot.
Connect to Azure PowerShell using the Service Principal
The Azure service principal has been created, and it has been assigned a role and scope. What’s left is to use it to connect to Azure PowerShell to test.
As you remember, the Azure service principal can have a secret text and certificate; both can be used to authenticate.
Password-Based Authentication
To authenticate using the secret text, run the below command. You only need to modify the values of $appId, $secret, and $tenantId variables.
# Enter the service principal details
$appId = 'service principal id'
$secret = 'secret text'
$tenantId = 'Azure tenant id'
# Create the credential object
$spCredential = [pscredential]::new(
$appId,
(ConvertTo-SecureString $secret -AsPlainText -Force)
)
# Connect to Azure PowerShell
Connect-AzAccount `
-ServicePrincipal `
-Credential $spCredential `
-Tenant $tenantId
Note. The secret value is in plain-text format and easily readable. When using this authentication type in production, use an encryption method or a secret vault not to expose the secret.
Certificate-Based Authentication
Below is the code to authenticate using the certificate. You only need to modify the values of $appId, $thumbprint, and $tenantId variables.
# Enter the service principal details
$appId = 'service principal id'
$thumbprint = 'certificate thumbprint'
$tenantId = 'Azure tenant id'
# Connect to Azure PowerShell
Connect-AzAccount `
-ServicePrincipal `
-ApplicationId $appId `
-CertificateThumbprint $thumbprint `
-Tenant $tenantId
Verify the Service Principal’s Permission to the Resource
After logging in using the Azure service principal, let’s confirm whether the role permission works.
First, let’s confirm that the account can only list the resources it has access.
Get-AzResource | Select-Object `
Name, ResourceGroupName, ResourceType
Remember that the service principal ‘Blue Turtle’ only has access to the GRDC01 virtual machine? As expected, the command returned only the GRDC01 virtual machine.
As the VM’s ” owner, ” this service principal can start and stop the VM. Let’s put it to the test.
Start-AzVM -Name GRDC01 -ResourceGroupName GADGETREVO
According to the below result, the start operation is successful.
To further confirm, let’s get the status of the VM.
Get-AzVM -Name GRDC01 -ResourceGroupName GADGETREVO -Status
Conclusion
Stop using hardcoded security credentials in your automation scripts. It isn’t pleasant. Especially now, you can create an Azure service principal with only the specific permissions needed to manage and access your Azure resources.
Password-based authentication is less secure than certificate-based authentication. But password-based authentication is still better than using a service account’s username and password without MFA.