Deploy a WordPress Azure Web App with an Alternative MySQL Database

I was recently presented with an interesting question about Azure Web Apps, WordPress, and MySQL. While not necessarily a “hard” question, the answer wasn’t as readily available as I first anticipated. I thought I would share my experience here in hopes of helping others.

The Question

How can you deploy a WordPress site using Azure Web Apps that uses a MySQL database instance that is not a ClearDB database available in the Azure subscription?

Background

Normally when you create a WordPress site using Azure Web Apps you are presented with an option to select an existing ClearDB MySQL database, or create a new one. But what if you don’t want to use an existing instance or create a new one? What if you want to use a MySQL database instance deployed to an Azure VM or you have a ClearDB MySQL database that doesn’t show in the Azure Portal (e.g. one of the ClearDB Basic offerings)?

Create_WordPress_ClearDB_Normal

The Answer(s)

Like most technology related questions (or life in general), there are a few ways to solve this challenge. There is the “easy” way, and there is the more powerful, yet slightly more complicated, some would argue the “right” way.

The Easy Way

The easiest approach is to create a WordPress site with Azure Web Apps and select either an existing ClearDB database or create a new ClearDB database. Once the WordPress site is deployed, you can then change the database connection string in the wp-config.php file to be the database you want (e.g. a ClearDB Basic instance or a MySQL instance on an Azure VM).

  1. Let the WordPress site be deployed, but do not complete the installation. In other words, once the site is deployed, browsing to the site’s URL should result in the standard WordPress default installation prompt.
  2. WordPress_Default_Install_1
  3. Open the Kudu console by going to http://your-site-name.scm.azurewebsites.net. If you’re already signed into the Azure Portal, you should proceed through without any authentication challenge. Otherwise you’ll be challenged for your authentication credentials.
  4. Navigate to the Debug console (via the menu on the top). Browse to the \site\wwroot\ directory.
  5. Kudo_wpconfig_1
  6. Edit the wp-config.php file by clicking on the pencil icon to the left of the file name. Doing so will switch to an edit view for the file. Don’t click on the delete icon. . . that’d be a bad thing.
  7. Within the wp-config.php file, change the DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST values to be that of the desired database. Save the file.
  8. WordPress_Default_Install_wpconfig_1
  9. Now reload your site – http://your-site-name.azurewebsites.net. This should load the default WordPress installation page prompting you to complete the WordPress installation.
  10. Complete the installation. This should use the database setting as configured in the wp-config.php file to finish the WordPress installation.
  11. If you created a free ClearDB database to start with, feel free to delete that ClearDB database.

The Alternative

And now the real fun begins! In this alternative approach, an Azure Resource Manager (ARM) template can be used to create the WordPress site on Azure Web Apps and wire up a database of your choosing. To make this happen you will need the ARM template and a MySQL database of your choosing.

To get the ARM template, my first thought was that I could download the template that the Azure Portal is using and simply modify the database connection details to be what I wanted. Wrong. The templates I tried turned out to be a bit more complicated that I wanted. However, they did provide a good start and helped me understand what I needed to do.

If you’re curious, you can get the templates by invoking the PowerShell script below.

# Retrieve all available items
$allGalleryItems = Invoke-WebRequest -Uri "https://gallery.azure.com/Microsoft.Gallery/GalleryItems?api-version=2015-04-01&includePreview=true" | ConvertFrom-Json

# Get all items published by WordPress
$allGalleryItems | Where-Object { $_.PublisherDisplayName -eq "WordPress" }
$allGalleryItems | Where-Object { $_.Identity -eq "WordPress.WordPress.1.0.0" }

# Save default template for all items under directory "C:\Templates"
$allGalleryItems | Foreach-Object {
$path = Join-Path -Path "C:\templates" -ChildPath $_.Identity
New-Item -type Directory -Path $path -Force

$.Artifacts | Where-Object { $.type -eq "template" } | ForEach-Object {
$templatePath = Join-Path -Path $path -ChildPath ( $_.Name + ".json" )

(Invoke-WebRequest -Uri $_.Uri).Content | Out-File -FilePath $templatePath
}
}

