Follow me on Twitter @AntonioMaio2

Sunday, October 2, 2016

Office 365 Nightly PowerShell Scripts: Encrypting Admin Credentials

When using remote PowerShell to perform tasks in Office 365 we typically need to provide our administrator credentials to create the initial connection.  These are typically a Global Administrator's username & password, or at least an Exchange or SharePoint administrator's username & password.  These are highly privileged accounts and we need to ensure that the username and password associated with these accounts do not get compromised or stolen.  So, when we need to run remote PowerShell scripts on a nightly automated basis, without administrator intervention, how do we secure those highly privileged credentials?  


Running such a script, automatically each night, may be part of some automatic synchronization process.  Or, it may be part of some process that automatically retrieves data from our Office 365 tenant each night.

Fortunately, we do have a number of PowerShell commands available which can encrypt our password, and we can be assured that it is secured when stored on the system that is hosting our nightly PowerShell script.


Protect the System Hosting the Nightly PowerShell Script

First of all, its important to note that the machine hosting your nightly script needs to be protected according to your corporate policies.  This protection needs to be applied as you would for any other critical piece of infrastructure - think on premise Exchange server, SharePoint farm server, etc.  If you're going to build several scripts to run nightly, I typically recommend setting up a dedicated Windows Server to host and run all automated scripts.  That way you only have 1 system to manage for hosting and running nightly scripts.  So, when a script fails for whatever reason, its easier to find the system on which to investigate the issue.  In addition, if you have multiple scripts running nightly and you want to ensure that they do not conflict with each other, its easier to configure the Windows task scheduler for all scripts on 1 machine.  Finally, if you are running a number of on premise Microsoft server applications and you're relying on Microsoft support (premiere or otherwise), if your script is hosted on some other application's server and the server runs into an issue, Microsoft may ask you to remove that script prior to investigating the issue to rule out the script being the cause.

In addition, we  would also recommend creating a dedicated service account (really just a user account) in Office 365 that will only be used by the script to connect to the Office 365 service it is designed to work with.  In the spirit of "least privilege", the account should only have the permissions required to connect to the necessary service - if the script only works with Exchange Online, then it should only have Exchange Online administrator permissions and should not have the Global Administrator role.


PowerShell CmdLets Required

Next, we need to understand a few PowerShell commands that we'll use to encrypt our administrator account password:

  • Read-Host (with the parameter -AsSecureString)

This cmdlet will request input from the user at the command line.  The -AsSecureString parameter will mask the input provided by the user so that it is not readable.  

  • ConvertFrom-SecureString
