Get meta data such as the default domain of foreign Azure tenants

For an Azure Managed Application Marketplace scenario, I needed to get information about the user logon domains and the company name of any tenants that purchased our Managed Application through the Azure Marketplace.

Azure Marketplace only has a CRM and a CRUD connection, these don’t send more than some basic name/email/company name and subscription ID’s, and thus the CRM and CRUD operations are hard to match up against one another and to really know who you’re dealing with.

With the following PS snippets, you can get all information from all tenants that have a link with you through Azure Lighthouse / Azure Marketplace:

(Invoke-RestMethod -Method GET -Uri "https://management.azure.com/tenants?api-version=2020-01-01&`$includeAllTenantCategories=true" -UseBasicParsing -Headers @{"Authorization" = "Bearer $azureToken"}).Value

You’ll of course need a bearer token for management.azure.com and I recommend using this snippet to get tenant ID’s from the subscriptions that use your marketplace offers. The output is in JSON and looks like this for Microsoft’s home tenant:

{
    "id":  "/tenants/72f988bf-86f1-41af-91ab-2d7cd011db47",
    "tenantId":  "72f988bf-86f1-41af-91ab-2d7cd011db47",
    "countryCode":  "US",
    "displayName":  "Microsoft",
    "domains":  [
                    "drawbridge.com",
                    "expresslogic.com",
                    "euevents.microsoft.com",
                    "nonprofits.microsoft.com",
                    "benefits.microsoft.com",
                    "forzaesports.com",
                    "bons.ai",
                    "bonsaiai.com",
                    "bonsai.ai",
                    "mileiq.com",
                    "mobiledatalabs.com",
                    "azmosa.io",
                    "fslogix.com",
                    "Howdy.ai",
                    "Xoxco.com",
                    "Botkit.ai",
                    "glintinc.com",
                    "maquette.ms",
                    "tibazdev.microsoft.com",
                    "mail.appcenter.ms",
                    "Hexadite.com",
                    "lobe.ai",
                    "appcenter.ms",
                    "github.com",
                    "gearspop.com",
                    "messages.microsoft.com",
                    "flipgrid.com",
                    "semanticmachines.com",
                    "video2brain.com",
                    "averesystems.com",
                    "initiativegaming.com",
                    "mail1.averesystems.com",
                    "seaofthieves.com",
                    "Intentional.com",
                    "m12.vc",
                    "email.bing.com",
                    "playfab.com",
                    "itsm.microsoft.com",
                    "Windows.mail.microsoft.com",
                    "smtphost.microsoft.com",
                    "exmail.microsoft.com",
                    "altvr.com",
                    "altspacevr.com",
                    "corp.microsoft.com",
                    "cyclecomputing.com",
                    "cloudyn.com",
                    "nuget.org",
                    "microsoftsmarthq.com.au",
                    "lockbox.microsoft.com",
                    "acompli.com",
                    "domains.microsoft",
                    "service.linkedin.com",
                    "microsoft.com",
                    "eventscommunication.microsoft.com",
                    "deis.com",
                    "Lynda.com",
                    "Slideshare.com",
                    "Newsle.com",
                    "linkedin.com",
                    "myemailing.microsoft.com",
                    "maluuba.com",
                    "internal.linkedin.cn",
                    "linkedin.biz",
                    "microsoftcan.onmicrosoft.com",
                    "educatorcommunity.microsoft.com",
                    "simplygon.com",
                    "MicrosoftAPC.onmicrosoft.com",
                    "messages2.microsoft.com",
                    "shadmorris.com",
                    "MicrosoftEur.onmicrosoft.com",
                    "security.microsoft.com",
                    "robovm.com",
                    "solaircorporate.com",
                    "wandlabs.com",
                    "azureemail.microsoft.com",
                    "genee.me",
                    "microsoftstudios.com",
                    "MICROSOFTCSR.COM",
                    "bigpark.com",
                    "bing.com",
                    "corp.webtv.net",
                    "HaloWaypoint.com",
                    "musiwave.com",
                    "navic.tv",
                    "ntdev.corp.microsoft.com",
                    "redmond.corp.microsoft.com",
                    "europe.corp.microsoft.com",
                    "middleeast.corp.microsoft.com",
                    "exchange.corp.microsoft.com",
                    "southamerica.corp.microsoft.com",
                    "fareast.corp.microsoft.com",
                    "winse.corp.microsoft.com",
                    "mslpa.corp.microsoft.com",
                    "windows.microsoft.com",
                    "africa.corp.microsoft.com",
                    "ntdev.microsoft.com",
                    "wingroup.windeploy.ntdev.microsoft.com",
                    "southpacific.corp.microsoft.com",
                    "segroup.winse.corp.microsoft.com",
                    "northamerica.corp.microsoft.com",
                    "service.microsoft.com",
                    "exchange.microsoft.com",
                    "xbox.com",
                    "zune.net",
                    "msg.microsoft.com",
                    "titanium.microsoft.com",
                    "microsoft.mail.onmicrosoft.com",
                    "filtering.exchange.microsoft.com",
                    "skype.net",
                    "hybrid.microsoft.com",
                    "fbt.microsoft.com",
                    "ageofempiresonline.com",
                    "yammer-inc.com",
                    "service.fbt.microsoft.com",
                    "service.exchange.microsoft.com",
                    "mslicense.com",
                    "office365.microsoft.com",
                    "crm.microsoft.com",
                    "mssales.microsoft.com",
                    "mssupport.microsoft.com",
                    "smc.microsoft.com",
                    "sharepointjournaling.exchange.microsoft.com",
                    "wingroup.microsoft.com",
                    "managed.microsoft.com",
                    "serivce.exchange.microsoft.com",
                    "primary.exchange.microsoft.com",
                    "filtering.service.exchange.microsoft.com",
                    "pioneer.exchange.microsoft.com",
                    "wmislabcon01.redmond.corp.microsoft.com",
                    "winfarmmail.ntdev.corp.microsoft.com",
                    "WOSTIX-TEST.NTDEV.corp.microsoft.com",
                    "SPSDOG4-27.redmond.corp.microsoft.com",
                    "SPSDOG4-34.redmond.corp.microsoft.com",
                    "spsdog4-16.redmond.corp.microsoft.com",
                    "cyrusb-z400.redmond.corp.microsoft.com",
                    "MOSSDOG2982.redmond.corp.microsoft.com",
                    "osgwebindex.redmond.corp.microsoft.com",
                    "wostcktiis01.redmond.corp.microsoft.com",
                    "osgemail.redmond.corp.microsoft.com",
                    "extranettest.microsoft.com",
                    "pssupport.microsoft.com",
                    "extranet.microsoft.com",
                    "munich.microsoft.com",
                    "news.microsoft.com",
                    "mpsd.microsoft.com",
                    "gmo.microsoft.com",
                    "ims.microsoft.com",
                    "partners.extranet.microsoft.com",
                    "parttest.extranettest.microsoft.com",
                    "mscourseware.com",
                    "placeware.com",
                    "nokia.microsoft.com",
                    "www.surfaceclub.sg",
                    "winse.microsoft.com",
                    "surface.com",
                    "rare.co.uk",
                    "screentonic.com",
                    "mds.microsoft.com",
                    "mail.microsoft.com",
                    "mailflowtest.mail.microsoft.com",
                    "t-dynmktge.com",
                    "aspproject.nl",
                    "metricshub.com",
                    "ageofempires.com",
                    "azure.com",
                    "fast.no",
                    "microsoft.co.nz",
                    "live.co.hu",
                    "groupme.com",
                    "aquantive.com",
                    "fastsearch.com",
                    "microsoft.tm.hu",
                    "microsoft.ccsctp.com",
                    "healthvault.com",
                    "perceptivepixel.com",
                    "marketingpilot.com",
                    "phonefactor.com",
                    "lucernepublishing.com",
                    "vexcel.co.at",
                    "vexscan.com",
                    "qik.com",
                    "parlano.com",
                    "musiwave.net",
                    "skype.com",
                    "slimbezig.nl",
                    "Softricity.com",
                    "windows-live.hu",
                    "xboxtest.com",
                    "groove.net",
                    "008.mgd.microsoft.com",
                    "vexcel.at",
                    "officelive.co.hu",
                    "windowslive.co.hu",
                    "xbox360.co.hu",
                    "xbox.co.hu",
                    "winlive.co.hu",
                    "windows-live.co.hu",
                    "microsoft.eu",
                    "datallegro.com",
                    "projectspark.com",
                    "Storesimple.com",
                    "Phonefactor.net",
                    "yadata.com",
                    "surfaceclub.sg",
                    "microsoft.onmicrosoft.com",
                    "zone.com",
                    "sentillion.com",
                    "view012.de",
                    "windowsmedia.hu",
                    "greenbutton.com",
                    "css.one.microsoft.com",
                    "proclarity.com",
                    "rareware.com",
                    "capptain.com",
                    "mgd.microsoft.com",
                    "064d.mgd.microsoft.com",
                    "inmage.net",
                    "inmage.com",
                    "bingnews.microsoft.com",
                    "aorato.com",
                    "api.yammer.com",
                    "email.microsoft.com",
                    "officelabs.microsoft.com",
                    "Codenauts.com",
                    "codenauts.de",
                    "Hockeyapp.com",
                    "qa2.parature.net",
                    "componentart.com",
                    "datazen.com",
                    "nuvolarosa.eu",
                    "bayiportali.mmdservice.com",
                    "inside-r.org",
                    "Getliveloop.com",
                    "Sunrise.am",
                    "incentgames.com",
                    "doublelabs.com",
                    "Fantasysalesteam.com",
                    "clickdimensions.Microsoft.com",
                    "volometrix.com",
                    "bluestripe.com",
                    "time.microsoft.com",
                    "revolutionanalytics.com",
                    "inside-r.com",
                    "revolution-computing.com",
                    "fieldone.com",
                    "Pioneerinteractive.com",
                    "msitsupp.microsoft.com",
                    "metanautix.com",
                    "dwh.io",
                    "pressplay.dk",
                    "adxstudio.com",
                    "Havok.com",
                    "Trinigy.net",
                    "Projectanarchy.com",
                    "Rocketbox.de",
                    "cloudappsecurity.com",
                    "email-2.microsoft.com",
                    "Swiftkey.com",
                    "Swiftkey.net",
                    "Swiftmoji.com",
                    "Touchtype-online.com",
                    "msfts2.onmicrosoft.com",
                    "msfts2.mail.onmicrosoft.com",
                    "Xamarin.com",
                    "secureislands.com",
                    "gears.gg",
                    "promoteiq.com",
                    "sangamemail.microsoft.com",
                    "preonboarding.microsoft.com",
                    "microsoftprd.onmicrosoft.com",
                    "bluetalon.com",
                    "citusdata.com",
                    "spotfront.com",
                    "dcat.microsoft.com",
                    "jclarity.com",
                    "msftdomains.microsoft.com",
                    "msra.microsoft.com",
                    "sales.microsoft.com",
                    "askhr.microsoft.com",
                    "idwebmail.microsoft.com",
                    "movere.io",
                    "experience.microsoft.com",
                    "thefightisinus.org",
                    "Unifiedlogic.com",
                    "mover.io",
                    "msads.microsoft.com",
                    "winautomation.com",
                    "softomotive.com"
                ],
    "tenantCategory":  "Home",
    "defaultDomain":  "microsoft.onmicrosoft.com",
    "tenantType":  "AAD"
}

