├── docs ├── workbook.md ├── troubleshooting.md ├── readme.md ├── deployment.md ├── faq.md ├── howitworks.md └── setupscript.md ├── config ├── templates │ ├── footer.html │ ├── version.html │ ├── header.html │ ├── travel.html │ ├── mfa.html │ └── tap.html └── seen.config ├── version.json ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── jsoncheck.yml ├── shared ├── readme.md ├── webconnections.json ├── storage.json └── emailnotification.json ├── LICENSE ├── README.md ├── deploy ├── readme.md ├── setup.ps1 ├── azuredeploy.json └── createUiDefinition.json └── modules ├── MFAMethods └── mfamethods.json ├── Travel └── travel.json └── TAP └── tap.json /docs/workbook.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # SEcurity End-user Notification 👀 - How it Works 2 | 3 | 4 | --- 5 | [Documentation Home](readme.md) -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "configFile":"1.1", 3 | "emailLA":"1.0", 4 | "configLA":"1.0", 5 | "modules" : { 6 | "mfa":"1.0", 7 | "tap":"1.0", 8 | "travel":"1.0" 9 | } 10 | } -------------------------------------------------------------------------------- /config/templates/version.html: -------------------------------------------------------------------------------- 1 | A new version of SEEN is available! 2 |

3 | {VersionTable} 4 |

5 | Check out the latest news! 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: For questions to the STAT project maintainers 4 | title: '[QUESTION] My Question' 5 | labels: 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ## Documentation Home 2 | 3 | ### Learn more 4 | 5 | * [How it works](howitworks.md) 6 | * [Frequently Asked Questions](faq.md) 7 | * [Deployment](deployment.md) 8 | * [setup.ps1 script](setupscript.md) 9 | * [Configuration](workbook.md) 10 | * [Troubleshooting](troubleshooting.md) 11 | -------------------------------------------------------------------------------- /.github/workflows/jsoncheck.yml: -------------------------------------------------------------------------------- 1 | name: JSON check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: json-lint 14 | uses: ocular-d/json-linter@0.0.2 15 | with: 16 | pattern: "\\.json$" -------------------------------------------------------------------------------- /config/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /config/templates/travel.html: -------------------------------------------------------------------------------- 1 | {Header} 2 | 3 |

Are you maybe traveling?

4 | 5 | Hello {UserFirstName}, you recently logged in from an unsual location. Here are the activities we have seen:

6 | 7 | {Activities} 8 |

9 | If you think it was not you, please contact {SupportEmail}, or call {SupportPhoneNumber} immediately.

10 | 11 | You can also review your activities and report suspicious signins.

12 | 13 | {Footer} 14 | -------------------------------------------------------------------------------- /config/templates/mfa.html: -------------------------------------------------------------------------------- 1 | {Header} 2 | 3 |

Your Multi-Factor-Authentication methods have changed!

4 | 5 | Hello {UserFirstName}, we recently detected the following changes on your personal security information:

6 | 7 | {Activities} 8 |

9 | ⚠️ If you did not do this, please contact {SupportEmail}, or call {SupportPhoneNumber} immediately.

10 |

11 | You can view, edit and delete your Multi Factor Authentication methods in your MySecurityInfo page.

12 | 13 | {Footer} 14 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | ## Deployment documentation 2 | 3 | The 4 | 5 | ### Standard deployment 6 | 7 | The "next, next, next" deployment mode 🙂 8 | 9 | ### Advanced deployment 10 | 11 | The advanced deployment mode let you do the following: 12 | 13 | - Customize the name of the logic apps 14 | - Customize the name of the workbook (not the GUID) 15 | - Disable the diagnostic logging for the email module (used to track email statistics) 16 | - Select a custom branch for deployment (useful if you want to test the preview of a feature or a fix) 17 | 18 | ### setup.ps1 script 19 | 20 | ### Post deployment configuration 21 | 22 | 23 | --- 24 | [Documentation Home](readme.md) -------------------------------------------------------------------------------- /shared/readme.md: -------------------------------------------------------------------------------- 1 | ## Shared Components 2 | 3 | This folder contains the ARM templates for shared components. Shared components are the resources used by the solution which are not considered modules. 4 | 5 | |ARM Template|Component| 6 | |---|---| 7 | |`config.json`|The **Config** logic app| 8 | |`emailnotification.json`|The **SendEmail** logic app| 9 | |`storage.json`|The storage account for blob and tables| 10 | |`webconnections.json`|The connection API object to manage managed identity access to the storage account| 11 | |`workbook.json`|The workbook to configure the solution and monitor it| 12 | 13 | Please refer to the [documentation](/docs/readme.md) for details. 14 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature] Feature Title" 5 | labels: 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] ModuleName - Bug Title" 5 | labels: 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Module Name** 14 | Name of the SEEN Module containing the bug (if known) 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. -------------------------------------------------------------------------------- /config/templates/tap.html: -------------------------------------------------------------------------------- 1 | {Header} 2 | 3 |

Temporary Access Pass activities were detected

4 | 5 | Hello {UserFirstName}, we recently detected Temporary Access Pass (TAP) activities on your account. This allows you to access your account without providing your password or when you do not have access to MFA methods. 6 | You must have talked to the helpdesk to obtain such a TAP. Here are the activities we have seen:

7 | 8 | {Activities} 9 |

10 | If you did not recall requesting a TAP, or using one, please contact {SupportEmail}, or call {SupportPhoneNumber} immediately.

11 | 12 | You can also review your activities and report suspicious signins.

