Using Application ID to automate Seamless SSO kerberos decryption key rollover - powershell

I would like to automate the rollover of kerberos description keys used for seamless SSO. In doing this, I cannot use global administrator USER accounts, as they may receive spray attacks (I cannot use domain IDs as there are so many domains, and thus I will have to make so many Global Admins). I am thinking that by using application ID and certificates, I can securely automate this job.
The problem is, every sample code I could find has uses the "AuthenticationContext". Something like this:
New-AzureADSSOAuthenticationContext -CloudCredentials $CloudCredentials
Update-AzureADSSOForest -OnPremCredentials $OnpremCredentials
Is it possible to use application IDs to automate this process? And if it is, how do i code it?
Thank you for your kind help.

I'm afraid you could not use the service principal(i.e. the application ID) to do that.
Currently, we could just use domain admin and tenant global admin credentials in a scheduled task.
You could refer to this sample:
# Requirements:
# Microsoft Online Services Sign-In Assistant.
# 64-bit Azure Active Directory module for Windows PowerShell.
$CloudUser = 'service_account#domain.com'
$CloudEncrypted = Get-Content "C:\Scripts\Cloud_Encrypted_Password.txt" | ConvertTo-SecureString
$CloudCred = New-Object System.Management.Automation.PsCredential($CloudUser,$CloudEncrypted)
$OnpremUser = 'DOMAIN\service_account'
$OnpremEncrypted = Get-Content "C:\Scripts\Onprem_Encrypted_Password.txt" | ConvertTo-SecureString
$OnpremCred = New-Object System.Management.Automation.PsCredential($OnpremUser,$OnpremEncrypted)
Import-Module 'C:\Program Files\Microsoft Azure Active Directory Connect\AzureADSSO.psd1'
New-AzureADSSOAuthenticationContext -CloudCredentials $CloudCred
Update-AzureADSSOForest -OnPremCredentials $OnpremCred
Besides, Azure Team is also working on getting this automated, see the feedback:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/33773926-automate-seamless-sso-kerberos-decryption-key-roll

Related

Is there an alternative way for Set-UnifiedGroup -UnifiedGroupWelcomeMessageEnabled?

A couple of years ago, we made a provisioning script which creates a unified group with PnPPowerShell. While there was no other way to disable the welcome message for new group members, we had to connect with Exchange and disable the welcome message with Exchange Online PowerShell using Set-UnifiedGroup -UnifiedGroupWelcomeMessageEnabled.
Due to changed requirements of the customer as a result of the status of the legacy auth at Microsoft, legacy authentication will no longer be available soon and should be disabled on our customer's environment asap.
And now the trouble begins. As we have an older environment with Exchange Online PowerShell V2 running on PowerShell 5, we can't connect with -ManagedIdentity but we would have to connect with a certificate to run the unattended script.
Now we encountered the next problem: according to this documentation at Microsoft Learn, the commandlet Set-UnifiedGroup will not work with app-only authentication for unattended scripts in Exchange Online PowerShell V2.
So we had a look into the MS Graph API documentation to update groups to find out if there is a property to achieve the disabling of the welcome messgae, but it seems that there is none.
Long story short, is there any way to update the group through an unattended script without legacy authentication, using Exchange Online PowerShell V2 on PowerShell 5? Upgrading PowerShell and the Exchange module would affect a bunch of other provisioning scripts where we are currently already able to connect with the -ManagedIdentity parameter.
It seems to me that resourceBehaviorOptions property on group resource object is what you are looking for.
It specifies the group behaviors that can be set for a Microsoft 365 group during creation. This can be set only as part of creation (POST) (New-MgGroup powershell cmdlet).
One of possible values for resourceBehaviorOptions is WelcomeEmailDisabled. If the value is specified then welcome emails are not sent to new members.
Example:
Import-Module Microsoft.Graph.Groups
$params = #{
Description = "My new group"
DisplayName = "Groupxxx"
GroupTypes = #(
"Unified"
)
MailEnabled = $true
MailNickname = "library"
SecurityEnabled = $false
ResourceBehaviorOptions = #(
"WelcomeEmailDisabled"
)
}
New-MgGroup -BodyParameter $params

My Azure Automation Runbook uses a stored credential to get a list of new groups, need an alternative that does not use stored credentials

