Automating with PowerShell: Enabling Secure Defaults (And SD explained)

In one of the groups I am in there was some confusion about how Secure Defaults work and how to deploy the Secure Defaults centrally, so I figured I would try to help with this.

Secure Defaults is Microsoft’s answer to our questions about deploying multi factor authentication to an entire tenant, of course security defaults does a lot more than just that.

So what does Security Defaults do?

  • Requires users to register for Multi-factor authentication. This allows a user to take up to 14 days to register MFA.
  • It also Disables legacy authentication protocols
  • Protects all privileged account logons, like your global administrator.
  • It requires MFA for each login into a protected portal such as Azure, and the O365 admin portal.
  • This one is key: it requires users to logon with MFA only when the logon is seen as risky.

So that last point is pretty big; Users are not prompted for MFA each time they logon. This has been done on purpose by our friends at Microsoft. Microsoft believes that with the data they gathered around security and multi-factor authentication this is the best solution as it avoids creating a pattern of “muscle” memory where users are continually prompted for MFA and just start quickly clicking “Approve”.

Users get a little less trigger-happy on MFA prompts when they are unusual, so that should help you in your security practices. If you want to know exactly what a risky event is, click here for some more information. Microsoft has a neat little table with the exact definition.

If you still want users to get prompted as each logon, as opposed to only some. You’ll have to go to the Multifactor admin page and click ‘enable’ for each user, after enabling Security Defaults. The users will then always get prompted on the method they configured for Security Defaults, you can use the script below to enable Security Defaults on all tenants, or a single tenant.

Permissions

As always you’ll need the secure application model for this script. You’ll also need to add some permissions:

  • Go to the Azure Portal.
  • Click on Azure Active Directory, now click on “App Registrations”.
  • Find your Secure App Model application. You can search based on the ApplicationID.
  • Go to “API Permissions” and click Add a permission.
  • Choose “Microsoft Graph” and “Application permission”.
  • Search for “Policy” and click on “Policy.Read.All and Policy.ReadWrite.ConditionalAccess”. Click on add permission.
  • Do the same for “Delegate Permissions”.
  • Finally, click on “Grant Admin Consent for Company Name.

Single Tenant Script

######### Secrets #########
$ApplicationId = 'AppID'
$ApplicationSecret = 'AppSecret' | ConvertTo-SecureString -Force -AsPlainText
$RefreshToken = 'VeryLongRefreshToken'
######### Secrets #########
$CustomerTenant = "YourClient.onmicrosoft.com"
########################## Script Settings  ############################
$Baseuri = "https://graph.microsoft.com/beta"
write-host "Generating token to log into Azure AD." -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $CustomerTenant
$Header = @{
    Authorization = "Bearer $($CustGraphToken.AccessToken)"
}

$SecureDefaultsState = (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method get -ContentType "application/json")
 
if ($SecureDefaultsState.IsEnabled -eq $true) {
    write-host "Secure Defaults is already enabled for $CustomerTenant. Taking no action."-ForegroundColor Green
}
else {
    write-host "Secure Defaults is disabled. Enabling for $CustomerTenant" -ForegroundColor Yellow
    $body = '{ "isEnabled": true }'
    (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method patch -Body $body -ContentType "application/json")
}

This script checks, and sets the Security Defaults to on, for a single tenant.

All tenants scripts

######### Secrets #########
$ApplicationId = 'AppID'
$ApplicationSecret = 'AppSecret' | ConvertTo-SecureString -Force -AsPlainText
$RefreshToken = 'VeryLongRefreshToken'
######### Secrets #########
$Skiplist = "Bla1.onmicrosoft.com", "bla2.onmicrosoft.com"
########################## Script Settings  ############################
 
$Baseuri = "https://graph.microsoft.com/beta"
write-host "Generating token to log into Azure AD." -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)

$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default'

Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
 
$customers = Get-MsolPartnerContract -All | Where-Object { $_.DefaultDomainName -notin $skiplist }

