Powershell script unable to decrypt password from Jenkins withCredentials() - powershell

I created a powershell script to run DB2 queries in Jenkins
withCredentials([usernamePassword(credentialsId: 'cred-id', usernameVariable: 'ID', passwordVariable: 'PASSWORD')]) {
$cn = new-object system.data.OleDb.OleDbConnection("Server=Server; Provider=IBMDADB2;DSN=DBName;User Id=$ID;Password=$PASSWORD");
$ds = new-object "System.Data.DataSet" "ds"
$q = "myQuery"
$da = new-object "System.Data.OleDb.OleDbDataAdapter" ($q, $cn)
$da.Fill($ds)
$cn.close()
}
If I run the script and hard code my credentials, it run fine.
With withCredentials(), I am getting the following error: Security processing failed with reason "15" ("PROCESSING FAILURE")
From some research, the error seems to be because DB2 can't handle encrypted data. Is there a way to overcome this error?
EDIT:
I tried to add
$SecurePassword = ConvertTo-SecureString $PASSWORD -AsPlainText -Force
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
at the beginning of my powershell script, but it still throws the same error even though the credential work fine if used in plain text

If I understand the docs for the Credentials Binding Jenkins plugin correctly, the variables designated in the withCredentials() call become environment variables, so as to enable their use across process boundaries.
Note that the values of these environment variables are not encrypted, so no extra (decryption) effort is required on the part of the target process.
Therefore, you need to use $env:[1] instead of just $ to refer to these variables in PowerShell:
$cn = new-object system.data.OleDb.OleDbConnection "Server=Server; Provider=IBMDADB2;DSN=DBName;User Id=$env:ID;Password=$env:PASSWORD"
[1] See the conceptual about_Environment_Variables help topic.

Related

Selenium (Powershell) Run as a different user

I want to start Selenium (Google Chrome - WebDriver) as a different user and I am pretty much lost as I don't know where to add $mycreds. This is quite easy to achieve if I want to start a normal Chrome session, however, I am stuck when it comes to achieving this using Selenium. Any advice ?
$username = "domain\name"
$password = ConvertTo-SecureString "password" -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $password
$Driver = Start-SeChrome
Enter-SeUrl -Url 'https://www.google.com/' -Driver $Driver
If I try to pass it along using -Arguments , it will still use my account to open the browser without returning any error.
$Driver = Start-SeChrome -Arguments $mycreds
The easy way to do this is by starting Powershell as-another-user instead. Make sure you have the module set up for that user as well as any other environment stuff you might need.
The selenium-powershell module itself does not support starting the selenium drivers as a different user from the current one.
You may also want to make sure you're using an updated version of the module as well, since (in pre-release):
Start-SeChrome and other have been removed in favor of Start-SeDriver -Browser Chrome (#100)

Alternative to the PowerShell global scope

I am writing a function in PS that caches a password that a user provides. It does this by encrypting the password and saving it to disk, then saving the encryption password and key as a secure string in a global variable. This way the next time the function is called, it will have access to the key. After x minutes a job deletes the file from disk, and if the session is closed, the encryption key is lost.
having ran the PSScriptAnalyzer through my code it is (rightly) complaining about using a global variable. This is an issue because at some point I could very well want to push it to a repository, so I can't just ignore that error.
I also don't want to save the key to disk, as that wouldn't get cleaned up at the end of the PS session.
Is there a more suitable place I could store this data? (that will get wiped when the session closes)
ETA
I'm looking for an alternative to storing data in the global scope, that won't persist across PowerShell sessions. i.e. when I close the window the data is lost. I already have encrypted data on disk, I want to throw away the encryption key when the session ends.
You might want to keep the credentials inside the variable (perhaps a $script: scope one). I heve build a function for this a time ago.
function Set-Credentials
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true,Position = 0)][ValidateNotNull()][String]$UserName,
[Parameter(Mandatory = $false,ValueFromPipeline = $true,Position = 1)][ValidateNotNull()][String]$UserPassword
)
if (-not$UserPassword)
{
$Password = Read-Host -Prompt "Specify password for $UserName" -AsSecureString
}
else
{
$Password = (ConvertTo-SecureString -String $UserPassword -AsPlainText -Force)
}
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList #($UserName, $Password)
Write-Output -InputObject $Credentials
}

How to run Powershell script on local computer but with credentials of a domain user