I have a requirement to disable external sharing on SPOnline sites associated with new Groups (e.g., MS Teams, Planner, etc.). When an Office 365 user provisions a new Team, Planner, or Group an SPOnline site is provisioned in the background to store files for the associated Group. Be default, this "background" SPOnline site allows external sharing of content.
I have created an Azure Automation Runbook that creates a session to Exchange Online using a Stored Credential. I then get a list of the groups created in the past day.
$groups = Get-UnifiedGroup -Filter "WhenCreated -gt '$dt'"
With the list of Groups, I then iterate through them, connect to SPOnline with an AppId and AppSecret and disable sharing.
Connect-PnPOnline -AppId $appId -AppSecret $appSecret -Url $tenantUrl
$site = Get-PnPTenantSite -Url $group.SharePointSiteUrl
Write-Output "Sharing Capability: " $site.SharingCapability
if($site.SharingCapability -ne "Disabled")
{
#Set-PnPTenantSite -Url $site.Url -Sharing Disabled
Write-Output "Disabling external sharing"
}
else
{
Write-Output "External sharing already disabled"
}
My problem is that our production environment uses a PCS system to manage passwords for accounts. This would require more scripting to access the PCS, checkout the password, update the stored credential then do the work that I want to do.
I am able to get the list of Groups directly from Azure AD but the returned objects do not have the WhenCreated and SharePointSiteURL properties. Without these two pieces of information, I am not able to limit my script to the past day nor change the setting in SharePoint.
My goal is to explore any alternative that allow me to get the list of new Groups and the associated SPOnline site URL without using a stored credential in Azure Automation. Any ideas?
Thank you for your time.
Generally you only have 4 options:
hardcode credentials
use variables + certificates (you can make them secure string, so they are encrypted)
use run as account (allows use of certificates)
use credentials + certificates (pretty much the same as variables)

Configurable token lifetimes in Azure Active Directory

I could not assign TokenLifetimePolicy Azure AD application policy from PowerShell. I had an error BadRequest : Message: Open navigation properties are not supported on OpenTypes.Property name: 'policies
I am trying to implement token expiry time from Configurable token lifetimes in Azure Active Directory
See screenshot below, any useful links and solutions on the AzureAD cmdlet Add-AzureADApplicationPolicy are welcome
I made it work by only using New-AzureADPolicy cmdlet and setting -IsOrganizationDefault $true not $false. The effect takes a while for you to see it. So wait for about 30 minutes to an hour (I don't know how long exactly). After that your new policy will be created and applied. Also remember that this is PowerShell, so no whitespaces in the cmdlet.
Example:
New-AzureADPolicy -Definition #('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"02:00:00","MaxInactiveTime":"02:00:00","MaxAgeSessionSingleFactor":"02:00:00"}}') -DisplayName "PolicyScenario" -IsOrganizationDefault $true -Type "TokenLifetimePolicy"
Multi-Line version:
New-AzureADPolicy -Definition #(
'
{
"TokenLifetimePolicy":
{
"Version": 1,
"AccessTokenLifetime": "02:00:00",
"MaxInactiveTime": "02:00:00",
"MaxAgeSessionSingleFactor": "02:00:00"
}
}
'
) -DisplayName "PolicyScenario" -IsOrganizationDefault $true -Type "TokenLifetimePolicy"
Microsoft may fix the issue with IsOrganizationDefault $true. Read more on this in the question: Azure AD Configurable Token Lifetimes not being Applied.
I test this quite a bit for my customers. I run into issues like this every now and then due to not on the latest version of PowerShell.
get-module
Latest Version 2.0.0.114 at the moment for AzureADPreview (V2)
Instructions to download here
There was an issue with -IsOrganizationDefault $true as Seth has pointed out.
Another issue I've found is having multiple versions of PowerShell on your system and it's loading the wrong one that doesn't have the updated bits. I hit this last Friday - I had to wipe everything and reinstall - then it fixed it.
Also -
There is a difference between:
Add-AzureADApplicationPolicy
and
Add-AzureADServicePrincipalPolicy
One is for an application object and the other is for a ServicePrincipal. If you are applying it to say, a SAML-Based application, then you should apply it to the ServicePrincpal.
Note: There is a different ObjectID for the application object and the servicePrincipal object. Don't get these confused. For an experiment, run the two cmds against your application:
Get-AzureADServicePrincipal -SearchString <name of app>
Get-AzureADApplication -SearchString <name of app>
If you grab the wrong ObjectID - no go when you go to apply the policy
The sequence for these Policies are: ServicePrincipal -> Application -> Tenant (organization)
Was the application created in B2C portal?
Assuming the answer is yes, this behavior is expected:
Microsoft has 2 authorization end points, V1 and V2.
B2C portal creates V2 apps. The token lifetime setting from powershell probably only works against the V1 apps.
There are settings on the b2c blade to change this.
The other option is to create an app from the azure active directory blade(as opposed to the b2c blade). Then you can set the token life time using powershell.