The ‘tenantCategory’ property will have a value of Home if you have an active account in that tenant (as a guest user for example). If lighthouse / CSP is the only authorization, it will say “ProjectedBy”. Both is also possible (tenantCategory is not a json array though, it is ‘seperated’ by a comma).

Get tenant ID using Azure Subscription ID

For a customer use case in an Azure Marketplace Managed Application scenario, I needed the ability to translate the (customers’) Azure Subscription ID (which is known to the publishing tenant) to a tenant ID. Using Get-AzSubscription, Lighthouse subscriptions don’t show the true tenant ID of the other tenant, but only show your own tenant ID.

The following PS function can retrieve the tenant ID for you (without authentication):

function get-tenantIdFromSubscriptionID($subId){
    $response = try {(Invoke-WebRequest -UseBasicParsing -Uri "https://management.azure.com/subscriptions/$($subId)?api-version=2015-01-01" -ErrorAction Stop).BaseResponse} catch { $_.Exception.Response } 
    $stringHeader = $response.Headers.ToString()
    return($stringHeader.SubString($stringHeader.IndexOf("login.windows.net")+18,36))
}

Azure static website with CDN, getting the regional code for an ARM template

When deploying a Static Website to Azure storage with CDN, the CDN endpoint requires the static website hostname. MS docs don’t show how to retrieve this data (other than clicking in the portal). Playing around with Fiddler, I noticed a call to management.azure.com when enabling a static site manually and tried to reproduce this with the Reference function in ARM, which returned the full static website endpoint url including the zone identifier:

