Often happens that agility and freedom conflict with security.
(Aaron Paul voice) Has this ever happened to you?
Have you ever had developer teams request ownership of a full subscription to be able to freely experiment?
You still want to keep isolation, segregate responsibilities and permissions.
Ability to experiment freely is paramount to innovation, but uncontrolled proliferation of subscriptions can bear a significant management overhead.
Can we have the best of both worlds? The short answer is: yes.
A resource group can be an effective boundary as it allows its contributors to yet create any resource, but also restrict the scope of access within a subscription.
You can also enforce tags and Azure policies to control costs and enforce security.
But now a team is restricted to creating resources within a single resource group and it can get messy quite quickly and permission-wise is not so granular within teams.
What if we could allow teams to create their own resource groups within a subscription with contributor access and not being able to read/write other resource groups?
We can quickly set up a Logic App to enable this. Orchestrating creation and role assignment of resource groups within a single workflow, this enables:
- Invoking the Logic App manually through a REST API
- Invoking from a DevOps pipeline to create resources as part of a dev/test automated environment
The Logic App can create a resource group and assign a certain contributor based on the input payload from the HTTP trigger.
Warning
Setting up this workflow could lead to a security loophole: What if someone uses the name of an existing resource group so the workflow grants access to other teams' resources? We need to make sure we address this concern when building our Logic App as, usually, Azure management operations are idempotent and the Logic App won't fail if we pass the name of an existing resource group.
The Logic App
Let's have a look at the flow:
The logic is pretty simple and most of the operations we require have a native connector; the only missing one is "Create role assignment", but we can easily perform the operation by invoking the Azure REST API and we do not have to worry about authentication as the managed identity will do this for us.
Now, you may have noticed that there is no condition stating: "If the group already exists, interrupt the flow"; but if you look at the picture above, you may notice a red dotted line between two operations, this is because we changed the Run after
settings of our create operation:
As you can see, the operation of creation (and so the role assignment after) will only occur if the Read resource group
failed, hence the group does not exist; this will block the loophole described above.
Now, we can prevent people from having broad access, but our Logic App's managed identity still requires owner permissions on the subscription; more secure, but we can do even better. Let's create a custom role that has only enough permissions to read/create a resource group and assign permissions to it:
We can now use the UI to create a custom role, but we may also want to script it and define it in JSON format:
Let's now assign that to the managed identity of our Logic App:
Now we should have all permissions in place. If you also want to use a security group, your Logic App identity may also require Directory.Read.All
permissions on your Azure AD instance.
Creating role assignment
I mentioned above that all the other actions can be performed with native Logic App connectors, but the role assignment, at the time of writing, requires the HTTP connector to invoke the Azure REST API, let's have a look at that:
Even if we cannot do it in idiomatic Logic App, that is yet pretty simple.
This is the API documentation: https://docs.microsoft.com/en-us/rest/api/authorization/role-assignments/create
We just need to set the right method (PUT
), the correct URL, using the resource group ID as scope from the output of the previous connector and we can auto-generate a random GUID as name for the assignment using Logic App expressions (guid()
). The body must contain the role definition ID
, which needs to be the built-in contributor
GUID under our subscription ID, I have used a variable for that to improve clarity:
The subscription ID is one of the input of our workflow and the hardcoded GUID can be found here: Contributor
To get values as input from the workflow invocation, we need to set the input JSON schema in the HTTP trigger:
We need:
-
principalId
The object ID of the assignee of the contributor role, this can be found looking up the user in Azure AD from the portal -
resourceGroupLocation
,resourceGroupName
,subscriptionId
Quite self-explanatory arguments
{
"properties": {
"principalId": {
"type": "string"
},
"resourceGroupLocation": {
"type": "string"
},
"resourceGroupName": {
"type": "string"
},
"subscriptionId": {
"type": "string"
}
},
"type": "object"
}
After adding the schema above to the trigger, those values will be available as variables in the rest of the workflow.
Testing
All there is left now is to test, let's run our workflow with the following input:
{
"subscriptionId": "<Target subscription ID to create groups>",
"resourceGroupName": "rg-test2",
"resourceGroupLocation": "West Europe",
"principalId": "<Object ID GUID of your user from AAD>"
}
And this is what happens, assuming rg-test2
does not exist:
Looks good, all the steps we wanted to run were successful.
Now, let's try and run this again with the same inputs:
OK, as you can see from the grey circles next to the actions below Read a resource group
, none of the other operations were performed, exactly as expected.
Now let's have a look at our newly created resource group IAM blade:
Exactly what we wanted; a new resource group of which I am contributor without requiring any permission on the subscription.
The full template for the Logic App is available on my GitHub so you can save the extra 5 minutes it took me to create it to enable this security feature for your teams.
You can also enhance security of the Logic App to prevent unauthorised users from calling it by fronting it with API Management or you can use Azure Active Directory Authorization Policies on the Logic App itself or a combination of the two.
Top comments (0)