What should I use in place of Select-AzureSubscription?

I am trying to remove deprecated cmdlets in a powershell script and one of the cmdlets is Select-AzureSubscription. I tried replacing it with Select-AzureRmSubscription but that requires user interaction to authenticate. Does anyone know what Azure-Rm cmdlet I should be using instead?
Select-AzureRmSubscription does change the approach that Azure uses for authentication. I had the same pain points when I converted my scripts.
The official way of approaching this via scripting is as follows -
$profile = Login-AzureRmAccount
Save-AzureRMProfile -Profile $profile -path $path
You can then use Select-AzureRmSubscription to none-interactively load those saved profiles.
Although ultimately I didn't go this route, I decided to add another layer of security and use a machine based certificate to encrypt / decrypt credentials to pass to Login-AzureRmAccount This way I could manage multiple sets of accounts and never have to be concerned about those tokens being exposed on vulnerable machines.

Get current user's credentials object in Powershell without prompting

I have a Powershell script that is going to be run through an automation tool against multiple servers.
It works fine on Windows machines, as the remote calls use the tool's service account without any need for prompting or exposing any credentials in code.
This script also runs against Linux machines via SSH using the SharpSSH package. SharpSSH does not automatically use the Powershell user's credentials but requires either a username and password, an RSA key file, or a PSCredential object.
I can't prompt for credentials using Get-Credential, because it's being run through the automation tool. I don't want to expose the username and password in code or have an RSA key sitting out there. I would like to construct a PSCredential object from the current Powershell user (the service account).
Trying [System.Net.CredentialCache]::DefaultNetworkCredentials shows a blank, and [System.Security.Principal.WindowsIdentity]::GetCurrent() doesn't provide the object or information I need.
Does anyone have a method for creating a PSCredential object from the current user? Or maybe a completely different alternative for this problem?
Many thanks!
The Windows API will not expose the information you need, which is why Powershell can't get to them. Its an intentional feature of the security subsystem. The only way for this to work is for the Linux machines to trust the calling machine, such as joining them to an Active Directory (or any kerberos setup really).
Aside from that, you'd need to store and pass this information somehow.
You could store the RSA key in the user's keystore and extract it at runtime (using the .NET Crypto/Keystore libs), so you aren't storing the key around with the code. That way the key itself would be protected by the OS and available only when the calling user was authenticated. You'd have one more thing to install, but may be the only way to achieve what you are aiming for.
"Trying [System.Net.CredentialCache]::DefaultNetworkCredentials shows a blank, and [System.Security.Principal.WindowsIdentity]::GetCurrent() doesn't provide the object or information I need."
You already have your answer. I use this to pass the currently logged in user's credentials along in several scripts:
$Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$Username = $Credentials.UserName
$Password = $Credentials.Password
If you try to dump them to any kind of readable output, those values are empty when you dump them (for obvious security reasons), however they do work where you need a PSCredential object.
How about encrypting the password using the service account's encryption key?
A quick example:
Run PowerShell as the service account, run the following and save the output to a text file (or embed it in the scheduled task call):
$String = '<PASSWORD>'
ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $String -AsPlainText -Force)
Use the following in your scheduled task in order to decrypt and utilize the password:
$EncryptedString = '<ENCRYPTED PASSWORD FROM ABOVE>'
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString -String $EncryptedString)))
That should do the trick. You cannot reuse the encrypted password on a different computer, though, or if you for whatever reason destroy you local key store :)
Since you can get the password in plaintext from a credential object, I doubt you can get this without prompting.