"[reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2019-06-01', 'Full').properties.primaryEndpoints.web]"

Sadly, ‘Reference’ is not available for use in other functions, such as Concat, so you’ll have to pass this as output from the storage template to your CDN template instead of combining both into a single template.

This brings us one step closer to a full ARM deployment of a static website, only actually enabling the static website feature on the storage account still requires using PS or Az Cli. But we’re in luck, running PowerShell in DevOps is easy 🙂

Here’s two ARM templates to showcase the Storage Account + CDN:

CDN Template

Storage Account

Then this is what the Pipeline more or less looks like:

the processStorageAccountOutput.ps1 contains:

Param(
  [parameter(Mandatory=$true)]
  $storageAccountOutput
)

Write-Host "##vso[task.setvariable variable=storageAccountWebEndpointURI;]$($(ConvertFrom-Json $storageAccountOutput).WebEndpointURI.value)"
Write-Host "##vso[task.setvariable variable=storageAccountName;]$($(ConvertFrom-Json $storageAccountOutput).storageAccountName.value.Replace('https://','').Split('.')[0])"

And the Azure Cli step in the DevOps pipeline is configured like this:

enabling azure static website through azure cli in azure devops pipeline

Using Azure Function MSI to connect to Azure SQL in Python

Since I had to spend a few hours figuring this out, and all examples/docs are wrong, here’s an example of how to use Python in an Azure Function to connect to an Azure PaaS database without credentials by utilizing the managed identity of the azure function app.

