Public/Configure-VaultServerForLDAPAuth.ps1
<#
.SYNOPSIS This function uses the HashiCorp Vault Server's REST API to configure the Vault Server for LDAP Authrntication. .DESCRIPTION See .SYNOPSIS .NOTES .PARAMETER VaultServerNetworkLocation This parameter is MANDATORY. This parameter takes a string that represents the network location (IP Address or DNS-Resolvable) of the Vault Server. .PARAMETER VaultServerPort This parameter is MANDATORY. This parameter takes an integer that represents a Port Number (8200, etc). The Vault Server typically uses port 8200. .PARAMETER EncrytNetworkTraffic This parameter is OPTIONAL, but is set by default to be $True. This parameter is a switch. If used, the Vault Server will be configured to encrypt network traffic via TLS. IMPORTANT NOTE: NEVER set this parameter to $False unless you are simply testing the Vault Server in Development Mode. In production, you MUST encrypt network traffic to/from the Vault Server, and therefore, this parameter must be $True. .PARAMETER VaultAuthToken This parameter is MANDATORY. This parameter takes a string that represents a Vault Authentiction token with permission to configure teh Vault Server for LDAP Authentication. .PARAMETER VaultLogFileName This parameter is OPTIONAL, but is set to 'vault_audit.log' by default. This parameter takes a string that represents the name of the log file on the Vault Server that logs all activity (i.e. Vault Operator Command Line as well as REST API calls). .PARAMETER VaultLogEndPointName This parameter is OPTIONAL, but is set to 'default-audit'. This parameter takes a string that represents the name of the Vault Server REST API Endpoint used to enable and configure the Vault Server activity log. For context, this value is used with a REST API URL similar to: "$VaultServerBaseUri/sys/audit/$VaultLogEndPointName" .PARAMETER PerformOptionalSteps This parameter is OPTIONAL, but highly recommended. This parameter is a switch. If used, the following additional configuration operations will be performed on the Vault Server: - A backup root token with username 'backupadmin' will be created. - A 'custom-root' policy will be created and applied to the "VaultAdmins" Group (which must already exist in LDAP). This policy effectively grants all users in the "VaultAdmins" Group root access to the Vault Server. - A 'vaultusers' policy will be created and applied to the "VaultUsers" Group (which must already exist in LDAP). Users in the "VaultUsers" Group will have all permissions except 'delete' and 'sudo'. .PARAMETER LDAPServerHostNameOrIP This parameter is MANDATORY. This parameter takes a string that represents either the IP Address or DNS-Resolvable name of the LDAP Server. In a Windows environment, this would be a Domain Controller. .PARAMETER LDAPServicePort This parameter is MANDATORY. This parameter takes an integer with possible values: 389, 636, 3268, or 3269. Depending on how you have LDAP configured, use the appropriate port number. If you are not sure, use the TestLDAP function to determine which ports are in use. .PARAMETER UseOpenSSL This parameter is OPTIONAL. This parameter is a switch. If you would like to use openssl to determine your LDAP Server's Certificate even .Net classes are sufficient in determining this information. .PARAMETER BindUserDN This parameter is MANDATORY. This parameter takes a string that represents an LDAP Path to a User Account Object - somthing like: cn=vault,ou=OrgUsers,dc=zero,dc=lab This User Account will be used by the Vault Server to search the LDAP database and confirm credentials for the user trying to login to the Vault Server against the LDAP database. This LDAP account should be dedicated for use by the Vault Server and should not have any other purpose. .PARAMETER LDAPBindCredentials This parameter is MANDATORY. This parameter takes a PSCredential. Th e UserName should corredpound to the UserName provided to the -BindUserDN parameter, but should be in format <DomainPrefix>\<UserName>. So, to be consistent with the example provided in the -BindUserDN comment-based-help, you could create the value for -LDAPBindCredentials via: $Creds = [pscredential]::new("zero\vault",$(Read-Host "Please Enter the Password for 'zero\vault'" -AsSecureString)) .PARAMETER LDAPUserOUDN This parameter is MANDATORY. This parameter takes a string tht represents an LDAP Path to an Organizational Unit (OU) that Vault will search in order to find User Accounts. To stay consistent with the example provided in the comment-based-help for the -BindUserDN parameter, this would be: ou=OrgUsers,dc=zero,dc=lab .PARAMETER LDAPGroupOUDN This parameter is MANDATORY. This parameter takes a string that represents an LDAP Path to the Organizational Unit (OU) that contains the Security Groups "VaultAdmins" and "VaultUsers". This could be something like: ou=Groups,dc=zero,dc=lab .PARAMETER LDAPVaultUsersSecurityGroupDN This parameter is OPTIONAL, however, it becomes MANDATORY when the -PerformOptionalSteps parameter is used. This parameter takes a string that represents the LDAP Path to the "VaultUsers" Security Group. To be consistent with the example provided in teh comment-based-help for the -LDAPGroupOUDN parameter, this should be something like: cn=VaultUsers,ou=Groups,dc=zero,dc=lab IMPORTANT NOTE: The Common Name (CN) for this LDAP Path MUST be 'VaultUsers' .PARAMETER LDAPVaultAdminsSecurityGroupDN This parameter is OPTIONAL, however, it becomes MANDATORY when the -PerformOptionalSteps parameter is used. This parameter takes a string that represents the LDAP Path to the "VaultAdmins" Security Group. To be consistent with the example provided in teh comment-based-help for the -LDAPGroupOUDN parameter, this should be something like: cn=VaultAdmins,ou=Groups,dc=zero,dc=lab IMPORTANT NOTE: The Common Name (CN) for this LDAP Path MUST be 'VaultAdmins' .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> $ConfigureVaultLDAPSplatParams = @{ VaultServerNetworkLocation = "vaultserver.zero.lab" VaultServerPort = 8200 VaultAuthToken = $VaultAuthToken LDAPServerHostNameOrIP = "ZeroDC01.zero.lab" LDAPServicePort = 636 LDAPBindCredentials = $LDAPBindCredentials BindUserDN = "cn=vault,ou=OrgUsers,dc=zero,dc=lab" LDAPUserOUDN = "ou=OrgUsers,dc=zero,dc=lab" LDAPGroupOUDN = "ou=Groups,dc=zero,dc=lab" PerformOptionalSteps = $True LDAPVaultUsersSecurityGroupDN = "cn=VaultUsers,ou=Groups,dc=zero,dc=lab" LDAPVaultAdminsSecurityGroupDN = "cn=VaultAdmins,ou=Groups,dc=zero,dc=lab" } PS C:\Users\zeroadmin> $ConfigureVaultLDAPResult = Configure-VaultServerForLDAPAuth @ConfigureVaultLDAPSplatParams #> function Configure-VaultServerForLDAPAuth { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [string]$VaultServerNetworkLocation, # Should be an IP Address of DNS-Resolvable HostName/FQDN [Parameter(Mandatory=$True)] [int]$VaultServerPort, # Typically 8200 [Parameter(Mandatory=$False)] [switch]$EncryptNetworkTraffic = $True, # Impacts using http/https, Vault Config, Generating TLS Certificates [Parameter(Mandatory=$True)] [string]$VaultAuthToken, # Get this via manual step preceeding this function using Vault CmdLine - 'vault operator init' [Parameter(Mandatory=$False)] [string]$VaultLogFileName = "vault_audit.log", [Parameter(Mandatory=$False)] [string]$VaultLogEndPointName = "default-audit", # Creates backup root token with username 'backupadmin', # Creates 'custom-root' policy applied to "VaultAdmins" group (all permissions) # Creates 'vaultusers' policy applied to "VaultUsers" group (all permissions except 'delete' and 'sudo') [Parameter(Mandatory=$False)] [switch]$PerformOptionalSteps, [Parameter(Mandatory=$True)] [string]$LDAPServerHostNameOrIP, [Parameter(Mandatory=$True)] [ValidateSet(389,636,3268,3269)] [int]$LDAPServicePort, [Parameter(Mandatory=$False)] [switch]$UseOpenSSL, [Parameter(Mandatory=$True)] [string]$BindUserDN, # Should be a path to a User Account LDAP object, like cn=vault,ou=OrgUsers,dc=zero,dc=lab # Should be a non-privileged LDAP/AD account whose sole purpose is allowing Vault to read the LDAP Database [Parameter(Mandatory=$True)] [pscredential]$LDAPBindCredentials, [Parameter(Mandatory=$True)] [string]$LDAPUserOUDN, # Something like ou=OrgUsers,dc=zero,dc=lab [Parameter(Mandatory=$True)] [string]$LDAPGroupOUDN, # Something like ou=Groups,dc=zero,dc=lab [Parameter(Mandatory=$False)] [ValidatePattern("^cn=VaultUsers")] [string]$LDAPVaultUsersSecurityGroupDN, # Something like cn=VaultUsers,ou=Groups,dc=zero,dc=lab [Parameter(Mandatory=$False)] [ValidatePattern("^cn=VaultAdmins")] [string]$LDAPVaultAdminsSecurityGroupDN # Something like cn=VaultAdmins,ou=Groups,dc=zero,dc=lab ) #region >> Prep if ($PSVersionTable.Platform -eq "Unix" -or $PSVersionTable.OS -match "Darwin" -and $env:SudoPwdPrompt) { if (GetElevation) { Write-Error "You should not be running the $($MyInvocation.MyCommand.Name) function as root! Halting!" $global:FunctionResult = "1" return } RemoveMySudoPwd NewCronToAddSudoPwd $env:SudoPwdPrompt = $False } if (!$PSVersionTable.Platform -or $PSVersionTable.Platform -eq "Win32NT") { [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" if (!$(GetElevation)) { Write-Error "The $($MyInvocation.MyCommand.Name) function must be run from an elevated PowerShell session! Halting!" $global:FunctionResult = "1" return } } # Create $Ouput Hashtable so we can add to it as we go and return whatever was done in case of error $Output = [ordered]@{} if ($EncryptNetworkTraffic) { $VaultServerBaseUri = "https://$VaultServerNetworkLocation" + ":$VaultServerPort/v1" } else { $VaultServerBaseUri = "http://$VaultServerNetworkLocation" + ":$VaultServerPort/v1" } if ($PerformOptionalSteps) { if (!$LDAPVaultUsersSecurityGroupDN -or !$LDAPVaultAdminsSecurityGroupDN) { Write-Error "When using the -PerformOptionalSteps switch, you must also supply values for -LDAPVaultUsersSecurityGroupDN and -LDAPVaultAdminsSecurityGroupDN! Halting!" $global:FunctionResult = "1" return } } # Make sure we can reach the LDAP Server try { $LDAPServerNetworkInfo = ResolveHost -HostNameOrIP $LDAPServerHostNameOrIP if (!$LDAPServerNetworkInfo) {throw "Unable to resolve $LDAPServerHostNameOrIP! Halting!"} } catch { Write-Error $_ $global:FunctionResult = "1" return } if (!$PSVersionTable.Platform -or $PSVersionTable.Platform -eq "Win32NT") { $CurrentlyLoadedAssemblies = [System.AppDomain]::CurrentDomain.GetAssemblies() if (![bool]$($CurrentlyLoadedAssemblies -match "System.DirectoryServices.AccountManagement")) { Add-Type -AssemblyName System.DirectoryServices.AccountManagement } $SimpleDomain = $LDAPServerNetworkInfo.Domain $SimpleDomainWLDAPPort = $SimpleDomain + ":$LDAPServicePort" [System.Collections.ArrayList]$DomainLDAPContainersPrep = @() foreach ($Section in $($SimpleDomain -split "\.")) { $null = $DomainLDAPContainersPrep.Add($Section) } $DomainLDAPContainers = $($DomainLDAPContainersPrep | foreach {"DC=$_"}) -join ", " # Make sure $LDAPBindCredentials work try { $SimpleUserName = $($LDAPBindCredentials.UserName -split "\\")[1] #$PasswordInPlainText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($LDAPBindCredentials.Password)) $BindPassword = $PasswordInPlainText = $LDAPBindCredentials.GetNetworkCredential().Password $PrincipleContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new( [System.DirectoryServices.AccountManagement.ContextType]::Domain, "$SimpleDomainWLDAPPort", "$DomainLDAPContainers", [System.DirectoryServices.AccountManagement.ContextOptions]::SimpleBind, "$($LDAPBindCredentials.UserName)", "$PasswordInPlainText" ) try { $UserPrincipal = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipleContext, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, "$SimpleUserName") $LDAPBindCredentialsAreValid = $True } catch { throw "The credentials provided to the -LDAPBindCredentials parameter are not valid for the domain $SimpleDomain! Halting!" } if ($LDAPBindCredentialsAreValid) { # Determine if the User Account is locked $AccountLocked = $UserPrincipal.IsAccountLockedOut() if ($AccountLocked -eq $True) { throw "The provided UserName $($LDAPBindCredentials.Username) is locked! Please unlock it before additional attempts at getting working credentials!" } } } catch { Write-Error $_ $global:FunctionResult = "1" return } # NOTE: With .Net, LDAP URIs always start with 'LDAP' - never lowercase and never with an 's|S' (i.e. never LDAPS|ldaps), # regardless of port $LDAPUri = "LDAP://$($LDAPServerNetworkInfo.FQDN):$LDAPServicePort" # Make sure $LDAPUserOUDN exists try { $LDAPUserOUDNDirectoryEntry = [System.DirectoryServices.DirectoryEntry]("$LDAPUri/$LDAPUserOUDN") $LDAPUserOUDNDirectoryEntry.Close() } catch { Write-Error "The LDAP Object $LDAPUserOUDN cannot be found! Halting!" $global:FunctionResult = "1" return } # Make sure $LDAPGroupOUDN exists try { $LDAPGroupOUDNDirectoryEntry = [System.DirectoryServices.DirectoryEntry]("$LDAPUri/$LDAPGroupOUDN") $LDAPGroupOUDNDirectoryEntry.Close() } catch { Write-Error "The LDAP Object $LDAPGroupOUDN cannot be found! Halting!" $global:FunctionResult = "1" return } } if ($PSVersionTable.Platform -eq "Unix" -or $PSVersionTable.OS -match "Darwin") { # Determine if we have the required Linux commands [System.Collections.ArrayList]$LinuxCommands = @( "echo" "host" "hostname" "ldapsearch" ) if (!$Domain) { $null = $LinuxCommands.Add("domainname") } [System.Collections.ArrayList]$CommandsNotPresent = @() foreach ($CommandName in $LinuxCommands) { $CommandCheckResult = command -v $CommandName if (!$CommandCheckResult) { $null = $CommandsNotPresent.Add($CommandName) } } if ($CommandsNotPresent.Count -gt 0) { [System.Collections.ArrayList]$FailedInstalls = @() if ($CommandsNotPresent -contains "echo" -or $CommandsNotPresent -contains "whoami") { try { #$null = InstallLinuxPackage -PossiblePackageNames "coreutils" -CommandName "echo" $InstallLinuxPackageAsString = ${Function:InstallLinuxPackage}.Ast.Extent.Text $SBAsString = @( "`$GetLocalGroupAndUsersAsString = @'`n$InstallLinuxPackageAsString`n'@" 'try {' ' Invoke-Expression $InstallLinuxPackageAsString' ' $null = InstallLinuxPackage -PossiblePackageNames "coreutils" -CommandName "echo"' ' Write-Host "`nOutputStartsBelow`n"' ' "Done" | ConvertTo-Json -Depth 3' '}' 'catch {' ' @("ErrorMsg",$_.Exception.Message) | ConvertTo-Json -Depth 3' '}' ) $SBAsString = $SBAsString -join "`n" $InstallPackageResultPrep = SudoPwsh -CmdString $SBAsString if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } if ($InstallPackageResultPrep.OutputType -eq "Error") { if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } else { throw $InstallPackageResultPrep.Output } } $InstallPackageResult = $InstallPackageResultPrep.Output } catch { $null = $FailedInstalls.Add("coreutils") } } if ($CommandsNotPresent -contains "host" -or $CommandsNotPresent -contains "hostname" -or $CommandsNotPresent -contains "domainname") { try { #$null = InstallLinuxPackage -PossiblePackageNames @("dnsutils","bindutils","bind-utils","bind-tools") -CommandName "nslookup" $InstallLinuxPackageAsString = ${Function:InstallLinuxPackage}.Ast.Extent.Text $SBAsString = @( "`$GetLocalGroupAndUsersAsString = @'`n$InstallLinuxPackageAsString`n'@" 'try {' ' Invoke-Expression $InstallLinuxPackageAsString' ' $null = InstallLinuxPackage -PossiblePackageNames @("dnsutils","bindutils","bind-utils","bind-tools") -CommandName "nslookup"' ' Write-Host "`nOutputStartsBelow`n"' ' "Done" | ConvertTo-Json -Depth 3' '}' 'catch {' ' @("ErrorMsg",$_.Exception.Message) | ConvertTo-Json -Depth 3' '}' ) $SBAsString = $SBAsString -join "`n" $InstallPackageResultPrep = SudoPwsh -CmdString $SBAsString if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } if ($InstallPackageResultPrep.OutputType -eq "Error") { if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } else { throw $InstallPackageResultPrep.Output } } $InstallPackageResult = $InstallPackageResultPrep.Output } catch { $null = $FailedInstalls.Add("dnsutils_bindutils_bind-utils_bind-tools") } } if ($CommandsNotPresent -contains "ldapsearch") { try { #$null = InstallLinuxPackage -PossiblePackageNames "openldap-clients" -CommandName "ldapsearch" $InstallLinuxPackageAsString = ${Function:InstallLinuxPackage}.Ast.Extent.Text $SBAsString = @( "`$GetLocalGroupAndUsersAsString = @'`n$InstallLinuxPackageAsString`n'@" 'try {' ' Invoke-Expression $InstallLinuxPackageAsString' ' $null = InstallLinuxPackage -PossiblePackageNames "openldap-clients" -CommandName "ldapsearch"' ' Write-Host "`nOutputStartsBelow`n"' ' "Done" | ConvertTo-Json -Depth 3' '}' 'catch {' ' @("ErrorMsg",$_.Exception.Message) | ConvertTo-Json -Depth 3' '}' ) $SBAsString = $SBAsString -join "`n" $InstallPackageResultPrep = SudoPwsh -CmdString $SBAsString if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } if ($InstallPackageResultPrep.OutputType -eq "Error") { if ($InstallPackageResultPrep.Output -match "ErrorMsg") { throw $InstallPackageResultPrep.Output[-1] } else { throw $InstallPackageResultPrep.Output } } $InstallPackageResult = $InstallPackageResultPrep.Output } catch { $null = $FailedInstalls.Add("openldap-clients") } } if ($FailedInstalls.Count -gt 0) { Write-Error "The following Linux packages are required, but were not able to be installed:`n$($FailedInstalls -join "`n")`nHalting!" $global:FunctionResult = "1" return } } [System.Collections.ArrayList]$CommandsNotPresent = @() foreach ($CommandName in $LinuxCommands) { $CommandCheckResult = command -v $CommandName if (!$CommandCheckResult) { $null = $CommandsNotPresent.Add($CommandName) } } if ($CommandsNotPresent.Count -gt 0) { Write-Error "The following Linux commands are required, but not present on $env:ComputerName:`n$($CommandsNotPresent -join "`n")`nHalting!" $global:FunctionResult = "1" return } try { if ($Domain) { $DomainControllerInfo = GetDomainController -Domain $Domain -ErrorAction Stop } else { $DomainControllerInfo = GetDomainController -ErrorAction Stop } if ($DomainControllerInfo.PrimaryDomainController -eq "unknown") { $PDC = $DomainControllerInfo.FoundDomainControllers[0] } else { $PDC = $DomainControllerInfo.PrimaryDomainController } $LDAPInfo = TestLDAP -ADServerHostNameOrIP $PDC -ErrorAction Stop if (!$DomainControllerInfo) {throw "Problem with GetDomainController function! Halting!"} if (!$LDAPInfo) {throw "Problem with TestLDAP function! Halting!"} } catch { Write-Error $_ $global:FunctionResult = "1" return } $SimpleDomainPrep = $PDC -split "\." $SimpleDomain = $SimpleDomainPrep[1..$($SimpleDomainPrep.Count-1)] -join "." [System.Collections.ArrayList]$DomainLDAPContainersPrep = @() foreach ($Section in $($SimpleDomain -split "\.")) { $null = $DomainLDAPContainersPrep.Add($Section) } $DomainLDAPContainers = $($DomainLDAPContainersPrep | foreach {"DC=$_"}) -join "," $BindUserName = $LDAPBindCredentials.UserName $BindUserNameForExpect = $BindUserName -replace [regex]::Escape('\'),'\\\' $BindPassword = $PasswordInPlainText = $LDAPBindCredentials.GetNetworkCredential().Password # Make sure $LDAPBindCredentials work $ldapSearchOutput = ldapsearch -x -h $PDC -D $BindUserName -w $BindPassword -b "$DomainLDAPContainers" -s sub "(objectClass=group)" cn if ($LASTEXITCODE -ne 0) { Write-Error "The credentials provided to the -LDAPBindCredentials parameter are not valid for the domain $SimpleDomain! Halting!" $global:FunctionResult = "1" return } # Make sure $LDAPUserOUDN exists $ldapSearchOutput = ldapsearch -x -h $PDC -D $BindUserName -w $BindPassword -b "$LDAPUserOUDN" -s sub "(objectClass=user)" cn if ($ldapSearchOutput -match "No such object") { Write-Error "The LDAP Object $LDAPUserOUDN cannot be found! Halting!" $global:FunctionResult = "1" return } # Make sure $LDAPGroupOUDN exists $ldapSearchOutput = ldapsearch -x -h $PDC -D $BindUserName -w $BindPassword -b "$LDAPGroupOUDN" -s sub "(objectClass=user)" cn if ($ldapSearchOutput -match "No such object") { Write-Error "The LDAP Object $LDAPUserOUDN cannot be found! Halting!" $global:FunctionResult = "1" return } } $HeadersParameters = @{ "X-Vault-Token" = $VaultAuthToken } #endregion >> Prep #region >> Main Body # Turn on Vault Audit Log # Vault CmdLine Equivalent: # vault audit enable file file_path=/vault/logs/vault_audit.log $jsonRequest = @" { "type": "file", "options": { "path": "/vault/logs/$VaultLogFileName" } } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for Turning on the Audit Log! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/sys/audit/$VaultLogEndPointName" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Put" } $TurnOnAuditLog = Invoke-RestMethod @IWRSplatParams $ConfirmAuditLogIsOn = $(Invoke-RestMethod -Uri "$VaultServerBaseUri/sys/audit" -Headers $HeadersParameters -Method Get).data if (!$ConfirmAuditLogIsOn) { Write-Error "Cannot confirm that the Vault Audit Log is turned on! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("EnableAuditLog",$ConfirmAuditLogIsOn) # Create a new policy that effectively has root access to Vault, and call it 'custom-root'. This policy will be applied # to Vault Administrators later on $jsonRequest = @" { "policy": "path \"*\" {\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n}" } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for creating the 'custom-root' policy! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/sys/policy/custom-root" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Put" } $RootPolicyResponse = Invoke-RestMethod @IWRSplatParams $ConfirmRootPolicy = Invoke-RestMethod -Uri "$VaultServerBaseUri/sys/policy/custom-root" -Headers $HeadersParameters -Method Get if (!$ConfirmRootPolicy) { Write-Error "Cannot confirm that the Vault policy 'custom-root' has been enabled! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("CreateCustomRootPolicy",$ConfirmRootPolicy) # Create a policy that is for typical Vault Users (i.e. not Vault Admins), that allows for everything except # delete and sudo. Change according to your preferences. $jsonRequest = @" { "policy": "path \"*\" {\n capabilities = [\"create\", \"read\", \"update\", \"list\"]\n}" } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for creating the 'vaultusers' policy! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/sys/policy/vaultusers" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Put" } $VaultUsersPolicyResponse = Invoke-RestMethod @IWRSplatParams $ConfirmVaultUsersPolicy = Invoke-RestMethod -Uri "$VaultServerBaseUri/sys/policy/vaultusers" -Headers $HeadersParameters -Method Get if (!$ConfirmVaultUsersPolicy) { Write-Error "Cannot confirm that the Vault policy 'vaultusers' has been enabled! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("CreateVaultUsersPolicy",$ConfirmVaultUsersPolicy) if ($PerformOptionalSteps) { # Create a user other than the initial root (i.e. the token $VaultAuthToken that we've been using thus far) that has root privileges # via the 'custom-root' policy. This is just for a backup root account for emergencies # Vault CmdLine Equivalent: # vault token create -policy=custom-root -display-name="backupadmin" -ttl="8760h" -renewable=true -metadata=user=backupadmin $jsonRequest = @" { "policies": [ "custom-root" ], "meta": { "user": "backupadmin" }, "ttl": "8760h", "renewable": true } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for creating the 'backupadmin' Vault Token! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/auth/token/create" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Post" } $NewUserTokenResponse = Invoke-RestMethod @IWRSplatParams if (!$NewUserTokenResponse) { Write-Error "There was a problem creating the 'backupadmin' Vault Token! Halting!" $global:FunctionResult = "1" return } $Output.Add("BackupRootToken",$NewUserTokenResponse) } # Enable LDAP Authentication # vault auth enable ldap -description="Login with LDAP" $jsonRequest = @" { "type": "ldap", "description": "Login with LDAP" } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for enabling the Vault LDAP Authentication Method! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/sys/auth/ldap" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Post" } $EnableLDAPResponse = Invoke-RestMethod @IWRSplatParams $ConfirmLDAPEnabled = Invoke-RestMethod -Uri "$VaultServerBaseUri/sys/auth" -Headers $HeadersParameters -Method Get if (!$ConfirmLDAPEnabled) { Write-Error "There was a problem enabling the LDAP Authentication Method for the Vault Server! Halting!" } $Output.Add("LDAPAuthEngineEnabled",$ConfirmLDAPEnabled) # Next, we need the LDAP Server's Root CA Public Certificate try { $GetLDAPCertSplatParams = @{ LDAPServerHostNameOrIP = $LDAPServerNetworkInfo.FQDN Port = $LDAPServicePort ErrorAction = "Stop" } if ($LDAPServicePort -eq 389 -or $LDAPServicePort -eq 3268) { $GetLDAPCertSplatParams.Add("AllowOpenSSLInstall",$True) } if ($UseOpenSSL) { $GetLDAPCertSplatParams.Add("UseOpenSSL",$True) if ($GetLDAPCertSplatParams.Keys -notcontains "AllowOpenSSLInstall") { $GetLDAPCertSplatParams.Add("AllowOpenSSLInstall",$True) } } $GetLDAPCertResult = Get-LDAPCert @GetLDAPCertSplatParams if (!$GetLDAPCertResult) {throw "The Get-LDAPCert function failed! Is your LDAP implementation using TLS? Halting!"} $RootCertificateInPemFormat = $GetLDAPCertResult.RootCACertificateInfo.PemFormat -join "`n" if (!$RootCertificateInPemFormat) {throw "The Get-LDAPCert function failed to get the Root CA Certificate in the LDAP Endpoint's Certificate Chain! Halting!"} } catch { Write-Error $_ if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } # The Vault Server handles LDAP Uris as expected (as opposed to .Net counterpart in above # 'Variable/Parameter Transforms and PreRun Prep' region) if ($LDAPServicePort -eq 389 -or $LDAPServicePort -eq 3268) { $LDAPUriForVault = "ldap://$($LDAPServerNetworkInfo.FQDN):$LDAPServicePort" } if ($LDAPServicePort -eq 636 -or $LDAPServicePort -eq 3269) { $LDAPUriForVault = "ldaps://$($LDAPServerNetworkInfo.FQDN):$LDAPServicePort" } $jsonRequest = @" { "url": "$LDAPUriForVault", "userattr": "samaccountname", "userdn": "$LDAPUserOUDN", "discoverdn": "true", "groupdn": "$LDAPGroupOUDN", "groupfilter": "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))", "groupattr": "cn", "certificate": "$RootCertificateInPemFormat", "insecure_tls": "false", "starttls": "true", "binddn": "$BindUserDN", "bindpass": "$PasswordInPlainText", "deny_null_bind": "true", "tls_max_version": "tls12", "tls_min_version": "tls12" } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for establishing Vault's LDAP configuration! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/auth/ldap/config" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Post" } $LDAPAuthConfigResponse = Invoke-RestMethod @IWRSplatParams $ConfirmLDAPAuthConfig = Invoke-RestMethod -Uri "$VaultServerBaseUri/auth/ldap/config" -Headers $HeadersParameters -Method Get if (!$ConfirmLDAPAuthConfig) { Write-Error "There was a problem setting the Vault LDAP Authentication configuration! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("LDAPAuthConfiguration",$ConfirmLDAPAuthConfig) # Remove $PasswordInPlainText from Memory as best we can $PasswordInPlainText = $null $PrincipleContext = $null $jsonRequest = $null $JsonRequestAsSingleLineString = $null if ($PerformOptionalSteps) { # Apply the 'custom-root' policy to the AD User Group 'VaultAdmins' # Vault Cmdline equivalent is: # vault write auth/ldap/groups/VaultAdmins policies=custom-root # Make sure $LDAPVaultAdminsSecurityGroupDN exists if (!$PSVersionTable.Platform -or $PSVersionTable.Platform -eq "Win32NT") { try { $LDAPVaultAdminsSecurityGroupDNDirectoryEntry = [System.DirectoryServices.DirectoryEntry]("$LDAPUri/$LDAPVaultAdminsSecurityGroupDN") $LDAPVaultAdminsSecurityGroupDNDirectoryEntry.Close() } catch { Write-Error "The LDAP Object $LDAPVaultAdminsSecurityGroupDN cannot be found! Halting!" $global:FunctionResult = "1" if ($Output.Count -gt 0) {[pscustomobject]$Output} return } } if ($PSVersionTable.Platform -eq "Unix" -or $PSVersionTable.OS -match "Darwin") { $ldapSearchOutput = ldapsearch -x -h $PDC -D $BindUserName -w $BindPassword -b "$LDAPVaultAdminsSecurityGroupDN" -s sub "(objectClass=user)" cn if ($ldapSearchOutput -match "No such object") { Write-Error "The LDAP Object $LDAPVaultAdminsSecurityGroupDN cannot be found! Halting!" $global:FunctionResult = "1" return } } $jsonRequest = @" { "policies": "custom-root" } "@ try { # Validate JSON $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for applying the 'custom-root' policy to the VaultAdmins Security Group! Halting!" $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/auth/ldap/groups/VaultAdmins" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Post" } $ApplyPolicyToVaultAdminsGroup = Invoke-WebRequest @IWRSplatParams $ConfirmPolicyOnVaultAdmins = Invoke-RestMethod -Uri "$VaultServerBaseUri/auth/ldap/groups/VaultAdmins" -Headers $HeadersParameters -Method Get if (!$ConfirmPolicyOnVaultAdmins) { Write-Error "Unable to confirm that the 'custom-root' Vault Policy was applied to the LDAP Security Group 'VaultAdmins'! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("AppliedVaultAdminsPolicy",$ConfirmPolicyOnVaultAdmins) # Apply the 'vaultusers' policy to the AD User Group 'VaultUsers' # Vault Cmdline equivalent is: # vault write auth/ldap/groups/VaultUsers policies=vaultusers # Make sure $LDAPVaultUsersSecurityGroupDN exists if (!$PSVersionTable.Platform -or $PSVersionTable.Platform -eq "Win32NT") { try { $LDAPVaultUsersSecurityGroupDNDirectoryEntry = [System.DirectoryServices.DirectoryEntry]("$LDAPUri/$LDAPVaultUsersSecurityGroupDN") $LDAPVaultUsersSecurityGroupDNDirectoryEntry.Close() } catch { Write-Error "The LDAP Object $LDAPVaultUsersSecurityGroupDN cannot be found! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } } if ($PSVersionTable.Platform -eq "Unix" -or $PSVersionTable.OS -match "Darwin") { $ldapSearchOutput = ldapsearch -x -h $PDC -D $BindUserName -w $BindPassword -b "$LDAPVaultUsersSecurityGroupDN" -s sub "(objectClass=user)" cn if ($ldapSearchOutput -match "No such object") { Write-Error "The LDAP Object $LDAPVaultUsersSecurityGroupDN cannot be found! Halting!" $global:FunctionResult = "1" return } } $jsonRequest = @" { "policies": "vaultusers" } "@ try { $JsonRequestAsSingleLineString = $jsonRequest | ConvertFrom-Json -EA Stop | ConvertTo-Json -Compress -EA Stop } catch { Write-Error "There was a problem with the JSON for applying the 'vaultusers' policy to the VaulUsers Security Group! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $IWRSplatParams = @{ Uri = "$VaultServerBaseUri/auth/ldap/groups/VaultUsers" Headers = $HeadersParameters Body = $JsonRequestAsSingleLineString Method = "Post" } $ApplyPolicyToVaultUsersGroup = Invoke-WebRequest @IWRSplatParams $ConfirmPolicyOnVaultUsers = Invoke-RestMethod -Uri "$VaultServerBaseUri/auth/ldap/groups/VaultUsers" -Headers $HeadersParameters -Method Get if (!$ConfirmPolicyOnVaultUsers) { Write-Error "Unable to confirm that the 'vaultusers' Vault Policy was applied to the LDAP Security Group 'VaultUsers'! Halting!" if ($Output.Count -gt 0) {[pscustomobject]$Output} $global:FunctionResult = "1" return } $Output.Add("AppliedVaultUsersPolicy",$ConfirmPolicyOnVaultUsers) } [pscustomobject]$Output #endregion >> Main Body } |