Automate NSG Rule Expiration
Build Logic Apps that automatically disable expired NSG rules using date-encoded names.
Introduction
30 Second Summary
Imagine granting someone temporary access to your network, like a visitor badge that should expire at 5pm. Now imagine forgetting to deactivate that badge, leaving the door open indefinitely.
In this project, you will build a pair of Azure Logic Apps that automatically manage temporary network access rules. One Logic App checks for expired connections on a schedule and disables them, while the other creates new time-limited rules on demand.
What You'll Build
Two Logic Apps that automate the full lifecycle of temporary Network Security Group rules by embedding expiration dates directly in rule names.
By the end of this project, you'll have:
- A scheduled Logic App that scans NSG rules for embedded expiration dates and automatically flips expired rules from Allow to Deny.
- An HTTP-triggered Logic App that accepts connection requests and creates properly named NSG rules with built-in expiration times.
- A Managed Identity configured for secure, credential-free access to the Azure REST API.
- Secret Mission: add email notifications that alert you whenever a rule is automatically disabled.
Are there any prerequisites?
You need an active Azure subscription with permissions to create resources and assign roles. No prior experience with Logic Apps or NSGs is required.
Create the Resource Group and NSG
Before you can automate anything, you need the infrastructure your Logic Apps will manage. The core problem is straightforward: temporary network connections need a home, and that home needs rules your automation can read and update.
In this step, you will create a Resource Group to hold all project resources, a Network Security Group to manage connection rules, and a set of sample rules that use a special naming convention your Logic App will parse later.
In this step, get ready to:
- Create a resource group to contain all project resources.
- Create a Network Security Group inside that resource group.
- Add three sample inbound rules using the expiration-date naming convention.
Create the resource group
A resource group is a container that holds related Azure resources together. Everything you build in this project will live inside a single resource group so you can manage and clean up resources easily.
- Sign in to the Azure portal.
- In the search bar at the top of the portal, type Resource groups and select Resource groups from the results.
- Click + Create.
- Select your Azure subscription from the Subscription dropdown.
- In the Resource group field, enter rg-nsg-expiration.
- In the Region dropdown, select a region close to you (for example, East US).
- Click Review + Create.
- Click Create.
After a few seconds, you should see a notification confirming the resource group was created. Click Go to resource group to open it.
Don't see the resource group?
Click Refresh at the top of the Resource groups list. It can take a moment to appear.
If you see a permissions error, confirm your account has Contributor or Owner access on the subscription.
Help me troubleshoot my resource group creation
Create the Network Security Group
A Network Security Group (NSG) contains a list of security rules that allow or deny network traffic. Your Logic App will read and update these rules programmatically using the Azure REST API.
- In the search bar at the top of the portal, type Network security group and select Network security groups from the results.
- Click + Create.
- Select your Azure subscription from the Subscription dropdown.
- In the Resource group dropdown, select rg-nsg-expiration.
- In the Network security group name field, enter nsg-connections.
- In the Region dropdown, select the same region you used for the resource group.
- Click Review + create.
- After you see the Validation passed message, click Create.
Wait for the deployment to complete, then click Go to resource to open your new NSG.
What is a Network Security Group?
An NSG acts as a virtual firewall for Azure resources. It contains a prioritized list of security rules that allow or deny inbound and outbound network traffic.
Each rule has a priority between 100 and 4096. Lower numbers are evaluated first. In this project, your Logic App will flip a rule's access from Allow to Deny when its embedded expiration date passes.
Add sample inbound security rules
Your Logic App will identify expired rules by reading the expiration date embedded in each rule's name. The naming convention is Allow-CustomerName-YYYYMMDD-HHmm.
Why encode the date in the rule name?
NSG rule names can be up to 80 characters and support letters, numbers, hyphens, underscores, and periods. By encoding the expiration date directly in the name, you avoid needing an external database or tracking system.
The format YYYYMMDD-HHmm avoids colons (which are not allowed in NSG rule names) and is easy for the Logic App to parse with substring expressions.
You will now add three rules to test different scenarios: one that already expired, one expiring soon, and one far in the future.
- Inside your nsg-connections NSG, click Inbound security rules in the Settings section of the left sidebar.
- Click + Add to open the Add inbound security rule pane.
Create the first rule. This one has an expiration date in the past, so your Logic App should flag it as expired.
- Set Source to IP Addresses and enter 10.0.1.0/24 in the source IP field.
- Leave Source port ranges as *.
- Leave Destination as Any.
- Set Destination port ranges to 443.
- Set Protocol to TCP.
- Set Action to Allow.
- Set Priority to 200.
- Set Name to Allow-CustomerA-20240101-0000.
- Click Add.
What does this rule name mean?
The name Allow-CustomerA-20240101-0000 tells your Logic App that this rule grants access to CustomerA and expired on January 1, 2024 at 00:00 UTC. Since that date is already in the past, your automation will detect it as expired.
Now add the second rule. This one should have an expiration time a few minutes in the future so you can watch it expire during testing.
- Click + Add again to open a new rule pane.
- Set Source to IP Addresses and enter 10.0.2.0/24.
- Leave Source port ranges as *.
- Leave Destination as Any.
- Set Destination port ranges to 1433.
- Set Protocol to TCP.
- Set Action to Allow.
- Set Priority to 300.
- For the Name, enter Allow-CustomerB- followed by a date and time 5 minutes from now in YYYYMMDD-HHmm format (for example, if it is currently May 11, 2026 at 14:30 UTC, enter Allow-CustomerB-20260511-1435).
- Click Add.
Finding the current UTC time
Check the current UTC time at the top-right of the Azure portal (click the clock icon). Use that time plus 5 minutes for the rule name. This gives you a rule that will expire while you test later.
Finally, add the third rule with a far-future expiration. This rule should remain untouched by your automation.
- Click + Add one more time.
- Set Source to IP Addresses and enter 10.0.3.0/24.
- Leave Source port ranges as *.
- Leave Destination as Any.
- Set Destination port ranges to 22.
- Set Protocol to TCP.
- Set Action to Allow.
- Set Priority to 400.
- Set Name to Allow-CustomerC-20991231-2359.
- Click Add.
You should now see all three rules listed under Inbound security rules with their Access column showing Allow for each.
Why three different rules?
Each rule tests a different scenario your Logic App must handle correctly.
- Allow-CustomerA-20240101-0000 is clearly expired. Your automation should flip it to Deny.
- Allow-CustomerB (near future) will expire during your testing window so you can watch the automation catch it.
- Allow-CustomerC-20991231-2359 won't expire until 2099. Your automation should leave it alone.
Rule name rejected?
NSG rule names must begin with a letter or number, end with a letter, number, or underscore, and can only contain letters, numbers, underscores, periods, or hyphens. Make sure there are no colons or spaces in your rule name.
If you see a priority conflict, each rule needs a unique priority number between 100 and 4096.
Help me fix my NSG rule creation error
Your infrastructure is ready. You have a resource group, an NSG, and three rules that your Logic App can scan. Next up, you will create the Logic App that reads these rules and automatically disables any that have expired.
Create the Expiration Checker Logic App
Your NSG has three rules with expiration dates baked into their names. Now you need something that can read those names, check whether the dates have passed, and flip expired rules to Deny.
That something is an Azure Logic App. In this step, you'll create a Consumption-tier Logic App and give it permission to manage your NSG rules using a Managed Identity.
In this step, get ready to:
- Create a Consumption Logic App inside your resource group.
- Enable a system-assigned Managed Identity on the Logic App.
- Assign the Network Contributor role so the Logic App can read and update NSG rules.
Create a Consumption Logic App
Azure Logic Apps come in two tiers. The Consumption tier runs pay-per-execution and includes 4,000 free built-in action executions per month. It is the right fit for a lightweight hourly check like this one.
- In the Azure Portal search bar at the top, type Logic apps and select Logic apps from the results.
- Click Add.
- Under Plan type, select Consumption.
- Set Subscription to your active Azure subscription.
- Set Resource Group to rg-nsg-expiration.
- In the Logic App name field, enter la-expiration-checker.
- Set Region to the same region you used for your resource group.
- Click Review + create.
- Click Create.
Wait for the deployment to complete. You should see a Your deployment is complete message.
- Click Go to resource to open your new Logic App.
Why Consumption instead of Standard?
The Standard tier requires a dedicated hosting plan that charges even when idle. Consumption charges only when the workflow executes. For an hourly check with a handful of actions, you will stay well within the 4,000 free executions per month.
Don't see the Consumption option?
Make sure you clicked Add from the Logic apps service page. If you landed on a quickstart or template gallery, close it and look for the Add button in the top toolbar.
help me create a Consumption Logic App in the Azure Portal
Enable the system-assigned Managed Identity
Your Logic App needs to call the Azure REST API to read and update NSG rules. A Managed Identity lets the Logic App authenticate to Azure without storing any credentials.
- In the Logic App sidebar, scroll down to Settings and click Identity.
- On the System assigned tab, toggle Status to On.
- Click Save.
- A confirmation dialog appears asking if you want to enable the system-assigned managed identity. Click Yes.
After a moment, you should see an Object (principal) ID appear on the page. This confirms the identity is active.
What is a Managed Identity?
A Managed Identity is an Azure-managed service principal tied to your resource. Azure handles the credential rotation and lifecycle automatically. Your Logic App can present this identity when calling Azure APIs, and Azure verifies it without any passwords or keys stored in your workflow.
Assign the Network Contributor role
The identity exists, but it has zero permissions right now. You need to grant it the Network Contributor role so it can read and update NSG rules inside your resource group.
- In the Azure Portal search bar, type Resource groups and select Resource groups.
- Click rg-nsg-expiration.
- In the resource group sidebar, click Access control (IAM).
- Click Add, then select Add role assignment.
- In the Role tab, search for Network Contributor and select it.
- Click Next.
- Under Assign access to, select Managed identity.
- Click Select members.
- In the side panel, set the Managed identity dropdown to Logic app (Consumption).
- Select la-expiration-checker from the list.
- Click Select.
- Click Review + assign twice to confirm.
You should see a notification confirming the role assignment was added successfully.
Why Network Contributor?
The Network Contributor role grants read/write/delete permissions on network resources like NSGs and their security rules. Your Logic App needs this to list existing rules and update their access property from Allow to Deny.
Don't see la-expiration-checker in the member list?
Make sure you set the Managed identity dropdown to Logic app (Consumption) specifically. If it is set to a different resource type, your Logic App won't appear.
Also confirm that you enabled the system-assigned identity in the previous substep. Go back to your Logic App's Identity page and check that Status is On with an Object ID visible.
help me assign a role to my Logic App's Managed Identity
Your Logic App is created, authenticated, and authorized. Next up, you'll build the actual workflow that scans rule names, parses expiration dates, and disables anything that's past due.
Build the Expiration Checking Workflow
Your Logic App has an identity and the right permissions to manage NSG rules. Now it needs a workflow that actually does the work.
The workflow needs to run on a schedule, fetch all the rules from the NSG, parse each rule name for an embedded expiration date, and flip any expired rules from Allow to Deny. The date-parsing expressions are complex enough that building them in the visual designer would be tedious. Instead, you will paste the entire workflow definition as JSON using the code view editor.
In this step, get ready to:
- Switch to code view in the Logic App designer.
- Paste the full workflow definition including the Recurrence trigger, HTTP actions, and expiration logic.
- Replace the placeholder subscription ID with your real one and save.
Open the Logic App code view
The code view editor lets you define the entire workflow as a JSON document. This gives you full control over expressions that the visual designer cannot easily produce.
- In the Azure Portal, navigate to your la-expiration-checker Logic App.
- On the Logic App sidebar menu, under Development Tools, select Logic app code view.
The code view editor opens and shows the workflow definition in JSON format. You should see a mostly empty definition with an empty triggers and actions section.
Why code view instead of the visual designer?
The expiration-checking logic uses substring and concat expressions to parse dates from rule names. These expressions are much easier to write correctly in JSON than to assemble through the designer's expression editor. Code view also lets you paste the entire workflow at once.
Paste the workflow definition
The workflow JSON defines everything: the hourly Recurrence trigger, the HTTP call to list NSG rules, a For Each loop that checks each rule, and a nested condition that disables expired ones.
- Select all the existing content in the code view editor and delete it.
- Paste the following JSON into the empty code view editor:
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"Recurrence": {
"type": "Recurrence",
"recurrence": {
"frequency": "Hour",
"interval": 1
}
}
},
"actions": {
"List_NSG_Rules": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://management.azure.com/subscriptions/your-subscription-id/resourceGroups/rg-nsg-expiration/providers/Microsoft.Network/networkSecurityGroups/nsg-connections/securityRules?api-version=2023-11-01",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://management.azure.com/"
}
},
"runAfter": {}
},
"For_Each_Rule": {
"type": "Foreach",
"foreach": "@body('List_NSG_Rules')?['value']",
"actions": {
"Condition_Is_Allow_Rule": {
"type": "If",
"expression": {
"and": [
{
"equals": [
"@items('For_Each_Rule')?['properties']?['access']",
"Allow"
]
},
{
"startsWith": [
"@items('For_Each_Rule')?['name']",
"Allow-"
]
}
]
},
"actions": {
"Condition_Is_Expired": {
"type": "If",
"expression": {
"and": [
{
"less": [
"@addToTime('1601-01-01T00:00:00Z', 0, 'Second', concat(substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 13), 4), '-', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 9), 2), '-', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 7), 2), 'T', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 4), 2), ':', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 2), 2), ':00Z'))",
"@utcNow()"
]
}
]
},
"actions": {
"Disable_Expired_Rule": {
"type": "Http",
"inputs": {
"method": "PUT",
"uri": "https://management.azure.com/subscriptions/your-subscription-id/resourceGroups/rg-nsg-expiration/providers/Microsoft.Network/networkSecurityGroups/nsg-connections/securityRules/@{items('For_Each_Rule')?['name']}?api-version=2023-11-01",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://management.azure.com/"
},
"body": {
"properties": {
"access": "Deny",
"direction": "@items('For_Each_Rule')?['properties']?['direction']",
"priority": "@items('For_Each_Rule')?['properties']?['priority']",
"protocol": "@items('For_Each_Rule')?['properties']?['protocol']",
"sourceAddressPrefix": "@items('For_Each_Rule')?['properties']?['sourceAddressPrefix']",
"sourcePortRange": "@items('For_Each_Rule')?['properties']?['sourcePortRange']",
"destinationAddressPrefix": "@items('For_Each_Rule')?['properties']?['destinationAddressPrefix']",
"destinationPortRange": "@items('For_Each_Rule')?['properties']?['destinationPortRange']"
}
}
},
"runAfter": {}
}
},
"else": {
"actions": {}
},
"runAfter": {}
}
},
"else": {
"actions": {}
},
"runAfter": {}
}
},
"runAfter": {
"List_NSG_Rules": [
"Succeeded"
]
}
}
},
"outputs": {}
},
"parameters": {}
}
What does this workflow do?
The workflow has four main pieces:
- Recurrence trigger fires the workflow every 1 hour.
- List_NSG_Rules calls the Azure REST API with a GET request to fetch every security rule in nsg-connections. It authenticates using the Logic App's Managed Identity.
- Condition_Is_Allow_Rule filters to only rules whose access is Allow and whose name starts with Allow-. This skips default rules and already-denied rules.
- Condition_Is_Expired extracts the last 13 characters of the rule name (the YYYYMMDD-HHmm portion), reconstructs an ISO timestamp, and compares it to utcNow(). If the expiration time is in the past, Disable_Expired_Rule sends a PUT request to update the rule's access from Allow to Deny.
Changing access to Deny instead of deleting the rule preserves an audit trail. You can always see which rules were active and when they expired.
How does the date-parsing expression work?
The substring expressions count backwards from the end of the rule name. For a rule named Allow-Contoso-20260515-1430, the last 13 characters are 20260515-1430.
The expression slices out the year (4 chars), month (2 chars), day (2 chars), hour (2 chars), and minute (2 chars). It then concatenates them with separators into a full ISO timestamp like 2026-05-15T14:30:00Z for comparison against the current UTC time.
Replace the subscription ID and save
The JSON contains two placeholder values of your-subscription-id that you need to replace with your actual Azure subscription ID.
- Find your subscription ID by searching Subscriptions in the Azure Portal search bar.
- On the Subscriptions page, find your subscription in the list and note the Subscription ID shown in the second column.
- Click the subscription name, then click the copy icon next to the Subscription ID in the Essentials section.
- Go back to your Logic App's code view.
- Use Cmd+F (macOS) or Ctrl+F (Windows) to search for your-subscription-id.
- Replace both occurrences with your copied subscription ID.
- Click Save on the code view toolbar.
Why are there two subscription ID placeholders?
The first appears in the List_NSG_Rules GET action URI. The second appears in the Disable_Expired_Rule PUT action URI. Both calls target the same NSG in the same subscription.
After saving, you should see a green notification confirming the workflow saved successfully. Azure publishes the changes immediately.
Seeing a validation error when you save?
Make sure you replaced the full text your-subscription-id and did not accidentally delete the surrounding slashes in the URI. The subscription ID format looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (a 36-character GUID with hyphens).
Also check that you did not accidentally remove any curly braces or quotation marks while editing. If in doubt, re-paste the full JSON and try again.
Help me fix a validation error in my Logic App code view
✔️ Awesome, I've got everything!
Great. Double check you have saved your workflow in code view before moving on.
ⓧ I'd like to double check the full code
Here is the complete workflow JSON you should have in your code view (with your-subscription-id replaced by your actual subscription ID):
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"Recurrence": {
"type": "Recurrence",
"recurrence": {
"frequency": "Hour",
"interval": 1
}
}
},
"actions": {
"List_NSG_Rules": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://management.azure.com/subscriptions/your-subscription-id/resourceGroups/rg-nsg-expiration/providers/Microsoft.Network/networkSecurityGroups/nsg-connections/securityRules?api-version=2023-11-01",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://management.azure.com/"
}
},
"runAfter": {}
},
"For_Each_Rule": {
"type": "Foreach",
"foreach": "@body('List_NSG_Rules')?['value']",
"actions": {
"Condition_Is_Allow_Rule": {
"type": "If",
"expression": {
"and": [
{
"equals": [
"@items('For_Each_Rule')?['properties']?['access']",
"Allow"
]
},
{
"startsWith": [
"@items('For_Each_Rule')?['name']",
"Allow-"
]
}
]
},
"actions": {
"Condition_Is_Expired": {
"type": "If",
"expression": {
"and": [
{
"less": [
"@addToTime('1601-01-01T00:00:00Z', 0, 'Second', concat(substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 13), 4), '-', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 9), 2), '-', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 7), 2), 'T', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 4), 2), ':', substring(items('For_Each_Rule')?['name'], sub(length(items('For_Each_Rule')?['name']), 2), 2), ':00Z'))",
"@utcNow()"
]
}
]
},
"actions": {
"Disable_Expired_Rule": {
"type": "Http",
"inputs": {
"method": "PUT",
"uri": "https://management.azure.com/subscriptions/your-subscription-id/resourceGroups/rg-nsg-expiration/providers/Microsoft.Network/networkSecurityGroups/nsg-connections/securityRules/@{items('For_Each_Rule')?['name']}?api-version=2023-11-01",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://management.azure.com/"
},
"body": {
"properties": {
"access": "Deny",
"direction": "@items('For_Each_Rule')?['properties']?['direction']",
"priority": "@items('For_Each_Rule')?['properties']?['priority']",
"protocol": "@items('For_Each_Rule')?['properties']?['protocol']",
"sourceAddressPrefix": "@items('For_Each_Rule')?['properties']?['sourceAddressPrefix']",
"sourcePortRange": "@items('For_Each_Rule')?['properties']?['sourcePortRange']",
"destinationAddressPrefix": "@items('For_Each_Rule')?['properties']?['destinationAddressPrefix']",
"destinationPortRange": "@items('For_Each_Rule')?['properties']?['destinationPortRange']"
}
}
},
"runAfter": {}
}
},
"else": {
"actions": {}
},
"runAfter": {}
}
},
"else": {
"actions": {}
},
"runAfter": {}
}
},
"runAfter": {
"List_NSG_Rules": [
"Succeeded"
]
}
}
},
"outputs": {}
},
"parameters": {}
}
Your expiration checker workflow is defined and saved. Next up, you will trigger it manually and watch it identify and disable your expired test rule.
Test the Expiration Checker
Your expiration checker workflow is fully built. It has the logic to list rules, parse names, compare dates, and disable expired connections. Now you need to prove it actually works.
In this step, you will manually trigger the Logic App and trace its execution through the run history. You will confirm that the expired rule you created earlier gets its access changed from Allow to Deny, while the other rules remain untouched.
In this step, get ready to:
- Manually trigger the Logic App using the Run Trigger button.
- Verify the run completed successfully and processed all rules.
- Confirm the expired NSG rule was changed from Allow to Deny.
Manually trigger the Logic App
The Recurrence trigger is set to run every hour, but you do not need to wait. Azure provides a manual trigger button that fires the workflow immediately.
- In the Azure Portal, navigate to your la-expiration-checker Logic App.
- On the Logic App's Overview page, locate the toolbar at the top.
- Click Run Trigger in the toolbar.
- Select Recurrence from the dropdown menu.
A notification appears confirming the run was queued. The workflow starts executing immediately.
Why use Run Trigger instead of waiting?
The Run Trigger button fires the workflow on demand without changing the schedule. Your hourly recurrence stays intact for production use. This is the standard way to test Logic App workflows during development.
Verify the run in the history
Every Logic App execution is recorded in the run history with full details about each action. This is where you confirm the workflow processed your rules correctly.
- On the Logic App's Overview page, scroll down to the Runs history section.
- Click on the most recent run (it should show a status of Succeeded with a green checkmark).
- You are now in the run details view, which shows each action as a visual block.
- Click on the List_NSG_Rules action to expand it.
- Check the Outputs section. You should see a JSON response containing all three of your NSG rules.
- Click on the For_Each_Rule action to expand it.
- Inside the loop, look for the iteration that processed your expired rule (e.g., Allow-CustomerA-20240101-0000).
- Confirm that Condition_Is_Allow_Rule evaluated to true and Condition_Is_Expired evaluated to true for that rule.
- Click on the Disable_Expired_Rule action. Its status should show Succeeded with a 200 or 201 status code in the output.
What should I see for non-expired rules?
For your rules with future expiration dates, the Condition_Is_Expired condition should evaluate to false. The Disable_Expired_Rule action should not appear for those iterations because it was skipped.
Run shows Failed status?
Click into the failed run and expand the action that shows a red X. Common causes:
- A 403 Forbidden on the List_NSG_Rules action means the Managed Identity does not have the Network Contributor role. Go back to your resource group's Access control (IAM) and verify the role assignment.
- A 404 Not Found means the subscription ID, resource group name, or NSG name in your HTTP URI does not match what you created. Double check the URI in your List_NSG_Rules action.
- An expression error in the Condition_Is_Expired action means a rule name does not follow the expected Allow-CustomerName-YYYYMMDD-HHmm format. Verify your rule names in the NSG.
Help me debug my Logic App run failure
Confirm the NSG rule was updated
The run history confirms the workflow executed successfully. Now verify the actual NSG reflects the change.
- In the Azure Portal search bar, type nsg-connections and select your Network Security Group.
- Click Inbound security rules in the left menu under Settings.
- Find your expired rule (e.g., Allow-CustomerA-20240101-0000).
- Verify that its Action column now shows Deny instead of Allow.
- Check that your other two rules (the one expiring soon and the one far in the future) still show Allow in the Action column.
Why Deny instead of deleting the rule?
Changing the access to Deny instead of deleting the rule preserves your audit trail. You can see which rules were active, when they expired, and which customer they belonged to. Deletion would lose that history entirely.
Your expiration checker is working. It correctly identifies expired rules and disables them while leaving active connections alone. Next up, you will build a second Logic App that creates new temporary connection rules on demand through an HTTP endpoint.
Build the New Connection Request Logic App
Your expiration checker can now automatically disable expired rules. But there's still a gap in the workflow.
Someone still needs to manually create NSG rules with the correct naming convention. If they get the date format wrong, the expiration checker won't recognize the rule. In this step, you'll build a second Logic App that accepts HTTP requests and creates properly named rules automatically.
In this step, get ready to:
- Create a second Consumption Logic App with Managed Identity configured.
- Define an HTTP Request trigger with a JSON schema for incoming connection requests.
- Add actions that calculate the expiration time and create a new NSG rule via the REST API.
Create the Logic App and configure Managed Identity
Each Consumption tier Logic App only supports one workflow. Since this workflow needs an HTTP Request trigger (not a Recurrence trigger), it must live in a separate Logic App resource.
- In the Azure Portal search bar, type Logic apps and select the service.
- Click Add.
- Select Consumption as the plan type.
- Set the Resource Group to rg-nsg-expiration.
- Enter la-new-connection-request as the Logic App name.
- Select the same region you used for your resource group.
- Click Review + create, then click Create.
- Wait for the deployment to complete, then click Go to resource.
Now enable the system-assigned Managed Identity so this Logic App can authenticate with the Azure REST API.
- In the left menu under Settings, click Identity.
- On the System assigned tab, toggle the Status to On.
- Click Save.
- A confirmation dialog appears asking if you want to enable the identity. Click Yes.
Next, assign the Network Contributor role so the identity can create and modify NSG rules.
- In the Azure Portal search bar, type Resource groups and select the service.
- Click rg-nsg-expiration.
- In the left menu, click Access control (IAM).
- Click Add, then Add role assignment.
- Search for Network Contributor and select it.
- Click Next.
- Select Managed identity, then click Select members.
- In the Managed identity dropdown, choose Logic app (Consumption).
- Select la-new-connection-request from the list, then click Select.
- Click Review + assign twice to complete the assignment.
Don't see the Logic App in the member list?
It can take a minute for the Managed Identity to register. Close the Select members panel and re-open it after 30 seconds.
Also confirm you toggled the identity to On and clicked Save in the previous step.
Help me troubleshoot why my Logic App identity isn't appearing in the role assignment panel.
Define the workflow with the HTTP Request trigger
This Logic App needs an HTTP Request trigger that fires when an external system sends a POST request. The trigger includes a JSON schema that validates incoming data before the workflow runs.
- Navigate back to la-new-connection-request in the Azure Portal.
- In the left menu under Development Tools, click Logic app code view.
- Select all existing content in the editor and delete it.
- Paste the following workflow JSON into the code view editor:
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"When_a_HTTP_request_is_received": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"customerName": {
"type": "string"
},
"sourceIP": {
"type": "string"
},
"destinationPort": {
"type": "string"
},
"protocol": {
"type": "string"
},
"durationHours": {
"type": "integer"
},
"priority": {
"type": "integer"
}
},
"required": [
"customerName",
"sourceIP",
"destinationPort",
"protocol",
"durationHours",
"priority"
]
}
}
}
},
"actions": {
"Calculate_Expiration": {
"type": "Compose",
"inputs": "@addHours(utcNow(), triggerBody()?['durationHours'])",
"runAfter": {}
},
"Format_Rule_Name": {
"type": "Compose",
"inputs": "@concat('Allow-', triggerBody()?['customerName'], '-', formatDateTime(outputs('Calculate_Expiration'), 'yyyyMMdd-HHmm'))",
"runAfter": {
"Calculate_Expiration": [
"Succeeded"
]
}
},
"Create_NSG_Rule": {
"type": "Http",
"inputs": {
"method": "PUT",
"uri": "https://management.azure.com/subscriptions/your-subscription-id/resourceGroups/rg-nsg-expiration/providers/Microsoft.Network/networkSecurityGroups/nsg-connections/securityRules/@{outputs('Format_Rule_Name')}?api-version=2023-11-01",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://management.azure.com/"
},
"body": {
"properties": {
"access": "Allow",
"direction": "Inbound",
"priority": "@triggerBody()?['priority']",
"protocol": "@triggerBody()?['protocol']",
"sourceAddressPrefix": "@triggerBody()?['sourceIP']",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "@triggerBody()?['destinationPort']"
}
}
},
"runAfter": {
"Format_Rule_Name": [
"Succeeded"
]
}
},
"Response": {
"type": "Response",
"kind": "Http",
"inputs": {
"statusCode": 200,
"body": {
"status": "success",
"ruleName": "@outputs('Format_Rule_Name')",
"expiresAt": "@outputs('Calculate_Expiration')"
}
},
"runAfter": {
"Create_NSG_Rule": [
"Succeeded"
]
}
}
},
"outputs": {}
},
"parameters": {}
}
What does this workflow do?
The trigger defines a JSON schema requiring six fields: customerName, sourceIP, destinationPort, protocol, durationHours, and priority. Any POST request missing a required field is rejected automatically.
The Calculate_Expiration action adds the requested duration to the current UTC time. Format_Rule_Name assembles the naming convention string (Allow-CustomerName-YYYYMMDD-HHmm) so the expiration checker can parse it later.
The Create_NSG_Rule action sends a PUT request to the Azure REST API to create the inbound security rule. The Response action returns a 200 status with the rule name and expiration time to the caller.
Replace the subscription ID and save
The JSON contains a placeholder subscription ID that you need to swap with your actual Azure subscription ID before saving.
- In the code view editor, use Cmd+F (macOS) or Ctrl+F (Windows) to open the find bar.
- Search for your-subscription-id.
- Replace it with your actual Azure subscription ID.
Find your subscription ID
Search for Subscriptions in the Azure Portal search bar. Your subscription ID is listed in the table. Copy it from there.
- Click Save at the top of the code view editor.
After saving, Azure generates a unique HTTP POST URL for your Logic App's trigger. You need this URL to send requests to the workflow.
- In the left menu, click Overview.
- Click Logic app designer in the left menu under Development Tools.
- Click the When a HTTP request is received trigger card to expand it.
- You should see an HTTP POST URL field populated with a long URL. Copy this URL and save it somewhere accessible.
HTTP POST URL is blank?
The URL only generates after you successfully save the workflow. Go back to Logic app code view and confirm there are no red error indicators in the JSON. Fix any syntax issues and save again.
Help me figure out why my Logic App HTTP POST URL is not generating.
Your new connection workflow is ready. In the next step, you'll send a test request to this endpoint and verify the full lifecycle works end to end.
Test the New Connection Workflow
Your new connection Logic App is built and ready to accept requests. Now you need to prove it actually works by sending a real HTTP POST and watching the NSG rule appear.
In this step, you will send a test request to the Logic App, confirm the new rule shows up in your NSG with the correct naming convention, and then re-run the expiration checker to verify both Logic Apps work together.
In this step, get ready to:
- Send an HTTP POST request to trigger the new connection workflow.
- Verify the new NSG rule appears with the correct name, IP, port, and protocol.
- Re-run the expiration checker to confirm it leaves the new rule untouched.
Send a test request with sample customer data
The Logic App's HTTP Request trigger generated a public URL that accepts POST requests. You will use Azure Cloud Shell to send a curl command with a JSON payload that simulates a new customer connection request.
- In the Azure Portal, click the Cloud Shell icon (the terminal icon) in the top toolbar.
- If prompted to choose a shell, select Bash.
- Navigate to your Logic App la-new-connection-request and open the Overview page.
- Find the Workflow URL field and click the copy icon next to the URL.
What is this URL?
The HTTP Request trigger generates a unique URL with a SAS token embedded in it. This means anyone with the URL can trigger your Logic App without needing Azure credentials.
In production, you would protect this endpoint with additional authentication. For testing, the SAS token is sufficient.
- Back in Cloud Shell, send a test request by running this command (replace the URL placeholder with the URL you just copied):
curl -X POST [[LOGIC_APP_URL="your Logic App HTTP POST URL"]] \
-H "Content-Type: application/json" \
-d '{
"customerName": "TestCustomer",
"sourceIP": "203.0.113.10",
"destinationPort": "443",
"protocol": "Tcp",
"durationHours": 24,
"priority": 200
}'
What does this request do?
- customerName identifies who the connection belongs to. This gets embedded in the rule name.
- sourceIP is the IP address that will be allowed through the NSG.
- durationHours tells the Logic App how many hours from now the rule should expire. The Logic App calculates the expiration time and encodes it in the rule name.
- priority sets the rule's evaluation order. Lower numbers are evaluated first.
- You should see a JSON response in Cloud Shell with a status of success, a ruleName following the Allow-TestCustomer-YYYYMMDD-HHmm format, and an expiresAt timestamp 24 hours from now.
Seeing an error response or no response?
- If you get a 401 or 403 error, double-check that you copied the full URL including the query string parameters (the sp=, sv=, and sig= parts).
- If you see a 500 error, open the Logic App's run history to see which action failed. The Managed Identity may not have the Network Contributor role assigned correctly.
- If the command hangs with no response, make sure the JSON payload is formatted exactly as shown with no trailing commas.
Help me debug my Logic App HTTP POST request
Verify the new rule appears in the NSG
The Logic App should have created a brand new inbound security rule in your NSG. You need to confirm the rule exists with the correct settings.
- In the Azure Portal, navigate to your NSG nsg-connections.
- Click Inbound security rules in the left menu under Settings.
- Look for a new rule with a name starting with Allow-TestCustomer- followed by today's date and a time 24 hours from when you sent the request.
You should see a fourth rule in the list with these settings:
- Name: Allow-TestCustomer-YYYYMMDD-HHmm (with actual date values).
- Source: 203.0.113.10.
- Destination port: 443.
- Protocol: Tcp.
- Priority: 200.
- Access: Allow.
Why does the naming convention matter?
The rule name Allow-TestCustomer-20260512-1430 (for example) contains everything the expiration checker needs. The Allow- prefix tells it to check this rule. The last 13 characters encode the expiration timestamp.
No external database or tracking spreadsheet needed. The metadata lives in the rule name itself.
Don't see the new rule?
- Click the Refresh button at the top of the inbound rules list. The Portal does not always auto-refresh.
- If the rule still does not appear, go to your Logic App la-new-connection-request and click Overview to check the Runs history. Open the most recent run and look for a failed action.
- Check that the priority value 200 is not already in use by one of your existing rules. NSG priorities must be unique.
Help me figure out why my NSG rule was not created
Run the expiration checker to confirm both Logic Apps work together
The new rule has an expiration time 24 hours in the future. When the expiration checker runs, it should recognize this rule as still valid and leave it alone.
- Navigate to your Logic App la-expiration-checker in the Azure Portal.
- Click Overview in the left menu.
- Click Run Trigger at the top, then select Recurrence.
- Wait for the run to complete. A green checkmark appears in the Runs history section when it finishes.
- Click on the completed run to open the run details.
- Expand the For_Each_Rule loop and look at the iteration for your new Allow-TestCustomer rule.
- Verify that Condition_Is_Allow_Rule evaluated to true (the rule starts with Allow- and has access set to Allow).
- Verify that Condition_Is_Expired evaluated to false (the expiration time is still in the future).
The Disable_Expired_Rule action should NOT have executed for this rule. The checker correctly identified it as not yet expired.
- Navigate back to your NSG nsg-connections and click Inbound security rules.
- Confirm that the Allow-TestCustomer rule still has Allow as its access value.
What just happened end to end?
You have now tested the full lifecycle. The new connection Logic App created a rule with an expiration date baked into its name. The expiration checker scanned all rules, parsed the date from the name, compared it to the current time, and correctly left the valid rule untouched.
When that 24-hour window passes, the next time the checker runs it will flip the rule from Allow to Deny automatically.
Expiration checker disabled the new rule?
- If the new rule was flipped to Deny, the expiration time in the rule name may have been set in the past. This can happen if the durationHours value in your POST request was 0 or negative.
- Check the rule name in the NSG. The date-time portion should be in the future. If it shows a past time, delete the rule and resend the POST with durationHours set to 24.
Why did my expiration checker disable a rule that should still be valid?
Both Logic Apps are working together. You have a complete system that provisions temporary NSG rules on demand and automatically disables them when they expire. Next up, the Secret Mission challenges you to add email notifications when rules are disabled.
Secret mission
Add Email Notifications for Disabled Rules
Your expiration checker silently disables rules, but nobody knows it happened. What if the Logic App could email you every time it revokes a connection, including the customer name and when the rule originally expired?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses the Logic Apps Consumption tier, which includes 4,000 free built-in action executions per month. The hourly Recurrence trigger and its actions stay well within that limit, so ongoing cost is $0.
Resources you used:
- Resource group: rg-nsg-expiration.
- Network Security Group: nsg-connections.
- Logic App (Consumption): Expiration Checker with Recurrence trigger.
- Logic App (Consumption): New Connection Request with HTTP Request trigger.
- System-assigned Managed Identity with Network Contributor role.
- Sample NSG inbound security rules using the naming convention.
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment with additional rules and triggers.
- Your Logic Apps will continue running on schedule within the free tier.
- The expiration checker will keep scanning and disabling expired rules every hour.
- No charges will apply as long as your total built-in actions stay under 4,000 per month.
Pause - I'll come back to this later
Disable the Logic Apps to stop all executions while keeping your configuration intact.
- In the Azure Portal, navigate to each Logic App.
- Click Disable in the top toolbar of the Logic App's Overview page.
- Repeat for both Logic Apps.
- When you are ready to resume, open each Logic App and click Enable.
Delete - I don't want to use this again
Remove all project resources by deleting the resource group. This removes everything inside it in one action.
- In the Azure Portal, search for Resource groups and open rg-nsg-expiration.
- Click Delete resource group in the top toolbar.
- Type the resource group name rg-nsg-expiration to confirm.
- Click Delete.
- This removes the resource group, both Logic Apps, the NSG, all security rules, and the Managed Identity assignment.
Nice Work!
Nice Work!
You built a fully automated NSG rule lifecycle management system using Azure Logic Apps. No traditional code, no external databases, no manual tracking spreadsheets.
You've learned how to:
- Built a Logic App that scans NSG rules on a schedule and automatically disables expired connections by flipping their access from Allow to Deny.
- Built a second Logic App with an HTTP Request trigger that provisions new temporary access rules on demand with embedded expiration dates.
- Configured Managed Identity authentication for secure, credential-free Azure REST API access from your workflows.
- Added email notifications that alert you whenever a connection rule is revoked, including the customer name and original expiration time.
Ready to quiz yourself?