__init__.py:

import logging
import os
import pyodbc
import requests 
import struct
import sys
import azure.functions as func

resource_uri="https://database.windows.net/"
sql_server="XXXXXX.database.windows.net"
sql_database="primary"

def get_bearer_token(resource_uri):
    identity_endpoint = os.environ["IDENTITY_ENDPOINT"]
    identity_header = os.environ["IDENTITY_HEADER"]
    logging.info('identity_endpoint: {}'.format(identity_endpoint))
    logging.info('identity_header : {}'.format(identity_header))
    token_auth_uri = f"{identity_endpoint}?resource={resource_uri}&api-version=2017-09-01"
    head_msi = {'X-IDENTITY-HEADER':identity_header}
    resp = requests.get(token_auth_uri, headers=head_msi)
    access_token = resp.json()['access_token']
    logging.info('response received from token endpoint: {}'.format(access_token))
    return access_token  

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Function Starting')
    try:
        access_token = get_bearer_token(resource_uri)
        accessToken = bytes(access_token, 'utf-8')
        exptoken = b""
        for i in accessToken:
                exptoken += bytes({i})
                exptoken += bytes(1)
        tokenstruct = struct.pack("=i", len(exptoken)) + exptoken  
        conn = pyodbc.connect("Driver={ODBC Driver 17 for SQL Server};Server=tcp:{},1433;Database={}".format(sql_server,sql_database), attrs_before = { 1256:bytearray(tokenstruct) })
        logging.info('connected to {} on {}'.format(sql_server,sql_database))
        cursor = conn.cursor()
        cursor.execute("select @@version")
        row = cursor.fetchall()
        logging.info('sql data: {}'.format(row[0])) 
        logging.info('finished')              
    except BaseException as error:
        logging.info('An exception occurred: {}'.format(error))   
    return func.HttpResponse("done!")

requirements.txt:

astroid==2.4.2
azure-functions==1.2.1
certifi==2020.6.20
chardet==3.0.4
colorama==0.4.3
idna==2.9
isort==4.3.21
lazy-object-proxy==1.4.3
mccabe==0.6.1
pylint==2.5.3
pyodbc==4.0.30
requests==2.24.0
six==1.15.0
toml==0.10.1
urllib3==1.25.9
wrapt==1.12.1

AADSTS50131: Device is not in required device state: known. Or, the request was blocked due to suspicious activity, access policy, or security policy decisions with WDATP

If you’re trying to use the Windows Defender Advanced Threat Protection through the API or through PowerBI and get an AADSTS50131 error, you’ll probably check your sign in logs to see if you’re being blocked by conditional access. If there’s nothing there, as I had the joy of discovering (tsk Microsoft, you really should log this) then check your classic policies and disable if present (old anyway):

Office 365, Azure, Enterprise Mobility and DevOps