I have to implement a solution where I have to deploy a SSIS project (xy.ispac) from one machine to another. So far I've managed to copy-cut-paste the following stuff from all around the internet:
# Variables
$ServerName = "target"
$SSISCatalog = "SSISDB" # sort of constant
$CatalogPwd = "catalog_password"
$ProjectFilePath = "D:\Projects_to_depoly\Project_1.ispac"
$ProjectName = "Project_name"
$FolderName = "Data_collector"
# Load the IntegrationServices Assembly
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.IntegrationServices")
# Store the IntegrationServices Assembly namespace to avoid typing it every time
$ISNamespace = "Microsoft.SqlServer.Management.IntegrationServices"
Write-Host "Connecting to server ..."
# Create a connection to the server
$sqlConnectionString = "Data Source=$ServerName;Initial Catalog=master;Integrated Security=SSPI;"
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString
$integrationServices = New-Object "$ISNamespace.IntegrationServices" $sqlConnection
$catalog = $integrationServices.Catalogs[$SSISCatalog]
# Create the Integration Services object if it does not exist
if (!$catalog) {
# Provision a new SSIS Catalog
Write-Host "Creating SSIS Catalog ..."
$catalog = New-Object "$ISNamespace.Catalog" ($integrationServices, $SSISCatalog, $CatalogPwd)
$catalog.Create()
}
$folder = $catalog.Folders[$FolderName]
if (!$folder)
{
#Create a folder in SSISDB
Write-Host "Creating Folder ..."
$folder = New-Object "$ISNamespace.CatalogFolder" ($catalog, $FolderName, $FolderName)
$folder.Create()
}
# Read the project file, and deploy it to the folder
Write-Host "Deploying Project ..."
[byte[]] $projectFile = [System.IO.File]::ReadAllBytes($ProjectFilePath)
$folder.DeployProject($ProjectName, $projectFile)
This seemed to be working surprisingly well on the development machine - test server pair. However, the live environment will be a bit different, the machine doing the deployment job (deployment server, or DS from now on) and the SQL Server (DB for short) the project is to be deployed are in different domains and since SSIS requires windows authentication, I'm going to need to run the above code locally on DS but using credentials of a user on the DB.
And that's the point where I fail. The only thing that worked is to start the Powershell command line interface using runas /netonly /user:thatdomain\anuserthere powershell, enter the password, and paste the script unaltered into it. Alas, this is not an option, since there's no way to pass the password to runas (at least once with /savecred) and user interactivity is not possible anyway (the whole thing has to be automated).
I've tried the following:
Simply unning the script on DS, the line $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString would use the credentials from DS which is not recognized by DB, and New-Object does not have a -Credential arg that I could pass to
Putting everything into an Invoke-Command with -Credential requires using -Computername as well. I guess it would be possible to use the local as 'remote' (using . as Computername) but it still complains about access being denied. I'm scanning through about_Remote_Troubleshooting, so far without any success.
Any hints on how to overcome this issue?
A solution might be to use a sql user (with the right access rights) instead of an AD used.
Something like this should work.
(Check also the answer to correct the connection string)

How to run powershell script using default credential from sql job agent

I am downloading a file from sharepoint.
I have already scheduled the job in SQL job agent
Its working fine when i use the following code
$UserName = "xxxx"
$PswdPath = "D:\securestring.txt"
$SecurePassword = cat $PswdPath| convertto-securestring
$fileName = [System.IO.Path]::GetFileName($FileUrl)
$DownloadPath = "D:\Excel\"
$downloadFilePath = [System.IO.Path]::Combine($DownloadPath,$fileName)
$client = New-Object System.Net.WebClient
$client.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $SecurePassword)
$client.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f")
$client.DownloadFile($FileUrl, $downloadFilePath)
$client.Dispose()
But the problem here i face is whenever i update my password i need to update the secure string as well
So i wanted to use the default credentials
so i used the following script
$webclient = New-Object System.Net.WebClient
$webclient.UseDefaultCredentials = $true
$webclient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f")
$webclient.DownloadFile($FileUrl, $DownloadPath)
but its getting failed with the following error
Exception calling "DownloadFile" with "2" argument(s): "The remote server returned an error: (401) Unauthorized."
went through different blogs all were suggesting the same approach which i have followed
Any help in this regard?
As far as I understand default credentials users the account and password the sql agent process is started and since it will not match the SharePoint online account it will fail. It would be easier if you create a powershell that updates all secure strings once password is changed.

ConvertTo-SecureString gives different experience on different servers

