{"id":27525,"date":"2018-10-03T00:04:33","date_gmt":"2018-10-03T07:04:33","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/premier-developer\/?p=27525"},"modified":"2019-02-14T20:17:49","modified_gmt":"2019-02-15T03:17:49","slug":"azure-ad-app-tracking-with-logic-apps","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/azure-ad-app-tracking-with-logic-apps\/","title":{"rendered":"Azure AD App Tracking with Logic Apps"},"content":{"rendered":"<p>In this post, App Dev Manager <a href=\"https:\/\/www.linkedin.com\/in\/jasonvenema\/\" target=\"_blank\" rel=\"noopener\">Jason Venema<\/a> outlines how Logic Apps is a great alternative to coding.<\/p>\n<p><!--more--><\/p>\n<hr \/>\n<p>I\u2019m a huge fan of Azure Logic Apps. Whenever I need a fast, practical solution to a problem and don\u2019t want to write code, it\u2019s my go-to service. With the plethora of out-of-the-box activities and connectors available, there is very little you can\u2019t do with a Logic App.<\/p>\n<p>Recently, I was asked by a customer with a very large internal Azure user base to help them find a way to keep track of Azure AD (AAD) application registrations in their directory. This customer has been on Azure for years, and the number of AAD application registrations has steadily grown during that time. There are so many applications now that it is hard for them to know which ones are still being used, and which are not. Furthermore, it\u2019s common for application teams to create a secret key for their application and then forget that the key will eventually expire. How does the operations team know whom to contact when the expiration date is approaching?<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image103.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb91.png\" alt=\"image\" width=\"725\" height=\"596\" border=\"0\" \/><\/a><\/p>\n<p>It turns out, there are several ways to solve this problem. It\u2019s simple to find all of the applications that no longer have any unexpired keys or certificates associated with them, and fairly safe to assume they are probably not in use anymore and can be deleted. To be extra careful, though, they wanted to send the owner of the application an email to give them a heads up that it was going to be deleted. The problem is, how do you know who the owner is?<\/p>\n<h3>Finding the Application Owner<\/h3>\n<p>A reasonable and obvious idea is to use the \u201cApplication Owner\u201d field to keep track of the owner. The problem is, that field is optional and accepts any string of characters as input. If you allow end users to create their own app registrations, then there is no way to enforce them to put a value in that field. More often than not, the field is empty.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image104.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb92.png\" alt=\"image\" width=\"840\" height=\"230\" border=\"0\" \/><\/a><\/p>\n<p>Our next thought was to leverage the AAD Audit Logs. Any time an event takes places in Azure AD \u2013 someone registers a new application or modifies an existing user account \u2013 that event gets recorded in the audit logs. In fact, the Azure AD team <a href=\"https:\/\/azure.microsoft.com\/en-us\/blog\/azure-monitor-aad-activity-logs-using-diagnostic-settings\/\">recently introduced the capability to route these audit logs to either a storage account or an Event Hub<\/a>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image105.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb93.png\" alt=\"image\" width=\"796\" height=\"380\" border=\"0\" \/><\/a><\/p>\n<p>It would be a pretty simple matter to just check the \u201cArchive to a storage account\u201d box and be done with it. And that would work \u2013 if they ever needed to find out who registered a particular application, they could open up the storage account and search for it. But they wanted a solution that emphasized ease of searchability using a language like SQL.<\/p>\n<h3>Logic Apps and Cosmos DB to the Rescue<\/h3>\n<p>Rather than searching through log entries in a storage account, we decided to send the logs to an Event Hub and then trigger a Logic App whenever new events arrive. This gave us a lot more flexibility in terms of the types of events we wanted to react to (application creations) and the actions we wanted to take when those events occurred. The Logic App we created simply monitor the Event Hub for new messages, and when one arrives it examines the contents of the message to determine the type of event that occurred. If the event was for an application registration, then we take the event information \u2013 most importantly, the email address of the person who created it \u2013 and store it in a Cosmos DB database. We chose Cosmos DB because it makes querying for specific applications dead simple, and it\u2019s easy and inexpensive to store data that will grow potentially very large over time.<\/p>\n<p>Here are the steps we took to create the solution.<\/p>\n<h4>Create the Event Hub<\/h4>\n<p>First, we needed an Event Hub to send the AAD Audit log data to.<\/p>\n<ol>\n<li>From the Azure Portal, open the Event Hubs service.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li>Select \u201cAdd\u201d and fill out the form to create a new namespace. Keep a record of the region you select, so you can use the same one later for your Logic App and Cosmos DB instances.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li>We chose 1 throughput unit, but you may have to adjust this accordingly based on how much activity your AAD instance gets.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li>Click \u201cCreate\u201d<\/li>\n<\/ol>\n<ol start=\"5\">\n<li>After the namespace is created, navigate to it in the portal. Then select \u201cEvent Hubs\u201d (under \u201cEntities\u201d).<\/li>\n<\/ol>\n<ol start=\"6\">\n<li>Click on the \u201c+Event Hub\u201d button and fill out the form to create a new Event Hub. We expected a fairly low volume of data, so we went with 2 partitions.<\/li>\n<\/ol>\n<h4>Configure AAD Diagnostic Settings<\/h4>\n<p>Next, we configured the AAD diagnostic settings to export the audit logs to the Event Hub instance we just created.<\/p>\n<ol>\n<li>From the Azure Portal, open the Azure Active Directory service.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li>Select \u201cAudit Logs\u201d from the left-hand menu, and then click \u201cExport Data Settings\u201d from the toolbar.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li>Click on the \u201cAdd diagnostic setting\u201d link.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li>Select the \u201cStream to an Event Hub\u201d checkbox. Directly below the checkbox, choose the Event Hub instance that you just created.<\/li>\n<\/ol>\n<ol start=\"5\">\n<li>Select the \u201cAuditLogs\u201d checkbox, but do not select \u201cSignInLogs\u201d. This will keep our logs to the bare minimum.<\/li>\n<\/ol>\n<ol start=\"6\">\n<li>Click on the \u201cSave\u201d button.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image106.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb94.png\" alt=\"image\" width=\"819\" height=\"605\" border=\"0\" \/><\/a><\/p>\n<h4>Create the Cosmos DB Database<\/h4>\n<p>Next, we need a Cosmos DB database to store the application registration events in.<\/p>\n<ol>\n<li>From the Azure Portal, open the Azure Cosmos DB service.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li>Click the \u201c+Add\u201d button to create a new instance.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li>In the \u201cAPI\u201d dropdown, choose \u201cSQL\u201d.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li>Choose the same region that you used for the Event Hub in the \u201cLocation\u201d dropdown.<\/li>\n<\/ol>\n<ol start=\"5\">\n<li>Click the \u201cCreate\u201d button.<\/li>\n<\/ol>\n<ol start=\"6\">\n<li>Once the Cosmos DB instance is created, navigate to it in the Azure Portal and select the \u201c+Add Collection\u201d button.<\/li>\n<\/ol>\n<ol start=\"7\">\n<li>Fill out the form and keep a note of the \u201cDatabase id\u201d and \u201cCollection Id\u201d values that you use.<\/li>\n<\/ol>\n<ol start=\"8\">\n<li>To keep costs low, we chose \u201cFixed (10GB)\u201d for storage capacity, and \u201cThroughput\u201d of 400 RU\/s.<\/li>\n<\/ol>\n<ol start=\"9\">\n<li>Click the \u201cOK\u201d button.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image107.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb95.png\" alt=\"image\" width=\"592\" height=\"599\" border=\"0\" \/><\/a><\/p>\n<h4>Create the Logic App<\/h4>\n<p>The final step to tie everything together is to create the Logic App.<\/p>\n<ol>\n<li>From the Azure Portal, open the Logic Apps service.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li>Click the \u201c+Add\u201d button to create a new instance, and be sure to choose the same region as in the previous steps for the \u201cLocation\u201d field.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li>When the Logic App is created, navigate to it in the portal.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li>Choose the \u201cBlank Logic App\u201d template.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image108.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb96.png\" alt=\"image\" width=\"451\" height=\"468\" border=\"0\" \/><\/a><\/p>\n<ol start=\"5\">\n<li>Search for the \u201cEvent Hubs\u201d trigger and select \u201cWhen events are available in Event Hub\u201d.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image109.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb97.png\" alt=\"image\" width=\"709\" height=\"495\" border=\"0\" \/><\/a><\/p>\n<ol start=\"6\">\n<li>Configure the Event Hub connection by choosing the namespace and Event Hub created previously, and then click \u201cCreate\u201d.<\/li>\n<\/ol>\n<ol start=\"7\">\n<li>The trigger for your Logic App should now look like this.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image113.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb98.png\" alt=\"image\" width=\"788\" height=\"321\" border=\"0\" \/><\/a><\/p>\n<ol start=\"8\">\n<li>Add a new action after the Event Hub trigger. Search for \u201cParse JSON\u201d in the search box and select the action to create it.<\/li>\n<\/ol>\n<ol start=\"9\">\n<li>In the \u201cContent\u201d field of the action use the Dynamic Content menu to select the \u201cContent\u201d field from the Event Hub action (see screenshot below). Note that it might take a few minutes after you create the Event Hub trigger for the dynamic content to populate with all of the fields.<\/li>\n<\/ol>\n<ol start=\"10\">\n<li>Below the \u201cSchema\u201d field, copy and paste in the following schema (Note: It is usually simpler to click the \u201cUse sample payload to generate schema\u201d option, but in this case I found that the generated schema needed to be modified slightly to account for the data.)<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image114.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb99.png\" alt=\"image\" width=\"824\" height=\"445\" border=\"0\" \/><\/a><\/p>\n<p>Copy and paste in the following schema:<\/p>\n<p>{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;records&#8221;:{&#8220;type&#8221;:&#8221;array&#8221;,&#8221;items&#8221;:{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;time&#8221;:{&#8220;type&#8221;:&#8221;string&#8221;},&#8221;resourceId&#8221;:{&#8220;type&#8221;:&#8221;string&#8221;},&#8221;operationName&#8221;:{&#8220;type&#8221;:&#8221;string&#8221;},&#8221;operationVersion&#8221;:{},&#8221;category&#8221;:{},&#8221;tenantId&#8221;:{},&#8221;resultType&#8221;:{},&#8221;resultSignature&#8221;:{},&#8221;durationMs&#8221;:{},&#8221;callerIpAddress&#8221;:{},&#8221;correlationId&#8221;:{},&#8221;identity&#8221;:{},&#8221;Level&#8221;:{},&#8221;properties&#8221;:{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;id&#8221;:{},&#8221;category&#8221;:{},&#8221;correlationId&#8221;:{},&#8221;result&#8221;:{},&#8221;resultReason&#8221;:{},&#8221;activityDisplayName&#8221;:{},&#8221;activityDateTime&#8221;:{},&#8221;loggedByService&#8221;:{},&#8221;initiatedBy&#8221;:{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;app&#8221;:{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;appId&#8221;:{},&#8221;displayName&#8221;:{},&#8221;servicePrincipalId&#8221;:{},&#8221;servicePrincipalName&#8221;:{}}}}},&#8221;targetResources&#8221;:{&#8220;type&#8221;:&#8221;array&#8221;,&#8221;items&#8221;:{&#8220;type&#8221;:&#8221;object&#8221;,&#8221;properties&#8221;:{&#8220;id&#8221;:{},&#8221;displayName&#8221;:{},&#8221;modifiedProperties&#8221;:{}}}},&#8221;additionalDetails&#8221;:{&#8220;type&#8221;:&#8221;array&#8221;}}}}}}}}<\/p>\n<ol start=\"11\">\n<li>Add a new step and search for \u201cCondition\u201d, then choose the first result.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image117.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb100.png\" alt=\"image\" width=\"783\" height=\"471\" border=\"0\" \/><\/a><\/p>\n<ol start=\"12\">\n<li>In the properties for the condition, use the Dynamic Content box to enter \u201coperationName\u201d as the value. Once you do this, the Logic App designer will automatically add a For-Each around the operation. This is expected, since the Event Hub event may contain multiple messages (and therefore, multiple operationName\u2019s). Simply click the \u201cGot It\u201d button.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image118.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb101.png\" alt=\"image\" width=\"811\" height=\"408\" border=\"0\" \/><\/a><\/p>\n<ol start=\"13\">\n<li>Add two conditions for operationName equal to \u201cAdd application\u201d or \u201cUpdate application\u201d in the condition step.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image119.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb102.png\" alt=\"image\" width=\"855\" height=\"334\" border=\"0\" \/><\/a><\/p>\n<ol start=\"14\">\n<li>Inside of the \u201cIf true\u201d branch of the condition, add a new Azure Cosmos DB action \u201cCreate or update document (preview)\u201d.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image120.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb103.png\" alt=\"image\" width=\"767\" height=\"551\" border=\"0\" \/><\/a><\/p>\n<ol start=\"15\">\n<li>Select the Cosmos DB instance that you created earlier.<\/li>\n<\/ol>\n<ol start=\"16\">\n<li>In the \u201cDocument\u201d field, enter the values from the Event Hub message that we parsed earlier. Here is an example of the document that I created:<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image121.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb104.png\" alt=\"image\" width=\"819\" height=\"768\" border=\"0\" \/><\/a><\/p>\n<p>NOTE: You will notice that when you add certain properties to your document from the Dynamic Content selector, your action is automatically wrapped in a For-Each loop. That is expected any time you add a property that is an array. In the example above, the \u201cid\u201d property is embedded inside of \u201ctargetResources\u201d, which is an array in the original JSON event. The \u201cmodifiedProperties &#8211; Item\u201d property is also an array. As a result, my Cosmos DB action was wrapped inside of two For-Each loops.<\/p>\n<p>Once completed, you should click the \u201cSave\u201d button. The completed Logic App looks like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image122.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb105.png\" alt=\"image\" width=\"855\" height=\"543\" border=\"0\" \/><\/a><\/p>\n<h3>Querying the Results<\/h3>\n<p>At this point, every time a new application gets registered in Azure AD you will see a record of that event stored in Cosmos DB. At this point, it is simple to query Cosmos DB to find out who registered a specific application. You can easily test your queries from the Cosmos DB data explorer in the Azure Portal:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image123.png\"><img decoding=\"async\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb106.png\" alt=\"image\" width=\"855\" height=\"678\" border=\"0\" \/><\/a><img \/><\/p>\n<h3>Conclusion<\/h3>\n<p>By combining Azure AD audit logs with Logic Apps and Cosmos DB, you can easily store information about selected events that are important to your business in an easy to query format. This approach has many other applications beyond audit logs. If you come up with other great use cases, let me know!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I was asked by a customer with a very large internal Azure user base to help them find a way to keep track of Azure AD (AAD) application registrations in their directory. This customer has been on Azure for years, and the number of AAD application registrations has steadily grown during that time. There are so many applications now that it is hard for them to know which ones are still being used, and which are not. Furthermore, it\u2019s common for application teams to create a secret key for their application and then forget that the key will eventually expire. How does the operations team know whom to contact when the expiration date is approaching?<\/p>\n","protected":false},"author":582,"featured_media":27526,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[25],"tags":[69,60,3],"class_list":["post-27525","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","tag-azure-ad","tag-logic-apps","tag-team"],"acf":[],"blog_post_summary":"<p>Recently, I was asked by a customer with a very large internal Azure user base to help them find a way to keep track of Azure AD (AAD) application registrations in their directory. This customer has been on Azure for years, and the number of AAD application registrations has steadily grown during that time. There are so many applications now that it is hard for them to know which ones are still being used, and which are not. Furthermore, it\u2019s common for application teams to create a secret key for their application and then forget that the key will eventually expire. How does the operations team know whom to contact when the expiration date is approaching?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/27525","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=27525"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/27525\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/27526"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=27525"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=27525"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=27525"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}