Skip to content

Commit 8748d2a

Browse files
authored
Merge pull request #50 from Keyfactor/release-2.5
Squash 2.5.0 to main
2 parents b53710b + 3070f01 commit 8748d2a

File tree

11 files changed

+117
-16
lines changed

11 files changed

+117
-16
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v2.5.0
2+
- Add new optional custom field and config.json entries to supply a user id other than "root" for the user to "sudo into" when UseSudo = "Y". There is an optional config.json DefaultSudoImpersonatedUser that will be used at the orchestrator level, and an optional new store type custom field, SudoImpersonatedUser, that overrides the config.json setting for each certificate store.
3+
- Modified the optional sudo command prefix to remove the "-i" option which was creating a new shell for the impersonated id (always root up until this release). Without this option the profile for the logged in user and not the impersonated user will be used when running commands.
4+
- Added Regex checks for Discovery fields (file names, file extensions, and file paths) to enhance security. Only alpha numeric, "/", and "\" characters are allowed for these values.
5+
16
v2.4.2
27
- Bug fix: Upgrade BouncyCastle.Cryptography to version 2.3.0 to allow for RFKDB HMAC-SHA-384 support
38

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ Please consult with your company's system administrator for more information on
169169
The Remote File Orchestrator Extension uses a JSON configuration file. It is located in the {Keyfactor Orchestrator Installation Folder}\Extensions\RemoteFile. None of the values are required, and a description of each follows below:
170170
{
171171
"UseSudo": "N",
172+
"DefaultSudoImpersonatedUser": "",
172173
"CreateStoreIfMissing": "N",
173174
"UseNegotiate": "N",
174175
"SeparateUploadFilePath": "",
@@ -177,7 +178,8 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is lo
177178
"DefaultOwnerOnStoreCreation": ""
178179
}
179180

180-
**UseSudo** (Applicable for Linux orchestrated servers only) - Y/N - Determines whether to prefix certain Linux command with "sudo". This can be very helpful in ensuring that the user id running commands over an ssh connection uses "least permissions necessary" to process each task. Setting this value to "Y" will prefix all Linux commands with "sudo" with the expectation that the command being executed on the orchestrated Linux server will look in the sudoers file to determine whether the logged in ID has elevated permissions for that specific command. For Windows orchestrated servers, this setting has no effect. Setting this value to "N" will result in "sudo" not being added to Linux commands. **Default value if missing - N**.
181+
**UseSudo** (Applicable for Linux orchestrated servers only) - Y/N - Determines whether to prefix certain Linux command with "sudo". This can be very helpful in ensuring that the user id running commands over an ssh connection uses "least permissions necessary" to process each task. Setting this value to "Y" will prefix all Linux commands with "sudo" with the expectation that the command being executed on the orchestrated Linux server will look in the sudoers file to determine whether the logged in ID has elevated permissions for that specific command. For Windows orchestrated servers, this setting has no effect. Setting this value to "N" will result in "sudo" not being added to Linux commands.
182+
**DefaultSudoImpersonatedUser** (Applicable for Linux orchestrated servers only) - Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is set to an empty string, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see later in this section) as well as permissions to execute the commands listed in the "Security Considerations" section above. This value will be used for all certificate stores managed by this orchestrator extension implementation UNLESS overriden by the SudoImpersonatedUser certificate store type custom field setting described later in the Certificate Store Types section.
181183
**CreateStoreOnAddIfMissing** - Y/N - Determines, during a Management-Add job, if a certificate store should be created if it does not already exist. If set to "N", and the store referenced in the Management-Add job is not found, the job will return an error with a message stating that the store does not exist. If set to "Y", the store will be created and the certificate added to the certificate store. **Default value if missing - N**.
182184
**UseNegotiateAuth** (Applicable for Windows orchestrated servers only) – Y/N - Determines if WinRM should use Negotiate (Y) when connecting to the remote server. **Default Value if missing - N**.
183185
**SeparateUploadFilePath**(Applicable for Linux managed servers only) – Set this to the path you wish to use as the location on the orchestrated server to upload/download and later remove temporary work files when processing jobs. If set to "" or not provided, the location of the certificate store itself will be used. File transfer itself is performed using SCP or SFTP protocols (see FileT ransferProtocol setting). **Default Value if missing - blank**.
@@ -211,6 +213,7 @@ When setting up the certificate store types you wish the Remote File Orchestrato
211213
*Custom Fields Tab:*
212214
- **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
213215
- **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner/group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group and owner need to be different values, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. Please confirm that the user name associated with this Keyfactor certificate store has valid permissions to chown the certificate file to this owner.
216+
- **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.
214217

215218
Entry Parameters Tab:
216219
- See specific certificate store type instructions below

RemoteFile/ApplicationSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public enum FileTransferProtocolEnum
2727

2828
private const string DEFAULT_LINUX_PERMISSION_SETTING = "600";
2929
private const string DEFAULT_OWNER_SETTING = "";
30+
private const string DEFAULT_SUDO_IMPERSONATION_SETTING = "";
3031

3132
private static Dictionary<string,string> configuration;
3233