This cmdlet converts a secure string (of type System.Security.SecureStringto an encrypted standard string (of type System.String). Unlike a secure string, an encrypted standard string can be saved in a file for use later.  ConvertFrom-SecureString doesn't accept empty an SecureString so you may wish to add a check for this in your PowerShell... but of course you would never have an empty string as an administrator's password.  You can find more information here: ConvertFrom-SecureString.


  • ConvertTo-SecureString
This cmdlet will convert plain text or a standard encrypted string to a secure string.  This is useful when you wish to pass an encrypted password that was stored to a cmdlet that requires a SecureString when authenticating. The SecureString object can be used with cmdlets that support the SecureString or PSCredential parameters, such as those necessary to create a remote PowerShell connection to Exchange Online (which we'll see later).  You can find more information here: ConvertTo-SecureString.


Example

In the following simple example, we use these cmdlets to check if we already have a password stored, then if not we request it from the user, and we 

#get the path the script is currently running under
$ScriptRoot = ""
Try
{
    $ScriptRoot = Get-Variable -Name PSScriptRoot -ValueOnly -ErrorAction Stop
}
Catch
{
    $ScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path
}

#Note there are some examples where this process does not return a proper path,
#like if you are running under the temp folder - in that case, I suggest putting 
#a test for an empty path and setting it to some default path

#create the necessary paths where username and password may have been stored
$sUserName = ""
$sPassword = ""
$usernameTokenPath = $($($ScriptRoot) + "usernameToken.txt")
$passwordTokenPath = $($($ScriptRoot) + "passwordToken.txt")

#Check if username & password files already exist 
#If they do not, then request them from an administrator and securely save them
if((Test-Path $usernameTokenPath) -and (Test-Path $passwordTokenPath))
{
    $sUserName = Get-Content $usernameTokenPath
    $sPassword = (Get-Content $passwordTokenPath | ConvertTo-SecureString)

    Write-Host "Using saved administrator credentials"
}
else
{
    $sUserName = Read-Host "Enter an administrator username" 
    $sPassword = Read-Host "Enter an administrator password" -AsSecureString

    $storeCreds = Read-Host "Would you like to securely store the administrator credentials for use next time? (y=yes)"

    if(($storeCreds -eq "y") -or ($storeCreds -eq "Y") -or ($storeCreds -eq "yes") -or ($storeCreds -eq "YES"))
    {
        #encrypt the password
        #store the username and encrypted password in separate output files
        #output files will reside in the same path as the script

        #Note: to reset the username and password, simply delete the 2 files
        # usernameToken.txt and passwordToken.txt

        $sUserName | Out-File $usernameTokenPath
        $encryptedPassword = ConvertFrom-SecureString $sPassword | Out-File $passwordTokenPath
    }
    else
    {
        Write-Host "Administrator credentials have not been stored at user's request"
    }
}

#Create the PSCredential object needed for remote PowerShell to Exchange Online 
$credential = New-Object System.Management.Automation.PsCredential ($sUserName,$sPassword)

$exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" –AllowRedirection

Import-PSSession $exchangeSession

Write-Host "Connection to Microsoft Exchange Online Established"

#
#do something in Exchange Online using remote PowerShell module
#

# Remember to release the PowerShell session that's connected to Exchange online

Remove-PSSession $exchangeSession



Run Down

You'll notice that the first time we run the script it checks whether the files containing the the username and password exist.  If they do, then we retrieve that data with the following:

    $sUserName = Get-Content $usernameTokenPath
    $sPassword = (Get-Content $passwordTokenPath | ConvertTo-SecureString)

We use ConvertTo-SecureString to convert the standard encrypted string, that we retrieved from a local file, to a SecureString.  The SecureString now stored in $sPassword can then be used later as the password when connecting to the Office 365 service.  Note: only the password is encrypted in this example, not the username.  

If we do not find files containing the username and password (we're looking for both files to exist) then we request them from the administrator at the command line, along with whether or not we should save them.  This is done with the following:

    $sUserName = Read-Host "Enter an administrator username" 
    $sPassword = Read-Host "Enter an administrator password" -AsSecureString

    $storeCreds = Read-Host "Would you like to securely store the administrator credentials for use next time? (y=yes)"

Notice we request the password as a SecureString object.

Next, if the administrator chose to, we store the inputted username and password in seperate files, in the same folder in which the script is running, with the following:
    $sUserName | Out-File $usernameTokenPath
    $encryptedPassword = ConvertFrom-SecureString $sPassword | Out-File $passwordTokenPath

Notice that we use ConvertFrom-SecureString to convert the SecureString $sPassword to a standard encrypted string which is stored in $encryptedPassword.  If we don't need to store it, then we can simply use the password in SecureString form later when connecting to the Office 365 service.

Security Notes
So, how is this secure?  A malicious user who gets access to the system hosting the PowerShell script could potentially copy the file containing the encrypted password and call similar PowerShell commands to retrieve the administrators password.  

First we take note of the fact that the PowerShell commands ConvertTo-SecureString and ConvertFrom-SecureString do have additional parameters like -Key and -SecureKey:

  • -Key: allows the caller to specify a specific key as a byte array
  • -SecureKey: allows the caller to specify a specific key as a SecureString
If neither of these parameters is specified, as in our example above, then the PowerShell cmdlets will use the Windows Data Protection API (DPAPI) to encrypt and decrypt the password.  The DPAPI will use the hashed password of the user that's logged in along with the machine identifier to encrypt the administrator's password.  This means that the stored encrypted password can only be decypted when the same user is logged in on the same machine.  You can find out more here: Windows Data Protection.  If you do specify -Key or -SecureKey then those strings need to be available on the system hosting he script

As mentioned above, the machine hosting the script (and storing the encrypted password) does need to be protected as you would any other critical infrastructure.

Finally, if more protection is required, then we recommend setting the execution policy for PowerShell on the machine running the script to -AllSigned and digitally signing your script.  This will help to ensure that only scripts digitally signed with your specific code signing certificate can be run on the machine hosting your automated scripts.  More information can be found on the digital signing process at the Scripting Guy Blog.

Enjoy.
   -Antonio

1 comment:

  1. Office 365 plays a most important role in IT department. This post is very descriptive and assists me to learn updated concept in detail.

    ReplyDelete