(original PowerShell sample from https://github.com/Azure/azure-powershell/issues/1064)

Using the ARM template obtained from the gallery sample as inspiration, I created a new ARM template. You can get the full sample on my GitHub repo at https://github.com/mcollier/AzureWebApp-WordPress-AlternativeDatabase.

"resources": [
{
"apiVersion": "2014-06-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"properties": {
"name": "[parameters('hostingPlanName')]",
"sku": "[parameters('sku')]",
"workerSize": "[parameters('workerSize')]",
"numberOfWorkers": 1
}
},
{
"apiVersion": "2014-06-01",
"name": "[variables('webSiteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[variables('webSiteName')]",
"serverFarm": "[parameters('hostingPlanName')]"
},
"resources": [
{
"apiVersion": "2014-11-01",
"name": "connectionstrings",
"type": "config",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('webSiteName'))]"
],
"properties": {
"defaultConnection": {
"value": "[variables('dbConnectionString')]",
"type": 0
}
}
},
{
"apiVersion": "2014-06-01",
"name": "web",
"type": "config",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('webSiteName'))]"
],
"properties": {
"phpVersion": "5.6"
}
},
{
"name": "MSDeploy",
"type": "extensions",
"location": "[resourceGroup().location]",
"apiVersion": "2014-06-01",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('webSiteName'))]",
"[concat('Microsoft.Web/Sites/', variables('webSiteName'), '/config/web')]"
],
"tags": {
"displayName": "WordPressDeploy"
},
"properties": {
"packageUri": "https://auxmktplceprod.blob.core.windows.net/packages/wordpress-4.3.1-IIS.zip",
"dbType": "MySQL",
"connectionString": "[variables('dbConnectionString')]",
"setParameters": {
"AppPath": "[variables('webSiteName')]",
"DbServer": "[parameters('databaseServerName')]",
"DbName": "[parameters('databaseName')]",
"DbUsername": "[parameters('databaseUsername')]",
"DbPassword": "[parameters('databasePassword')]",
"DbAdminUsername": "[parameters('databaseUsername')]",
"DbAdminPassword": "[parameters('databasePassword')]"
}
}
}
]
}

The most relevant section is the MSDeploy resource extension (around line 60). It is this extension that deploys WordPress and gets the default database connection string set up. You provide the database server name, database name, database username and database password as input parameters to the ARM template. The ARM template will use those parameters to construct a database connection string in the proper format (set in a variable in the template).

Once the template is created, it can be deployed with a few lines of PowerShell:

#Login-AzureRmAccount

#NOTE - Ensure the correct Azure subscription is current before continuing. View all via Get-AzureRmSubscription -All
#Select-AzureRmSubscription -SubscriptionId "[your-id-goes-here]" -TenantId "[your-azure-ad-tenant-id-goes-here]"

$ResourceGroupName = "dg-wordpress-001"
$ResourceGroupLocation = "East US"
$TemplateFile = "azuredeploy.json"
$TemplateParametersFile = "azuredeploy.parameters.json"

Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
-Verbose

# Create or update the resource group using the specified template file and template parameters file
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop

New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
-ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
-Force -Verbose

The reason I like this approach is that it is very clear what is being deployed. I can customize the template however I like, adding or removing additional resources as needed. Plus, I don’t have to go through that “create a database just to delete it” dance.

For instance, I can envision a version of this ARM template that may optionally set up a MySQL database on an Azure VM. Oh . . . look here, https://azure.microsoft.com/en-us/documentation/templates/wordpress-mysql-replication/. Someone already did mostly just that! That template could be modified to have some options to allow for the creation of a database in a few different configurations. Thanks for saving me some work. Naturally I found this after I went through all the work above. Go figure!  :)

Join me as a Cloud Solution Architect

Earlier this year I started a new chapter in my career as I joined Microsoft as a Cloud Solution Architect (CSA). Working for Microsoft has been a goal of mine for long time (since sometime in high school probably). Working for Microsoft in a role that allows me to continue working with Azure and solving interesting problems . . .  well, that’s just a lot of fun!

