-
Notifications
You must be signed in to change notification settings - Fork 7
Home
Checkout the Powershell Gallery!
You can simply import the module using Import-Module TMF if the module has been placed in one of your module directory. (Checkout $env:PSModulePath) It is also possible to directly import the module using Import-Module <PATH_TO_MODULE>/TMF/TMF.psd1
We are using the Microsoft.Graph module to make changes in the targeted Azure AD Tenant. This module also has a sub-module for authentication against Microsoft Graph. You can connect using the following command.
PS> Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All"
https://github.com/microsoftgraph/msgraph-sdk-powershell
Please make sure you are connected to the correct Tenant before invoking configurations!
The required scopes depend on what components (resources) you want to configure.
Resource | Required scopes |
---|---|
Groups | Group.ReadWrite.All, GroupMember.ReadWrite.All |
Users | User.ReadWrite.All |
Named Locations | Policy.ReadWrite.ConditionalAccess |
Agreements (Terms of Use) | Agreement.ReadWrite.All |
Conditional Access Policies | Policy.ReadWrite.ConditionalAccess, Policy.Read.All, RoleManagement.Read.Directory, Application.Read.All, Agreement.Read.All |
Entitlement Management (Access Packages, Access Package Catalogs) | EntitlementManagement.ReadWrite.All |
Administrative Units | AdministrativeUnit.ReadWrite.All, Directory.AccessAsUser.All, RoleManagement.ReadWrite.Directory |
Role Management (assignments, definitions, management policies) | RoleManagement.ReadWrite.Directory, Directory.AccessAsUser.All, RoleEligibilitySchedule.ReadWrite.Directory, |
RoleAssignmentSchedule.ReadWrite.Directory", "RoleManagementPolicy.ReadWrite.Directory" | |
Policies (authentication/authorization policies) | Policy.ReadWrite.AuthenticationMethod, Policy.ReadWrite.Authorization, Policy.ReadWrite.AuthenticationFlows |
You can also use Get-TmfRequiredScope to get the required scopes and combine it with Connect-MgGraph.
PS> Connect-MgGraph -Scopes (Get-TmfRequiredScope -All)
A Tenant Management Framework configuration is a collection of resource definition files in a predefined folder structure. The definition files describe instances of different resource types (eg. Groups, Conditional Access Policies, Named Locations) in the JavaScript Object Notation (.json).
Configurations always contain a configuration.json file at the root level. This file contains the properties that describe a configuration and is automatically created when using New-TmfConfiguration.
{
"Name": "Example Configuration",
"Description": "This is a example configuration.",
"Author": "Mustermann, Max",
"Weight": 50,
"Prerequisite": [
"Default Configuration"
]
}
Property | Description |
---|---|
Name | The name of the configuration. Must be uniqe when using multiple configurations. |
Description | Description of the configuration. Here you can discribe, for which tenants this configurations should be used. |
Author | The responsible team or person for this configuration. |
Weight | When activating multiple configurations, the configuration with the highest weight is loaded last. This means that a resource definition will be overwriten, if the last configuration contains a definition with the same displayName. |
Prerequisite | With this setting you can define a relationship to an another configuration by the configuration name. For example when a configurations requires a baseline configuration. It is also possible to allow different configurations as prerequisite using an OR-operator. This can be helpful when the target tenants are slightly different. For example: TenantConfig1 |
{
"Name": "Example Configuration",
"Description": "This is a example configuration.",
"Author": "Mustermann, Max",
"Weight": 50,
"Prerequisite": [
"Default_Config",
"DEV_Tenant_Config || QA_Tenant_Config || PROD_Tenant_Config"
]
}
For each supported resource type there is a subfolder. These subfolders always contain an empty .json file and example.md.
The empty .json file is used to define resource instances. As an example a resource instance can be the definition of an Azure AD Security group or a Conditional Access policy. You can place multiple .json in a single resource type subfolder. By creating multiple .json files it is possible to structure resource definitions in a understandable way.
The folder names are mandatory for the functionality of the framework! Folders that do not represent a supported resource type will be ignored!
The example.md file contains example resource instances and further information.
# Folder structure of a newly created configuration
├───accessReviews
│ accessReviews.json
│ example.md
│
├───administrativeUnits
│ administrativeUnits.json
│ example.md
│
├───agreements
│ │ agreements.json
│ │ example.md
│ │
│ └───files
│ Example Terms of Use.pdf
│
├───conditionalAccessPolicies
│ example.md
│ policies.json
│
├───directoryRoles
│ directoryRoles.json
│ example.md
│
├───entitlementManagement
│ ├───accessPackageCatalogs
│ │ accessPackageCatalogs.json
│ │ example.md
│ │
│ └───accessPackages
│ accessPackages.json
│ example.md
│
├───groups
│ example.md
│ groups.json
│
├───namedLocations
│ example.md
│ namedLocations.json
│
├───policies
│ ├───authenticationFlowsPolicies
│ │ authenticationFlowsPolicies.json
│ │ example.md
│ │
│ ├───authenticationMethodsPolicies
│ │ authenticationMethodsPolicies.json
│ │ example.md
│ │
│ └───authorizationPolicies
│ authorizationPolicies.json
│ example.md
│
├───roleManagement
│ ├───roleAssignments
│ │ roleAssignments.json
│ │ example.md
│ │
│ ├───roleDefinitions
│ │ roleDefinitions.json
│ │ example.md
│ │
│ ├───roleManagementPolicies
│ │ roleManagementPolicies.json
│ │ example.md
│ │
│ └───roleManagementPolicyRuleTemplates
│ roleManagementPolicyRuleTemplates.json
│ example.md
│
└────stringMappings
stringMappings.json
You can create new configuration by simple using the function New-TmfConfiguration. This function will create the required folder structure and the configuration.json file in the given location.
PS> New-TmfConfiguration -Name "Example Configuration" -Description "This is an example configuration for the Tenant Management Framework!" -Author "Mustermann, Max" -Weight 50 -OutPath "$env:USERPROFILE\Desktop\Example_Configuration" -Force
[16:02:04][New-TmfConfiguration] Creating configuration directory C:\Users\username\Desktop\Example_Configuration. [DONE]
[16:02:04][New-TmfConfiguration] Copying template structure to C:\Users\username\Desktop\Example_Configuration. [DONE]
[16:02:05][Activate-TmfConfiguration] Activating Example Configuration (C:\Users\username\Desktop\Example_Configuration\configuration.json). This configuration will be considered when applying Tenant configuration. [DONE]
[16:02:05][Activate-TmfConfiguration] Sorting all activated configurations by weight. [DONE]
[16:02:05][New-TmfConfiguration] Creation has finished! Have fun! [DONE]
The -Force paramter tells the functions to automatically create the target directory or overwrite a configuration at the target directory. In the example it would create the folder "Example_Configuration".
A newly created configuration will be automatically activated. This means when using Load-TmfConfiguration the defined resources are loaded from the .json files and can be directly invoked or tested against the connected tenant.
To invoke or test defined resources against a tenant, you need to activate the containing configuration at the beginning. This means that you have to tell the TMF which configurations you want it to consider in the next steps.
This activation can simply be done using Activate-TmfConfiguration.
PS> Activate-TmfConfiguration "$env:USERPROFILE\Desktop\Example_Configuration" -Force
[16:10:46][Activate-TmfConfiguration] Activating Example Configuration (C:\Users\username\Desktop\Example_Configuration\configuration.json). This configuration will be considered when applying Tenant configuration. [DONE]
[16:10:46][Activate-TmfConfiguration] Sorting all activated configurations by weight. [DONE]
You can use Get-TmfActiveConfiguration to checkout all already activated configurations.
PS> Get-TmfActiveConfiguration
Name : Example Configuration
Path : C:\Users\username\Desktop\Example_Configuration
Description : This is an example configuration for the Tenant Management Framework!
Author : Mustermann, Max
Weight : 50
Prerequisite : {}
To deactivate a configuration use Deactivate-TmfConfiguration. After deactivating a configuration the TMF won't considere it in further steps.
Deactivate-TmfConfiguration -Name "Example Configuration" # By name
Deactivate-TmfConfiguration -Path "$env:USERPROFILE\Desktop\Example_Configuration" # By path
Deactivate-TmfConfiguration -All # Or all activated configurations!
[16:18:08][Deactivate-TmfConfiguration] Deactivating Example Configuration. This configuration will not be considered when applying Tenant configuration. [DONE]
We recommend you to store configurations in a git repository. By adding a source control system you get enforced documentation and versioning. In our case we store multiple configurations (Default configuration, DEV configuration, QA configuration and so on) in a single Azure DevOps repository.
The Load-TmfConfiguration function checks all .json files from the activated configurations and registers them into a runtime store. Technically this is the same process as if you use a register function for a single resource type (eg. Register-TmfGroup). All loaded resource definitions are considered when using test or invoke functions.
PS> Load-TmfConfiguration
You can check the currently loaded desired configuration with Get-TmfDesiredConfiguration. This returns the desired configuration as a hashtable.
PS> Get-TmfDesiredConfiguration
Name Value
---- -----
accessPackages {}
groups {@{displayName=Some group; description=This is a security group; groupTypes=System.String[]; securityEnabled=True; mailEnabled=False; mailNickname=someGroupForMembers; present=True...
namedLocations {}
accessPackageCatalogs {}
conditionalAccessPolicies {}
agreements {}
stringMappings {}
# You can also checkout single resource definitions
PS> (Get-TmfDesiredConfiguration)["groups"]
displayName : Some group
description : This is a security group
groupTypes : {}
securityEnabled : True
mailEnabled : False
mailNickname : someGroupForMembers
present : True
sourceConfig : Example Configuration
owners : {group.owner@example.org}
members : {max.mustermann@example.org}
# Filtering is also possible with Where-Object
(Get-TmfDesiredConfiguration)["groups"] | Where-Object {$_.displayName -eq "Some group"}
displayName : Some group
description : This is a security group
groupTypes : {}
securityEnabled : True
mailEnabled : False
mailNickname : someGroupForMembers
present : True
sourceConfig : Example Configuration
owners : {group.owner@example.org}
members : {max.mustermann@example.org}
It is possible to run tests for a single resource type, for an resouce type group (eg. Entitlement Management) or for a whole tenant.
If you want to only test your configured groups, you can use Test-TmfGroup. This will only consider all definitions of the resource type "groups".
PS> Test-TmfGroup
ActionType : Create
ResourceType : Group
ResourceName : Example group
Changes :
Tenant : TENANT_NAME
TenantId : d369908f-8803-46bc-90cb-3c82854ddf93
DesiredConfiguration : @{displayName=Example group; description=This is an example security group; groupTypes=System.String[]; securityEnabled=True; mailEnabled=False; mailNickname=someGroupForMembers;
present=True; sourceConfig=Example Configuration}
GraphResource :
The resource type specific test functions always return test result objects. These objects show you, which actions are required in your tenant, to achive the desired configuration.
Additionally you can test only specific resources of a resource type group by adding the -specificResources parameter. This parameter accepts a string array including wildcards.
PS> Test-TmfGroup -specificResources "Example *"
ActionType : Create
ResourceType : Group
ResourceName : Example group
Changes :
Tenant : TENANT_NAME
TenantId : d369908f-8803-46bc-90cb-3c82854ddf93
DesiredConfiguration : @{displayName=Example group; description=This is an example security group; groupTypes=System.String[]; securityEnabled=True; mailEnabled=False; mailNickname=someGroupForMembers;
present=True; sourceConfig=Example Configuration}
GraphResource :
You can test all available resource types using Test-TmfTenant. The Test-TmfTenant function and also all resource type group (eg. Test-TmfEntitlementManagement) functions automatically beautify the test results.
PS> Test-TmfTenant
[20:35:51][Test-TmfTenant] Currently connected to <TENANT_NAME> (d369908f-XXXX-XXXX-90cb-3c82854ddf93)
[20:35:52][Test-TmfTenant] Starting tests for groups
[20:35:52][TMF] [Tenant: TENANT_NAME][Group Resource: Example group] Required Action (Create)
With Beautify-TmfTestResult you are able to beautify the results of any resouce type specific test command.
PS> Test-TmfGroup | Beautify-TmfTestResult
[20:37:34][TMF] [Tenant: TENANT_NAME][Group Resource: Example group] Required Action (Create)
The invoke functions are available for a single resource type, for a resouce type group (eg. Entitlement Management) or for the whole tenant. These functions are capable of creating, updating or deleting resources using Microsoft Graph. The executed actions depend on the results the test functions return.
Example based on Access Packages:
- Invoke-TmfAccessPackage: Only invokes actions for the defined Access Packages
- Invoke-TmfEntitlementManagement: Invokes the required actions for Access Packages and also for all other resource types required for Entitlement Management.
- Invoke-TmfTenant: Invokes the required actions for each resource type defined in your configurations.
PS> Invoke-TmfGroup
[20:44:17][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Required Action (Create)
[20:44:17][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Completed.
PS> Invoke-TmfTenant
[20:49:46][Invoke-TmfTenant] Currently connected to TENANT_NAME (d369908f-8803-46bc-90cb-3c82854ddf93)
Is this the correct tenant? [y/n]: y
[20:49:48][Invoke-TmfTenant] Invoking groups
[20:49:49][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Required Action (NoActionRequired)
[20:49:49][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Completed.
Additionally you can invoke only specific resources of a resource type group by adding the -specificResources parameter. This parameter accepts a string array including wildcards.
PS> Invoke-TmfGroup -specificResources "Example*"
[20:44:17][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Required Action (Create)
[20:44:17][Invoke-TmfGroup] [Tenant: TENANT_NAME][Group Resource: Example group] Completed.
You can use the register functions to manually register a resource definition. When registering a resource definition it will be added to the desired configuration.
A resource must be registered before the Tenant Management Framework can test it's configuration against the Tenant.
The displayName property must be uniqe in the desired configuration! Resources are searched by the displayName.
The supported resources are based on the endpoints and resource types provided by Microsoft Graph. Most of the definition files use the json syntax that the API endpoint also uses.
All main resource types support the following general properties.
Name | Type | Use case | Not supported by |
---|---|---|---|
displayName | string | Is the mainly used identifier of a resource. We are always using the displayName to search resources. | |
oldNames | string[] | Allows you to rename a resource by searching it using an old name. Example: When group "Group A" should now be called "Group B", you can specify "Group A" in the oldNames property and set the displayName to "Group B". The TMF will update the displayName in the tenant automatically. | entitlementManagement (accessPackages, accessPackageCatalogs, accessPackageResource, accessPackageAssignmentPolicies) |
present | bool | Is true by default. If you set it to false, the resource is deleted. |
An example definition for a simple Azure AD security group with a predefined member and a predefined owner.
{
"displayName": "Some group",
"description": "This is a security group",
"groupTypes": [],
"securityEnabled": true,
"mailEnabled": false,
"mailNickname": "someGroupForMembers",
"members": ["max.mustermann@example.org"],
"owners": ["group.owner@example.org"],
"present": true
}
Please check the Groups example.md for further information.
An example policy definition that would affect all members of a group to accept ToU and and provide MFA.
{
"displayName" : "Require MFA and ToU for all members of Some group",
"excludeGroups": ["Some group for CA"],
"excludeUsers": ["max.mustermann@TENANT_NAME.onmicrosoft.com"],
"includeApplications": ["All"],
"includeLocations": ["All"],
"clientAppTypes": ["browser", "mobileAppsAndDesktopClients"],
"includePlatforms": ["All"],
"grantControls": {
"builtInControls": ["mfa"],
"operator": "AND",
"termsOfUse": ["ToU for Some group"]
},
"state" : "enabledForReportingButNotEnforced",
"present" : true
}
Please check the Conditional Access Policy example.md for further information.
An example IP Named Location definition.
{
"type": "ipNamedLocation",
"displayName": "Untrusted IP named location",
"isTrusted": false,
"ipRanges": [
{
"@odata.type": "#microsoft.graph.iPv4CidrRange",
"cidrAddress": "12.34.221.11/22"
},
{
"@odata.type": "#microsoft.graph.iPv6CidrRange",
"cidrAddress": "2001:0:9d38:90d6:0:0:0:0/63"
}
],
"present": true
}
Please check the Named Location example.md for further information.
An example Agreement definition with a single PDF file added.
{
"displayName": "An example agreement with a single files",
"isViewingBeforeAcceptanceRequired": true,
"isPerDeviceAcceptanceRequired": false,
"userReacceptRequiredFrequency": "P90D",
"termsExpiration": {
"startDateTime": "05.03.2021 00:00:00",
"frequency": "PT1M"
},
"files": [
{
"fileName": "Example Terms of Use.pdf",
"language": "en",
"isDefault": true,
"filePath": "files/Example Terms of Use.pdf"
}
],
"present": true
}
Please check the Agreements example.md for further information.
Entitlement Management can be done by the following resource types. For further information about Azure AD Entitlement Management you can read the official documentation: https://docs.microsoft.com/en-us/azure/active-directory/governance/entitlement-management-overview.
A simple Access Package Catalog definition.
{
"displayName": "Access package catalog for testing",
"description": "Sample access package catalog",
"isExternallyVisible": false,
"present": true
}
Please check the Access Package Catalogs example.md for further information.
{
"displayName":"Access Package",
"oldNames": [],
"description":"Access Package description",
"isHidden":true,
"isRoleScopesVisible":true,
"catalog":"General",
"present":true,
"accessPackageResources":[
{
"resourceIdentifier":"Some group",
"resourceRole":"Member",
"originSystem":"AadGroup"
}
],
"assignmentPolicies":[
{
"displayName":"Initial policy",
"oldNames": [],
"canExtend":false,
"durationInDays":8,
"accessReviewSettings":{
"isEnabled":false,
"recurrenceType":"monthly",
"reviewerType":"Reviewers",
"durationInDays":14,
"reviewers":[
{
"type":"singleUser",
"reference":"max.mustermann@tmacdev.onmicrosoft.com",
"isBackup":false
},
{
"type":"requestorManager",
"managerLevel":1,
"isBackup":false
}
]
},
"requestApprovalSettings":{
"isApprovalRequired":true,
"isApprovalRequiredForExtension":false,
"isRequestorJustificationRequired":true,
"approvalMode":"SingleStage",
"approvalStages":[
{
"approvalStageTimeOutInDays":14,
"isApproverJustificationRequired":true,
"isEscalationEnabled":false,
"escalationTimeInMinutes":11520,
"primaryApprovers":[
{
"type":"singleUser",
"reference":"max.mustermann@tmacdev.onmicrosoft.com",
"isBackup":false
}
]
}
]
},
"requestorSettings":{
"scopeType":"SpecificDirectorySubjects",
"acceptRequests":true,
"allowedRequestors":[
{
"type":"singleUser",
"reference":"max.mustermann@tmacdev.onmicrosoft.com",
"isBackup":false
}
]
}
}
]
}
Please check the Access Packages example.md for further information.
Access Package Resources are directly defined in the depending Access Package definition.
Access Package Assignment Policies are directly defined the depending Access Package definition.
A simple Administrative Unit definition.
{
"displayName": "Administrative Unit for Testing",
"description": "This AU is used for testing",
"visibility": "Public",
"members": ["max.mustermann@tmacdev.onmicrosoft.com"],
"groups": [],
"scopedRoleMembers": [
{
"role": "Groups Administrator",
"identity": "Max Mustermann"
},
{
"role": "User Administrator",
"identity": "Max Mustermann"
}
],
"present": true
}
Please check the Administrative Units example.md for further information.
{
"displayName": "Displayname of the access review",
"present": true,
"scope": {
"type": "group",
"reference": "some group"
},
"reviewers": [
{
"type": "groupMembers",
"reference":"some group"
}
],
"settings": {
"mailNotificationsEnabled": true,
"reminderNotificationsEnabled": true,
"justificationRequiredOnApproval": true,
"defaultDecisionEnabled": false,
"defaultDecision": "None",
"instanceDurationInDays": 14,
"autoApplyDecisionsEnabled": false,
"recommendationsEnabled": true,
"recurrence": {
"pattern": {
"type": "absoluteMonthly",
"interval": 3,
"month": 0,
"dayOfMonth": 0,
"daysOfWeek": [],
"firstDayOfWeek": "sunday",
"index": "first"
},
"range": {
"type": "noEnd",
"numberOfOccurrences": 0,
"recurrenceTimeZone": null,
"startDate": "2022-03-01",
"endDate": "9999-12-31"
}
}
}
}
Please check the Access Reviews example.md for further information.
{
"present": true,
"displayName": "Role displayname",
"members": [
{
"type": "group",
"reference": "some group"
},
{
"type": "singleUser",
"reference": "givenname.sn@tenant.onmicrosoft.com"
}
]
}
Please check the Directory Roles example.md for further information.
[
{
"displayName": "Authentication flows policy",
"selfServiceSignUpEnabled": false
}
]
Please check the .... example.md for further information.
[
{
"displayName": "Authentication Methods Policy",
"registrationEnforcement": {
"authenticationMethodsRegistrationCampaign": {
"snoozeDurationInDays": 1,
"state": "default",
"excludeTargets": [],
"includeTargets": [
{
"id": "all_users",
"targetType": "group",
"targetedAuthenticationMethod": "microsoftAuthenticator"
}
]
}
},
"authenticationMethodConfigurations": [
{
"id": "Fido2",
"state": "disabled",
"isSelfServiceRegistrationAllowed": true,
"isAttestationEnforced": true
},
{
"id": "MicrosoftAuthenticator",
"state": "disabled"
},
{
"id": "Sms",
"state": "disabled"
},
{
"id": "TemporaryAccessPass",
"state": "disabled",
"defaultLifetimeInMinutes": 60,
"defaultLength": 8,
"minimumLifetimeInMinutes": 60,
"maximumLifetimeInMinutes": 480,
"isUsableOnce": false
},
{
"id": "Email",
"state": "enabled",
"allowExternalIdToUseEmailOtp": "enabled"
},
{
"id": "X509Certificate",
"state": "disabled",
"certificateUserBindings": [
{
"x509CertificateField": "PrincipalName",
"userProperty": "onPremisesUserPrincipalName",
"priority": 1
},
{
"x509CertificateField": "RFC822Name",
"userProperty": "userPrincipalName",
"priority": 2
}
],
"authenticationModeConfiguration": {
"x509CertificateAuthenticationDefaultMode": "x509CertificateSingleFactor",
"rules": []
}
}
]
}
]
Please check the .... example.md for further information.
[
{
"displayName": "Authorization Policy",
"allowInvitesFrom": "adminsAndGuestInviters",
"allowedToSignUpEmailBasedSubscriptions": false,
"allowedToUseSSPR": true,
"allowEmailVerifiedUsersToJoinOrganization": false,
"blockMsolPowerShell": false,
"guestUserRole": "Guest User",
"allowedToCreateApps": false,
"allowedToCreateSecurityGroups": false,
"allowedToReadOtherUsers": true,
"allowedToReadBitlockerKeysForOwnedDevice": true,
"permissionGrantPolicyIdsAssignedToDefaultUserRole": []
}
]
Please check the .... example.md for further information.
{
"present": true,
"type": "eligible",
"principalReference": "Group name",
"principalType": "group",
"roleReference": "directory role name",
"directoryScopeReference": "/",
"directoryScopeType": "directory",
"startDateTime": "2022-04-28T00:00:00.00Z",
"expirationType": "noExpiration"
}
{
"present": true,
"type": "eligible",
"principalReference": "Group name",
"principalType": "group",
"roleReference": "directory role name",
"directoryScopeReference": "name of administrativeUnit",
"directoryScopeType": "administrativeUnit",
"startDateTime": "2022-04-28T00:00:00.00Z",
"expirationType": "noExpiration"
}
{
"present": true,
"type": "eligible",
"principalReference": "Some group",
"principalType": "group",
"roleReference": "Owner",
"subscriptionReference": "Subscription name",
"scopeReference": "Subscription name",
"scopeType": "subscription",
"startDateTime": "2022-03-30T00:00:00.00Z",
"expirationType": "AfterDateTime",
"endDateTime": "2023-03-30T00:00:00.00Z"
}
Please check the .... example.md for further information.
{
"present": true,
"displayName": "Some role name",
"description": "Some description",
"subscriptionReference": "Subscription name",
"assignableScopes": [
"/subscriptions/subscriptionID",
"/subscriptions/subscriptionID/ResourceGroups/resourceGroupName"
],
"permissions": [
{
"actions": [
"Microsoft.Resources/subscriptions/resourceGroups/write",
"Microsoft.Resources/subscriptions/resourceGroups/delete"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
{
"present": true,
"displayName": "Some role name",
"description": "Some description",
"rolePermissions": [
{
"allowedResourceActions": [
"microsoft.directory/groups/standard/read",
"microsoft.directory/groups/memberOf/read",
"microsoft.directory/groups/members/read",
"microsoft.directory/groups/owners/read"
],
"condition": null
}
]
}
Please check the .... example.md for further information.
{
"roleReference": "directory role name",
"activationApprover": [],
"scopeReference": "/",
"scopeType": "directory",
"ruleTemplate": "some rule template"
}
{
"roleReference": "role name",
"subscriptionReference": "subscription name",
"scopeReference": "subscription name",
"scopeType": "subscription",
"activationApprover": [
{
"reference": "userPrincipalName",
"type": "user"
}
],
"ruleTemplate": "some rule template"
}
Please check the .... example.md for further information.
roleManagementPolicyRuleTemplates include all rules but the "Approval_EndUser_Assignment". The approvers are set within the roleManagementPolicies configurations.
RoleManagementPolicy ruleset with maximum 9 months eligible assignment possible, permanent active assignment possible and activation duration of 12 hours
{
"displayName": "AzureAD_Tier0",
"rules": [
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule",
"id": "Expiration_Admin_Eligibility",
"isExpirationRequired": true,
"maximumDuration": "P270D",
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Eligibility",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyEnablementRule",
"id": "Enablement_Admin_Eligibility",
"enabledRules": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Eligibility",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Admin_Admin_Eligibility",
"notificationType": "Email",
"recipientType": "Admin",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Eligibility",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Requestor_Admin_Eligibility",
"notificationType": "Email",
"recipientType": "Requestor",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Eligibility",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Approver_Admin_Eligibility",
"notificationType": "Email",
"recipientType": "Approver",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Eligibility",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule",
"id": "Expiration_Admin_Assignment",
"isExpirationRequired": false,
"maximumDuration": "P270D",
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyEnablementRule",
"id": "Enablement_Admin_Assignment",
"enabledRules": [
"Justification"
],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Admin_Admin_Assignment",
"notificationType": "Email",
"recipientType": "Admin",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Requestor_Admin_Assignment",
"notificationType": "Email",
"recipientType": "Requestor",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Approver_Admin_Assignment",
"notificationType": "Email",
"recipientType": "Approver",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "Admin",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule",
"id": "Expiration_EndUser_Assignment",
"isExpirationRequired": true,
"maximumDuration": "PT12H",
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyEnablementRule",
"id": "Enablement_EndUser_Assignment",
"enabledRules": [
"Justification"
],
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyAuthenticationContextRule",
"id": "AuthenticationContext_EndUser_Assignment",
"isEnabled": false,
"claimValue": null,
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Admin_EndUser_Assignment",
"notificationType": "Email",
"recipientType": "Admin",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Requestor_EndUser_Assignment",
"notificationType": "Email",
"recipientType": "Requestor",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
},
{
"@odata.type": "#microsoft.graph.unifiedRoleManagementPolicyNotificationRule",
"id": "Notification_Approver_EndUser_Assignment",
"notificationType": "Email",
"recipientType": "Approver",
"notificationLevel": "All",
"isDefaultRecipientsEnabled": true,
"notificationRecipients": [],
"target": {
"caller": "EndUser",
"operations": [
"All"
],
"level": "Assignment",
"inheritableSettings": [],
"enforcedSettings": []
}
}
]
}
Please check the .... example.md for further information.
String mappings can help you with parameterization of your TMF configurations.
You can create mappings between strings and the values they should be replaced with. Place the mappings in the stringMappings.json file in the stringMappings folder of your configuration.
Property | Description |
---|---|
name | The name of the replacement string. Only digits (0-9) and letters (a-z A-Z) are allowed. |
replace | The future value after replacing. |
{
"name": "GroupManagerName",
"replace": "group.manager@example.org"
}
Currently not all resource properties are considered. All string properties on the first level are replaced. String mappings also work when resolving users (eg. group owners or members), groups (in CA policies), applications (in CA Policies), namedLocations (in CA policies), agreements (in CA policies), accessPackages, accessPackageCatalogs, accessPackageResources, accessPackageAssignmentPolicies.
Resource | Supported properties |
---|---|
agreements | displayName, userReacceptRequiredFrequency |
groups | description, mailNickname, members, owners |
namedLocations | displayName |
To use the string mapping in a configuration file, you need to mention it by the name you provided in curly braces. Example: {{ GroupManagerName }}
{
"displayName": "Some group",
"description": "This is a security group. The group manager is {{ GroupManagerName }}",
"groupTypes": [],
"securityEnabled": true,
"mailEnabled": false,
"mailNickname": "someGroupForMembers",
"members": ["max.mustermann@example.org"],
"owners": ["group.owner@example.org"],
"present": true
}
First of all you need to create a new configuration using New-TmfConfiguration
New-TmfConfiguration -Name "Simple Group Example" -Author "Mustermann, Max" -Weight 50 -OutPath "$env:USERPROFILE\Desktop\Simple_Group_Example" -Force
After that you need to add the definition for the include group into your configuration. We will include this group into our Conditional Access Policy. Just place the definition it into the groups/groups.json file in your newly created configuration.
The groups.json should now look like that.
[
{
"displayName": "Some group to include into Conditional Access",
"description": "This is a simple security group",
"securityEnabled": true,
"mailEnabled": false,
"mailNickname": "someGroupForConditionalAccess",
"present": true
}
]
The same is possible for the exclude group. Just add an additional group definition.
[
{
"displayName": "Some group to include into Conditional Access",
"description": "This is a simple security group",
"securityEnabled": true,
"mailEnabled": false,
"mailNickname": "someGroupIncludeConditionalAccess",
"present": true
},
{
"displayName": "Some group to exclude from Conditional Access",
"description": "This is a simple security group",
"securityEnabled": true,
"mailEnabled": false,
"mailNickname": "someGroupExcludeConditionalAccess",
"present": true
}
]
The required groups are now defined. Finally we can define our Conditional Access Policy. For this example we just want MFA required for all members of the group Some group to include into Conditional Access.
You can add the following example Conditional Access Policy definition to conditionalAccessPolicies/policies.json.
{
"displayName" : "An example Conditional Access Policy",
"excludeGroups": ["Some group to include into Conditional Access"],
"excludeGroups": ["Some group to exclude from Conditional Access"],
"includeApplications": ["All"],
"includeLocations": ["All"],
"clientAppTypes": ["browser", "mobileAppsAndDesktopClients"],
"includePlatforms": ["All"],
"grantControls": {
"builtInControls": ["mfa"],
"operator": "AND"
},
"state" : "enabled",
"present" : true
}
Now that all required resource are defined, we can invoke the required actions. Simply use Invoke-TmfTenant to do the actions directly or use Test-TmfTenant to only test the configuration without changing anything.
Invoke-TmfTenant
Import-Module TMF -DisableNameChecking
Connect-MgGraph -Scopes (Get-TmfRequiredScope -NamedLocations)
$namedLocations = (Invoke-MgGraphRequest -Method GET -Uri "v1.0/identity/conditionalAccess/namedLocations/?`$filter=isof('microsoft.graph.ipNamedLocation')").value | Select-Object @{n = "displayName"; e = {$_["displayName"]}}, @{n = "type";e = {"ipNamedLocation"}}, @{n = "ipRanges";e = {$_["ipRanges"]}}, @{n = "isTrusted";e = {$_["isTrusted"]}}
foreach ($location in $namedLocations) {
$ipRanges = @()
switch ($location.ipRanges.GetType().Name) {
"Object[]" {
$location.ipRanges | ForEach-Object {
$ipRanges += [PSCustomObject]@{
"@odata.type" = $_["@odata.type"]
"cidrAddress" = $_["cidrAddress"]
}
}
}
default {
$ipRanges += [PSCustomObject]@{
"@odata.type" = $location.ipRanges["@odata.type"]
"cidrAddress" = $location.ipRanges["cidrAddress"]
}
}
}
$location.ipRanges = $ipRanges
}
$namedLocations | ConvertTo-Json -Depth 6 | Out-File -FilePath "namedLocations.json" -Encoding UTF8
Import-Module TMF -DisableNameChecking
Connect-MgGraph -Scopes (Get-TmfRequiredScope -ConditionalAccessPolicies)
$policies = (Invoke-MgGraphRequest -Method GET -Uri "v1.0/identity/conditionalAccess/policies").Value | Select-Object @{n = "displayName"; e = {$_["displayName"]}}, @{n = "conditions"; e = {$_["conditions"]}}, @{n = "grantControls"; e = {$_["grantControls"]}}, @{n = "state"; e = {$_["state"]}}
foreach ($policy in $policies) {
#region conditions properties to first level
foreach ($property in $policy.conditions.GetEnumerator()) {
if ($property.Value) {
switch ($property.Value.GetType().Name) {
"Hashtable" {
foreach ($childProperty in $policy.conditions.$($property.Key).GetEnumerator()) {
if ($childProperty.Value) {
Add-Member -InputObject $policy -MemberType NoteProperty -Name $childProperty.Key -Value $childProperty.Value
}
}
}
default {
Add-Member -InputObject $policy -MemberType NoteProperty -Name $property.Key -Value $property.Value
}
}
}
}
#endregion
}
$policies | ConvertTo-Json -Depth 6 | Out-File -FilePath "policies.json" -Encoding UTF8
Connect-MgGraph -Scopes (Get-TmfRequiredScope -Groups)
Select-MgProfile -Name beta
$groups = Get-MgGroup -Property id, displayName, description, groupTypes, securityEnabled, mailEnabled, visibility, mailNickname | Select-Object id, displayName, description, groupTypes, securityEnabled, mailEnabled, visibility, mailNickname
foreach ($group in $groups) {
<# Uncomment if you want to add the group members to your configuration.
$members = @(Get-MgGroupMember -GroupId $group.Id -Property userPrincipalName | Foreach-Object {$_.AdditionalProperties["userPrincipalName"]})
if ($members) {
Add-Member -InputObject $group -MemberType NoteProperty -Name "members" -Value $members
} #>
<# Uncomment if you want to add the group owners to your configuration.
$owners = @(Get-MgGroupOwner -GroupId $group.Id -Property userPrincipalName | Foreach-Object {$_.AdditionalProperties["userPrincipalName"]})
if ($owners) {
Add-Member -InputObject $group -MemberType NoteProperty -Name "owners" -Value $owners
} #>
Add-Member -InputObject $group -MemberType NoteProperty -Name "present" -Value $true
}
$groups | ConvertTo-Json -Depth 6 | Out-File -FilePath "groups.json" -Encoding UTF8