I am running into an issue creating a Credential Object from an XML File on a remote server. Here is the code I am using to test
XML File
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Selected.System.Management.Automation.PSCredential</T>
<T>System.Management.Automation.PSCustomObject</T>
<T>System.Object</T>
</TN>
<MS>
<S N="UserName">domain\username</S>
<S N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb010000001f19c6a42b9b0d48af2c531892e737ce000000000200000000001066000000010000200000006fb8862fbaea7b83cd2bcab35d7a8c8b4d71b7764c2a91d68eb3873864bc9d83000000000e8000000002000020000000fcbcc5552c3eb40ec337594f8286b08780709c1ac583d4679dcd7a3f5a92441b20000000c8e274811ed7a411b6741b2c65a67363f6aef380e684d13218d1ecc1281dfdb940000000c7279e81e21a1e57eed7da61e969f34fe2adf3d7e534bb5e10b89902adf4fdf20a69ec7e9b9e56dab512c789043a3b2cf0611e3b4893658b7c20f7892ce0ddfd</S>
</MS>
PowerShell Code
$cred = Import-Clixml "Payload\DeploymentCredential.xml"
write-host $cred
$cred.Password = ConvertTo-SecureString $cred.Password
write-host $cred.Password
$Credential = New-Object System.Management.Automation.PsCredential($cred.UserName, $cred.Password)
write-host $Credential.GetNetworkCredential().password
On one server (my local machine) it works completely fine, but on the remote server, I get this error
Key not valid for use in specified state.
+ CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString], CryptographicException
+ FullyQualifiedErrorId : ImportSecureString_InvalidArgument_CryptographicError,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand
Both have the same version of PowerShell (3.0 Build -1 Revision -1), so I am not sure what the issue is.
The issue is how the original credential is created before being exported to xml.
When you use the command ConvertTo-SecureString it encrypts the plaintext password with the encryption key on the local machine, under your user account. This means that if you export it to xml, you can only use it on that same local machine.
The minute you copy the xml file to another machine and try to import the credential object, it won't work because it will be trying to decrypt it with it's local keys which don't match. (hence the error message). This is an important security measure as it prevents me from copying the file and using it on another computer.
If you need to have the user account on another computer to run something, then there is two options:
(Most secure) Create the credential object on each remote computer that you need it. This way it will use the local encryption keys and will prevent people from being able to steal the account.
(Least secure) When you create the credential with ConvertTo-SecureString you can specify the -Key or -SecureKey parameter. This way instead of using the local encryption keys, it will use the one you specify. Then in your script, you provide the same key to decrypt it. This is less secure because all I have to do is steal the credential file, and take a look inside your script (to see the key) and then I have stolen the account.
--Edit--
Here is an example of how to use a shared key. It is literally only one step up from writing in a plaintext password in your script, and is only used to obfuscate the password. There are many other -better- ways of running scripts on remote machines like PowerShell Remoting (See: Learn to Use Remoting in PowerShell). Or using Task Scheduler with saved credentials.
$PlainPassword = "P#ssw0rd"
$SecurePassword = $PlainPassword | ConvertTo-SecureString -AsPlainText -Force
$key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)
$SecurePasswordKey = ConvertFrom-SecureString $SecurePassword -Key $key
#Output the hash
$SecurePasswordKey
#Output
76492d1116743f0423413b16050a5345MgB8ADIAKwBZAEkALwB0ADUAZwBQAHoAbwBNAEEAUwA0AFQAagB0AGsANwBmAHcAPQA9AHwAYgA3ADgAMwBjAGIANAAzADIAZAAwADEAYQA1AGUAMwBjAGUAYgA2AGMAMQBkADcAYQA3ADMAZAA1ADQAYwA0ADMAYgBlAGEANQAyAGQANQA0AGUAYgA5AGEAMgA0AGIANwBhAGIAMQAzADAAMwAzAGEANAA4ADEANQA0AGEAMAA=
On remote machine:
$SecurePasswordKey = '76492d1116743f0423413b16050a5345MgB8ADIAKwBZAEkALwB0ADUAZwBQAHoAbwBNAEEAUwA0AFQAagB0AGsANwBmAHcAPQA9AHwAYgA3ADgAMwBjAGIANAAzADIAZAAwADEAYQA1AGUAMwBjAGUAYgA2AGMAMQBkADcAYQA3ADMAZAA1ADQAYwA0ADMAYgBlAGEANQAyAGQANQA0AGUAYgA5AGEAMgA0AGIANwBhAGIAMQAzADAAMwAzAGEANAA4ADEANQA0AGEAMAA='
$key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)
$SecurePassword = ConvertTo-SecureString -String $SecurePasswordKey -Key $key
Here's one method for some randomness in creating the key if you choose to use the answer from HAL9256.
[byte[]]$Rand = for($var=1;$var -le 24){
Get-Random -min 1 -max 255
$var++
}
We create an array of bytes which is filled with 24 random numbers from 1 to 255. These numbers are not displayed and exist only when the script is run.
Then we have a key which can be used in the above answer. The value of $Rand will disappear once the script executes, or you use Remove-Variable Rand
Just be sure to save the data from $Rand to some place secure or the key used to encrypt the data is lost.