It is no secret that Microsoft is moving full steam ahead with Azure. The pace of innovation is mind blowing!  Part of my job as a CSA is to keep up on all the latest technologies in Azure – understanding how they really work and how to best leverage the various services/features to solve problems. It is a tough job, but someone has to do it. It is a fun job too!

The Azure platform is incredibly large. There are so many different services that is is in reality impossible to be an expert in all of the services. In that light, Microsoft has recently created a new role, the Data Solution Architect (DSA), that compliments the Cloud Solution Architect role. The DSA role focuses on data platform technologies such as Azure SQL Database, Machine Learning, HDInsight, etc. That allows the CSA role to focus on more of the compute and infrastructure services. Naturally, there is a bit of crossover in some areas . . . that is OK.

If you have ever wanted to work in Azure – in any capacity – now is a great time to join Microsoft. There are 292 current job openings currently showing on the Microsoft Careers site.

My team is currently hiring for both the Cloud Solution Architect and Data Solution Architect role. I currently work in SMS&P (that’s Microsoft speak for ‘Small and Midmarket Solutions & Partners’) in Central region for Microsoft. Current open positions include:

  • DSA – Austin, TX
  • CSA – Austin, TX
  • CSA – Minneapolis, MN
  • DSA – St. Louis, MO
  • DSA – Nashville, TN
  • CSA – Houston, TX
  • DSA – Chicago, IL
  • DSA – Milwaukee, WI
  • DSA – Indianapolis, IN

If you want to see the official job description for a CSA or DSA, check out the links below:

If a CSA or DSA sounds like something you would be interested, please contact me.

Being a CSA or DSA at Microsoft is a pretty cool job. But don’t just take my word for it; check out Walter Myer’s video below where he talks about his experience as a CSA.

Speaking at Visual Studio Live! New York

I’ll be speaking at Visual Studio Live!, September 28-October 1 in New York. Surrounded by your fellow industry professionals, Visual Studio Live! provides you with immediately usable training and education that will keep you relevant in the workforce.

I’ll be presenting the following sessions:
· Automating Your Azure Environment
· Inside the Azure Resource Manager

SPECIAL OFFER: As a speaker, I can extend $400 savings on the 4-day package. Register here: http://bit.ly/NYSPK07Reg

Amplify your knowledge at Visual Studio Live! New York — bring the issues that keep you up at night and prepare to leave this event with the answers, guidance and training you need. Register now: http://bit.ly/NYSPK07Reg

CloudDevelop – Call for Speakers

As you may know, I am one of the organizers of a cloud computing conference in Columbus, OH called CloudDevelop. CloudDevelop’s mission is to bring together the brightest minds in cloud computing for a day of networking, sharing, and learning about all things cloud computing. I am extremely excited that CloudDevelop is coming back for its 4th year on Friday, October 23rd. This year, CloudDevelop is returning to were it all started at the Ohio Union on the campus of The Ohio State University.

CloudDevelop is currently accepting speaker submissions for potential sessions at CloudDevelop. The call for speakers window opened on Monday, June 8th and runs through midnight on Friday, June 26th. The organizing committee for CloudDevelop is looking for sessions on all things cloud. CloudDevelop is platform and technology agnostic – we want sessions on anything related to cloud computing. We want a diverse mix of sessions – meaning sessions on Amazon AWS, Microsoft Azure, Google, Salesforce, Rackspace, Heroku, etc. Anything cloud related – CloudDevelop is interested in it!

You can find more information on the call for speakers at http://blog.clouddevelop.org/2015/06/15/clouddevelop-2015-calling-all-speakers/.

To submit a session, please do so at http://bit.ly/CloudDevelopSpeakers2015.

Lock Down Your Azure Resources

Note: This post is cross-post with http://blogs.msdn.com/b/cloud_solution_architect.

A common ask when working with Azure is “how do I protect my critical resources?” Often this ask is related to protecting those resources from a human doing something they shouldn’t be doing.  There are a few ways to apply varying levels of control to Azure resources: role-based access control (RBAC) and resource locks.