@@ -36,6 +37,7 @@ public enum FileTransferProtocolEnum
3637
public static string SeparateUploadFilePath { get { return configuration.ContainsKey("SeparateUploadFilePath") ? AddTrailingSlash(configuration["SeparateUploadFilePath"]) : string.Empty; } }
3738
public static string DefaultLinuxPermissionsOnStoreCreation { get { return configuration.ContainsKey("DefaultLinuxPermissionsOnStoreCreation") ? configuration["DefaultLinuxPermissionsOnStoreCreation"] : DEFAULT_LINUX_PERMISSION_SETTING; } }
3839
public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"] : DEFAULT_OWNER_SETTING; } }
40+
public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } }
3941
public static FileTransferProtocolEnum FileTransferProtocol
4042
{
4143
get

RemoteFile/Discovery.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Keyfactor.Orchestrators.Common.Enums;
1616

1717
using Microsoft.Extensions.Logging;
18+
using Newtonsoft.Json;
1819

1920
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
2021
{
@@ -57,7 +58,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd
5758
ApplicationSettings.Initialize(this.GetType().Assembly.Location);
5859

5960
certificateStore = new RemoteCertificateStore(config.ClientMachine, userName, userPassword, directoriesToSearch[0].Substring(0, 1) == "/" ? RemoteCertificateStore.ServerTypeEnum.Linux : RemoteCertificateStore.ServerTypeEnum.Windows);
60-
certificateStore.Initialize();
61+
certificateStore.Initialize(ApplicationSettings.DefaultSudoImpersonatedUser);
6162

6263
if (directoriesToSearch.Length == 0)
6364
throw new RemoteFileException("Blank or missing search directories for Discovery.");

RemoteFile/InventoryBase.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Keyfactor.Extensions.Orchestrator.RemoteFile.Models;
1616

1717
using Microsoft.Extensions.Logging;
18+
using Newtonsoft.Json;
1819

1920
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
2021
{
@@ -46,8 +47,13 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
4647
string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword);
4748

4849
ApplicationSettings.Initialize(this.GetType().Assembly.Location);
50+
dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString());
51+
string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ?
52+
ApplicationSettings.DefaultSudoImpersonatedUser :
53+
properties.SudoImpersonatedUser.Value;
54+
4955
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties);
50-
certificateStore.Initialize();
56+
certificateStore.Initialize(sudoImpersonatedUser);
5157
certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, true);
5258

5359
List<X509Certificate2Collection> collections = certificateStore.GetCertificateChains();

RemoteFile/ManagementBase.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
5151
string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword);
5252

5353
ApplicationSettings.Initialize(this.GetType().Assembly.Location);
54+
dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString());
55+
string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ?
56+
ApplicationSettings.DefaultSudoImpersonatedUser :
57+
properties.SudoImpersonatedUser.Value;
58+
5459
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties);
55-
certificateStore.Initialize();
60+
certificateStore.Initialize(sudoImpersonatedUser);
5661

5762
PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath);
5863

