Differences

This shows you the differences between the selected revisions of the page.

guide_protecting_credentials_for_automation 2014-08-04 guide_protecting_credentials_for_automation 2024-04-22 (current)
Line 4: Line 4:
  * The script/code is often stored in a revision control system, making the credentials easily accessible.   * The script/code is often stored in a revision control system, making the credentials easily accessible.
  * The script/code may often need to be accessible on the production system for review or auditing purposes or reuse, while the credentials should not.   * The script/code may often need to be accessible on the production system for review or auditing purposes or reuse, while the credentials should not.
 +
 +There is no way to store passwords in script in an encrypted way. In general, it is not possible to encrypt any kind of information in a way that still allows for its use in an automatic way. If WinSCP should be able to decrypt the information, anyone can.((For a real encryption, one needs to use a key. And the key needs to be stored somewhere again. It's the chicken or the egg problem.))
Solution is to separate the credentials from the script/code into a configuration file. While the script/code without explicit credentials can be safely stored into a revision system and be otherwise accessible, the configuration file should be protected as much as possible. Particularly its file permissions should be restricted only to administrators (for writing) and user under which the script/code runs (for reading). The configuration file can also be encrypted, for example with [[wp>Encrypting_File_System|built-in NTFS filesystem-level encryption]]. Solution is to separate the credentials from the script/code into a configuration file. While the script/code without explicit credentials can be safely stored into a revision system and be otherwise accessible, the configuration file should be protected as much as possible. Particularly its file permissions should be restricted only to administrators (for writing) and user under which the script/code runs (for reading). The configuration file can also be encrypted, for example with [[wp>Encrypting_File_System|built-in NTFS filesystem-level encryption]].
Line 11: Line 13:
In [[scripting|script]], you can replace actual credentials with reference to environment variables. You can then call WinSCP from a batch file that sets these variables. The batch file itself then serves as a "configuration file". In [[scripting|script]], you can replace actual credentials with reference to environment variables. You can then call WinSCP from a batch file that sets these variables. The batch file itself then serves as a "configuration file".
-For example, following script (''example.txt''):+For example, the following script (''example.txt'')((Using [[scriptcommand_open#username|''-username'']] and [[scriptcommand_open#password|''-password'']] switches instead of specifying credentials in [[session_url|session URL]], because the switches do not require the values to be [[session_url#special|URL-encoded]].)):
<code winscp> <code winscp>
-option batch abort +open -username=%USERNAME% -password=%PASSWORD% sftp://example.com/
-option confirm off +
-open sftp://%USERNAME%:%PASSWORD%@example.com+
... ...
</code> </code>
Line 22: Line 22:
can be called from this batch file ("configuration file"): can be called from this batch file ("configuration file"):
-<code>+<code batch>
@echo off @echo off
set USERNAME=martin set USERNAME=martin
set PASSWORD=mypassword set PASSWORD=mypassword
winscp.com /script=example.txt winscp.com /script=example.txt
 +</code>
 +----
 +Another way is to store the password to a separate file and use [[scriptcommand_open#passwordsfromfiles|''-passwordsfromfiles'']]:
 +<code winscp>
 +open -password=C:\path\password.txt -passwordsfromfiles sftp://username@example.com/
</code> </code>
===== Using WinSCP .NET assembly ===== ===== Using WinSCP .NET assembly =====
-==== PowerShell ====+==== [[powershell]] PowerShell ====
-In [[library_powershell|PowerShell]] code using [[library|WinSCP .NET library]] you can use ''[[http://technet.microsoft.com/en-us/library/hh849787.aspx|Get-Content]]'' cmdlet to read an XML configuration file.+In [[library_powershell|PowerShell]] code using [[library|WinSCP .NET library]] you can use ''[[ps&gt;microsoft.powershell.management/get-content|Get-Content]]'' cmdlet to read an XML configuration file.
-For example with following XML configuration file (''config.xml''):+For example with following %%XML%% configuration file (''config.xml''):
<code xml> <code xml>
Line 51: Line 56:
# Use read credentials # Use read credentials
-$sessionOptions = New-Object WinSCP.SessionOptions +$sessionOptions = New-Object WinSCP.SessionOptions -Property @{ 
-$sessionOptions.Protocol = [WinSCP.Protocol]::Sftp +····Protocol = [WinSCP.Protocol]::Sftp 
-$sessionOptions.HostName = "example.com" +····HostName = "example.com" 
-$sessionOptions.UserName = $config.Configuration.UserName +····UserName = $config.Configuration.UserName 
-$sessionOptions.Password = $config.Configuration.Password+····Password = $config.Configuration.Password 
 +}
... ...
 +</code>
 +
 +You can also leverage Windows Data Protection API to encrypt the password in the %%XML%% file.
 +
 +If you want to encrypt the password within the configuration file, you can use use ''[[ps>microsoft.powershell.security/convertfrom-securestring|ConvertFrom-SecureString]]'' cmdlet. Put the following code to an ad-hoc script (or an interactive PowerShell console):
 +
 +<code powershell>
 +Read-Host -AsSecureString | ConvertFrom-SecureString
 +</code>
 +
 +A password encrypted this way can be decrypted by the same Windows account only.
 +
 +Store the encrypted password to the %%XML%% file instead of the plain-text one:
 +
 +<code xml>
 +<Configuration>
 +  <UserName>martin</UserName>
 +  <Password>01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cf6dbc52515...</Password>
 +</Configuration>
 +</code>
 +
 +To decrypt the password, use ''[[ps>microsoft.powershell.security/convertto-securestring|ConvertTo-SecureString]]'' cmdlet and assign the resulting ''[[dotnet>system.security.securestring|SecureString]]'' to [[library_sessionoptions#securepassword|''SessionOptions.SecurePassword'']], instead of using plain text ''SessionOptions.Password'':
 +
 +<code powershell>
 +$sessionOptions.SecurePassword = ConvertTo-SecureString $config.Configuration.Password
 +</code>
 +
 +==== [[vs]] Visual Studio (C#, VB.NET) ====
 +
 +In .NET projects (C#, VB.NET) an application configuration file (''App.config'') is used to store the settings. Though this file contains other configuration that needs to be shared and/or stored in a revision control system. To separate the credentials from other settings, you can link another configuration file like shown below.
 +
 +Use the ''file'' attribute of ''appSettings'' element in the primary application configuration file (''App.config''):
 +
 +<code xml>
 +<?xml version="1.0" encoding="utf-8" ?>
 +<configuration>
 +  <startup>
 +    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
 +  </startup>
 +  <appSettings file="ProtectedConfig.config">
 +    <!-- other settings -->
 +  </appSettings>
 +</configuration>
 +</code>
 +
 +Add a new %%XML%% file to the project and name it ''ProtectedConfig.config'':
 +
 +<code xml>
 +<?xml version="1.0" encoding="utf-8" ?>
 +<appSettings>
 +  <add key="UserName" value="martin" />
 +  <add key="Password" value="test" />
 +</appSettings>
 +</code>
 +
 +Set the //Copy to Output Directory// property of the file to //Copy if newer// and the //Build Action// to //Content// to have the new configuration file correctly deployed.
 +
 +To read the settings from the configuration file use ''[[dotnet>system.configuration.configurationmanager.appsettings|ConfigurationManager.AppSettings]]'':((You need to reference the ''System.configuration'' assembly in your project to use the ''ConfigurationManager'' class.))
 +
 +<code csharp>
 +SessionOptions sessionOptions = new SessionOptions
 +{
 +    Protocol = Protocol.Sftp,
 +    HostName = "example.com",
 +    UserName = ConfigurationManager.AppSettings["UserName"],
 +    Password = ConfigurationManager.AppSettings["Password"],
 +};
 +</code>
 +
 +You can also leverage Windows Data Protection API to encrypt the password in the XML file.
 +
 +If you want to encrypt the password within the configuration file, you can use use PowerShell ''ConvertFrom-SecureString'' cmdlet:
 +
 +<code>
 +powershell.exe -Command "& Read-Host -AsSecureString | ConvertFrom-SecureString"
 +</code>
 +
 +A password encrypted this way can be decrypted by the same Windows account only.
 +
 +Store the encrypted password to the %%XML%% file instead of the plain-text one:
 +
 +<code xml>
 +<?xml version="1.0" encoding="utf-8" ?>
 +<appSettings>
 +  <add key="UserName" value="martin" />
 +  <add key="Password" value="01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cf6dbc52515..." />
 +</appSettings>
 +</code>
 +
 +To decrypt the password, use ''[[dotnet>system.security.cryptography.protecteddata.unprotect|ProtectedData.Unprotect]]'':((You need to reference the ''System.Security'' assembly in your project to use the ''ProtectedData'' class.))
 +
 +<code csharp>
 +string hex = ConfigurationManager.AppSettings["Password"];
 +byte[] bytes =
 +    Enumerable.Range(0, hex.Length / 2).
 +        Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16)).ToArray();
 +byte[] decrypted = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser);
 +sessionOptions.Password = Encoding.Unicode.GetString(decrypted);
</code> </code>
Line 64: Line 168:
See [[library_ssis#example|SSIS example]]. See [[library_ssis#example|SSIS example]].
 +
 +==== Session URL ====
 +
 +Instead of storing credentials (username and password) individually, you can use a single string, the [[session_url|session URL]] (a kind of "connection string"). This gives you an additional flexibility of changing even protocol by mere configuration.
 +
 +Use the ''[[library_sessionoptions_parseurl|SessionOptions.ParseUrl]]'' to parse the URL into individual components.
 +
 +You can change the [[#powershell|above PowerShell example]] as below.
 +
 +The configuration file:
 +
 +<code xml>
 +<Configuration>
 +  <SessionUrl>sftp://martin:mypassword@example.com/</SessionUrl>
 +</Configuration>
 +</code>
 +
 +The code:
 +
 +<code powershell>
 +# Read XML configuration file
 +[xml]$config = Get-Content ".\config.xml"
 +
 +# Use read credentials
 +$sessionOptions = New-Object WinSCP.SessionOptions
 +$sessionOptions.ParseUrl($config.Configuration.SessionUrl)
 +
 +...
 +</code>
 +
 +You can apply the same technique to the [[#vs|above C# example]].
 +
 +Only if you want to encrypt the password as shown in the previous sections, you need to separate the password from the rest of the session %%URL%%. First call the ''SessionOptions.ParseUrl'' and then separately set the ''SessionOptions.Password''.

Last modified: by martin