Role-Based Access Control (RBAC)

Using role-based access control, it is possible to restrict control-plane access to various Azure resources. Roles can be assigned at the subscription, resource group, and individual resource scope. A role is associated with specific rights (one or more “action” and “not action” values). There are currently 23 built-in roles.

Leveraging RBAC is a great way to help protect resources in Azure. With the Reader and various Contributor roles (e.g. Virtual Machine Contributor, Storage Account Contributor, SQL DB Contributor, etc.), you can effectively limit the actions that a user can take against a resource. However, even with one of the Contributor roles, it is still possible to delete specific resources. This makes it very easy to accidently delete an item . . . often an item that is not so easy to get back, like an Azure storage account. Oops! Resource locks provide a way to prevent the deletion of resources.

To learn more about RBAC, please check out Neil Mackenzie’s earlier blog post entitled “RBAC and the Azure Resource Manager”, as well as “Role-based access control in the Microsoft Azure portal” on the main Azure site.

Resource Locks

The remainder of this post will focus on a relatively new feature with Azure Resource Manager, resource locks. In order to work with resource locks, you will need to use the Azure PowerShell cmdlets in Azure Resource Manager mode.

Switch-AzureMode AzureResourceManager

You will also need to be in either an Owner or User Access Administrator role for the desired scope, as only Owners and User Access Administrators have permission to write against the Microsoft.Authorization resources, which is the resource provider that handles resource locks.

For more on information on the “actions” and “not actions” associated with specific roles, please see https://azure.microsoft.com/en-us/documentation/articles/role-based-access-control-configure/#built-in-roles.

Create a Resource Lock

A resource lock can be applied at either the resource group or resource scope. As of Azure PowerShell version 0.9.3, there is only one lock level available – CanNotDelete.

To see resource locks in action, let’s use a basic example of creating an Azure Storage account and applying the CanNotDelete resource lock.

$resourceGroup = 'CollierMedia'
$location = 'East US'
$storageAccountName = 'collierfiles2014'

# Create a new resource group.
New-AzureResourceGroup -Name $resourceGroup -Location $location

# Create a new Azure storage account in the new resource group.
New-AzureStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName -Location $location -Type Standard_GRS

# Apply a resource lock to the storage account.
New-AzureResourceLock -LockLevel CanNotDelete `
-LockNotes 'No deleting!' `
-LockName 'CollierLock' `
-ResourceName $storageAccountName `
-ResourceType 'Microsoft.Storage/storageAccounts' `
-ResourceGroup $resourceGroup -Verbose

# List all the resource locks in the current subscription.
Get-AzureResourceLock

After executing the above PowerShell commands, you should see the following output result:

powershell - create account and apply lock

Notice in the above screenshot that the “CollierMedia” resource group was created, a new ‘collierfiles2014’ storage account was created in the “CollierMedia” resource group, and a resource lock applied to the storage account. Take particular note of the ResourceId for the lock – that will be helpful when trying to delete a lock.

With the resource lock in place an attempt to delete the storage account fails with an error message indicating the specified resource is locked and needs to be removed before being deleted:

powershell - delete account (masked)

The above example demonstrated creating a resource lock for a specific resource. But what about locking an entire resource group? Placing a resource lock on an entire group could be helpful in situations where you want to ensure no resources in that group are deleted – for example, a resource group containing storage accounts used for Azure Virtual Machines. The example below demonstrates placing a lock on the entire “CollierMedia” resource group.

New-AzureResourceLock -LockLevel CanNotDelete `
-LockNotes 'No deleting!' `
-LockName 'CollierGroupLock' `
-ResourceGroup 'CollierMedia' -Verbose

powershell - lock resource group (masked)

Remove a Resource Lock

One easy way to remove the resource is to use the ResourceId associated with the lock. The ResourceId is returned when creating the lock. It can also be obtained via the Get-AzureResourceLock cmdlet. By default, Get-AzureResourceLock returns a list of all the locks in the current Azure subscription.

powershell - get-azureresourcelock (masked)