13 | 14 | {Footer} 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Pierre Audonnet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/seen.config: -------------------------------------------------------------------------------- 1 | { 2 | "version":"1.1", 3 | "link":"https://raw.githubusercontent.com/piaudonn/SecurityNotifications/main/config/seen.config", 4 | "mailFrom":"security@contoso.com", 5 | "replyTo":"no-reply@scontoso.xyz", 6 | "saveToSentItems":false, 7 | "timeZone":"Canada/Eastern", 8 | "testEmail":"test@contoso.com", 9 | "contact" : { 10 | "supportEmail":"support@contoso.com", 11 | "supportPhone": "(555) 123-1111" 12 | }, 13 | "versionManagement": { 14 | "notifyAdmins":true, 15 | "checkFrequencyInDays":15, 16 | "adminEmail":"admin@contoso.com", 17 | "subject":"A new version of SEEN is available!" 18 | }, 19 | "modules": { 20 | "mfa" : { 21 | "testMode":true, 22 | "subject":"Your security information has changed", 23 | "ccManager":false, 24 | "bcc":"", 25 | "includeGuests":false 26 | }, 27 | "tap" : { 28 | "testMode":true, 29 | "subject":"Temporary Access Pass activity was detected on your account", 30 | "ccManager":false, 31 | "bcc":"", 32 | "includeUsage":false 33 | }, 34 | "travel" : { 35 | "testMode":true, 36 | "subject":"You are connecting from an unusual location with your account", 37 | "ccManager":false, 38 | "bcc":"", 39 | "includeDismissed":false 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 👀 Security End-user Notification (SEEN) 2 | 3 | ### ❓ What is **SEEN**? 4 | 5 | **SEEN** allows you to send automatic email notifications to end-users when specific security events are detected on their Azure AD accounts. Events such as: 6 | - a Multi Factor Authentication method was added, updated or removed 7 | - a Temporary Access Pass ([TAP](https://learn.microsoft.com/en-us/azure/active-directory/authentication/howto-authentication-temporary-access-pass)) was created or used 8 | - an Atypical travel was detected by [Azure AD Identity Protection](https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/overview-identity-protection) 9 | 10 | **SEEN** let you customize the emails sent to inform the users of these events and encourage them to reach out to your security team or support. 11 | 12 | **SEEN** is leveraging a combination of Logic Apps to automate the detection of the security events and the notification to end users with many customizable options. **SEEN** read the Azure AD sign-in logs and audit logs from a Log Analytics workspace (`SigninLogs` and `AuditLogs` tables). 13 | 14 | ### ⚙️ Deployment 15 | 16 | The full solution is available for deployment in the [Deployment](/deploy/) section and additional documentation can be found in [Docs](/docs/). 17 | 18 | If you have any questions about this project or would like to provide suggestions to the **SEEN** project maintainers please open an [issue](https://github.com/piaudonn/SecurityNotifications/issues/new/choose). 19 | -------------------------------------------------------------------------------- /shared/webconnections.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": {}, 5 | "variables": { 6 | "azureTablesConnectionName": "azuretables", 7 | "azureBlobConnectionName": "azureblob" 8 | }, 9 | "resources": [ 10 | { 11 | "type": "Microsoft.Web/connections", 12 | "apiVersion": "2016-06-01", 13 | "name": "[variables('azureTablesConnectionName')]", 14 | "location": "[resourceGroup().location]", 15 | "kind": "V1", 16 | "properties": { 17 | "displayName": "Table Access", 18 | "parameterValueSet": { 19 | "name": "managedIdentityAuth", 20 | "values": {} 21 | }, 22 | "customParameterValues": {}, 23 | "api": { 24 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuretables')]" 25 | } 26 | } 27 | }, 28 | { 29 | "type": "Microsoft.Web/connections", 30 | "apiVersion": "2016-06-01", 31 | "name": "[variables('azureBlobConnectionName')]", 32 | "location": "[resourceGroup().location]", 33 | "kind": "V1", 34 | "properties": { 35 | "displayName": "Azure Blob Storage", 36 | "parameterValueSet": { 37 | "name": "managedIdentityAuth", 38 | "values": {} 39 | }, 40 | "customParameterValues": {}, 41 | "api": { 42 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azureblob')]" 43 | } 44 | } 45 | } 46 | ], 47 | "outputs": { 48 | "TableConnectionResourceID": { 49 | "type": "string", 50 | "value": "[resourceId('Microsoft.Web/connections', variables('azureTablesConnectionName'))]" 51 | }, 52 | "BlobConnectionResourceID": { 53 | "type": "string", 54 | "value": "[resourceId('Microsoft.Web/connections', variables('azureBlobConnectionName'))]" 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | ## Frequently Asked Questions 2 | 3 | ### Sentinel requirements 4 | 5 | The solution does not require Microsoft Sentinel, only to send Azure AD logs (`SigninLogs` and `AuditLog`) to Log Analtics workspace. 6 | The solution is deployed and used the same way whether Microsoft Sentinel is present in the environment or not. There is no interaction. 7 | 8 | ### Log Analytics workspace retention 9 | 10 | The calls to the workspace are usually only looking at the last 15 minutes. However, they capped the query to one day of data. So, as ong as you have **one day** of retention, the detection will work. 11 | 12 | ### Restrict Mail.Send permission 13 | 14 | When an identity is granted the application permission `Mail.Send` on the Microsoft Graph API, it can send emails on behalf any users in the organization. It is recommended to restrict this permission by configuring an [Application Access Policy](https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access) in Exchange Online. To restrict the managed identity permission to send emails only from the account you want, follow these steps: 15 | 16 | 1. Create a mail-enabled security group (i.e. Seen-Notifications@contoso.com). 17 | 2. Add the user from which you want to send email from (the mailFrom from the configuration) into the group (i.e. security@contoso.com). 18 | 3. Create an access policy to allow the managed identity to send email only on behalf the member of the group. You can find the application ID of the SEEN-SendEmail managed identity in the Azure AD portal (go to the Enterprise application blade, select Managed Identities in the application type filter and look for your SEEN-SendEmail application, take note of the Application ID - not the Object ID). 19 | 20 | Example: 21 | 22 | ```powershell 23 | #Install-Module ExchangeOnlineManagement 24 | Connect-ExchangeOnline -UserPrincipalName admin@contoso.com 25 | 26 | New-DistributionGroup -Name "SEEN Notifications" -Alias "Seen-Notifications" -Type security 27 | Add-DistributionGroupMember -Identity "Seen-Notifications@contoso.com" -Member "security@contoso.com" 28 | 29 | New-ApplicationAccessPolicy ` 30 | -AppId 4f0c7083-49f1-43fc-bae4-8f3dd788fefa ` 31 | -PolicyScopeGroupId Seen-Notifications@contoso.com ` 32 | -AccessRight RestrictAccess ` 33 | -Description "Restrict SEEN managed identity" 34 | ``` 35 | 36 | ### Detecting atypical travels 37 | 38 | This notification relies on having Azure AD Identity Protection with Azure AD Premium P2 licences. If you enabled this module but do not have this level of license, the module will not trigger an notification. 39 | 40 | ### Use Azure Communication Services 41 | 42 | As of today, the solution is only using an Office 365 mailbox to send the emails from. It is not configurable to use Azure Communication Services. 43 | -------------------------------------------------------------------------------- /docs/howitworks.md: -------------------------------------------------------------------------------- 1 | ## How it Works 2 | 3 | ### Module's framework 4 | 5 | The modules are the logic apps which run on a time trigger (every 15 minutes) and are looking for specific events and activities to notify the users on. 6 | 7 | Each module is based on the following logic: 8 | 9 | image 10 | 11 | The modules are always composed of 4 sections delimited in [scopes](https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-control-flow-run-steps-group-scopes). 12 | 1. **Config** is initializing the module, get its timestamp for the query and get its configuration from the Config logic app. 13 | 2. **Query** is where the KQL query is defined and executed. 14 | 3. **Notification** is where the results of the query are parsed, and an email is sent to the final recipients according to the module’s config. 15 | 4. **Exit** is to update the timestamp for the next execution. 16 | 17 | The **Config LA** is designed to retrieve the configuration for the caller module. It will get this configuration by reader the configuration files from the storage account. The config is also retrieve the email template for the caller module. 18 | 19 | The **Notification LA** sole purpose is to send email. Tracked properties allow statistics gathering if the integration to Log Analytics is enabled. 20 | 21 | ### List of current modules 22 | 23 | You can click on the module's name to explore its documentation. 24 | - The [Multi Factor Authenticatation module](mfa.md) is designed to notify end-users of changes of their MFA methods on ther accounts. 25 | - The [Temporary Access Pass module](tap.md) is designed to notify end-users of TAP creations and usages on their accounts. 26 | - The [Atypical travel module](travel.md) is designed to notify end-users of signins of their accounts from unusual locations. 27 | 28 | Modules are using trackers stored in storage account tables. Trackers are timestamps which allow the modules to know from what starting time they need to start their lookup when they run. That way, there are no blind spots between modules executions. The setup script is initializing the trackers with the timestamp of the installation's time of the solution. 29 | 30 | ### Permission managements 31 | 32 | Each module has the following permissions: 33 | 34 | - Read and modify their trackers in the storage tables, the setup script is giving these permissions to the module's managed identity by giving the RBAC role **Storage Table Data Contributor** on the resource group where the storage account is. 35 | - Read the log analytics workspace where the data is, the setup script is giving these permissions to the module's managed identity by giving the RBAC role **Log Analytics Reader** on the resource group where the log analytics workspace is. 36 | 37 | The other logic apps also need access to some aspect of the solution: 38 | - The config logic app needs to be able to initialize the trackers and is given **Storage Table Data Contributor** on the resource group where the storage account by the setup script. It also needs to install and udpate the templates and the configuration file itself, and it is given **Storage Blob Data Contributor** on the resource group where the storage account by the setup script. 39 | 40 | ## Solution management 41 | 42 | Once the solution is installed, the entire management is done through the [SEEN workbook](workbook.md). 43 | 44 | 45 | --- 46 | [Documentation Home](readme.md) -------------------------------------------------------------------------------- /docs/setupscript.md: -------------------------------------------------------------------------------- 1 | ## Setup script 2 | 3 | ### PowerShell requirements 4 | 5 | The script requires the following PowerShell modules: 6 | - `Microsoft.Graph.Applications` used to lookup managed identity application IDs and to set API permissions 7 | - `Az.Resources` used to set RBAC roles on resources 8 | - `Az.LogicApp` used to trigger the config logic app that will then download the config file and template files to the storage account 9 | 10 | The script contains emojies which requires a file encoding with [BOM](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding?view=powershell-7.3). 11 | 12 | ### Permission requirements 13 | 14 | You need the following permissions to run the script: 15 | 16 | - Azure AD **Global Administrator** or an **Azure AD Privileged Role Administrator** to execute the Set-APIPermissions function 17 | - **Resource Group Owner** or **User Access Administrator** on the resource groups hosting the logic app and the storage account to execute the Set-RBACPermissions function 18 | 19 | ### Input parameters 20 | 21 | |Parameter|Required|Default value|Definition| 22 | |---|---|---|---| 23 | |`TenantId`|`true`||The Azure AD tenant ID| 24 | |`AzureSubscriptionId`|`true`||The ID of the subscription where SEEN is deployed| 25 | |`SEENResourceGroupName`|`true`||The **resource group** where you deployed SEEN| 26 | |`StorageAccountResourceGroupName`|`false`|The value you provided for `SEENResourceGroupName`|The **resource group** where the storage account for SEEN is located| 27 | |`WorkspaceResourceGroupName`|`true`||The **resource group** where your log analytic workspace with the SigninLogs and AuditLogs tables are| 28 | |`ConfigLogicAppName`|`false`|SEEN-Config|The name of the config logic app| 29 | |`SendEmailLogicAppName`|`false`|SEEN-SendEmail|The name of the send email logic app| 30 | |`MFAMethodsLogicAppName`|`false`|SEEN-MFAMethods|The name of the MFA module logic app| 31 | |`TAPLogicAppName`|`false`|SEEN-TAPLogicAppName|The name of the TAP module logic app| 32 | |`TravelLogicAppName`|`false`|SEEN-TravelLogicAppName|The name of the travel module logic app| 33 | 34 | ### Error management 35 | 36 | There is some error management in the script, but it is minimal. The output of the script should be self-explanatory if there is something to fix before re-running it. If that's not obvious, please open an [issue](https://github.com/piaudonn/SecurityNotifications/issues/new/choose). 37 | 38 | ### API permissions and RBAC details 39 | 40 | The setup.ps1 script configures the permissions for the managed identities. Here are the application permissions and RBAC roles required by each logic apps' managed identities. 41 | 42 | |Module|API|Permissions|Resource/RBAC| 43 | |---|---|---|---| 44 | |config|-|-|Storage account / `Storage Blob Data Contributor` and `Storage Table Data Contributor`| 45 | |sendemail|Microsoft Graph API|`Mail.Send`|-| 46 | |mfa|Microsoft Graph API|`User.Read.All`|Storage account / `Storage Table Data Contributor`
Log Analytics workspace / `Log Analytics Reader`| 47 | |tap|Microsoft Graph API|`User.Read.All`|Storage account / `Storage Table Data Contributor`
Log Analytics workspace / `Log Analytics Reader`| 48 | |travel|Microsoft Graph API|`User.Read.All`|Storage account / `Storage Table Data Contributor`
Log Analytics workspace / `Log Analytics Reader`| 49 | 50 | ### Final step 51 | 52 | The final step of the script is run the config logica add (with `Start-AzLogicApp`). Whent the config logic app is called from the setup script, it uses a specific path that will do the following: 53 | - Initialize the trackers on the storage account tables with the installation time timestamp 54 | - Copy the configuration file to the blob storage account container 55 | - Copy the templates from the repository to the blob storage account container 56 | 57 | ⚠️ If that steps fails, the solution cannot be used. -------------------------------------------------------------------------------- /deploy/readme.md: -------------------------------------------------------------------------------- 1 | ## 👀 Security End-user Notification (SEEN) - Deployment 2 | 3 | The deployment has 3 steps: 4 | 5 | 1. Deploy the ARM template in your subscription 6 | 2. Run the setup script to set the permissions and trigger the initial configuration 7 | 3. Use the provided workbook to customize and enable the modules 8 | 9 | Make sure you have met the prerequisites outlined in this page prior deploying the solution. 10 | 11 | For detailed advanced deployment documentation, refer to the [deployment documentation article](/docs/deployment.md). 12 | 13 | ## Prerequisites 14 | 15 | ### Azure AD logs 16 | 17 | To use this solution, you need an existing Log Analytics Workspace with the `SigninLogs` and `AuditLogs` data connected from Azure AD. 18 | 19 | If that's currently not the case, refer to the following documentation to set it up: [Integrate Azure AD logs with Log Analytics](https://learn.microsoft.com/azure/active-directory/reports-monitoring/howto-integrate-activity-logs-with-log-analytics). 20 | 21 | ### Deployment permissions 22 | 23 | To deploy the ARM template you will need to be a contributor on the targeted resource group. The deployment will create the following resource types: 24 | 25 | image Logic apps 26 | image Azure Monitor workbook 27 | image API connections 28 | image Storage account 29 | 30 | All modules are using system managed identities and do not require the creation of generic accounts or any type of other user accounts in your Azure AD tenant. 31 | 32 | ### Setup script permissions 33 | 34 | The setup script needs to be executed AFTER you deployed the ARM template. It will be used for the following tasks: 35 | - grant permissions to the system managed identities 36 | - populate the storage account table with starter values for trackers 37 | - trigger the config logic to install the templates in your storage account 38 | 39 | To run the script, you will need the following permissions: 40 | - Azure AD Global Administrator or an Azure AD Privileged Role Administrator to set permission for the managed identities 41 | - Resource Group Owner or User Access Administrator on the resource groups hosting the logic app and the storage account to set RBAC roles 42 | 43 | ## Deployment template 44 | 45 | You can deploy the ARM templates to your Azure Subscription using the link below: 46 | 47 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/seendeploy) 48 | 49 | ## Execute setup.ps1 script 50 | 51 | You can download the script [here](https://raw.githubusercontent.com/piaudonn/SecurityNotifications/main/deploy/setup.ps1). 52 | To run the script you will to provide the following parameters: 53 | 54 | - `TenantId` the Azure AD tenant ID of your environment 55 | - `AzureSubscriptionId` the Azure subscription ID of your deployment 56 | - `StorageAccountResourceGroupName` the name of the resource group where the storage account deployed for the solution is 57 | - `WorkspaceResourceGroupName` the name of the resource group where the log analytic workspace is 58 | - `SEENResourceGroupName` the name of the resource group where the logic apps modules are 59 | 60 | Example: 61 | 62 | ```powershell 63 | .\Setup.ps1 ` 64 | -TenantId "120cd98f-1002-45b7-80ff-69fc68bdd027" ` 65 | -AzureSubscriptionId "e893f408-3d86-419f-c1a6-9c91c6872761" ` 66 | -StorageAccountResourceGroupName "default-1" ` 67 | -WorkspaceResourceGroupName "default-1" ` 68 | -SEENResourceGroupName "default-1" 69 | ``` 70 | 71 | For advanced script parameters, refer to the [script documentation](/docs/setupscript.md). 72 | 73 | At the end of the script execution, you are given a hyper link to the workbook to customize and enable the solution. Note that you can also access this workbook directly from the Azure portal in the resource group used for the deployment. 74 | 75 | ## Post deployment 76 | 77 | By default, all the modules are disabled. It means that the end users will not receive emails yet. You must use the workbook to customize and enable the modules. 78 | 79 | In the **SEEN-Manage and monitor** workbook, make sure you are in the **Configuration** tab and scroll until you see the **SEEN Configuration** section: 80 | 81 | image 82 | 83 | - Replace the **Mail From** value with the email address of the account from which you want to send notifications 84 | - Replace the **Test Email** value with the email address to who you want to send the notification when the solution runs in **Test Mode**. 85 | - Leave the modules in **Test Mode**. All emails will be sent to the TestEmail you specified instead of the end-user. 86 | 87 | image 88 | 89 | Validate the configuration by clicking the **Save the configuration** button and confirming. 90 | 91 | Then in the list of Logic App at the top of the workbook, start the two modules which are disabled by default. Note that it takes few seconds for the modules to start. You can refresh the module until you confirmed the modules are started. 92 | 93 | image 94 | 95 | 96 | 👏 **The solution is now running in test mode.** 97 | 98 | Refer to the documentation for detailed explanations of customization options and templates. 99 | 100 | Note that you will need to switch the **Test Mode** from **Enabled** to **Disabled** once you are familiar with the solution to start sending notifications to end-users. Refer to the [Disable Test Mode](/docs/faq.md) documentation. 101 | 102 | -------------------------------------------------------------------------------- /shared/storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "StorageAccountName": { 6 | "defaultValue": "seen", 7 | "type": "String" 8 | }, 9 | "StorageAccountType": { 10 | "defaultValue": "", 11 | "type": "String" 12 | }, 13 | "StorageAccountKind": { 14 | "defaultValue": "", 15 | "type": "String" 16 | } 17 | }, 18 | "variables": { 19 | "containerName": "seen", 20 | "blobSasFunctionValues": { 21 | "canonicalizedResource": "[concat('/blob/', parameters('StorageAccountName'), '/seen')]", 22 | "signedResource": "c", 23 | "signedPermission": "r", 24 | "signedExpiry": "2099-01-01T00:00:00Z" 25 | } 26 | }, 27 | "resources": [ 28 | { 29 | "type": "Microsoft.Storage/storageAccounts", 30 | "apiVersion": "2022-05-01", 31 | "name": "[parameters('StorageAccountName')]", 32 | "location": "[resourceGroup().location]", 33 | "sku": { 34 | "name": "[parameters('StorageAccountType')]", 35 | "tier": "Standard" 36 | }, 37 | "kind": "[parameters('StorageAccountKind')]", 38 | "properties": { 39 | "dnsEndpointType": "Standard", 40 | "defaultToOAuthAuthentication": false, 41 | "publicNetworkAccess": "Enabled", 42 | "allowCrossTenantReplication": true, 43 | "minimumTlsVersion": "TLS1_2", 44 | "allowBlobPublicAccess": true, 45 | "allowSharedKeyAccess": true, 46 | "networkAcls": { 47 | "resourceAccessRules": [], 48 | "bypass": "AzureServices", 49 | "virtualNetworkRules": [], 50 | "ipRules": [], 51 | "defaultAction": "Allow" 52 | }, 53 | "supportsHttpsTrafficOnly": true, 54 | "encryption": { 55 | "requireInfrastructureEncryption": false, 56 | "services": { 57 | "file": { 58 | "keyType": "Account", 59 | "enabled": true 60 | }, 61 | "blob": { 62 | "keyType": "Account", 63 | "enabled": true 64 | } 65 | }, 66 | "keySource": "Microsoft.Storage" 67 | } 68 | } 69 | }, 70 | { 71 | "type": "Microsoft.Storage/storageAccounts/blobServices", 72 | "apiVersion": "2022-05-01", 73 | "name": "[concat(parameters('StorageAccountName'), '/default')]", 74 | "dependsOn": [ 75 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 76 | ], 77 | "sku": { 78 | "name": "[parameters('StorageAccountType')]", 79 | "tier": "Standard" 80 | }, 81 | "properties": { 82 | "changeFeed": { 83 | "enabled": false 84 | }, 85 | "restorePolicy": { 86 | "enabled": false 87 | }, 88 | "containerDeleteRetentionPolicy": { 89 | "enabled": true, 90 | "days": 7 91 | }, 92 | "cors": { 93 | "corsRules": [] 94 | }, 95 | "deleteRetentionPolicy": { 96 | "allowPermanentDelete": false, 97 | "enabled": true, 98 | "days": 7 99 | }, 100 | "isVersioningEnabled": false 101 | } 102 | }, 103 | { 104 | "type": "Microsoft.Storage/storageAccounts/queueServices", 105 | "apiVersion": "2022-05-01", 106 | "name": "[concat(parameters('StorageAccountName'), '/default')]", 107 | "dependsOn": [ 108 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 109 | ], 110 | "properties": { 111 | "cors": { 112 | "corsRules": [] 113 | } 114 | } 115 | }, 116 | { 117 | "type": "Microsoft.Storage/storageAccounts/tableServices", 118 | "apiVersion": "2022-05-01", 119 | "name": "[concat(parameters('StorageAccountName'), '/default')]", 120 | "dependsOn": [ 121 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 122 | ], 123 | "properties": { 124 | "cors": { 125 | "corsRules": [] 126 | } 127 | } 128 | }, 129 | { 130 | "type": "Microsoft.Storage/storageAccounts/blobServices/containers", 131 | "apiVersion": "2022-05-01", 132 | "name": "[concat(parameters('StorageAccountName'), '/default/', variables('containerName'))]", 133 | "dependsOn": [ 134 | "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('StorageAccountName'), 'default')]", 135 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 136 | ], 137 | "properties": { 138 | "immutableStorageWithVersioning": { 139 | "enabled": false 140 | }, 141 | "defaultEncryptionScope": "$account-encryption-key", 142 | "denyEncryptionScopeOverride": false, 143 | "publicAccess": "None" 144 | } 145 | }, 146 | { 147 | "type": "Microsoft.Storage/storageAccounts/tableServices/tables", 148 | "apiVersion": "2022-05-01", 149 | "name": "[concat(parameters('StorageAccountName'), '/default/trackers')]", 150 | "dependsOn": [ 151 | "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('StorageAccountName'), 'default')]", 152 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 153 | ], 154 | "properties": {} 155 | } 156 | ], 157 | "outputs": { 158 | "resourceID": { 159 | "type": "string", 160 | "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName'))]" 161 | }, 162 | "storageAccountName": { 163 | "type": "string", 164 | "value": "[parameters('StorageAccountName')]" 165 | }, 166 | "storageUrl": { 167 | "type": "string", 168 | "value": "[concat('https://', parameters('StorageAccountName'), '.blob.core.windows.net')]" 169 | }, 170 | "storageSas": { 171 | "type": "string", 172 | "value": "[concat('?', listServiceSas(resourceId('Microsoft.Storage/storageAccounts', parameters('StorageAccountName')), '2018-02-01', variables('blobSasFunctionValues')).serviceSasToken)]" 173 | } 174 | 175 | } 176 | } -------------------------------------------------------------------------------- /deploy/setup.ps1: -------------------------------------------------------------------------------- 1 | # Documentation and license information available at SEEN - https://aka.ms/seen 2 | # 3 | # Required Permissions 4 | # - Azure AD Global Administrator or an Azure AD Privileged Role Administrator to execute the Set-APIPermissions function 5 | # - Resource Group Owner or User Access Administrator on the resource groups hosting the logic app and the storage account to execute the Set-RBACPermissions function 6 | 7 | # Required PowerShell modules: 8 | # - MgGraph to grant MSI permissions using the Microsoft Graph API 9 | # - Az grant permissons on Azure resources and trigger the config logic app 10 | 11 | #Requires -Modules Microsoft.Graph.Applications, Az.Resources, Az.LogicApp 12 | 13 | param( 14 | $TenantId, 15 | $AzureSubscriptionId, 16 | $WorkspaceResourceGroupName, 17 | $SEENResourceGroupName, 18 | $StorageAccountResourceGroupName = $SEENResourceGroupName, 19 | $ConfigLogicAppName = "SEEN-Config", 20 | $SendEmailLogicAppName = "SEEN-SendEmail", 21 | $MFAMethodsLogicAppName = "SEEN-MFAMethods", 22 | $TravelLogicAppName = "SEEN-Travel", 23 | $TAPLogicAppName = "SEEN-TemporaryAccessPass" 24 | ) 25 | 26 | #region Connection 27 | Write-Host "⚙️ Connect to the Azure AD tenant: $TenantId" 28 | Connect-MgGraph -TenantId $TenantId -Scopes AppRoleAssignment.ReadWrite.All, Application.Read.All | Out-Null 29 | Write-Host "⚙️ Connecting to to the Azure subscription: $AzureSubscriptionId" 30 | try 31 | { 32 | Login-AzAccount -Subscription $AzureSubscriptionId -Tenant $TenantId -ErrorAction Stop | Out-Null 33 | $Domain = (Get-AzTenant | Where-Object { $_.Id -eq "550a9b78-cb2e-43e0-9c5b-db194784b875" }).Domains[0] 34 | Set-AzContext -SubscriptionId $AzureSubscriptionId -TenantId $TenantId | Out-Null 35 | } 36 | catch 37 | { 38 | Write-Host "⛔ Login to Azure Management failed. $($error[0])" 39 | } 40 | #endregion 41 | 42 | #region Functions 43 | function Set-APIPermissions ($MSIName, $AppId, $PermissionName) { 44 | Write-Host "⚙️ Setting permission $PermissionName on $MSIName" 45 | $MSI = Get-AppIds -AppName $MSIName 46 | if ( $MSI.count -gt 1 ) 47 | { 48 | Write-Host "❌ Found multiple principals with the same name." -ForegroundColor Red 49 | return 50 | } elseif ( $MSI.count -eq 0 ) { 51 | Write-Host "❌ Principal not found." -ForegroundColor Red 52 | return 53 | } 54 | #Start-Sleep -Milliseconds 500 # Wait in case the MSI identity creation tool some time 55 | $GraphServicePrincipal = Get-MgServicePrincipal -Filter "appId eq '$AppId'" 56 | $AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"} 57 | try 58 | { 59 | New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MSI.Id -PrincipalId $MSI.Id -ResourceId $GraphServicePrincipal.Id -AppRoleId $AppRole.Id -ErrorAction Stop | Out-Null 60 | } 61 | catch 62 | { 63 | if ( $_.Exception.Message -eq "Permission being assigned already exists on the object" ) 64 | { 65 | Write-Host "ℹ️ $($_.Exception.Message)" 66 | } else { 67 | Write-Host "❌ $($_.Exception.Message)" -ForegroundColor Red 68 | } 69 | return 70 | } 71 | Write-Host "✅ Permission granted" -ForegroundColor Green 72 | } 73 | 74 | function Get-AppIds ($AppName) { 75 | Get-MgServicePrincipal -Filter "displayName eq '$AppName'" 76 | } 77 | 78 | function Set-RBACPermissions ($MSIName, $Role, $ResourceGroup) { 79 | Write-Host "⚙️ Adding $Role to $MSIName" 80 | $MSI = Get-AppIds -AppName $MSIName 81 | if ( $MSI.count -gt 1 ) 82 | { 83 | Write-Host "❌ Found multiple principals with the same name." -ForegroundColor Red 84 | return 85 | } elseif ( $MSI.count -eq 0 ) { 86 | Write-Host "❌ Principal not found." -ForegroundColor Red 87 | return 88 | } 89 | $Assign = New-AzRoleAssignment -ApplicationId $MSI.AppId -Scope "/subscriptions/$($AzureSubscriptionId)/resourceGroups/$($ResourceGroup)" -RoleDefinitionName $Role -ErrorAction SilentlyContinue -ErrorVariable AzError 90 | if ( $Assign ) 91 | { 92 | Write-Host "✅ Role added" -ForegroundColor Green 93 | } elseif ( $AzError[0].Exception.Message -like "*Conflict*" ) { 94 | Write-Host "ℹ️ Role already assigned" 95 | } else { 96 | Write-Host "❌ $($AzError[0].Exception.Message)" -ForegroundColor Red 97 | } 98 | } 99 | #endregion 100 | 101 | #region Permissions 102 | #Allow installer to trigger the config Logic App 103 | <# 104 | $AssignLogicAppOperator = New-AzRoleAssignment ` 105 | -Scope "/subscriptions/$($AzureSubscriptionId)/resourceGroups/$($SEENResourceGroupName)/providers/Microsoft.Logic/workflows/$($ConfigLogicAppName)" ` 106 | -RoleDefinitionName "Logic App Operator" ` 107 | -ErrorAction SilentlyContinue ` 108 | -ErrorVariable AzErrorLogicAppOperator 109 | if ( $AssignLogicAppOperator ) 110 | { 111 | Write-Host "✅ Role added" -ForegroundColor Green 112 | } elseif ( $AzErrorLogicAppOperator[0].Exception.Message -like "*Conflict*" ) { 113 | Write-Host "ℹ️ Role already assigned" 114 | } else { 115 | Write-Host "❌ $($AzErrorLogicAppOperator[0].Exception.Message)" -ForegroundColor Red 116 | } 117 | #> 118 | 119 | #Config Logic App 120 | Set-RBACPermissions -MSIName $ConfigLogicAppName -Role "Storage Blob Data Contributor" -ResourceGroup $StorageAccountResourceGroupName 121 | Set-RBACPermissions -MSIName $ConfigLogicAppName -Role "Storage Table Data Contributor" -ResourceGroup $StorageAccountResourceGroupName 122 | 123 | #Send Email Logic App 124 | Set-APIPermissions -MSIName $SendEmailLogicAppName -AppId "00000003-0000-0000-c000-000000000000" -PermissionName "Mail.Send" 125 | 126 | #MFA Methods Logic APp 127 | Set-APIPermissions -MSIName $MFAMethodsLogicAppName -AppId "00000003-0000-0000-c000-000000000000" -PermissionName "User.Read.All" 128 | Set-RBACPermissions -MSIName $MFAMethodsLogicAppName -Role "Storage Table Data Contributor" -ResourceGroup $StorageAccountResourceGroupName 129 | Set-RBACPermissions -MSIName $MFAMethodsLogicAppName -Role "Log Analytics Reader" -ResourceGroup $WorkspaceResourceGroupName 130 | 131 | #TAP Logic APp 132 | Set-APIPermissions -MSIName $TAPLogicAppName -AppId "00000003-0000-0000-c000-000000000000" -PermissionName "User.Read.All" 133 | Set-RBACPermissions -MSIName $TAPLogicAppName -Role "Storage Table Data Contributor" -ResourceGroup $StorageAccountResourceGroupName 134 | Set-RBACPermissions -MSIName $TAPLogicAppName -Role "Log Analytics Reader" -ResourceGroup $WorkspaceResourceGroupName 135 | 136 | #TAP Logic APp 137 | Set-APIPermissions -MSIName $TravelLogicAppName -AppId "00000003-0000-0000-c000-000000000000" -PermissionName "User.Read.All" 138 | Set-RBACPermissions -MSIName $TravelLogicAppName -Role "Storage Table Data Contributor" -ResourceGroup $StorageAccountResourceGroupName 139 | Set-RBACPermissions -MSIName $TravelLogicAppName -Role "Log Analytics Reader" -ResourceGroup $WorkspaceResourceGroupName 140 | #endregion 141 | 142 | #region Config LA in setup mode 143 | Write-Host "⚙️ Triggering the $ConfigLogicAppName logic app to provision the storage account used for SEEN." 144 | try 145 | { 146 | Start-AzLogicApp -ResourceGroupName $SEENResourceGroupName -Name $ConfigLogicAppName -TriggerName "manual" 147 | } 148 | catch 149 | { 150 | Write-Host "⛔ Cannot trigger the logic app. The configuration is not finished.`n$($_.Exception.Message)" 151 | } 152 | #endregion 153 | 154 | Write-Host "⚙️ End of the script. Please review the output and check for potential failures." 155 | Write-Host "`n👏 You can now open the ""Manage and monitor"" workbook in the $SEENResourceGroupName resource group to configure and enable the modules. `n`n`t🔗 Click here: " -NoNewline 156 | Write-Host "https://portal.azure.com/#@$($Domain)/resource/subscriptions/$($AzureSubscriptionId)/resourceGroups/$($SEENResourceGroupName)/providers/microsoft.insights/workbooks/7bea244d-869b-45a8-996a-2472c0d7bc5f/workbook `n`n" 157 | -------------------------------------------------------------------------------- /shared/emailnotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "PlaybookName": { 6 | "defaultValue": "SEEN-SendEmail", 7 | "type": "String" 8 | }, 9 | "ProjectName": { 10 | "defaultValue": "SEEN", 11 | "type": "String" 12 | }, 13 | "ModuleVersion": { 14 | "defaultValue": "1.0", 15 | "type": "String" 16 | }, 17 | "LogAnalyticsResourceId": { 18 | "defaultValue": "", 19 | "type": "String" 20 | }, 21 | "EnableDiagnostics": { 22 | "defaultValue": true, 23 | "type": "bool" 24 | } 25 | }, 26 | "variables": {}, 27 | "resources": [ 28 | { 29 | "type": "Microsoft.Logic/workflows", 30 | "apiVersion": "2017-07-01", 31 | "name": "[parameters('PlaybookName')]", 32 | "location": "[resourceGroup().location]", 33 | "identity": { 34 | "type": "SystemAssigned" 35 | }, 36 | "properties": { 37 | "state": "Enabled", 38 | "accessControl": { 39 | "triggers": { 40 | "allowedCallerIpAddresses": [] 41 | } 42 | }, 43 | "definition": { 44 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 45 | "contentVersion": "1.0.0.0", 46 | "parameters": { 47 | "ProjectName": { 48 | "defaultValue": "[parameters('ProjectName')]", 49 | "type": "String" 50 | }, 51 | "ModuleVersion": { 52 | "defaultValue": "[parameters('ModuleVersion')]", 53 | "type": "String" 54 | } 55 | }, 56 | "triggers": { 57 | "manual": { 58 | "type": "Request", 59 | "kind": "Http", 60 | "inputs": { 61 | "schema": { 62 | "properties": { 63 | "bcc": { 64 | "type": "string" 65 | }, 66 | "body": { 67 | "type": "string" 68 | }, 69 | "cc": { 70 | "type": "string" 71 | }, 72 | "importance": { 73 | "type": "string" 74 | }, 75 | "mailFrom": { 76 | "type": "string" 77 | }, 78 | "replyTo": { 79 | "type": "string" 80 | }, 81 | "saveToSentItems": { 82 | "type": "boolean" 83 | }, 84 | "subject": { 85 | "type": "string" 86 | }, 87 | "to": { 88 | "type": "string" 89 | }, 90 | "tracking": { 91 | "type": "string" 92 | } 93 | }, 94 | "type": "object" 95 | } 96 | } 97 | } 98 | }, 99 | "actions": { 100 | "Compose": { 101 | "runAfter": {}, 102 | "type": "Compose", 103 | "inputs": { 104 | "message": { 105 | "bccRecipients": "@json(if(or(equals(triggerBody()?['bcc'],''),equals(triggerBody()?['bcc'],null)),'[]',concat('[{\"emailAddress\":{\"address\":\"',triggerBody()?['bcc'],'\"}}],')))", 106 | "body": { 107 | "content": "@{triggerBody()?['body']}", 108 | "contentType": "HTML" 109 | }, 110 | "ccRecipients": "@json(if(or(equals(triggerBody()?['cc'],''),equals(triggerBody()?['bcc'],null)),'[]',concat('[{\"emailAddress\":{\"address\":\"',triggerBody()?['cc'],'\"}}],')))", 111 | "replyTo": [ 112 | { 113 | "emailAddress": { 114 | "address": "@{triggerBody()?['replyTo']}" 115 | } 116 | } 117 | ], 118 | "subject": "@{triggerBody()?['subject']}", 119 | "toRecipients": [ 120 | { 121 | "emailAddress": { 122 | "address": "@{triggerBody()?['to']}" 123 | } 124 | } 125 | ] 126 | }, 127 | "saveToSentItems": "@{triggerBody()?['saveToSentItems']}" 128 | } 129 | }, 130 | "HTTP_sendMail": { 131 | "runAfter": { 132 | "Compose": [ 133 | "Succeeded" 134 | ] 135 | }, 136 | "type": "Http", 137 | "inputs": { 138 | "authentication": { 139 | "audience": "https://graph.microsoft.com/", 140 | "type": "ManagedServiceIdentity" 141 | }, 142 | "body": "@outputs('Compose')", 143 | "headers": { 144 | "Content-type": "application/json" 145 | }, 146 | "method": "POST", 147 | "uri": "https://graph.microsoft.com/v1.0/users/@{triggerBody()?['mailFrom']}/sendMail" 148 | } 149 | }, 150 | "Response": { 151 | "runAfter": { 152 | "HTTP_sendMail": [ 153 | "Succeeded", 154 | "Failed" 155 | ] 156 | }, 157 | "trackedProperties": { 158 | "EmailBcc": "@action()['inputs']['body']['emailBcc']", 159 | "EmailCc": "@action()['inputs']['body']['emailCc']", 160 | "EmailFrom": "@action()['inputs']['body']['emailFrom']", 161 | "EmailRecipient": "@action()['inputs']['body']['emailRecipient']", 162 | "EmailStatus": "@action()['inputs']['body']['emailStatus']", 163 | "EmailSubject": "@action()['inputs']['body']['emailSubject']", 164 | "Tracking": "@action()['inputs']['body']['tracking']" 165 | }, 166 | "type": "Response", 167 | "kind": "Http", 168 | "inputs": { 169 | "body": { 170 | "emailBcc": "@{triggerBody()?['bcc']}", 171 | "emailCc": "@{triggerBody()?['cc']}", 172 | "emailFrom": "@{triggerBody()?['mailFrom']}", 173 | "emailRecipient": "@{triggerBody()?['to']}", 174 | "emailStatus": "@outputs('HTTP_sendMail')['statusCode']", 175 | "emailSubject": "@{triggerBody()?['subject']}", 176 | "tracking": "@triggerBody()?['tracking']" 177 | }, 178 | "statusCode": "@coalesce(outputs('HTTP_sendMail')['statusCode'],500)" 179 | } 180 | } 181 | }, 182 | "outputs": {} 183 | }, 184 | "parameters": {} 185 | } 186 | }, 187 | { 188 | "condition": "[parameters('EnableDiagnostics')]", 189 | "type": "Microsoft.Logic/workflows/providers/diagnosticSettings", 190 | "apiVersion": "2017-05-01-preview", 191 | "name": "[concat(parameters('PlaybookName'), '/', 'Microsoft.Insights/send-to-la')]", 192 | "location": "[resourceGroup().location]", 193 | "dependsOn": [ 194 | "[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]" 195 | ], 196 | "properties": { 197 | "workspaceId": "[parameters('LogAnalyticsResourceId')]", 198 | "metrics": [], 199 | "logs": [ 200 | { 201 | "category": "WorkflowRuntime", 202 | "enabled": true 203 | } 204 | ] 205 | } 206 | } 207 | ], 208 | "outputs": { 209 | "resourceID": { 210 | "type": "string", 211 | "value": "[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]" 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /deploy/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "StorageAccountName": { 6 | "defaultValue": "seen", 7 | "type": "String" 8 | }, 9 | "StorageAccountKind": { 10 | "defaultValue": "StorageV2", 11 | "type": "String" 12 | }, 13 | "StorageAccountType": { 14 | "defaultValue": "Standard_LRS", 15 | "type": "String" 16 | }, 17 | "LogAnalyticsResourceId": { 18 | "defaultValue": "", 19 | "type": "String" 20 | }, 21 | "ConfigPlaybookName": { 22 | "defaultValue": "SEEN-Config", 23 | "type": "String" 24 | }, 25 | "EmailPlaybookName": { 26 | "defaultValue": "SEEN-SendEmail", 27 | "type": "String" 28 | }, 29 | "WorkbookName": { 30 | "defaultValue": "SEEN-Manage and monitor", 31 | "type": "String" 32 | }, 33 | "MFAPlaybookName": { 34 | "defaultValue": "SEEN-MFAMethods", 35 | "type": "String" 36 | }, 37 | "TAPPlaybookName": { 38 | "defaultValue": "SEEN-TemporaryAccessPass", 39 | "type": "String" 40 | }, 41 | "TravelPlaybookName": { 42 | "defaultValue": "SEEN-Travel", 43 | "type": "String" 44 | }, 45 | "TestEmailPlaybookName": { 46 | "defaultValue": "SEEN-TestEmail", 47 | "type": "String" 48 | }, 49 | "EnableDiagnostics": { 50 | "defaultValue": true, 51 | "type": "bool" 52 | }, 53 | "DeployFromBranch": { 54 | "defaultValue": "main", 55 | "type": "String" 56 | } 57 | }, 58 | "variables": { 59 | "storageTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/shared/storage.json')]", 60 | "webConnectionTemplate": "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/shared/webconnections.json')]", 61 | "configTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/shared/config.json')]", 62 | "emailTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/shared/emailnotification.json')]", 63 | "workbookTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/shared/workbook.json')]", 64 | "mfaTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/modules/MFAMethods/mfamethods.json')]", 65 | "tapTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/modules/TAP/tap.json')]", 66 | "travelTemplate" : "[concat('https://raw.githubusercontent.com/piaudonn/SecurityNotifications/', parameters('DeployFromBranch'), '/modules/Travel/travel.json')]", 67 | "workbookId" : "7bea244d-869b-45a8-996a-2472c0d7bc5f", 68 | "workbookType": "workbook" 69 | }, 70 | "resources": [ 71 | { 72 | "apiVersion": "2019-10-01", 73 | "name": "StorageTemplate", 74 | "type": "Microsoft.Resources/deployments", 75 | "dependsOn": [], 76 | "properties": { 77 | "mode": "incremental", 78 | "templateLink": { 79 | "uri": "[variables('storageTemplate')]", 80 | "contentVersion": "1.0.0.0" 81 | }, 82 | "parameters": { 83 | "StorageAccountName": { 84 | "value": "[parameters('StorageAccountName')]" 85 | }, 86 | "StorageAccountKind": { 87 | "value": "[parameters('StorageAccountKind')]" 88 | }, 89 | "StorageAccountType": { 90 | "value": "[parameters('StorageAccountType')]" 91 | } 92 | } 93 | } 94 | }, 95 | { 96 | "apiVersion": "2019-10-01", 97 | "name": "WebConnectionTemplate", 98 | "type": "Microsoft.Resources/deployments", 99 | "dependsOn": [], 100 | "properties": { 101 | "mode": "incremental", 102 | "templateLink": { 103 | "uri": "[variables('webConnectionTemplate')]", 104 | "contentVersion": "1.0.0.0" 105 | }, 106 | "parameters": {} 107 | } 108 | }, 109 | { 110 | "apiVersion": "2019-10-01", 111 | "name": "ConfigTemplate", 112 | "type": "Microsoft.Resources/deployments", 113 | "dependsOn": [ 114 | "[resourceId('Microsoft.Resources/deployments', 'StorageTemplate')]", 115 | "[resourceId('Microsoft.Resources/deployments', 'WebConnectionTemplate')]", 116 | "[resourceId('Microsoft.Resources/deployments', 'EmailTemplate')]" 117 | ], 118 | "properties": { 119 | "mode": "incremental", 120 | "templateLink": { 121 | "uri": "[variables('configTemplate')]", 122 | "contentVersion": "1.0.0.0" 123 | }, 124 | "parameters": { 125 | "PlaybookName": { 126 | "value": "[parameters('ConfigPlaybookName')]" 127 | }, 128 | "BlobConnectionResourceID": { 129 | "value": "[reference('WebConnectionTemplate').outputs.BlobConnectionResourceID.value]" 130 | }, 131 | "TablesConnectionResourceID": { 132 | "value": "[reference('WebConnectionTemplate').outputs.TableConnectionResourceID.value]" 133 | }, 134 | "EmailNotificationResourceId": { 135 | "value": "[reference('EmailTemplate').outputs.resourceID.value]" 136 | }, 137 | "StorageAccountName": { 138 | "value": "[reference('StorageTemplate').outputs.storageAccountName.value]" 139 | } 140 | } 141 | } 142 | }, 143 | { 144 | "apiVersion": "2019-10-01", 145 | "name": "EmailTemplate", 146 | "type": "Microsoft.Resources/deployments", 147 | "dependsOn": [], 148 | "properties": { 149 | "mode": "incremental", 150 | "templateLink": { 151 | "uri": "[variables('emailTemplate')]", 152 | "contentVersion": "1.0.0.0" 153 | }, 154 | "parameters": { 155 | "PlaybookName": { 156 | "value": "[parameters('EmailPlaybookName')]" 157 | }, 158 | "LogAnalyticsResourceId": { 159 | "value": "[parameters('LogAnalyticsResourceId')]" 160 | }, 161 | "EnableDiagnostics": { 162 | "value": "[parameters('EnableDiagnostics')]" 163 | } 164 | } 165 | } 166 | }, 167 | { 168 | "apiVersion": "2019-10-01", 169 | "name": "MFATemplate", 170 | "type": "Microsoft.Resources/deployments", 171 | "dependsOn": [ 172 | "[resourceId('Microsoft.Resources/deployments', 'StorageTemplate')]", 173 | "[resourceId('Microsoft.Resources/deployments', 'WebConnectionTemplate')]", 174 | "[resourceId('Microsoft.Resources/deployments', 'ConfigTemplate')]" 175 | ], 176 | "properties": { 177 | "mode": "incremental", 178 | "templateLink": { 179 | "uri": "[variables('mfaTemplate')]", 180 | "contentVersion": "1.0.0.0" 181 | }, 182 | "parameters": { 183 | "PlaybookName": { 184 | "value": "[parameters('MFAPlaybookName')]" 185 | }, 186 | "TablesConnectionResourceId": { 187 | "value": "[reference('WebConnectionTemplate').outputs.TableConnectionResourceID.value]" 188 | }, 189 | "ConfigResourceId": { 190 | "value": "[reference('ConfigTemplate').outputs.resourceID.value]" 191 | }, 192 | "EmailNotificationResourceId": { 193 | "value": "[reference('EmailTemplate').outputs.resourceID.value]" 194 | }, 195 | "StorageAccountName": { 196 | "value": "[reference('StorageTemplate').outputs.storageAccountName.value]" 197 | }, 198 | "LogAnalyticsWorkspaceId": { 199 | "value": "[reference(parameters('LogAnalyticsResourceId'), '2021-06-01', 'Full').properties.customerId]" 200 | } 201 | } 202 | } 203 | }, 204 | { 205 | "apiVersion": "2019-10-01", 206 | "name": "TravelTemplate", 207 | "type": "Microsoft.Resources/deployments", 208 | "dependsOn": [ 209 | "[resourceId('Microsoft.Resources/deployments', 'StorageTemplate')]", 210 | "[resourceId('Microsoft.Resources/deployments', 'WebConnectionTemplate')]", 211 | "[resourceId('Microsoft.Resources/deployments', 'ConfigTemplate')]" 212 | ], 213 | "properties": { 214 | "mode": "incremental", 215 | "templateLink": { 216 | "uri": "[variables('travelTemplate')]", 217 | "contentVersion": "1.0.0.0" 218 | }, 219 | "parameters": { 220 | "PlaybookName": { 221 | "value": "[parameters('TravelPlaybookName')]" 222 | }, 223 | "TablesConnectionResourceId": { 224 | "value": "[reference('WebConnectionTemplate').outputs.TableConnectionResourceID.value]" 225 | }, 226 | "ConfigResourceId": { 227 | "value": "[reference('ConfigTemplate').outputs.resourceID.value]" 228 | }, 229 | "EmailNotificationResourceId": { 230 | "value": "[reference('EmailTemplate').outputs.resourceID.value]" 231 | }, 232 | "StorageAccountName": { 233 | "value": "[reference('StorageTemplate').outputs.storageAccountName.value]" 234 | }, 235 | "LogAnalyticsWorkspaceId": { 236 | "value": "[reference(parameters('LogAnalyticsResourceId'), '2021-06-01', 'Full').properties.customerId]" 237 | } 238 | } 239 | } 240 | }, 241 | { 242 | "apiVersion": "2019-10-01", 243 | "name": "TAPTemplate", 244 | "type": "Microsoft.Resources/deployments", 245 | "dependsOn": [ 246 | "[resourceId('Microsoft.Resources/deployments', 'StorageTemplate')]", 247 | "[resourceId('Microsoft.Resources/deployments', 'WebConnectionTemplate')]", 248 | "[resourceId('Microsoft.Resources/deployments', 'ConfigTemplate')]" 249 | ], 250 | "properties": { 251 | "mode": "incremental", 252 | "templateLink": { 253 | "uri": "[variables('tapTemplate')]", 254 | "contentVersion": "1.0.0.0" 255 | }, 256 | "parameters": { 257 | "PlaybookName": { 258 | "value": "[parameters('TAPPlaybookName')]" 259 | }, 260 | "TablesConnectionResourceId": { 261 | "value": "[reference('WebConnectionTemplate').outputs.TableConnectionResourceID.value]" 262 | }, 263 | "ConfigResourceId": { 264 | "value": "[reference('ConfigTemplate').outputs.resourceID.value]" 265 | }, 266 | "EmailNotificationResourceId": { 267 | "value": "[reference('EmailTemplate').outputs.resourceID.value]" 268 | }, 269 | "StorageAccountName": { 270 | "value": "[reference('StorageTemplate').outputs.storageAccountName.value]" 271 | }, 272 | "LogAnalyticsWorkspaceId": { 273 | "value": "[reference(parameters('LogAnalyticsResourceId'), '2021-06-01', 'Full').properties.customerId]" 274 | } 275 | } 276 | } 277 | }, 278 | { 279 | "apiVersion": "2019-10-01", 280 | "name": "WorkbookTemplate", 281 | "type": "Microsoft.Resources/deployments", 282 | "dependsOn": [ 283 | "[resourceId('Microsoft.Resources/deployments', 'StorageTemplate')]", 284 | "[resourceId('Microsoft.Resources/deployments', 'WebConnectionTemplate')]", 285 | "[resourceId('Microsoft.Resources/deployments', 'ConfigTemplate')]", 286 | "[resourceId('Microsoft.Resources/deployments', 'EmailTemplate')]" 287 | ], 288 | "properties": { 289 | "mode": "incremental", 290 | "templateLink": { 291 | "uri": "[variables('workbookTemplate')]", 292 | "contentVersion": "1.0.0.0" 293 | }, 294 | "parameters": { 295 | "workbookDisplayName": { 296 | "value": "[parameters('WorkbookName')]" 297 | }, 298 | "workbookType": { 299 | "value": "[variables('workbookType')]" 300 | }, 301 | "workbookSourceId": { 302 | "value": "[parameters('LogAnalyticsResourceId')]" 303 | }, 304 | "SEENLogicAppsPath": { 305 | "value": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name ,'/providers/Microsoft.Logic/workflows/')]" 306 | }, 307 | "storageUrl": { 308 | "value": "[reference('StorageTemplate').outputs.storageUrl.value]" 309 | }, 310 | "storageSas": { 311 | "value": "[reference('StorageTemplate').outputs.storageSas.value]" 312 | }, 313 | "workbookId": { 314 | "value": "[variables('workbookId')]" 315 | }, 316 | "ConfigLAId": { 317 | "value": "[reference('ConfigTemplate').outputs.resourceID.value]" 318 | }, 319 | "EmailNotificationResourceId": { 320 | "value": "[reference('EmailTemplate').outputs.resourceID.value]" 321 | } 322 | } 323 | } 324 | } 325 | ] 326 | } 327 | -------------------------------------------------------------------------------- /deploy/createUiDefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", 3 | "handler": "Microsoft.Azure.CreateUIDef", 4 | "version": "0.1.2-preview", 5 | "parameters": { 6 | "config": { 7 | "isWizard": true, 8 | "basics": { 9 | "description": "## Security End User Notifications (SEEN) Deployment\n### Post Deployment \nAfter deploying this template, ensure to run the [setup.ps1](https://github.com/piaudonn/SecurityNotifications/blob/main/deploy/setup.ps1) script to grant permissions to the SEEN modules. It may take up to 1 hour for the permissions to become effective.", 10 | "resourceGroup": { 11 | "constraints": { 12 | "validations": [ 13 | { 14 | "permission": "Microsoft.Logic/workflows/write", 15 | "message": "You must have permission to create and update Logic Apps" 16 | }, 17 | { 18 | "permission": "Microsoft.Storage/storageAccounts/write", 19 | "message": "You must have permission to create and update Storage Accounts" 20 | } 21 | ] 22 | } 23 | }, 24 | "location": { 25 | "label": "Location", 26 | "toolTip": "Location for all resources", 27 | "resourceTypes": [ 28 | "Microsoft.Logic", 29 | "Microsoft.Storage" 30 | ] 31 | } 32 | } 33 | }, 34 | "basics": [], 35 | "steps": [ 36 | { 37 | "name": "deploymentTypeStep", 38 | "label": "Deployment Type", 39 | "elements": [ 40 | { 41 | "name": "deployTextBlock2", 42 | "type": "Microsoft.Common.TextBlock", 43 | "visible": true, 44 | "options": { 45 | "text": "Select the type of deployment you want for SEEN" 46 | } 47 | }, 48 | { 49 | "name": "deployTextBlock3", 50 | "type": "Microsoft.Common.TextBlock", 51 | "visible": true, 52 | "options": { 53 | "text": "A standard deployment allows you to deploy or update the entire SEEN solution with minimal input. An advanced deployment allows for additional configuration such as providing customized names to the Logic Apps.", 54 | "link": { 55 | "label": "Learn more", 56 | "uri": "https://github.com/piaudonn/SecurityNotifications/tree/main/deploy" 57 | } 58 | } 59 | }, 60 | { 61 | "name": "deploymentType", 62 | "type": "Microsoft.Common.OptionsGroup", 63 | "label": "Select the deployment type", 64 | "defaultValue": "Standard (recommended)", 65 | "toolTip": "", 66 | "constraints": { 67 | "allowedValues": [ 68 | { 69 | "label": "Standard (recommended)", 70 | "value": "standard" 71 | }, 72 | { 73 | "label": "Advanced", 74 | "value": "advanced" 75 | } 76 | ], 77 | "required": true 78 | }, 79 | "visible": true 80 | } 81 | ] 82 | }, 83 | { 84 | "name": "seenModules", 85 | "label": "SEEN Setup", 86 | "elements": [ 87 | { 88 | "name": "standardSetup", 89 | "type": "Microsoft.Common.Section", 90 | "label": "Standard Setup - STAT", 91 | "elements": [ 92 | { 93 | "name": "textBlock1", 94 | "type": "Microsoft.Common.TextBlock", 95 | "visible": true, 96 | "options": { 97 | "text": "The standard setup mode will deploy SEEN with default names and configuration", 98 | "link": { 99 | "label": "Learn more", 100 | "uri": "https://github.com/piaudonn/SecurityNotifications" 101 | } 102 | } 103 | } 104 | ], 105 | "visible": "[if(equals(steps('deploymentTypeStep').deploymentType, 'standard'), true, false)]" 106 | }, 107 | { 108 | "name": "advancedSetup", 109 | "type": "Microsoft.Common.Section", 110 | "label": "Advanced Setup - STAT", 111 | "elements": [ 112 | { 113 | "name": "textBlock1", 114 | "type": "Microsoft.Common.TextBlock", 115 | "visible": true, 116 | "options": { 117 | "text": "The Advanced Setup allows you to configure your own names for the resources created by this deployment. When updating the SEEN solution you must use the same names or new copies of the Logic Apps will be deployed.", 118 | "link": { 119 | "label": "Learn more", 120 | "uri": "https://github.com/piaudonn/SecurityNotifications" 121 | } 122 | } 123 | }, 124 | { 125 | "name": "ConfigPlaybookName", 126 | "type": "Microsoft.Common.TextBox", 127 | "label": "Configuration Playbook Name", 128 | "placeholder": "", 129 | "defaultValue": "SEEN-Config", 130 | "toolTip": "", 131 | "constraints": {}, 132 | "visible": true 133 | }, 134 | { 135 | "name": "EmailPlaybookName", 136 | "type": "Microsoft.Common.TextBox", 137 | "label": "Email Notification Playbook Name", 138 | "placeholder": "", 139 | "defaultValue": "SEEN-SendEmail", 140 | "toolTip": "", 141 | "constraints": {}, 142 | "visible": true 143 | }, 144 | { 145 | "name": "MFAPlaybookName", 146 | "type": "Microsoft.Common.TextBox", 147 | "label": "MFA Playbook Name", 148 | "placeholder": "", 149 | "defaultValue": "SEEN-MFAMethods", 150 | "toolTip": "", 151 | "constraints": {}, 152 | "visible": true 153 | }, 154 | { 155 | "name": "TravelPlaybookName", 156 | "type": "Microsoft.Common.TextBox", 157 | "label": "Travel Playbook Name", 158 | "placeholder": "", 159 | "defaultValue": "SEEN-Travel", 160 | "toolTip": "", 161 | "constraints": {}, 162 | "visible": true 163 | }, 164 | { 165 | "name": "TAPPlaybookName", 166 | "type": "Microsoft.Common.TextBox", 167 | "label": "Temporary Access Pass Playbook Name", 168 | "placeholder": "", 169 | "defaultValue": "SEEN-TemporaryAccessPass", 170 | "toolTip": "", 171 | "constraints": {}, 172 | "visible": true 173 | }, 174 | { 175 | "name": "WorkbookName", 176 | "type": "Microsoft.Common.TextBox", 177 | "label": "SEEN Workbook Name", 178 | "placeholder": "", 179 | "defaultValue": "SEEN-Manage and monitor", 180 | "toolTip": "", 181 | "constraints": {}, 182 | "visible": true 183 | }, 184 | { 185 | "name": "enableDiagnostics", 186 | "type": "Microsoft.Common.DropDown", 187 | "label": "Enable Diagnostic Logging", 188 | "placeholder": "", 189 | "defaultValue": "True", 190 | "toolTip": "", 191 | "multiselect": false, 192 | "selectAll": false, 193 | "multiLine": false, 194 | "constraints": { 195 | "allowedValues": [ 196 | { 197 | "label": "True", 198 | "value": true 199 | }, 200 | { 201 | "label": "False", 202 | "value": false 203 | } 204 | ], 205 | "required": true 206 | }, 207 | "visible": true 208 | }, 209 | { 210 | "name": "DeploymentBranch", 211 | "type": "Microsoft.Common.TextBox", 212 | "label": "Deploy from a specific GitHub Branch", 213 | "placeholder": "", 214 | "defaultValue": "main", 215 | "toolTip": "", 216 | "constraints": {}, 217 | "visible": true 218 | } 219 | ], 220 | "visible": "[if(equals(steps('deploymentTypeStep').deploymentType, 'advanced'), true, false)]" 221 | }, 222 | { 223 | "name": "laTextBlock1", 224 | "type": "Microsoft.Common.TextBlock", 225 | "visible": true, 226 | "options": { 227 | "text": "Log Analytics Workspace" 228 | } 229 | }, 230 | { 231 | "name": "logAnalyticsSelector", 232 | "type": "Microsoft.Solutions.ResourceSelector", 233 | "label": "Select Log Analytics Workspace", 234 | "resourceType": "microsoft.operationalinsights/workspaces", 235 | "options": { 236 | "filter": { 237 | } 238 | } 239 | } 240 | ] 241 | }, 242 | { 243 | "name": "seenStorage", 244 | "label": "Storage Setup", 245 | "elements": [ 246 | { 247 | "name": "storageTextBlock1", 248 | "type": "Microsoft.Common.TextBlock", 249 | "visible": true, 250 | "options": { 251 | "text": "SEEN uses a storage account to store email templates and track previous runs of SEEN modules." 252 | } 253 | }, 254 | { 255 | "name": "storageTextBlock2", 256 | "type": "Microsoft.Common.TextBlock", 257 | "visible": true, 258 | "options": { 259 | "text": "We recommend using a dedicated storage account for SEEN. If you choose to use an existing storage account, it must be in the same resource group as your SEEN deployment." 260 | } 261 | }, 262 | { 263 | "name": "storageAccount", 264 | "type": "Microsoft.Common.Section", 265 | "label": "Storage Account Name", 266 | "elements": [ 267 | { 268 | "name": "singleStorage", 269 | "type": "Microsoft.Storage.StorageAccountSelector", 270 | "label": "Storage account", 271 | "toolTip": "", 272 | "defaultValue": { 273 | "name": "", 274 | "type": "Standard_LRS" 275 | }, 276 | "constraints": { 277 | "allowedTypes": [] 278 | }, 279 | "options": { 280 | "hideExisting": false 281 | }, 282 | "visible": true 283 | } 284 | ], 285 | "visible": true 286 | } 287 | ] 288 | } 289 | ], 290 | "outputs": { 291 | "StorageAccountName": "[steps('seenStorage').storageAccount.singleStorage.name]", 292 | "StorageAccountType": "[steps('seenStorage').storageAccount.singleStorage.type]", 293 | "StorageAccountKind": "[steps('seenStorage').storageAccount.singleStorage.kind]", 294 | "LogAnalyticsResourceId": "[steps('seenModules').logAnalyticsSelector.id]", 295 | "ConfigPlaybookName": "[coalesce(steps('seenModules').advancedSetup.ConfigPlaybookName,'SEEN-Config')]", 296 | "EmailPlaybookName": "[coalesce(steps('seenModules').advancedSetup.EmailPlaybookName,'SEEN-SendEmail')]", 297 | "MFAPlaybookName": "[coalesce(steps('seenModules').advancedSetup.MFAPlaybookName,'SEEN-MFAMethods')]", 298 | "TravelPlaybookName": "[coalesce(steps('seenModules').advancedSetup.TravelPlaybookName,'SEEN-Travel')]", 299 | "TAPPlaybookName": "[coalesce(steps('seenModules').advancedSetup.TAPPlaybookName,'SEEN-TemporaryAccessPass')]", 300 | "WorkbookName": "[coalesce(steps('seenModules').advancedSetup.WorkbookName,'SEEN-Manage and monitor')]", 301 | "EnableDiagnostics": "[coalesce(steps('seenModules').advancedSetup.enableDiagnostics, true)]", 302 | "DeployFromBranch": "[coalesce(steps('seenModules').advancedSetup.DeploymentBranch,'main')]", 303 | "location": "[location()]" 304 | } 305 | } 306 | } -------------------------------------------------------------------------------- /modules/MFAMethods/mfamethods.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "PlaybookName": { 6 | "defaultValue": "SEEN-MFAMethods", 7 | "type": "String" 8 | }, 9 | "ConfigResourceId": { 10 | "defaultValue": "", 11 | "type": "String" 12 | }, 13 | "EmailNotificationResourceId": { 14 | "defaultValue": "", 15 | "type": "String" 16 | }, 17 | "TablesConnectionResourceId": { 18 | "defaultValue": "", 19 | "type": "String" 20 | }, 21 | "StorageAccountName": { 22 | "defaultValue": "", 23 | "type": "String" 24 | }, 25 | "LogAnalyticsWorkspaceId": { 26 | "defaultValue": "", 27 | "type": "String" 28 | }, 29 | "ProjectName": { 30 | "defaultValue": "SEEN", 31 | "type": "String" 32 | }, 33 | "ModuleVersion": { 34 | "defaultValue": "1.0", 35 | "type": "String" 36 | } 37 | }, 38 | "variables": {}, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Logic/workflows", 42 | "apiVersion": "2017-07-01", 43 | "name": "[parameters('PlaybookName')]", 44 | "location": "[resourceGroup().location]", 45 | "identity": { 46 | "type": "SystemAssigned" 47 | }, 48 | "properties": { 49 | "state": "Disabled", 50 | "definition": { 51 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 52 | "contentVersion": "1.0.0.0", 53 | "parameters": { 54 | "$connections": { 55 | "defaultValue": {}, 56 | "type": "Object" 57 | }, 58 | "WorkspaceId": { 59 | "defaultValue": "[parameters('LogAnalyticsWorkspaceId')]", 60 | "type": "String" 61 | }, 62 | "ProjectName": { 63 | "defaultValue": "[parameters('ProjectName')]", 64 | "type": "String" 65 | }, 66 | "ModuleVersion": { 67 | "defaultValue": "[parameters('ModuleVersion')]", 68 | "type": "String" 69 | } 70 | }, 71 | "triggers": { 72 | "Recurrence": { 73 | "recurrence": { 74 | "frequency": "Minute", 75 | "interval": 15 76 | }, 77 | "evaluatedRecurrence": { 78 | "frequency": "Minute", 79 | "interval": 15 80 | }, 81 | "type": "Recurrence" 82 | } 83 | }, 84 | "actions": { 85 | "Config": { 86 | "actions": { 87 | "Current_time": { 88 | "runAfter": {}, 89 | "type": "Expression", 90 | "kind": "CurrentTime", 91 | "inputs": {} 92 | }, 93 | "Get_entity_Tracker": { 94 | "runAfter": { 95 | "Current_time": [ 96 | "Succeeded" 97 | ] 98 | }, 99 | "type": "ApiConnection", 100 | "inputs": { 101 | "host": { 102 | "connection": { 103 | "name": "@parameters('$connections')['azuretables']['connectionId']" 104 | } 105 | }, 106 | "method": "get", 107 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''MFAMethods'')}'')')]", 108 | "queries": { 109 | "$select": "Tracker" 110 | } 111 | } 112 | }, 113 | "SEEN-Config": { 114 | "runAfter": { 115 | "Current_time": [ 116 | "Succeeded" 117 | ] 118 | }, 119 | "type": "Workflow", 120 | "inputs": { 121 | "body": { 122 | "module": "mfa" 123 | }, 124 | "host": { 125 | "triggerName": "manual", 126 | "workflow": { 127 | "id": "[parameters('ConfigResourceId')]" 128 | } 129 | } 130 | } 131 | } 132 | }, 133 | "runAfter": {}, 134 | "type": "Scope" 135 | }, 136 | "Exit": { 137 | "actions": { 138 | "Terminate": { 139 | "runAfter": { 140 | "Update_Entity_Tracker": [ 141 | "Succeeded" 142 | ] 143 | }, 144 | "type": "Terminate", 145 | "inputs": { 146 | "runStatus": "@{result('Notification')[0]['status']}" 147 | } 148 | }, 149 | "Update_Entity_Tracker": { 150 | "runAfter": {}, 151 | "type": "ApiConnection", 152 | "inputs": { 153 | "body": { 154 | "Tracker": "@{body('Current_time')}" 155 | }, 156 | "host": { 157 | "connection": { 158 | "name": "@parameters('$connections')['azuretables']['connectionId']" 159 | } 160 | }, 161 | "method": "put", 162 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''MFAMethods'')}'')')]" 163 | } 164 | } 165 | }, 166 | "runAfter": { 167 | "Notification": [ 168 | "Succeeded", 169 | "Failed" 170 | ] 171 | }, 172 | "type": "Scope" 173 | }, 174 | "Notification": { 175 | "actions": { 176 | "For_each_record": { 177 | "foreach": "@body('Log_Analytics_Call')?['tables']?[0]?['rows']", 178 | "actions": { 179 | "AAD_Call": { 180 | "runAfter": { 181 | "Debug": [ 182 | "Succeeded" 183 | ] 184 | }, 185 | "type": "Http", 186 | "inputs": { 187 | "authentication": { 188 | "audience": "https://graph.microsoft.com", 189 | "type": "ManagedServiceIdentity" 190 | }, 191 | "headers": { 192 | "ConsistencyLevel": "eventual" 193 | }, 194 | "method": "GET", 195 | "uri": "https://graph.microsoft.com/v1.0/users/@{item()[1]}/?$expand=manager($levels=1;$select=mail)" 196 | } 197 | }, 198 | "AAD_Debug": { 199 | "runAfter": { 200 | "AAD_Call": [ 201 | "Failed", 202 | "Succeeded" 203 | ] 204 | }, 205 | "type": "Compose", 206 | "inputs": "@coalesce(body('AAD_Call')?['error']?['message'], concat('Mail to: ',body('AAD_Call')?['mail']))" 207 | }, 208 | "Create_HTML_Activity_table": { 209 | "runAfter": { 210 | "Debug": [ 211 | "Succeeded" 212 | ] 213 | }, 214 | "type": "Table", 215 | "inputs": { 216 | "columns": [ 217 | { 218 | "header": "Time (UTC)", 219 | "value": "@concat('📆 ',item()['TimeGenerated'])" 220 | }, 221 | { 222 | "header": "Activity", 223 | "value": "@item()['Type']" 224 | } 225 | ], 226 | "format": "HTML", 227 | "from": "@json(item()[2])" 228 | } 229 | }, 230 | "Debug": { 231 | "runAfter": {}, 232 | "type": "Compose", 233 | "inputs": "@item()" 234 | }, 235 | "Email_Body": { 236 | "runAfter": { 237 | "AAD_Call": [ 238 | "Succeeded" 239 | ], 240 | "Create_HTML_Activity_table": [ 241 | "Succeeded" 242 | ] 243 | }, 244 | "type": "Compose", 245 | "inputs": "@replace(replace(body('SEEN-Config')['template'],'{UserFirstName}',coalesce(body('AAD_Call')['givenName'],body('AAD_Call')['displayName'])),'{Activities}',body('Create_HTML_Activity_table'))" 246 | }, 247 | "SEEN-SendNotification": { 248 | "runAfter": { 249 | "Email_Body": [ 250 | "Succeeded" 251 | ] 252 | }, 253 | "type": "Workflow", 254 | "inputs": { 255 | "body": { 256 | "bcc": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('SEEN-Config')['bcc'],null)}", 257 | "body": "@{outputs('Email_Body')}", 258 | "cc": "@{if(equals(body('SEEN-Config')?['recipient'],''),if(equals(body('SEEN-Config')['ccManager'],true),body('AAD_Call')?['manager']?['mail'],null),null)}", 259 | "importance": "high", 260 | "mailFrom": "@{body('SEEN-Config')['mailFrom']}", 261 | "replyTo": "@{body('SEEN-Config')['replyTo']}", 262 | "saveToSentItems": "@body('SEEN-Config')['saveToSentItems']", 263 | "subject": "@{body('SEEN-Config')['subject']}", 264 | "to": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('AAD_Call')['mail'],body('SEEN-Config')?['recipient'])}", 265 | "tracking": "mfa" 266 | }, 267 | "host": { 268 | "triggerName": "manual", 269 | "workflow": { 270 | "id": "[parameters('EmailNotificationResourceId')]" 271 | } 272 | } 273 | } 274 | } 275 | }, 276 | "runAfter": {}, 277 | "type": "Foreach" 278 | } 279 | }, 280 | "runAfter": { 281 | "Query": [ 282 | "Succeeded" 283 | ] 284 | }, 285 | "type": "Scope" 286 | }, 287 | "Query": { 288 | "actions": { 289 | "KQL_Query": { 290 | "runAfter": {}, 291 | "type": "Compose", 292 | "inputs": "let IncludeGuests = @{body('SEEN-Config')['includeGuests']};\nlet StartTime = todatetime(\"@{body('Get_entity_Tracker')['Tracker']}\") ;\nlet EndTime = todatetime(\"@{body('Current_time')}\") ;\nlet TimeZone = \"@{body('SEEN-Config')['timeZone']}\" ;\nAuditLogs\n| where ingestion_time() between (StartTime..EndTime)\n| where OperationName in (\"User registered security info\",\"User deleted security info\")\n| where Result == \"success\"\n| extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName)\n| extend UserId = tostring(TargetResources[0].id)\n| extend IsGuest = iif(UserPrincipalName contains \"#EXT#\", true, false)\n| where IncludeGuests == true or IsGuest == false\n| extend Details = bag_pack(\"TimeGenerated\", format_datetime(datetime_utc_to_local(TimeGenerated,TimeZone), 'yyyy-MM-dd HH:mm:ss'), \"Type\", ResultDescription)\n| project UserPrincipalName, UserId, Details\n| summarize Summary = make_set(Details) by UserPrincipalName, UserId" 293 | }, 294 | "Log_Analytics_Call": { 295 | "runAfter": { 296 | "KQL_Query": [ 297 | "Succeeded" 298 | ] 299 | }, 300 | "type": "Http", 301 | "inputs": { 302 | "authentication": { 303 | "audience": "https://api.loganalytics.io", 304 | "type": "ManagedServiceIdentity" 305 | }, 306 | "body": { 307 | "query": "@{outputs('KQL_Query')}", 308 | "timespan": "P1D" 309 | }, 310 | "method": "POST", 311 | "uri": "https://api.loganalytics.io/v1/workspaces/@{parameters('WorkspaceId')}/query" 312 | } 313 | } 314 | }, 315 | "runAfter": { 316 | "Config": [ 317 | "Succeeded" 318 | ] 319 | }, 320 | "type": "Scope" 321 | } 322 | }, 323 | "outputs": {} 324 | }, 325 | "parameters": { 326 | "$connections": { 327 | "value": { 328 | "azuretables": { 329 | "connectionId": "[parameters('TablesConnectionResourceId')]", 330 | "connectionName": "azuretables", 331 | "connectionProperties": { 332 | "authentication": { 333 | "type": "ManagedServiceIdentity" 334 | } 335 | }, 336 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuretables')]" 337 | } 338 | } 339 | } 340 | } 341 | } 342 | } 343 | ], 344 | "outputs": { 345 | "resourceID": { 346 | "type": "string", 347 | "value": "[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]" 348 | }, 349 | "resourcePrincipalID": { 350 | "type": "string", 351 | "value": "[reference(resourceId('Microsoft.Logic/workflows', parameters('PlaybookName')), '2017-07-01', 'Full').identity.principalId]" 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /modules/Travel/travel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "PlaybookName": { 6 | "defaultValue": "SEEN-Travel", 7 | "type": "String" 8 | }, 9 | "ConfigResourceId": { 10 | "defaultValue": "", 11 | "type": "String" 12 | }, 13 | "EmailNotificationResourceId": { 14 | "defaultValue": "", 15 | "type": "String" 16 | }, 17 | "TablesConnectionResourceId": { 18 | "defaultValue": "", 19 | "type": "String" 20 | }, 21 | "StorageAccountName": { 22 | "defaultValue": "", 23 | "type": "String" 24 | }, 25 | "LogAnalyticsWorkspaceId": { 26 | "defaultValue": "", 27 | "type": "String" 28 | }, 29 | "ProjectName": { 30 | "defaultValue": "SEEN", 31 | "type": "String" 32 | }, 33 | "ModuleVersion": { 34 | "defaultValue": "1.0", 35 | "type": "String" 36 | } 37 | }, 38 | "variables": {}, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Logic/workflows", 42 | "apiVersion": "2017-07-01", 43 | "name": "[parameters('PlaybookName')]", 44 | "location": "[resourceGroup().location]", 45 | "identity": { 46 | "type": "SystemAssigned" 47 | }, 48 | "properties": { 49 | "state": "Disabled", 50 | "definition": { 51 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 52 | "contentVersion": "1.0.0.0", 53 | "parameters": { 54 | "$connections": { 55 | "defaultValue": {}, 56 | "type": "Object" 57 | }, 58 | "WorkspaceId": { 59 | "defaultValue": "[parameters('LogAnalyticsWorkspaceId')]", 60 | "type": "String" 61 | }, 62 | "ProjectName": { 63 | "defaultValue": "[parameters('ProjectName')]", 64 | "type": "String" 65 | }, 66 | "ModuleVersion": { 67 | "defaultValue": "[parameters('ModuleVersion')]", 68 | "type": "String" 69 | } 70 | }, 71 | "triggers": { 72 | "Recurrence": { 73 | "recurrence": { 74 | "frequency": "Minute", 75 | "interval": 15 76 | }, 77 | "evaluatedRecurrence": { 78 | "frequency": "Minute", 79 | "interval": 15 80 | }, 81 | "type": "Recurrence" 82 | } 83 | }, 84 | "actions": { 85 | "Config": { 86 | "actions": { 87 | "Current_time": { 88 | "runAfter": {}, 89 | "type": "Expression", 90 | "kind": "CurrentTime", 91 | "inputs": {} 92 | }, 93 | "Get_entity_Tracker": { 94 | "runAfter": { 95 | "Current_time": [ 96 | "Succeeded" 97 | ] 98 | }, 99 | "type": "ApiConnection", 100 | "inputs": { 101 | "host": { 102 | "connection": { 103 | "name": "@parameters('$connections')['azuretables']['connectionId']" 104 | } 105 | }, 106 | "method": "get", 107 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''Travel'')}'')')]", 108 | "queries": { 109 | "$select": "Tracker" 110 | } 111 | } 112 | }, 113 | "SEEN-Config": { 114 | "runAfter": { 115 | "Current_time": [ 116 | "Succeeded" 117 | ] 118 | }, 119 | "type": "Workflow", 120 | "inputs": { 121 | "body": { 122 | "module": "travel" 123 | }, 124 | "host": { 125 | "triggerName": "manual", 126 | "workflow": { 127 | "id": "[parameters('ConfigResourceId')]" 128 | } 129 | } 130 | } 131 | } 132 | }, 133 | "runAfter": {}, 134 | "type": "Scope" 135 | }, 136 | "Exit": { 137 | "actions": { 138 | "Terminate": { 139 | "runAfter": { 140 | "Update_Entity_Tracker": [ 141 | "Succeeded" 142 | ] 143 | }, 144 | "type": "Terminate", 145 | "inputs": { 146 | "runStatus": "@{result('Notification')[0]['status']}" 147 | } 148 | }, 149 | "Update_Entity_Tracker": { 150 | "runAfter": {}, 151 | "type": "ApiConnection", 152 | "inputs": { 153 | "body": { 154 | "Tracker": "@{body('Current_time')}" 155 | }, 156 | "host": { 157 | "connection": { 158 | "name": "@parameters('$connections')['azuretables']['connectionId']" 159 | } 160 | }, 161 | "method": "put", 162 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''Travel'')}'')')]" 163 | } 164 | } 165 | }, 166 | "runAfter": { 167 | "Notification": [ 168 | "Succeeded", 169 | "Failed" 170 | ] 171 | }, 172 | "type": "Scope" 173 | }, 174 | "Notification": { 175 | "actions": { 176 | "For_each_record": { 177 | "foreach": "@body('Log_Analytics_Call')?['tables']?[0]?['rows']", 178 | "actions": { 179 | "AAD_Call": { 180 | "runAfter": { 181 | "Debug": [ 182 | "Succeeded" 183 | ] 184 | }, 185 | "type": "Http", 186 | "inputs": { 187 | "authentication": { 188 | "audience": "https://graph.microsoft.com", 189 | "type": "ManagedServiceIdentity" 190 | }, 191 | "headers": { 192 | "ConsistencyLevel": "eventual" 193 | }, 194 | "method": "GET", 195 | "uri": "https://graph.microsoft.com/v1.0/users/@{item()[1]}/?$expand=manager($levels=1;$select=mail)" 196 | } 197 | }, 198 | "AAD_Debug": { 199 | "runAfter": { 200 | "AAD_Call": [ 201 | "Failed", 202 | "Succeeded" 203 | ] 204 | }, 205 | "type": "Compose", 206 | "inputs": "@coalesce(body('AAD_Call')?['error']?['message'], concat('Mail to: ',body('AAD_Call')?['mail']))" 207 | }, 208 | "Create_HTML_Activity_table": { 209 | "runAfter": { 210 | "Debug": [ 211 | "Succeeded" 212 | ] 213 | }, 214 | "type": "Table", 215 | "inputs": { 216 | "columns": [ 217 | { 218 | "header": "Time", 219 | "value": "@concat('📆 ',item()['TimeGenerated'])" 220 | }, 221 | { 222 | "header": "Activity", 223 | "value": "@item()['Type']" 224 | } 225 | ], 226 | "format": "HTML", 227 | "from": "@json(item()[2])" 228 | } 229 | }, 230 | "Debug": { 231 | "runAfter": {}, 232 | "type": "Compose", 233 | "inputs": "@item()" 234 | }, 235 | "Email_Body": { 236 | "runAfter": { 237 | "AAD_Call": [ 238 | "Succeeded" 239 | ], 240 | "Create_HTML_Activity_table": [ 241 | "Succeeded" 242 | ] 243 | }, 244 | "type": "Compose", 245 | "inputs": "@replace(replace(body('SEEN-Config')['template'],'{UserFirstName}',coalesce(body('AAD_Call')['givenName'],body('AAD_Call')['displayName'])),'{Activities}',body('Create_HTML_Activity_table'))" 246 | }, 247 | "SEEN-SendNotification": { 248 | "runAfter": { 249 | "Email_Body": [ 250 | "Succeeded" 251 | ] 252 | }, 253 | "type": "Workflow", 254 | "inputs": { 255 | "body": { 256 | "bcc": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('SEEN-Config')['bcc'],null)}", 257 | "body": "@{outputs('Email_Body')}", 258 | "cc": "@{if(equals(body('SEEN-Config')?['recipient'],''),if(equals(body('SEEN-Config')['ccManager'],true),body('AAD_Call')?['manager']?['mail'],null),null)}", 259 | "importance": "high", 260 | "mailFrom": "@{body('SEEN-Config')['mailFrom']}", 261 | "replyTo": "@{body('SEEN-Config')['replyTo']}", 262 | "saveToSentItems": "@body('SEEN-Config')['saveToSentItems']", 263 | "subject": "@{body('SEEN-Config')['subject']}", 264 | "to": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('AAD_Call')['mail'],body('SEEN-Config')?['recipient'])}", 265 | "tracking": "travel" 266 | }, 267 | "host": { 268 | "triggerName": "manual", 269 | "workflow": { 270 | "id": "[parameters('EmailNotificationResourceId')]" 271 | } 272 | } 273 | } 274 | } 275 | }, 276 | "runAfter": {}, 277 | "type": "Foreach" 278 | } 279 | }, 280 | "runAfter": { 281 | "Query": [ 282 | "Succeeded" 283 | ] 284 | }, 285 | "type": "Scope" 286 | }, 287 | "Query": { 288 | "actions": { 289 | "KQL_Query": { 290 | "runAfter": {}, 291 | "type": "Compose", 292 | "inputs": "let IncludeDismissed = @{body('SEEN-Config')?['includeDismissed']};\nlet StartTime = todatetime(\"@{body('Get_entity_Tracker')['Tracker']}\") ;\nlet EndTime = todatetime(\"@{body('Current_time')}\") ;\nlet TimeZone = \"@{body('SEEN-Config')['timeZone']}\" ;\nSigninLogs\n| where ingestion_time() between (StartTime..EndTime)\n| where RiskEventTypes contains \"unlikelyTravel\"\n| summarize arg_max(TimeGenerated,*) by Id\n| extend IsDismissed = iif(RiskState == \"dismissed\",true,false)\n| where IncludeDismissed == true or IsDismissed == false \n| extend Place = strcat(tostring(LocationDetails.city),\", \",tostring(LocationDetails.state),\", \",tostring(LocationDetails.countryOrRegion))\n| extend Details = bag_pack(\"TimeGenerated\", format_datetime(datetime_utc_to_local(TimeGenerated,TimeZone), 'yyyy-MM-dd HH:mm:ss'), \"Type\", strcat(\"Location: \",Place))\n| project UserId, UserPrincipalName = tolower(UserPrincipalName), Details\n| summarize Summary = make_set(Details) by UserPrincipalName, UserId" 293 | }, 294 | "Log_Analytics_Call": { 295 | "runAfter": { 296 | "KQL_Query": [ 297 | "Succeeded" 298 | ] 299 | }, 300 | "type": "Http", 301 | "inputs": { 302 | "authentication": { 303 | "audience": "https://api.loganalytics.io", 304 | "type": "ManagedServiceIdentity" 305 | }, 306 | "body": { 307 | "query": "@{outputs('KQL_Query')}", 308 | "timespan": "P1D" 309 | }, 310 | "method": "POST", 311 | "uri": "https://api.loganalytics.io/v1/workspaces/@{parameters('WorkspaceId')}/query" 312 | } 313 | } 314 | }, 315 | "runAfter": { 316 | "Config": [ 317 | "Succeeded" 318 | ] 319 | }, 320 | "type": "Scope" 321 | } 322 | }, 323 | "outputs": {} 324 | }, 325 | "parameters": { 326 | "$connections": { 327 | "value": { 328 | "azuretables": { 329 | "connectionId": "[parameters('TablesConnectionResourceId')]", 330 | "connectionName": "azuretables", 331 | "connectionProperties": { 332 | "authentication": { 333 | "type": "ManagedServiceIdentity" 334 | } 335 | }, 336 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuretables')]" 337 | } 338 | } 339 | } 340 | } 341 | } 342 | } 343 | ], 344 | "outputs": { 345 | "resourceID": { 346 | "type": "string", 347 | "value": "[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]" 348 | }, 349 | "resourcePrincipalID": { 350 | "type": "string", 351 | "value": "[reference(resourceId('Microsoft.Logic/workflows', parameters('PlaybookName')), '2017-07-01', 'Full').identity.principalId]" 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /modules/TAP/tap.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "PlaybookName": { 6 | "defaultValue": "SEEN-TemporaryAccessPass", 7 | "type": "String" 8 | }, 9 | "ConfigResourceId": { 10 | "defaultValue": "", 11 | "type": "String" 12 | }, 13 | "EmailNotificationResourceId": { 14 | "defaultValue": "", 15 | "type": "String" 16 | }, 17 | "TablesConnectionResourceId": { 18 | "defaultValue": "", 19 | "type": "String" 20 | }, 21 | "StorageAccountName": { 22 | "defaultValue": "", 23 | "type": "String" 24 | }, 25 | "LogAnalyticsWorkspaceId": { 26 | "defaultValue": "", 27 | "type": "String" 28 | }, 29 | "ProjectName": { 30 | "defaultValue": "SEEN", 31 | "type": "String" 32 | }, 33 | "ModuleVersion": { 34 | "defaultValue": "1.0", 35 | "type": "String" 36 | } 37 | }, 38 | "variables": {}, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Logic/workflows", 42 | "apiVersion": "2017-07-01", 43 | "name": "[parameters('PlaybookName')]", 44 | "location": "[resourceGroup().location]", 45 | "identity": { 46 | "type": "SystemAssigned" 47 | }, 48 | "properties": { 49 | "state": "Disabled", 50 | "definition": { 51 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 52 | "contentVersion": "1.0.0.0", 53 | "parameters": { 54 | "$connections": { 55 | "defaultValue": {}, 56 | "type": "Object" 57 | }, 58 | "WorkspaceId": { 59 | "defaultValue": "[parameters('LogAnalyticsWorkspaceId')]", 60 | "type": "String" 61 | }, 62 | "ProjectName": { 63 | "defaultValue": "[parameters('ProjectName')]", 64 | "type": "String" 65 | }, 66 | "ModuleVersion": { 67 | "defaultValue": "[parameters('ModuleVersion')]", 68 | "type": "String" 69 | } 70 | }, 71 | "triggers": { 72 | "Recurrence": { 73 | "recurrence": { 74 | "frequency": "Minute", 75 | "interval": 15 76 | }, 77 | "evaluatedRecurrence": { 78 | "frequency": "Minute", 79 | "interval": 15 80 | }, 81 | "type": "Recurrence" 82 | } 83 | }, 84 | "actions": { 85 | "Config": { 86 | "actions": { 87 | "Current_time": { 88 | "runAfter": {}, 89 | "type": "Expression", 90 | "kind": "CurrentTime", 91 | "inputs": {} 92 | }, 93 | "Get_entity_Tracker": { 94 | "runAfter": { 95 | "Current_time": [ 96 | "Succeeded" 97 | ] 98 | }, 99 | "type": "ApiConnection", 100 | "inputs": { 101 | "host": { 102 | "connection": { 103 | "name": "@parameters('$connections')['azuretables']['connectionId']" 104 | } 105 | }, 106 | "method": "get", 107 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''TAPUsage'')}'')')]", 108 | "queries": { 109 | "$select": "Tracker" 110 | } 111 | } 112 | }, 113 | "SEEN-Config": { 114 | "runAfter": { 115 | "Current_time": [ 116 | "Succeeded" 117 | ] 118 | }, 119 | "type": "Workflow", 120 | "inputs": { 121 | "body": { 122 | "module": "tap" 123 | }, 124 | "host": { 125 | "triggerName": "manual", 126 | "workflow": { 127 | "id": "[parameters('ConfigResourceId')]" 128 | } 129 | } 130 | } 131 | } 132 | }, 133 | "runAfter": {}, 134 | "type": "Scope" 135 | }, 136 | "Exit": { 137 | "actions": { 138 | "Terminate": { 139 | "runAfter": { 140 | "Update_Entity_Tracker": [ 141 | "Succeeded" 142 | ] 143 | }, 144 | "type": "Terminate", 145 | "inputs": { 146 | "runStatus": "@{result('Notification')[0]['status']}" 147 | } 148 | }, 149 | "Update_Entity_Tracker": { 150 | "runAfter": {}, 151 | "type": "ApiConnection", 152 | "inputs": { 153 | "body": { 154 | "Tracker": "@{body('Current_time')}" 155 | }, 156 | "host": { 157 | "connection": { 158 | "name": "@parameters('$connections')['azuretables']['connectionId']" 159 | } 160 | }, 161 | "method": "put", 162 | "path": "[concat('/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent(''', parameters('StorageAccountName'), '''))}/tables/@{encodeURIComponent(''trackers'')}/entities(PartitionKey=''@{encodeURIComponent(''seen'')}'',RowKey=''@{encodeURIComponent(''TAPUsage'')}'')')]" 163 | } 164 | } 165 | }, 166 | "runAfter": { 167 | "Notification": [ 168 | "Succeeded", 169 | "Failed" 170 | ] 171 | }, 172 | "type": "Scope" 173 | }, 174 | "Notification": { 175 | "actions": { 176 | "For_each_record": { 177 | "foreach": "@body('Log_Analytics_Call')?['tables']?[0]?['rows']", 178 | "actions": { 179 | "AAD_Call": { 180 | "runAfter": { 181 | "Debug": [ 182 | "Succeeded" 183 | ] 184 | }, 185 | "type": "Http", 186 | "inputs": { 187 | "authentication": { 188 | "audience": "https://graph.microsoft.com", 189 | "type": "ManagedServiceIdentity" 190 | }, 191 | "headers": { 192 | "ConsistencyLevel": "eventual" 193 | }, 194 | "method": "GET", 195 | "uri": "https://graph.microsoft.com/v1.0/users/@{item()[1]}/?$expand=manager($levels=1;$select=mail)" 196 | } 197 | }, 198 | "AAD_Debug": { 199 | "runAfter": { 200 | "AAD_Call": [ 201 | "Failed", 202 | "Succeeded" 203 | ] 204 | }, 205 | "type": "Compose", 206 | "inputs": "@coalesce(body('AAD_Call')?['error']?['message'], concat('Mail to: ',body('AAD_Call')?['mail']))" 207 | }, 208 | "Create_HTML_Activity_table": { 209 | "runAfter": { 210 | "Debug": [ 211 | "Succeeded" 212 | ] 213 | }, 214 | "type": "Table", 215 | "inputs": { 216 | "columns": [ 217 | { 218 | "header": "Time", 219 | "value": "@concat('📆 ',item()['TimeGenerated'])" 220 | }, 221 | { 222 | "header": "Activity", 223 | "value": "@item()['Type']" 224 | } 225 | ], 226 | "format": "HTML", 227 | "from": "@json(item()[2])" 228 | } 229 | }, 230 | "Debug": { 231 | "runAfter": {}, 232 | "type": "Compose", 233 | "inputs": "@item()" 234 | }, 235 | "Email_Body": { 236 | "runAfter": { 237 | "AAD_Call": [ 238 | "Succeeded" 239 | ], 240 | "Create_HTML_Activity_table": [ 241 | "Succeeded" 242 | ] 243 | }, 244 | "type": "Compose", 245 | "inputs": "@replace(replace(body('SEEN-Config')['template'],'{UserFirstName}',coalesce(body('AAD_Call')['givenName'],body('AAD_Call')['displayName'])),'{Activities}',body('Create_HTML_Activity_table'))" 246 | }, 247 | "SEEN-SendNotification": { 248 | "runAfter": { 249 | "Email_Body": [ 250 | "Succeeded" 251 | ] 252 | }, 253 | "type": "Workflow", 254 | "inputs": { 255 | "body": { 256 | "bcc": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('SEEN-Config')['bcc'],null)}", 257 | "body": "@{outputs('Email_Body')}", 258 | "cc": "@{if(equals(body('SEEN-Config')?['recipient'],''),if(equals(body('SEEN-Config')['ccManager'],true),body('AAD_Call')?['manager']?['mail'],null),null)}", 259 | "importance": "high", 260 | "mailFrom": "@{body('SEEN-Config')['mailFrom']}", 261 | "replyTo": "@{body('SEEN-Config')['replyTo']}", 262 | "saveToSentItems": "@body('SEEN-Config')['saveToSentItems']", 263 | "subject": "@{body('SEEN-Config')['subject']}", 264 | "to": "@{if(equals(body('SEEN-Config')?['recipient'],''),body('AAD_Call')['mail'],body('SEEN-Config')?['recipient'])}", 265 | "tracking": "tap" 266 | }, 267 | "host": { 268 | "triggerName": "manual", 269 | "workflow": { 270 | "id": "[parameters('EmailNotificationResourceId')]" 271 | } 272 | } 273 | } 274 | } 275 | }, 276 | "runAfter": {}, 277 | "type": "Foreach" 278 | } 279 | }, 280 | "runAfter": { 281 | "Query": [ 282 | "Succeeded" 283 | ] 284 | }, 285 | "type": "Scope" 286 | }, 287 | "Query": { 288 | "actions": { 289 | "KQL_Query": { 290 | "runAfter": {}, 291 | "type": "Compose", 292 | "inputs": "let IncludeUsage = @{body('SEEN-Config')['includeUsage']};\nlet StartTime = todatetime(\"@{body('Get_entity_Tracker')['Tracker']}\") ;\nlet EndTime = todatetime(\"@{body('Current_time')}\") ;\nlet TimeZone = \"@{body('SEEN-Config')['timeZone']}\" ;\nunion (AuditLogs\n| where ingestion_time() between (StartTime..EndTime)\n| where OperationName == \"Admin registered security info\"\n| where ResultDescription == \"Admin registered temporary access pass method for user\"\n| where Result == \"success\"\n| extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName)\n| extend UserId = tostring(TargetResources[0].id)\n| extend Details = bag_pack(\"TimeGenerated\", format_datetime(datetime_utc_to_local(TimeGenerated,TimeZone), 'yyyy-MM-dd HH:mm:ss'), \"Type\", ResultDescription)\n| project UserPrincipalName = tolower(UserPrincipalName), UserId, Details),\n(SigninLogs\n| where IncludeUsage == true \n| where ingestion_time() between (StartTime..EndTime)\n| where ResultType == 0\n| where AuthenticationDetails contains \"Temporary Access Pass\"\n| extend Details = bag_pack(\"TimeGenerated\", format_datetime(datetime_utc_to_local(TimeGenerated,TimeZone), 'yyyy-MM-dd HH:mm:ss'), \"Type\", strcat(\"TAP was used to access \",AppDisplayName))\n| project UserId, UserPrincipalName = tolower(UserPrincipalName), Details )\n| summarize Summary = make_set(Details) by UserPrincipalName, UserId" 293 | }, 294 | "Log_Analytics_Call": { 295 | "runAfter": { 296 | "KQL_Query": [ 297 | "Succeeded" 298 | ] 299 | }, 300 | "type": "Http", 301 | "inputs": { 302 | "authentication": { 303 | "audience": "https://api.loganalytics.io", 304 | "type": "ManagedServiceIdentity" 305 | }, 306 | "body": { 307 | "query": "@{outputs('KQL_Query')}", 308 | "timespan": "P1D" 309 | }, 310 | "method": "POST", 311 | "uri": "https://api.loganalytics.io/v1/workspaces/@{parameters('WorkspaceId')}/query" 312 | } 313 | } 314 | }, 315 | "runAfter": { 316 | "Config": [ 317 | "Succeeded" 318 | ] 319 | }, 320 | "type": "Scope" 321 | } 322 | }, 323 | "outputs": {} 324 | }, 325 | "parameters": { 326 | "$connections": { 327 | "value": { 328 | "azuretables": { 329 | "connectionId": "[parameters('TablesConnectionResourceId')]", 330 | "connectionName": "azuretables", 331 | "connectionProperties": { 332 | "authentication": { 333 | "type": "ManagedServiceIdentity" 334 | } 335 | }, 336 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuretables')]" 337 | } 338 | } 339 | } 340 | } 341 | } 342 | } 343 | ], 344 | "outputs": { 345 | "resourceID": { 346 | "type": "string", 347 | "value": "[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]" 348 | }, 349 | "resourcePrincipalID": { 350 | "type": "string", 351 | "value": "[reference(resourceId('Microsoft.Logic/workflows', parameters('PlaybookName')), '2017-07-01', 'Full').identity.principalId]" 352 | } 353 | } 354 | } 355 | --------------------------------------------------------------------------------