foreach ($customer in $customers) {
    $CustomerTenant = $customer.defaultdomainname
    $CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $CustomerTenant
    $Header = @{
        Authorization = "Bearer $($CustGraphToken.AccessToken)"
    }

    $SecureDefaultsState = (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method get -ContentType "application/json")
 
    if ($SecureDefaultsState.IsEnabled -eq $true) {
        write-host "Secure Defaults is already enabled for $CustomerTenant. Taking no action."-ForegroundColor Green
    }
    else {
        write-host "Secure Defaults is disabled. Enabling for $CustomerTenant" -ForegroundColor Yellow
        $body = '{ "isEnabled": true }'
        (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method patch -Body $body -ContentType "application/json")
    }

}

And this one processes all tenants. If you want to skip a couple of them, just enter their default domain names in the skiplist variable.

That’s it! as always, Happy PowerShelling.

10 Comments

  1. Jason Massey February 15, 2021 at 6:10 pm

    I’m getting errors when running the script. I gave the API the updated permissions. When I run it I get this error:
    Invoke-RestMethod : {
    “error”: {
    “code”: “AccessDenied”,
    “message”: “You cannot perform the requested operation, required scopes are missing in the token.”,
    “innerError”: {
    “date”: “2021-02-15T17:05:52”,
    “request-id”: “”,
    “client-request-id”: “”
    }
    }
    }

    I examined the CustGraphtoken.Access token with jtw.ms and it appears to be using Azure Active Directory Graph permissions. I added the Policy.ReadAll as a delegated permission and the error changed to:

    Invoke-RestMethod : The remote server returned an error: (403) Forbidden.

    All errors are occurring on the line:
    $SecureDefaultsState = (Invoke-RestMethod -Uri “$baseuri/policies/identitySecurityDefaultsEnforcementPolicy” -Headers $Header -Method get -ContentType “application/json”)

    Any idea on what I’m missing?

  2. Graz February 17, 2021 at 11:46 pm

    You are a god among gods. Thank you for sharing.

  3. John Olesen February 23, 2021 at 8:59 am

    Hi,

    I have searched the web over and over for something close to this.
    However, I need a script that would disable the Seciruty Defaults, and I do not have the know how to reverse the script. Is there a little light at the end of the tunnel? 🙂 🙂 🙂

    1. Kelvin Tegelaar February 23, 2021 at 9:45 am

      Why would you want to turn security defaults off?

      1. John Olesen February 23, 2021 at 11:17 am

        Because reasons 🙂 We are not yet ready to implement this setting for all our customers, and therefor need this to be disabled for the time being.

  4. Rens April 23, 2021 at 9:05 pm

    Like John, I also need a script to toggle it of , can you help us

    1. Kelvin Tegelaar April 23, 2021 at 9:11 pm

      I’m really not going to create scripts that turn of recommended security configurations, not having MFA deployed via either CA or SD is not a choice that you should be able to make.

  5. Pebkac September 3, 2021 at 4:18 pm

    Hi Kevin.

    I have just started playing with your scripts and come across this one.

    I have created the SecureAppModel and all seems to be ok there.

    However when running this script I get the following:

    New-PartnerAccessToken : AADSTS9002313: Invalid request. Request is malformed or invalid.
    Trace ID: c7f0a94a-be35-40e7-bca5-3f49f9f38000
    Correlation ID: 6751f7b0-bdee-4885-8916-3e770a9a19ba
    Timestamp: 2021-09-03 14:12:49Z
    At line:11 char:19
    + … raphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Cre …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : CloseError: (:) [New-PartnerAccessToken], MsalUiRequiredExcep
    tion
    + FullyQualifiedErrorId : Microsoft.Store.PartnerCenter.PowerShell.Commands.NewPartnerA
    ccessToken

    Invoke-RestMethod : The remote server returned an error: (401) Unauthorized.
    At line:16 char:25
    + … ltsState = (Invoke-RestMethod -Uri “$baseuri/policies/identitySecurit …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest)
    [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.I
    nvokeRestMethodCommand

    What am I doing wrong?

    1. Pebkac September 3, 2021 at 4:22 pm

      Ok so instantly found part of the problem, the refresh token had cut short.

      However now I find I have a 403 Forbidden error:

      Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
      At line:16 char:25
      + … ltsState = (Invoke-RestMethod -Uri “$baseuri/policies/identitySecurit …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest)
      [Invoke-RestMethod], WebException
      + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.I
      nvokeRestMethodCommand

      We are a partner and this on a test tenant I created about an hour ago

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.