View the help (> get-help Get-AzureResourceLock) for additional ways to filter the locks returned.

Use the Remove-AzureResourceLock cmdlet, providing the desired ResourceId, to remove the lock.

Remove-AzureResourceLock -ResourceId '/subscriptions/0bbbc191-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/CollierMedia/providers/Microsoft.Storage/storageAccounts/collierfiles2014/providers/Microsoft.Authorization/locks/CollierLock'

Lock Resources using an Azure Resource Manager Template

Another way to lock a resource is to do so at creation time with an Azure Resource Manager (ARM) template. An ARM template is a JSON-formatted file that describes the desired state of a logically related group of Azure resources. The template could contain the resources needed for a specific web site (i.e. Azure Redis Cache, Azure SQL Database, Azure Storage account and Azure Web App.) or perhaps just a group of related Azure Virtual Machines – the choice is yours. For more information on Azure Resource Manager templates, please follow the links below:

The ARM template below shows an example of creating an Azure storage account and applying the CannotDelete resource lock.

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"newStorageAccountName": {
"type": "string",
"metadata": {
"description": "Name of the Storage Account"
}

},

"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS"
],

"metadata": {
"description": "Storage Account type"

}

},

"location": {
"type": "string",
"allowedValues": [
"East US",
"West US",
"West Europe",
"East Asia",
"Southeast Asia"
],

"metadata": {
"description": "Location of storage account"
}
}
},

"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('newStorageAccountName')]",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties": {
"accountType": "[parameters('storageAccountType')]"
},

"resources": [
{
"type": "Microsoft.Storage/storageAccounts/providers/locks",
"name": "[concat(parameters('newStorageAccountName'), '/Microsoft.Authorization/collierLock')]",
"apiVersion": "2015-01-01",
"dependsOn": [ "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]" ],
"properties": {
"level": "CannotDelete",
"notes": "Mike's important files - do not delete!"
}
}
]
}
]
}

The corresponding template parameters files is as follows:

{

"location": {

"value": "East US"

},

"newStorageAccountName" : {

"value" : "collierimages2015"

},

"storageAccountType": {

"value": "Standard_GRS"

}

}

To execute this template, the following PowerShell cmdlet can be used:

New-AzureResourceGroupDeployment -ResourceGroupName 'CollierMedia' `
-TemplateFile 'azuredeploy.json' `
-TemplateParameterFile 'azuredeploy.parameters.json' `
-Verbose

Tip: If you would like to validate an ARM template before deploying it, you can use the Test-AzureResourceGroupTemplate cmdlet, such as:

Test-AzureResourceGroupTemplate -ResourceGroupName 'CollierMedia' `
-TemplateFile 'azuredeploy.json' `
-TemplateParameterFile 'azuredeploy.parameters.json' `
-Verbose

Executing the above cmdlets should result in the following output:

powershell - new-azureresourcegroup - create account with locks 2

If we execute the Get-AzureResourceLock cmdlet again, we would notice the resource lock, “collierlock”, successfully applied to just the newly created ‘collierimages2015’ storage account.

powershell - get-azureresourcelock 2 (masked)

One thing to notice about the ARM template for creating the resource lock on the storage account is the name of the nested resource. In the template (see screenshot below as well), you will notice the resource name is a concatenation of the storage account name and the name of the lock – specifically “/Microsoft.Authorization/collierlock”. In this example, “collierlock” is the name of the resource lock.

arm - resource lock name example

The resource name needs to reflect that this is a nested resource. Otherwise, if the resource name was something as simple as “collierlock”, attempting to validate the template with Test-AzureResourceGroupTemplate will result in an error such as the following:

Code    : InvalidTemplate

Message : Deployment template validation failed: ‘The template resource ‘collierlock’ for type ‘Microsoft.Storage/storageAccounts/providers/locks’ at line ’49’ and column ’14’ has incorrect segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name.’.

Wrapping It Up

