diff --git a/GMSACredential/GMSACredential.psd1 b/GMSACredential/GMSACredential.psd1 new file mode 100644 index 0000000..57d98de --- /dev/null +++ b/GMSACredential/GMSACredential.psd1 @@ -0,0 +1,123 @@ +# +# Module manifest for module 'GMSACredential' +# +# Generated by: Ryan Ephgrave +# +# Generated on: 2/5/2021 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = '.\GMSACredential.psm1' + +# Version number of this module. +ModuleVersion = '0.5' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '6598f2d7-4aa1-4dbe-a89f-731a6c2c9419' + +# Author of this module +Author = 'Ryan Ephgrave' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) 2021 Ryan Ephgrave. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Provides functions to get a usable PSCredential for a GMSA account' + +# Minimum version of the Windows PowerShell engine required by this module +# PowerShellVersion = '' + +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# CLRVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @('Get-GMSACredential') + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/GMSACredential/GMSACredential.psm1 b/GMSACredential/GMSACredential.psm1 new file mode 100644 index 0000000..9816b83 --- /dev/null +++ b/GMSACredential/GMSACredential.psm1 @@ -0,0 +1,3 @@ +. "$PSScriptRoot\Get-GMSACredential.ps1" + +Export-ModuleMember -Function 'Get-GMSACredential' \ No newline at end of file diff --git a/GMSACredential/Get-GMSACredential.ps1 b/GMSACredential/Get-GMSACredential.ps1 new file mode 100644 index 0000000..9d0c843 --- /dev/null +++ b/GMSACredential/Get-GMSACredential.ps1 @@ -0,0 +1,71 @@ +Function Get-GMSACredential{ + <# + .SYNOPSIS + Given a GMSA account, will return a usable PSCredential object + + .DESCRIPTION + Checks AD for the GMSA account information and returns a usable credential. Must be run with an account that has permissions to the password + + .PARAMETER GMSAName + Identity of the GMSA account + + .PARAMETER Domain + Domain logon name of the account + + .PARAMETER SearchRoot + Root to search for the account (most cases can be omitted) + + .EXAMPLE + Get-GMSACredential -GMSAName 'gmsaUser$' -Domain 'Home.Lab' + + .NOTES + .Author: Ryan Ephgrave + #> + Param( + [Parameter(Mandatory=$true)] + [string]$GMSAName, + [Parameter(Mandatory=$true)] + [string]$Domain, + [Parameter(Mandatory=$false)] + [string]$SearchRoot = $(([adsisearcher]"").Searchroot.path) + ) + + $dEntryRoot = New-Object System.DirectoryServices.DirectoryEntry -ArgumentList $SearchRoot + + $searcher = New-Object System.DirectoryServices.DirectorySearcher -ArgumentList $dEntryRoot + + $searcher.Filter = "(&(name=$($GMSAName.TrimEnd('$')))(ObjectCategory=msDS-GroupManagedServiceAccount))" + [void]$searcher.PropertiesToLoad.Add('Name') + [void]$searcher.PropertiesToLoad.Add('msDS-ManagedPassword') + + $searcher.SearchRoot.AuthenticationType = 'Sealing' + + $Accounts = $searcher.FindAll() + foreach($a in $accounts){ + if($a.Properties.'msds-managedpassword'){ + $pw = $a.Properties.'msds-managedpassword' + [Byte[]]$byteBlob = $pw.Foreach({$PSItem}) + $MemoryStream = New-Object System.IO.MemoryStream -ArgumentList (,$byteBlob) + $Reader = New-Object System.IO.BinaryReader -ArgumentList $MemoryStream + + # have to move the reader to the pw offset + $null = $Reader.ReadInt16() + $null = $Reader.ReadInt16() + $null = $Reader.ReadInt32() + + $PWOffset = $Reader.ReadInt16() + $Length = $byteBlob.Length - $PWOffset + $stringBuilder = New-Object System.Text.StringBuilder -ArgumentList $Length + for($i = $PWOffset; $i -le $byteBlob.Length; $i += [System.Text.UnicodeEncoding]::CharSize){ + $currentChar = [System.BitConverter]::ToChar($byteBlob, $i) + if($currentChar -eq [char]::MinValue) { break; } + [void]$stringBuilder.Append($currentChar) + } + return ( New-Object PSCredential -ArgumentList @( + "$($Domain)\$($GMSAName)", + (ConvertTo-SecureString $stringBuilder.ToString() -AsPlainText -Force) + )) + } + } +} + diff --git a/README.md b/README.md index 5886f8f..5f2764e 100644 --- a/README.md +++ b/README.md @@ -1 +1,59 @@ -# gMSACredentialModule \ No newline at end of file +# gMSA Credential Module + +Gets a usable PSCredential for a gMSA. + +## Special thanks + +This module would not be possible without the helpful open source tools from [DSInternals](https://www.dsinternals.com/en/retrieving-cleartext-gmsa-passwords-from-active-directory/). + +## Install instructions + +``` powershell +Install-Module GMSACredential +``` + +## Using This Module + +gMSA's are accounts whose password is requested and is not known. Active Directory effectively becomes your password manager and you request the password from an account that has permissions to request it. Generally this is done by Windows and you use these accounts as IIS App Pools or to run services, but there's no reason we can't get a PSCredential and use them in PowerShell! + +### The basics + +``` powershell +$Cred = Get-GMSACredential -GMSAName 'gMSATest' -Domain 'Home.Lab' +invoke-command -ComputerName localhost -Credential $cred -ScriptBlock { whoami } +``` + +The above command outputs ```home\gmsatest$``` to prove it is working. gMSA's generally have a $ at the end of the account, but it's not required for this module. + +There's not much to this, the only gotcha is you need to run this as an account with permissions to get the gMSA password + +### Setting up gMSAs in the lab + +If you want to try this out in your lab, it's real easy to get gMSAs up and running! + +First, if you've never set up a KDS root key, you'll want to run this command from a Domain Administrator: + +``` powershell +Add-KDSRootKey -EffectiveImmediately +``` + +Note, effective immediately means it's added immediately but not ready immediately (it always waits for replication). Per the [documentation](https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/create-the-key-distribution-services-kds-root-key) you can run this instead to make it really effective immediately: + +``` powershell +Add-KdsRootKey -EffectiveTime ((get-date).addhours(-10)) +``` + +Generally at work we give an AD group permissions to get our passwords and then we can add or remove accounts. So in the lab, since everything's a Domain Admin, let's use that! + +Create your account and give Domain Admins permissions to get the password (feel free to change this to any AD group you want): + +``` powershell +$ADGroupName = 'Domain Admins' +$GMSAName = 'gMSATest' +$DomainFqdn = 'home.lab' +$ServiceAccount = New-ADServiceAccount -Name 'gMSATest' -DNSHostName "$GMSAName.$($DomainFqdn)" -PrincipalsAllowedToRetrieveManagedPassword $ADGroupName -Enabled $true -PassThru +``` + +Now you'll have the account "gMSATest$" available to use! Give the account permissions to whatever you want to access, then use it in your scripts with ```Get-GMSACredential``` + +Please let me know if you have any issues! \ No newline at end of file