RemoteFile/RemoteCertificateStore.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal RemoteCertificateStore(string server, string serverId, string serverPas
7272
UploadFilePath = !string.IsNullOrEmpty(ApplicationSettings.SeparateUploadFilePath) && ServerType == ServerTypeEnum.Linux ? ApplicationSettings.SeparateUploadFilePath : StorePath;
7373
logger.LogDebug($"UploadFilePath: {UploadFilePath}");
7474

75-
if (!IsStorePathValid())
75+
if (!IsValueSafeRegex(StorePath + StoreFileName))
7676
{
7777
logger.LogDebug("Store path not valid");
7878
string partialMessage = ServerType == ServerTypeEnum.Windows ? @"'\', ':', " : string.Empty;
@@ -133,6 +133,14 @@ internal void Terminate()
133133
internal List<string> FindStores(string[] paths, string[] extensions, string[] files, bool includeSymLinks)
134134
{
135135
logger.MethodEntry(LogLevel.Debug);
136+
137+
if (!AreValuesSafeRegex(paths))
138+
throw new RemoteFileException(@"Invalid/unsafe directories to search value supplied. Only alphanumeric, /, and \ characters are allowed.");
139+
if (!AreValuesSafeRegex(extensions))
140+
throw new RemoteFileException(@"Invalid/unsafe file extension value supplied. Only alphanumeric, /, and \ characters are allowed.");
141+
if (!AreValuesSafeRegex(files))
142+
throw new RemoteFileException(@"Invalid/unsafe file name value supplied. Only alphanumeric, /, and \ characters are allowed.");
143+
136144
logger.MethodExit(LogLevel.Debug);
137145

138146
if (DiscoveredStores != null)
@@ -328,12 +336,12 @@ internal static PathFile SplitStorePathFile(string pathFileName)
328336
}
329337
}
330338

331-
internal void Initialize()
339+
internal void Initialize(string sudoImpersonatedUser)
332340
{
333341
logger.MethodEntry(LogLevel.Debug);
334342

335343
if (ServerType == ServerTypeEnum.Linux)
336-
RemoteHandler = new SSHHandler(Server, ServerId, ServerPassword);
344+
RemoteHandler = new SSHHandler(Server, ServerId, ServerPassword, sudoImpersonatedUser);
337345
else
338346
RemoteHandler = new WinRMHandler(Server, ServerId, ServerPassword);
339347

@@ -342,15 +350,27 @@ internal void Initialize()
342350
logger.MethodExit(LogLevel.Debug);
343351
}
344352

345-
private bool IsStorePathValid()
353+
private bool AreValuesSafeRegex(string[] values)
354+
{
355+
bool valueIsSafe = true;
356+
foreach(string value in values)
357+
{
358+
valueIsSafe = IsValueSafeRegex(value.Replace("*",String.Empty));
359+
if (!valueIsSafe)
360+
break;
361+
}
362+
return valueIsSafe;
363+
}
364+
365+
private bool IsValueSafeRegex(string value)
346366
{
347367
logger.MethodEntry(LogLevel.Debug);
348368

349369
Regex regex = new Regex(ServerType == ServerTypeEnum.Linux ? $@"^[\d\s\w-_/.]*$" : $@"^[\d\s\w-_/.:)(\\\\]*$");
350370

351371
logger.MethodExit(LogLevel.Debug);
352372

353-
return regex.IsMatch(StorePath + StoreFileName);
373+
return regex.IsMatch(value);
354374
}
355375

356376
private List<string> FindStoresLinux(string[] paths, string[] extensions, string[] fileNames, bool includeSymLinks)

RemoteFile/RemoteHandlers/SSHHandler.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ class SSHHandler : BaseRemoteHandler
2626
{
2727
private const string LINUX_PERMISSION_REGEXP = "^[0-7]{3}$";
2828
private ConnectionInfo Connection { get; set; }
29+
private string SudoImpersonatedUser { get; set; }
2930

3031
private SshClient sshClient;
3132

32-
internal SSHHandler(string server, string serverLogin, string serverPassword)
33+
internal SSHHandler(string server, string serverLogin, string serverPassword, string sudoImpersonatedUser)
3334
{
3435
_logger.MethodEntry(LogLevel.Debug);
3536

3637
Server = server;
38+
SudoImpersonatedUser = sudoImpersonatedUser;
3739

3840
List<AuthenticationMethod> authenticationMethods = new List<AuthenticationMethod>();
3941
if (serverPassword.Length < PASSWORD_LENGTH_MAX)
@@ -99,13 +101,18 @@ public override string RunCommand(string commandText, object[] arguments, bool w
99101
_logger.MethodEntry(LogLevel.Debug);
100102
_logger.LogDebug($"RunCommand: {commandText}");
101103

102-
string sudo = $"sudo -i -S ";
104+
string sudo = $"sudo -S ";
103105
string echo = $"echo -e '\n' | ";
104106

105107
try
106108
{
107109
if (withSudo)
108-
commandText = sudo + commandText;
110+
{
111+
if (string.IsNullOrEmpty(SudoImpersonatedUser))
112+
commandText = sudo + commandText;
113+
else
114+
commandText = sudo + $"-u {SudoImpersonatedUser}" + " " + commandText;
115+
}
109116

110117
commandText = echo + commandText;
111118

@@ -211,7 +218,6 @@ public override void UploadCertificateFile(string path, string fileName, byte[]
211218

212219
if (!string.IsNullOrEmpty(ApplicationSettings.SeparateUploadFilePath))
213220
{
214-
//RunCommand($"cat {uploadPath} > {path}/{fileName}", null, ApplicationSettings.UseSudo, null);
215221
RunCommand($"tee {path}/{fileName} < {uploadPath} > /dev/null", null, ApplicationSettings.UseSudo, null);
216222
RunCommand($"rm {uploadPath}", null, ApplicationSettings.UseSudo, null);
217223
}
@@ -235,7 +241,8 @@ public override byte[] DownloadCertificateFile(string path)
235241
SplitStorePathFile(path, out altPathOnly, out altFileNameOnly);
236242
downloadPath = ApplicationSettings.SeparateUploadFilePath + altFileNameOnly;
237243
RunCommand($"cp {path} {downloadPath}", null, ApplicationSettings.UseSudo, null);
238-
RunCommand($"chown {Connection.Username} {downloadPath}", null, ApplicationSettings.UseSudo, null);
244+
if (string.IsNullOrEmpty(SudoImpersonatedUser))
245+
RunCommand($"chown {Connection.Username} {downloadPath}", null, ApplicationSettings.UseSudo, null);
239246
}
240247

241248
bool scpError = false;

RemoteFile/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
22
"UseSudo": "N",
3+
"DefaultSudoImpersonatedUser": "",
34
"CreateStoreIfMissing": "N",
45
"UseNegotiate": "N",
56
"SeparateUploadFilePath": "",
67
"FileTransferProtocol": "SCP",
78
"DefaultLinuxPermissionsOnStoreCreation": "600",
8-
"DefaultOwnerOnStoreCreation": ""
9+
"DefaultOwnerOnStoreCreation": ""
910
}

0 commit comments

Comments
 (0)