Azure’s Role Based Access Control features, along with resource locks, provide multiple options helping to secure critical Azure resources. RBAC roles provide a great way to limit actions against various types of Azure resources. However, an Owner can still delete a resource. Mistakes happen, and a valuable resource might be accidentally deleted. By leveraging a resource lock on those most crucial Azure resources, you can help to eliminate those “oops . . . did I delete that” moments.

The preceding sections demonstrated how to create a resource lock against an individual storage account, removing the resource lock, and applying a resource lock as part of an Azure Resource Manager template. For another example of using resource locks, please see Alexandre Brisebois’ post at https://alexandrebrisebois.wordpress.com/2015/05/26/protect-mission-critial-azure-virtual-machines-from-accidental-deletion/. In his post, Alexandre demonstrates how to create a resource lock against Azure Virtual Machines.

 

Special thanks to Neil Mackenzie, Charles Lamanna, and Ryan Jones for helping to review this post. Credit to Ryan Jones’ sample on GitHub for information on how to create the resource lock for the storage account.

Audit Logs for Azure Events

Note: This post is cross-post with http://blogs.msdn.com/b/cloud_solution_architect.

A common ask when working with Microsoft Azure is, “how can I view audit logs to determine who made changes to the subscription(s) and the related Azure resources?” With the new Audit Logs feature now available in the Azure Preview Portal (https://portal.azure.com), keeping track of changes becomes easier than ever.

Before getting started, I would like to point out that Dushyant Gill has an excellent post in which he provides one way to answer three very important questions:

  1. Who all has access to my subscriptions?
  2. What access does a specific user have on my subscription?
  3. Who gained (or lost) access to my subscriptions in the past n days?

In Dushyant’s post, he describes a PowerShell module he wrote that makes it easy to generate reports which answer the above questions.

In this post, I will describe one approach for working with the new Azure Audit Logs to determine what changed, and also how to view logs related to the addition or removal of co-administrators. Along with the PowerShell module described in Dushyant’s post, this should allow much more insights into the actions taken against an Azure subscription.

What Changed?

By opening the new Audit Logs blade in the Azure Preview Portal, you can view many of the events that occurred against the subscription.

Audit Logs - Single

The Audit Logs blade will show five key pieces of information for the list of operations:

  1. Operation name
  2. Level: Critical, Error, Warning, or Informational
  3. Status: Succeeded or Failed
  4. Resource
  5. Time

Note 1: Use the Filter action at the top of the Audit Logs blade to modify the operations displayed.

Note 2: Audit logs are available for only the Azure resources available in the Azure Preview Portal and/or the Azure Resource Manager API.

 

Clicking on a specific operation will open additional blades to view more detailed information.

Full Audit Logs

The Detail blade will allow you to view key pieces of information such as who made the change, when the change was made, what was the Azure resource and resource group affected, etc.

Audit Logs - Detail Blade

If the change is related to a role assignment (i.e. adding a user to one of the new Role Based Access Control (RBAC) related roles – Virtual Machine Contributor, Website Contributor, etc.), the Detail blade will show additional information to show the user that was added to the role. At the time of this writing, however, the user and role is displayed as a GUID and not the corresponding friendly name (e.g. email address or role name). It is anticipated this will be cleaned up in a feature update to the Azure Preview Portal. In the meantime, you can leverage a few Azure PowerShell cmdlets to piece together the relevant information.

In the Details blade, have a look at the PROPERTIES value. There are two key pieces of information here: the PrincipalId and the RoleDefinitionId.

Audit Logs - Add User to RBAC Role

For the cmdlets below, you will need to switch to the Azure Resource Manager mode in Azure Powershell by executing the command below.  Azure PowerShell version 0.8.15 is used in this post, although the cmdlets below first appeared in version 0.8.14.

Switch-AzureMode AzureResourceManager

 

To determine the role to which the user was assigned, use the Get-AzureRoleDefintion cmdlet. This cmdlet returns all the role definitions, so you will need to search for the specific definition (the GUID after “roleDefinition”) as indicated the PROPERTIES in the Detail blade.

Get-AzureRoleDefinition | where { ($_.Id -like "*de139f84-1756-47ae-9be6-808fbbe84772" )}

Get-AzureRoleDefinition

To get the user added to the role, use the Get-AzureAdUser cmdlet. For the -ObjectId parameter, use the PrincipalId value from the PROPERTIES in the Detail blade.

Get-AzureADUser -ObjectId b458cfbc-a101-45c1-9575-1ec43aefcc45

Get-AzureAdUser

What about Co-Administrators and Management Certificates?

When it comes to permissions in Azure, there are three approaches that can be used: the co-administrator model, management certificates, and Role Based Access Control (RBAC). The co-administrator model has been the security model in working with Azure for quite some time. People that wanted to work with Azure (assuming they were not the subscription owner) could be granted access to the Azure subscription as a co-administrator. As a co-administrator, they could take any action they desired against any Azure service.  If non-interactive administrative access was needed, a management certificate could be added to the target subscription(s).

Going back to the earlier topic of what change was made and by whom – how can we tell when a co-administrator or management certificate was added to the subscription?

For management certificates, you can use the List Subscription Operations service management API call. There is an Operation Name parameter in the response that should include AddSubscriptionCertificate and RemoveSubscriptionCertificate for the respective action.

AddSubscriptionCertificate

An astute observer of the MSDN documentation for List Subscription Operations will notice there is no corresponding operation for co-administrators. To obtain information about co-administrators being added or removed from a subscription, we can leverage the new audit logs available in the Azure Preview Portal and Azure Resource Manager API. Co-administrators in the Azure Management Portal actually correspond to the Owner role available in the Azure Preview Portal (and thus Azure Resource Manager). Adding or removing a co-administrator via the Azure Management Portal creates an event with Azure Resource Manager that is then logged. However, as of the time of this writing, the addition or removal of co-administrators is not yet reported in the Azure Preview Portal’s Audit Logs.

To get the audit logs related to the addition or remove of co-administrators, you can use the Get-AzureResourceProviderLog PowerShell cmdlet. This cmdlet retrieves the operations associated for a specific Resource Provider – in this case we would want operations against the Microsoft.Authoriztion provider. For example, to view the logs for a certain date range, you could execute a command similar to the following:

Get-AzureResourceProviderLog -ResourceProvider "Microsoft.Authorization" -StartTime "2015-03-10" -EndTime "2015-03-11" -DetailedOutput

A screenshot of the output is below. In the screenshot you’ll notice you can see the adminEmail and adminType for the user added via the Azure Management Portal.

Get-AzureResourceProviderLog - Add CoAdmins

Alternatively, you could get all the authorization events starting from a specific date up until the current time:

Get-AzureResourceProviderLog -ResourceProvider "Microsoft.Authorization" -StartTime "2015-03-11" -DetailedOutput

Get-AzureResourceProviderLog - Remove CoAdmins

It should be noted that there currently does seem to be at least two known bugs related to the shown information:

  1. If the removed co-admin is an Azure AD user (as opposed to a Microsoft Account), the adminEmail is empty
  2. The Caller value is not populating

 

But Wait! There’s More!!

The Get-AzureResourceProviderLog cmdlet shown above is just one of the new audit log cmdlets now available. It is easy to view the additional cmdlets by executing the following command:

get-help get-*Azure*log*
Name Description
Get-AzureSubscriptionIdLog Gets the operations associated with the current subscriptionId
Get-AzureResourceProviderLog Gets the operations associated with a Resource Provider
Get-AzureResourceLog Gets the operations associated with a ResourceId
Get-AzureResourceGroupLog Gets the deployment log for a resource group
Get-AzureCorrelationIdLog Gets the operations associated with a CorrelationId.

 

Summary

The new Audit Logs feature in the Azure Preview Portal, combined with new Azure PowerShell cmdlets, make it easier than ever to have insights into the changes made to an Azure subscription. Together, with the module described in Dushyant Gill’s post, many of the administrative actions taken against an Azure subscription and related resources are now easily viewable.

Please be sure to be using at least version 0.8.14 (that’s when the cmdlets mentioned in this post first appeared) of the Azure PowerShell cmdlets, and switch to use the AzureResourceManager mode for the cmdlets as well.