Simplifying Kubernetes deployments on ADFS Azure Stack systems
he public preview template for Kubernetes on Azure Stack has been out for a few months now, but the ability/guidance has only been available for a short while to deploy on systems using ADFS as the identity provider. That guidance is here: https://docs.microsoft.com/en-us/azure/azure-stack/user/azure-stack-solution-template-kubernetes-adfs
Feel free to follow the instructions provided, as they do work, but they are fiddly.
Before you start, you have to ensure the following pre-reqs are met before running the template (taken from the doc, but with further comments from me) :
The public preview template for Kubernetes on Azure Stack has been out for a few months now, but the ability/guidance has only been available for a short while to deploy on systems using ADFS as the identity provider. That guidance is here: https://docs.microsoft.com/en-us/azure/azure-stack/user/azure-stack-solution-template-kubernetes-adfs
Feel free to follow the instructions provided, as they do work, but they are fiddly.
Before you start, you have to ensure the following pre-reqs are met before running the template (taken from the doc, but with further comments from me) :
Generate a SSH public and private key pair for the Linux VM’s the template creates. I use PuTTyGen. (Instructions here on generating a key: https://www.ssh.com/ssh/putty/windows/puttygen )
Have a valid tenant subscription where you are at least a contributor. The subscription/region should have enough public IP addresses (at least 2 for core deployment, you’ll need more for services you run on K8s)
Your tenant subscription will need the Key Vault service assigned within the the plan/offer
You’ll need the Kubernetes Cluster marketplace item, or you can just use the ARM template from here: https://github.com/msazurestackworkloads/azurestack-gallery/blob/master/kubernetes/template/DeploymentTemplates/azuredeploy.json
The next part of the doc talks about creating a service principal. This has to be done by an Azure Stack Operator. Currently, the Kubernetes template only supports service principals with certificates for ADFS systems, despite the fact that client secrets was introduced in 1811. Hopefully this will be addressed and supported in a future version, as it will remove the requirement for the certificate and KeyVault.
Once you’ve got the certificate, it needs to be uploaded to a KeyVault within your tenant subscription. The script provided in the doc does this for you, but you need to plug in quite a bit of information and there is the prospect of getting it wrong.
I’ve simplified the process of creating the cert, service principal, creating a key vault and uploading the cert as a secret by producing a script to do the hard work for you. To run it, you need to be an Azure Stack Operator (ability to connect to the ERCS) as well as having access to tenant subscription on the stamp of which you are a contributor.
The script does the following:
Checks if a KeyVault exists on the Azure Stack region with the name you have specified (if it does, it quits)
Creates a self-signed cert on the system you’re running the script on
Connects to the ERCS and creates a service principal using the cert
Exports the cert to a PFX file, with password of your choosing
Connects to Tenant subscription (If you have more than one subscription within the region, it will let you choose which one to deploy to)
Creates a Resource Group, a KeyVault within it and sets access policy to your user account
Uploads the certificate to the KeyVault as a secret
Dumps all the information you need for the template to screen and file
Param (
$ERCS = (Read-Host -Prompt "ERCS"),
$OutputPath = $ENV:Temp,
$SubscriptionID,
[ValidateNotNullOrEmpty()]
$appNamePrefix = "appSPN",
[ValidateNotNullOrEmpty()]
$ResourceGroup = "K8sDemoAdFsRG",
[ValidateNotNullOrEmpty()]
$KeyvaultName = "K8sDemoAdFsKV51",
[ValidateNotNullOrEmpty()]
$keyVaultSecretName = "K8sSecret",
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$ErcsCredential = (Get-Credential -Message "Enter CloudAdmin Credentials for ERCS"),
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$cloudCredential = (Get-Credential -Message "Enter Azure Stack Tenant Credentials"),
[ValidateNotNullOrEmpty()]
[Security.SecureString]$PfxPassword=(Read-Host "Enter PFX Password" -AsSecureString)
)
[bool]$GeneratePFX = $true
function write-log($logentry){
Write-output "$logentry" | out-file $detailFile -Append
Write-output "$logentry"
}
# Location to write PFX file and log file
$OutputPath = $OutputPath.Trim('\')
if (!(Test-Path $OutputPath -pathType container)) {
New-Item $OutputPath -type directory -Force
}
# Creating a PSSession to the ERCS PrivilegedEndpoint
$session = New-PSSession -ComputerName $ERCS -ConfigurationName PrivilegedEndpoint -Credential $ErcsCredential
$AzureStackInfo = Invoke-Command -Session $session -ScriptBlock { get-azurestackstampinformation }
# For Azure Stack development kit, this value is set to https://management.local.azurestack.external. We will read this from the AzureStackStampInformation output of the ERCS VM.
$ArmEndpoint = $AzureStackInfo.TenantExternalEndpoints.TenantResourceManager
# For Azure Stack development kit, this value is set to https://graph.local.azurestack.external/. We will read this from the AzureStackStampInformation output of the ERCS VM.
$GraphAudience = "https://graph." + $AzureStackInfo.ExternalDomainFQDN + "/"
# TenantID for the stamp. We will read this from the AzureStackStampInformation output of the ERCS VM.
$TenantID = $AzureStackInfo.AADTenantID
# Register an AzureRM environment that targets your Azure Stack instance
Add-AzureRMEnvironment ` -Name "azurestacktenant" ` -ArmEndpoint $ArmEndpoint
$location = $AzureStackInfo.RegionName
# Set the GraphEndpointResourceId value
$AzsEnv = Set-AzureRmEnvironment ` -Name "azurestacktenant" -GraphAudience $GraphAudience -EnableAdfsAuthentication:$true
$KeyVaultSuffix = $azsEnv.AzureKeyVaultDnsSuffix
$KeyvaultDnsName = "https://" + $KeyvaultName + "." + $KeyVaultSuffix
$KVSuffix = '/secrets/Secret1?api-version=2016-10-01'
$KVCheckURI = $KeyvaultDnsName + $KVSuffix
# This block of code in untidy, but tests whether the KeyVault namespace exists on the Stamp already (401) or not (404)
try {
(Invoke-WebRequest -Uri $KVCheckURI -ErrorAction Stop).BaseResponse
} catch [System.Net.WebException] {
# Messy, but we're not using a token to authenticate, just seeing if the name is already in use
$Status = $_.Exception.Response.StatusCode.value__
If ($Status -eq 404) {
$stat = "does not exist"
}
else
{
$stat = "exists already"
}
Write-Debug ("KeyVault Namespace {0} {1} in Region {2}" -f $KeyvaultDnsName, $stat, $Location)
}
# Only carry on if the KeyVault namespace doesn't exist on the Stamp
If ($Status -eq 404) {
Write-Debug "Creating Self-signed cert and new Graph APplication..."
# This produces a self signed cert for testing purposes. It is preferred to use a managed certificate for this.
if ($GeneratePFX) {
$cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" -Subject "CN=$appNamePrefix" -KeySpec KeyExchange
$ServicePrincipal = Invoke-Command -Session $session {New-GraphApplication -Name $args[0] -ClientCertificates $args[1]} -ArgumentList $appNamePrefix,$cert
}
else {
$ServicePrincipal = Invoke-Command -Session $session {New-GraphApplication -Name $args[0] -GenerateClientSecret} -ArgumentList $appNamePrefix
}
$session|remove-pssession
$SPNName = $ServicePrincipal.ApplicationName
$PFXFile = "$OutputPath\$SPNName.pfx"
$detailfile = "$OutputPath\$SPNName-details.txt"
write-Log "Client Id : $($ServicePrincipal.ClientId)"
if ($GeneratePFX) { write-output "Cert Thumbprint : $($ServicePrincipal.Thumbprint)"}
else { write-output "Client Secret : $($ServicePrincipal.ClientSecret)"}
write-Log "Application Name : $($ServicePrincipal.ApplicationName)"
write-Log "TenantID : $TenantID"
write-Log "ARM EndPoint : $ArmEndpoint"
write-Log "Admin Endpoint : $AdminEndpoint"
write-Log "Graph Audience : $GraphAudience"
# Now Export the cert to PFX
if ($GeneratePFX){
# enter a password for the PFX file...
$pw = $PfxPassword
# Store the cert in the designated output directory
Export-PfxCertificate -cert $cert -FilePath $PFXFile -Password $pw
write-Log "PFX Certificate : $PFXFile"
}
# Connect to the Stamp
If ($SubscriptionID) {
$AzsUser = Login-AzureRmAccount -Environment azurestacktenant -Credential $Cloudcreds -Subscription $subscriptionId
}
else
{
$AzsUser = Login-AzureRmAccount -Environment azurestacktenant -Credential $Cloudcreds
$Subs = Get-AzureRmSubscription
# Show a list of subs if more than one is available
If ($Subs.Count -gt 1) {
$context = $Subs | Out-GridView -PassThru
Set-AzureRmContext -Subscription $context
}
}
#Get the SID for the user account you've used to connect to the Subscription
$adfsuserID = $null
try {
# using the get-azurermaduser means the script can be used on non-domain joined systems :)
$adfsuserID = (get-azurermaduser -UserPrincipalName $azsuser.Context.Account.Id).AdfsID
}
catch {
}
# This can be used for currently logged in user:
<#
if (-not $adfsuserID) {
$Filter = "name = '" + $env:USERNAME + "' AND domain = '" + $env:USERDOMAIN + "'"
$adfsuserID = (Get-WmiObject win32_useraccount -Filter "$Filter").SID
}
#>
# Create new Resource group and key vault
New-AzureRmResourceGroup -Name $ResourceGroup -Location $location -Force
New-AzureRmKeyVault -VaultName $KeyvaultName -ResourceGroupName $ResourceGroup -Location $location -EnabledForTemplateDeployment
Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyvaultName -ResourceGroupName $ResourceGroup -ObjectId $adfsuserID -BypassObjectIdValidation -PermissionsToKeys all -PermissionsToSecrets all
#Convert the secure pw to something that can be used
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pw)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$certContentInBytes = [io.file]::ReadAllBytes($PFXFile)
$pfxAsBase64EncodedString = [System.Convert]::ToBase64String($certContentInBytes)
$jsonObject = @"
{
"data": "$pfxAsBase64EncodedString",
"dataType" :"pfx",
"password": "$password"
}
"@
$jsonObjectBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonObject)
$jsonEncoded = [System.Convert]::ToBase64String($jsonObjectBytes)
$secret = ConvertTo-SecureString -String $jsonEncoded -AsPlainText -Force
$keyVaultSecret = Set-AzureKeyVaultSecret -VaultName $KeyvaultName -Name $keyVaultSecretName -SecretValue $secret
#Give the new Service Principal Contributor rights to the Subscription
New-AzureRmRoleAssignment -ApplicationID ($ServicePrincipal.ClientId) -RoleDefinitionName "Contributor" -Scope "/subscriptions/$($context.Id)"
Write-Log ('')
Write-Log "Service principal clientId : $($ServicePrincipal.ClientId)"
Write-Log "Key vault resource group : $ResourceGroup "
Write-Log "Key vault name : $KeyvaultName"
Write-Log "Key vault secret : $keyVaultSecretName"
$detailfile
}
else {
write-Error "Certificate and Keyvault processing halted as KeyVault namespace already exists in this region. Please try another name"
}
When you run the script, you should hopefully see output resembling this:
I’ve formatted it so that you can copy paste it into the template. I could have created a parameter file, but for my purposes this was fine.
For a deeper understanding of whats happening when deploying the template, take a look at Ned Bellavance’s great post here: https://nedinthecloud.com/2019/02/19/azure-stack-kubernetes-cluster-is-not-aks/ .
Azure Stack update 1811 - my favorite feature in this release
Microsoft have just released Azure Stack Update 1.1811.0.101, and for me, it is one I am looking forward to implementing now that I have read the release notes on the new capabilities.
Microsoft have just released Azure Stack Update 1.1811.0.101, and for me, it is one I am looking forward to implementing now that I have read the release notes on the new capabilities. For some, the headline feature is the introduction of Extension Host, which simplifies access to the portals and management endpoints over SSL (it acts as a reverse proxy). This has been known about for some months, as Microsoft have been warning operators of additional certificate requirements and to be ready for it: https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-extension-host-prepare. This is good, as it means less firewall rules are required and I'm all for simplification, but not the most exciting introduction for me - that's the support for service principals using client secrets.
Why's that?
I've been working with Azure Stack with AD FS as the identity provider for many months and previously the only way to provision Service Principals (for use by automation or applications) was to use X509 certs for authentication. Setting up the certs is pretty cumbersome , as they have to be generated, imported to systems that you want to run the automation on, grab the thumbprint, generate PEM files with the private key for use with Azure CLI. For me, too many areas where stuff might not work (e.g., the certificate may not be present in the local computer store where the automation is running and throw an error.)
Using X509 certs to authenticate worked for a some scenarios, but not for others. For instance, a number of third party solutions//tools (and first party!) couldn't be used, as they were written to be compatible with Azure AD Service Principals (which primarily uses secrets). One example is the TerraForm provider; prior to this update, it could only be used for Azure AD implementations, but in theory it's now open to AD FS as well. What this release also opens up is the possibility of deploying the Kubernetes ARM template that is currently in preview. The template requires a Service Principal ClientID and Client Secret, so blocked deployment to disconnected systems previously.
I haven't had the chance to apply the update yet, but I will do it ASAP and look forward to testing whether client secrets for ADFS works as I expect.
Azure Stack portal bug
As I’m mainly working with Azure Stack deployments that use AD FS as the identity provider, I’m coming across some differences and bugs compared to where Azure AD is used.
As I’m mainly working with Azure Stack deployments that use AD FS as the identity provider, I’m coming across some differences and bugs compared to where Azure AD is used. One such bug is the following:
A user is a member of a global AD group that is assigned Contributor role to a Tenant Subscription. They aren’t added directly as a user to the subscription.
When that user connects to the portal, they will be presented with the following if they click on the subscription:
If they try and create a resource within the subscription, they get the following:
By connecting as this same user via PowerShell or Azure CLI, they can create a resource group and resources and do everything expected of a Contributor.
I logged a support case with Microsoft and they have confirmed this is a bug in the portal and that it will be fixed in an imminent release (potentially 1811).
In the meantime, the workaround is to assign users directly to the role rather than via a global group or to use the API / PowerShell / Az CLI to manage resources.
Creating an Azure Stack AD FS SPN for use with az CLI
Following on from my previous blog post on filling in the gaps for AD FS on Azure Stack integrated systems, here are some more complete instructions on creating a Service Principal on Azure Stack systems using AD FS as the identity provider. Why do you need this? Well, check out the following scenarios as taken from https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-integrate-identity#spn-creation:
There are many scenarios that require the use of a service principal name (SPN) for authentication. The following are some examples:
CLI usage with AD FS deployment of Azure Stack
System Center Management Pack for Azure Stack when deployed with AD FS
Resource providers in Azure Stack when deployed with AD FS
Various third party applications
You require a non-interactive logon
Following on from my previous blog post on filling in the gaps for AD FS on Azure Stack integrated systems, here are some more complete instructions on creating a Service Principal on Azure Stack systems using AD FS as the identity provider. Why do you need this? Well, check out the following scenarios as taken from https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-integrate-identity#spn-creation:
There are many scenarios that require the use of a service principal name (SPN) for authentication. The following are some examples:
CLI usage with AD FS deployment of Azure Stack
System Center Management Pack for Azure Stack when deployed with AD FS
Resource providers in Azure Stack when deployed with AD FS
Various third party applications
You require a non-interactive logon
I’ve highlighted the first point ‘CLI usage with AD FS deployment of Azure Stack’. This is significant as AD FS only supports interactive login. At this point in time, the AZ CLI does not support interactive mode, so you must use a service principal.
There are a few areas that weren’t clear to me at first, so I worked it all out and tried to simplify the process.
At a high level, these are the tasks:
Create an X509 certificate (or use an existing one) to use for authentication
Create a new Service Principal (Graph Application) on the internal Azure Stack domain via PEP PowerShell session
Return pertinent details, such as Client ID, cert thumbprint, Tenant ID and relevant external endpoints for the Azure Stack instance
Export the certificate as PFX (for use on clients using PowerShell) and PEM file including private certificate (for use with Azure CLI)
Give the Service Principal permissions to the subscription
Here’s the link to the official doc’s: https://docs.microsoft.com/en-gb/azure/azure-stack/azure-stack-create-service-principals#create-service-principal-for-ad-fs
I’ve automated the process by augmenting the script provided in the link above. It creates a self-signed cert, AD FS SPN and files required to connect. It needs to be run on a system that has access to the PEP and also has the Azure Stack PowerShell module installed.
The script includes the steps to export the PFX (so you can use it with PowerShell on other systems) and PEM files, plus output ALL the relevant info you will need to connect via AZ CLI/ PoSh
# Following code taken from https://github.com/mongodb/support-tools/blob/master/ssl-windows/Convert-PfxToPem.ps1
Add-Type @'
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.Text;
public class Cert_Utils
{
public const int Base64LineLength = 64;
private static byte[] EncodeInteger(byte[] value)
{
var i = value;
if (value.Length > 0 && value[0] > 0x7F)
{
i = new byte[value.Length + 1];
i[0] = 0;
Array.Copy(value, 0, i, 1, value.Length);
}
return EncodeData(0x02, i);
}
private static byte[] EncodeLength(int length)
{
if (length < 0x80)
return new byte[1] { (byte)length };
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
var encodedLength = new byte[bytesRequired + 1];
encodedLength[0] = (byte)(bytesRequired | 0x80);
for (var i = bytesRequired - 1; i >= 0; i--)
encodedLength[bytesRequired - i] = (byte)(length >> (8 * i) & 0xff);
return encodedLength;
}
private static byte[] EncodeData(byte tag, byte[] data)
{
List result = new List();
result.Add(tag);
result.AddRange(EncodeLength(data.Length));
result.AddRange(data);
return result.ToArray();
}
public static string RsaPrivateKeyToPem(RSAParameters privateKey)
{
// Version: (INTEGER)0 - v1998
var version = new byte[] { 0x02, 0x01, 0x00 };
// OID: 1.2.840.113549.1.1.1 - with trailing null
var encodedOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
List privateKeySeq = new List();
privateKeySeq.AddRange(version);
privateKeySeq.AddRange(EncodeInteger(privateKey.Modulus));
privateKeySeq.AddRange(EncodeInteger(privateKey.Exponent));
privateKeySeq.AddRange(EncodeInteger(privateKey.D));
privateKeySeq.AddRange(EncodeInteger(privateKey.P));
privateKeySeq.AddRange(EncodeInteger(privateKey.Q));
privateKeySeq.AddRange(EncodeInteger(privateKey.DP));
privateKeySeq.AddRange(EncodeInteger(privateKey.DQ));
privateKeySeq.AddRange(EncodeInteger(privateKey.InverseQ));
List privateKeyInfo = new List();
privateKeyInfo.AddRange(version);
privateKeyInfo.AddRange(encodedOID);
privateKeyInfo.AddRange(EncodeData(0x04, EncodeData(0x30, privateKeySeq.ToArray())));
StringBuilder output = new StringBuilder();
var encodedPrivateKey = EncodeData(0x30, privateKeyInfo.ToArray());
var base64Encoded = Convert.ToBase64String(encodedPrivateKey, 0, (int)encodedPrivateKey.Length);
output.AppendLine("-----BEGIN PRIVATE KEY-----");
for (var i = 0; i < base64Encoded.Length; i += Base64LineLength)
output.AppendLine(base64Encoded.Substring(i, Math.Min(Base64LineLength, base64Encoded.Length - i)));
output.Append("-----END PRIVATE KEY-----");
return output.ToString();
}
public static string PfxCertificateToPem(X509Certificate2 certificate)
{
var certBase64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));
var builder = new StringBuilder();
builder.AppendLine("-----BEGIN CERTIFICATE-----");
for (var i = 0; i < certBase64.Length; i += Cert_Utils.Base64LineLength)
builder.AppendLine(certBase64.Substring(i, Math.Min(Cert_Utils.Base64LineLength, certBase64.Length - i)));
builder.Append("-----END CERTIFICATE-----");
return builder.ToString();
}
}
'@
# Credential for accessing the ERCS PrivilegedEndpoint typically domain\cloudadmin
$creds = Get-Credential
$pepIP = "172.16.101.224"
$date = (get-date).ToString("yyMMddHHmm")
$appName = "appSPN"
$PEMFile = "c:\temp\$appName-$date.pem"
$PFXFile = "c:\temp\$appName-$date.pfx"
# Creating a PSSession to the ERCS PrivilegedEndpoint
$session = New-PSSession -ComputerName $pepIP -ConfigurationName PrivilegedEndpoint -Credential $creds
# This produces a self signed cert for testing purposes. It is preferred to use a managed certificate for this.
$cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" -Subject "CN=$appName" -KeySpec KeyExchange
$ServicePrincipal = Invoke-Command -Session $session {New-GraphApplication -Name $args[0] -ClientCertificates $args[1]} -ArgumentList $appName,$cert
$AzureStackInfo = Invoke-Command -Session $session -ScriptBlock { get-azurestackstampinformation }
$session|remove-pssession
# For Azure Stack development kit, this value is set to https://management.local.azurestack.external. We will read this from the AzureStackStampInformation output of the ERCS VM.
$ArmEndpoint = $AzureStackInfo.TenantExternalEndpoints.TenantResourceManager
$AdminEndpoint = $AzureStackInfo.AdminExternalEndpoints.AdminResourceManager
# For Azure Stack development kit, this value is set to https://graph.local.azurestack.external/. We will read this from the AzureStackStampInformation output of the ERCS VM.
$GraphAudience = "https://graph." + $AzureStackInfo.ExternalDomainFQDN + "/"
# TenantID for the stamp. We will read this from the AzureStackStampInformation output of the ERCS VM.
$TenantID = $AzureStackInfo.AADTenantID
# Register an AzureRM environment that targets your Azure Stack instance
Add-AzureRMEnvironment ` -Name "azurestacktenant" ` -ArmEndpoint $ArmEndpoint
Add-AzureRMEnvironment ` -Name "azurestackadmin" ` -ArmEndpoint $AdminEndpoint
# Set the GraphEndpointResourceId value
Set-AzureRmEnvironment ` -Name "azurestacktenant" -GraphAudience $GraphAudience -EnableAdfsAuthentication:$true
Add-AzureRmAccount -EnvironmentName "azurestacktenant" `
-ServicePrincipal ` -CertificateThumbprint $ServicePrincipal.Thumbprint `
-ApplicationId $ServicePrincipal.ClientId `
-TenantId $TenantID
# Output details required to pass to PowrShell or AZ CLI
write-host "ClientID : $($ServicePrincipal.ClientId)"
write-host "Cert Thumbprint : $($ServicePrincipal.Thumbprint)"
write-host "Application Name : $($ServicePrincipal.ApplicationName)"
write-host "TenantID : $TenantID"
write-host "ARM EndPoint : $ArmEndpoint"
write-host "Admin Endpoint : $AdminEndpoint"
write-host ""
write-host "PEM Cert path : $PEMFile"
write-host "PFX Cert Path : $PFXFile"
# Export the Cert to a pem file for user with Azure CLI
$result = [Cert_Utils]::PfxCertificateToPem($cert)
$parameters = ([Security.Cryptography.RSACryptoServiceProvider] $cert.PrivateKey).ExportParameters($true)
$result += "`r`n" + [Cert_Utils]::RsaPrivateKeyToPem($parameters);
$result | Out-File -Encoding ASCII -ErrorAction Stop $PEMFile
# Now Export the cert to PFX
$pw = Read-Host "Enter PFX Certificate Password" -AsSecureString
Export-PfxCertificate -cert $cert -FilePath $PFXFile -Password $pw
Here is an example of the output produced:
Next, connect to the Tenant Portal and give the Service Principal access to the subscription you want it to have access to:
Once you’ve done the above, here are the high-level steps to use the Service Principal account with Azure CLI:
Trust the Azure Stack CA Root Certificate (if using Enterprise CA / ASDK) within AZ CLI (Python). This is a one-time operation per system you’re running AZ CLI on.
Register Azure Stack environment (either tenant/user or admin)
Set the active cloud environment for CLI
Set the CLI to use Azure Stack compatible API version
Sign into the Azure Stack environment with service principal account
For reference, here are the official links with the information on how to do it. It works well, so just follow those:
https://docs.microsoft.com/en-us/azure/azure-stack/user/azure-stack-version-profiles-azurecli2
az cloud register -n AzureStackUser --endpoint-resource-manager 'https://management.' --suffix-storage-endpoint '' --suffix-keyvault-dns '.vault.'
az cloud register -n AzureStackAdmin --endpoint-resource-manager 'https://adminmanagement.' --suffix-storage-endpoint '' --suffix-keyvault-dns '.vault.'
az cloud set -n AzureStackUser
az cloud update --profile 2017-03-09-profile
az login --tenant --service-principal -u -p
AD FS identity integration on Azure Stack – filling in the gaps
One of the clients I’ve been engaged with use AD FS as the identity provider for their Azure Stack integrated system. All well and good, as setting that up using the instructions provided here is *fairly* straightforward: https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-integrate-identity. Here’s a high level of the tasks that need to be performed:
One of the clients I’ve been engaged with use AD FS as the identity provider for their Azure Stack integrated system. All well and good, as setting that up using the instructions provided here is *fairly* straightforward: https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-integrate-identity. Here’s a high level of the tasks that need to be performed:
On Azure Stack (by the operator via Privileged Endpoint PowerShell session):
Setup Graph integration (configure to point to on-premises AD Domain, so user / group searches can be performed, used by IAM/RBAC)
Setup AD FS integration (Create federation data metafile and use automation to configure claims provider trust with on-premises AD FS)
Set Service Admin Owner to user in the on-premises AD Domain
On customer AD FS server by an admin with correct permissions:
Configure claims provider trust (either by helper script provided in Azure Stack tools, or manually)
If performing manually:
Enable Windows forms-based authentication
Add the relying party trust
Configure AD FS properties to ignore token ring bindings (If using IE / Edge browsers and AD FS is running on WS 2016)
Set AD FS Relying party trust with Token lifetime of 1440
If performing by helper script:
From Azure Stack tools directory, navigate to \DatacenterIntegration\Identity and run setupadfs.ps1
The only gotcha with the instructions that I encountered was that the certificate chain for AD FS was different than was provisioned for Azure Stack endpoints, so I tried to follow the instructions for this scenario provided in the link above, but they didn’t work.
It turns out that there was a problem with me running the provided PowerShell code:
[XML]$Metadata = Invoke-WebRequest -URI https:///federationmetadata/2007-06/federationmetadata.xml -UseBasicParsing
$Metadata.outerxml|out-file c:\metadata.xml
$federationMetadataFileContent = get-content c:\metadata.cml
$creds=Get-Credential
Enter-PSSession -ComputerName -ConfigurationName PrivilegedEndpoint -Credential $creds
Register-CustomAdfs -CustomAdfsName Contoso -CustomADFSFederationMetadataFileContent $using:federationMetadataFileContent
…and here’s one that is correctly configured:
The things to check for are that the correct FQDN to the AD domain are provided and the user / password combination is correct. Graph just needs a ‘normal’ user account with no special permissions. Make sure the password for the user is set to not expire!
You can re-run the command from the PEP without having to run Reset-DatacenterIntegationConfiguration. Only run this when AD FS integration is broken.
If you want to use AD Groups via Graph for RBAC control, keep in mind that they need to be Universal, otherwise they will not appear.
Hopefully this information will help some of you out.
In my next blog post, I’ll fill in the gaps on creating AD FS SPN’s for use by automation / Azure CLI on Azure Stack.
Topic Search
-
Securing TLS in WAC (Windows Admin Center) https://t.co/klDc7J7R4G
Posts by Date
- March 2025 1
- February 2025 1
- October 2024 1
- August 2024 1
- July 2024 1
- October 2023 1
- September 2023 1
- August 2023 3
- July 2023 1
- June 2023 2
- May 2023 1
- February 2023 3
- January 2023 1
- December 2022 1
- November 2022 3
- October 2022 7
- September 2022 2
- August 2022 4
- July 2022 1
- February 2022 2
- January 2022 1
- October 2021 1
- June 2021 2
- February 2021 1
- December 2020 2
- November 2020 2
- October 2020 1
- September 2020 1
- August 2020 1
- June 2020 1
- May 2020 2
- March 2020 1
- January 2020 2
- December 2019 2
- November 2019 1
- October 2019 7
- June 2019 2
- March 2019 2
- February 2019 1
- December 2018 3
- November 2018 1
- October 2018 4
- September 2018 6
- August 2018 1
- June 2018 1
- April 2018 2
- March 2018 1
- February 2018 3
- January 2018 2
- August 2017 5
- June 2017 2
- May 2017 3
- March 2017 4
- February 2017 4
- December 2016 1
- November 2016 3
- October 2016 3
- September 2016 5
- August 2016 11
- July 2016 13