diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18a0ca7e..ea719bee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+v2.11.4
+- Bug Fix: Handle condition where a certificate store definition that contains an invalid value for `FileTransferProtocol`
+would return empty inventory. If no value is set or an invalid value is set, the default value of `Both` will be used
+and a warning status will be returned for the job.
+
+> [!IMPORTANT]
+> Due to an issue in Keyfactor Command versions through 25.2.1, when adding multiple choice store properties to exiting
+> certificate store types, existing certificate stores receive incorrect initialization data for the new property.
+> If you have upgraded your store type from something prior to version 2.10.0, ensure that each existing certificate
+> store has a valid value for `FileTransferProtocol`. Valid values are `SCP`, `SFTP`, `Both`, otherwise inventory jobs **may report
+> empty certificate store inventories**. Extension version 2.11.4 compensates for this, but upgrading customers should
+> check their store’s configuration for proper `FileTransferProtocol` values.
+
v2.11.3
- Change returned result of a Management-Create job for a store that already exists from 'Failure' to 'Warning'
diff --git a/README.md b/README.md
index 9f9536b0..3af1e6b8 100644
--- a/README.md
+++ b/README.md
@@ -956,12 +956,13 @@ The Remote File Universal Orchestrator extension implements 6 Certificate Store
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFJKS" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions) |
| Store Path | The full path and file name, including file extension if one exists where the certificate store file is located. For Linux orchestrated servers, StorePath will begin with a forward slash (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should begin with a drive letter (i.e. c:\folder\path\storename.ext). |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFJKS` certificates. Specifically, one with the `RFJKS` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -996,6 +997,7 @@ The Remote File Universal Orchestrator extension implements 6 Certificate Store
| Container | Optional container to associate certificate store with. |
| Client Machine | The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions) |
| Store Path | The full path and file name, including file extension if one exists where the certificate store file is located. For Linux orchestrated servers, StorePath will begin with a forward slash (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should begin with a drive letter (i.e. c:\folder\path\storename.ext). |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFJKS` certificates. Specifically, one with the `RFJKS` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1025,7 +1027,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
@@ -1055,12 +1057,13 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFPEM" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.ext) for Windows orchestrated servers. Example: '/folder/path/storename.pem' or 'c:\folder\path\storename.pem'. |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
| Orchestrator | Select an approved orchestrator capable of managing `RFPEM` certificates. Specifically, one with the `RFPEM` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1099,6 +1102,7 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.ext) for Windows orchestrated servers. Example: '/folder/path/storename.pem' or 'c:\folder\path\storename.pem'. |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
| Orchestrator | Select an approved orchestrator capable of managing `RFPEM` certificates. Specifically, one with the `RFPEM` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1132,7 +1136,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
@@ -1162,12 +1166,13 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFPkcs12" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.p12) for Windows orchestrated servers. Example: '/folder/path/storename.p12' or 'c:\folder\path\storename.p12'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFPkcs12` certificates. Specifically, one with the `RFPkcs12` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1202,6 +1207,7 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.p12) for Windows orchestrated servers. Example: '/folder/path/storename.p12' or 'c:\folder\path\storename.p12'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFPkcs12` certificates. Specifically, one with the `RFPkcs12` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1231,7 +1237,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
@@ -1261,12 +1267,13 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFDER" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.der) for Windows orchestrated servers. Example: '/folder/path/storename.der' or 'c:\folder\path\storename.der'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFDER` certificates. Specifically, one with the `RFDER` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1302,6 +1309,7 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.der) for Windows orchestrated servers. Example: '/folder/path/storename.der' or 'c:\folder\path\storename.der'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFDER` certificates. Specifically, one with the `RFDER` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1332,7 +1340,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
@@ -1362,12 +1370,13 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFKDB" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.kdb) for Windows orchestrated servers. Example: '/folder/path/storename.kdb' or 'c:\folder\path\storename.kdb'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFKDB` certificates. Specifically, one with the `RFKDB` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1402,6 +1411,7 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.kdb) for Windows orchestrated servers. Example: '/folder/path/storename.kdb' or 'c:\folder\path\storename.kdb'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFKDB` certificates. Specifically, one with the `RFKDB` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1431,7 +1441,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
@@ -1461,12 +1471,13 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- | ----------- |
+ | Attribute | Description |
+ | --------- |---------------------------------------------------------|
| Category | Select "RFORA" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name of the Oracle Wallet, including the 'eWallet.p12' file name by convention. Example: '/path/to/eWallet.p12' or 'c:\path\to\eWallet.p12'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFORA` certificates. Specifically, one with the `RFORA` capability. |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1502,6 +1513,7 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
| Container | Optional container to associate certificate store with. |
| Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
| Store Path | The Store Path field should contain the full path and file name of the Oracle Wallet, including the 'eWallet.p12' file name by convention. Example: '/path/to/eWallet.p12' or 'c:\path\to\eWallet.p12'. |
+ | Store Password | Password used to secure the Certificate Store |
| Orchestrator | Select an approved orchestrator capable of managing `RFORA` certificates. Specifically, one with the `RFORA` capability. |
| Properties.ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| Properties.ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
@@ -1532,7 +1544,7 @@ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installa
| --------- | ----------- |
| ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
| ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
- | StorePassword | Password to use when reading/writing to store |
+ | StorePassword | Password used to secure the Certificate Store |
Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
diff --git a/RemoteFile.UnitTests/ApplicationSettingsTests.cs b/RemoteFile.UnitTests/ApplicationSettingsTests.cs
new file mode 100644
index 00000000..175f27c4
--- /dev/null
+++ b/RemoteFile.UnitTests/ApplicationSettingsTests.cs
@@ -0,0 +1,23 @@
+using Xunit;
+using Keyfactor.Extensions.Orchestrator.RemoteFile;
+
+namespace RemoteFile.UnitTests;
+
+public class ApplicationSettingsTests
+{
+ [Fact]
+ public void FileTransferProtocol_WhenPopulatedWithValidValue_ReturnsValue()
+ {
+ var path = Path.Combine(Directory.GetCurrentDirectory(), "fixtures", "config", "valid", "config.json");
+ ApplicationSettings.Initialize(path);
+ Assert.Equal(ApplicationSettings.FileTransferProtocolEnum.SCP, ApplicationSettings.FileTransferProtocol);
+ }
+
+ [Fact]
+ public void FileTransferProtocol_WhenAllThreePopulated_DefaultsToBoth()
+ {
+ var path = Path.Combine(Directory.GetCurrentDirectory(), "fixtures", "config", "file_transfer_protocol_all_three", "config.json");
+ ApplicationSettings.Initialize(path);
+ Assert.Equal(ApplicationSettings.FileTransferProtocolEnum.Both, ApplicationSettings.FileTransferProtocol);
+ }
+}
diff --git a/RemoteFile.UnitTests/PropertyUtilitiesTests.cs b/RemoteFile.UnitTests/PropertyUtilitiesTests.cs
new file mode 100644
index 00000000..42b63cd8
--- /dev/null
+++ b/RemoteFile.UnitTests/PropertyUtilitiesTests.cs
@@ -0,0 +1,48 @@
+using Keyfactor.Extensions.Orchestrator.RemoteFile;
+using Xunit;
+
+namespace RemoteFile.UnitTests;
+
+public class PropertyUtilitiesTests
+{
+ [Theory]
+ [InlineData("SCP", ApplicationSettings.FileTransferProtocolEnum.SCP)]
+ [InlineData("SFTP", ApplicationSettings.FileTransferProtocolEnum.SFTP)]
+ [InlineData("Both", ApplicationSettings.FileTransferProtocolEnum.Both)]
+ public void TryEnumParse_WhenProvidedAValidEnumString_MapsToExpectedEnumValue(string input,
+ ApplicationSettings.FileTransferProtocolEnum expected)
+ {
+ var isValid = PropertyUtilities.TryEnumParse(input, out var isFlagCombination,
+ out ApplicationSettings.FileTransferProtocolEnum result);
+
+ Assert.True(isValid);
+ Assert.Equal(expected, result);
+ Assert.False(isFlagCombination);
+ }
+
+ [Fact]
+ public void TryEnumParse_WhenProvidedAFlagCombination_SetsIsFlagCombination()
+ {
+ var input = "SCP,SFTP,Both";
+
+ var isValid = PropertyUtilities.TryEnumParse(input, out var isFlagCombination,
+ out ApplicationSettings.FileTransferProtocolEnum result);
+
+ Assert.True(isValid);
+ Assert.Equal((ApplicationSettings.FileTransferProtocolEnum) 3, result);
+ Assert.True(isFlagCombination);
+ }
+
+ [Fact]
+ public void TryEnumParse_WhenProvidedAnInvalidMapping_MarksIsValidAsFalse()
+ {
+ var input = "randomstring";
+
+ var isValid = PropertyUtilities.TryEnumParse(input, out var isFlagCombination,
+ out ApplicationSettings.FileTransferProtocolEnum result);
+
+ Assert.False(isValid);
+ Assert.Equal(ApplicationSettings.FileTransferProtocolEnum.SCP, result);
+ Assert.False(isFlagCombination);
+ }
+}
diff --git a/RemoteFile.UnitTests/RemoteFile.UnitTests.csproj b/RemoteFile.UnitTests/RemoteFile.UnitTests.csproj
new file mode 100644
index 00000000..b71b933b
--- /dev/null
+++ b/RemoteFile.UnitTests/RemoteFile.UnitTests.csproj
@@ -0,0 +1,40 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/RemoteFile.UnitTests/fixtures/config/file_transfer_protocol_all_three/config.json b/RemoteFile.UnitTests/fixtures/config/file_transfer_protocol_all_three/config.json
new file mode 100644
index 00000000..4ce8261b
--- /dev/null
+++ b/RemoteFile.UnitTests/fixtures/config/file_transfer_protocol_all_three/config.json
@@ -0,0 +1,11 @@
+{
+ "UseSudo": "N",
+ "DefaultSudoImpersonatedUser": "",
+ "CreateStoreIfMissing": "N",
+ "UseNegotiate": "N",
+ "SeparateUploadFilePath": "",
+ "FileTransferProtocol": "Both,SCP,SFTP",
+ "DefaultLinuxPermissionsOnStoreCreation": "600",
+ "DefaultOwnerOnStoreCreation": "",
+ "SSHPort": ""
+}
\ No newline at end of file
diff --git a/RemoteFile.UnitTests/fixtures/config/valid/config.json b/RemoteFile.UnitTests/fixtures/config/valid/config.json
new file mode 100644
index 00000000..affa362e
--- /dev/null
+++ b/RemoteFile.UnitTests/fixtures/config/valid/config.json
@@ -0,0 +1,11 @@
+{
+ "UseSudo": "N",
+ "DefaultSudoImpersonatedUser": "",
+ "CreateStoreIfMissing": "N",
+ "UseNegotiate": "N",
+ "SeparateUploadFilePath": "",
+ "FileTransferProtocol": "SCP",
+ "DefaultLinuxPermissionsOnStoreCreation": "600",
+ "DefaultOwnerOnStoreCreation": "",
+ "SSHPort": ""
+}
\ No newline at end of file
diff --git a/RemoteFile.sln b/RemoteFile.sln
index cc0e53ff..120f03d5 100644
--- a/RemoteFile.sln
+++ b/RemoteFile.sln
@@ -5,6 +5,10 @@ VisualStudioVersion = 16.0.31702.278
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteFile", "RemoteFile\RemoteFile.csproj", "{A006BFAB-20F7-4F42-8B5F-591268ACE836}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{856DF77E-EB78-45EB-836F-50C3B017B4C2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteFile.UnitTests", "RemoteFile.UnitTests\RemoteFile.UnitTests.csproj", "{2769EBA9-6C62-4409-B637-FFA86E23749E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +19,10 @@ Global
{A006BFAB-20F7-4F42-8B5F-591268ACE836}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A006BFAB-20F7-4F42-8B5F-591268ACE836}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A006BFAB-20F7-4F42-8B5F-591268ACE836}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2769EBA9-6C62-4409-B637-FFA86E23749E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2769EBA9-6C62-4409-B637-FFA86E23749E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2769EBA9-6C62-4409-B637-FFA86E23749E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2769EBA9-6C62-4409-B637-FFA86E23749E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -22,4 +30,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8F3245C7-FCC9-4666-99E0-F8D63BBE8373}
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {2769EBA9-6C62-4409-B637-FFA86E23749E} = {856DF77E-EB78-45EB-836F-50C3B017B4C2}
+ EndGlobalSection
EndGlobal
diff --git a/RemoteFile/ApplicationSettings.cs b/RemoteFile/ApplicationSettings.cs
index 6cd8b207..af5f55cf 100644
--- a/RemoteFile/ApplicationSettings.cs
+++ b/RemoteFile/ApplicationSettings.cs
@@ -16,7 +16,7 @@
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
{
- class ApplicationSettings
+ public class ApplicationSettings
{
public enum FileTransferProtocolEnum
{
@@ -63,15 +63,27 @@ public static FileTransferProtocolEnum FileTransferProtocol
{
get
{
+ ILogger logger = LogHandler.GetClassLogger();
+
string protocolNames = string.Empty;
foreach (string protocolName in Enum.GetNames(typeof(FileTransferProtocolEnum)))
{
protocolNames += protocolName + ", ";
}
protocolNames = protocolNames.Substring(0, protocolNames.Length - 2);
+ string? protocolValue = configuration["FileTransferProtocol"];
- if (!Enum.TryParse(configuration["FileTransferProtocol"], out FileTransferProtocolEnum protocol))
- throw new RemoteFileException($"Invalid optional config.json FileTransferProtocol option of {configuration["FileTransferProtocol"]}. If present, must be one of these values: {protocolNames}.");
+ if (!PropertyUtilities.TryEnumParse(protocolValue, out bool isFlagCombination, out FileTransferProtocolEnum protocol))
+ throw new RemoteFileException($"Invalid optional config.json FileTransferProtocol option of {protocolValue}. If present, must be one of these values: {protocolNames}.");
+
+ // Issue: If received a comma-delimited list ("SCP,SFTP,Both"), it's treating it as a flag combination (i.e. mapping it to 0+1+2=3)
+ // If this happens, we want to default it to Both so it's resolved as a valid mapping.
+ if (isFlagCombination)
+ {
+ logger.LogWarning($"FileTransferProtocol config value {protocolValue} mapped to a flag combination. Setting FileTransferProtocol explicitly to Both.");
+ protocol = FileTransferProtocolEnum.Both;
+ }
+
return protocol;
}
}
diff --git a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
index 9015ca2f..aa737d27 100644
--- a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
+++ b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
@@ -200,6 +200,9 @@ private void LoadCustomProperties(string storeProperties)
IncludesChain = properties.IncludesChain == null || string.IsNullOrEmpty(properties.IncludesChain.Value) ? false : bool.Parse(properties.IncludesChain.Value);
SeparatePrivateKeyFilePath = properties.SeparatePrivateKeyFilePath == null || string.IsNullOrEmpty(properties.SeparatePrivateKeyFilePath.Value) ? String.Empty : properties.SeparatePrivateKeyFilePath.Value;
IgnorePrivateKeyOnInventory = properties.IgnorePrivateKeyOnInventory == null || string.IsNullOrEmpty(properties.IgnorePrivateKeyOnInventory.Value) ? false : bool.Parse(properties.IgnorePrivateKeyOnInventory.Value);
+
+ logger.LogDebug("Custom Properties have been loaded:");
+ logger.LogDebug($"IsTrustStore: {IsTrustStore}, IncludesChain: {IncludesChain}, SeparatePrivateKeyFilePath: {SeparatePrivateKeyFilePath}, IgnorePrivateKeyOnInventory: {IgnorePrivateKeyOnInventory}");
logger.MethodExit(LogLevel.Debug);
}
diff --git a/RemoteFile/InventoryBase.cs b/RemoteFile/InventoryBase.cs
index b7b4e930..27ae27c1 100644
--- a/RemoteFile/InventoryBase.cs
+++ b/RemoteFile/InventoryBase.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Keyfactor.Orchestrators.Extensions;
@@ -73,7 +74,12 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
catch (Exception ex)
{
logger.LogError($"Exception for {config.Capability}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}");
- return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:") };
+ return new JobResult
+ {
+ Result = OrchestratorJobStatusJobResult.Failure,
+ JobHistoryId = config.JobHistoryId,
+ FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:")
+ };
}
finally
{
@@ -84,14 +90,31 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
try
{
submitInventory.Invoke(inventoryItems);
- logger.LogDebug($"...End {config.Capability} job for job id {config.JobId}");
- return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId };
+ logger.LogDebug("...End {ConfigCapability} job for job id {ConfigJobId}", config.Capability, config.JobId);
+ var jobResultStatus = OrchestratorJobStatusJobResult.Success;
+ var jobMsg = string.Empty;
+ if (certificateStore.RemoteHandler != null && certificateStore.RemoteHandler.Warnings.Length > 0)
+ {
+ jobResultStatus = OrchestratorJobStatusJobResult.Warning;
+ jobMsg = certificateStore.RemoteHandler.Warnings.Aggregate(jobMsg, (current, warning) => current + (warning + Environment.NewLine));
+ }
+ return new JobResult
+ {
+ Result = jobResultStatus,
+ JobHistoryId = config.JobHistoryId,
+ FailureMessage = jobMsg
+ };
}
catch (Exception ex)
{
string errorMessage = RemoteFileException.FlattenExceptionMessages(ex, string.Empty);
logger.LogError($"Exception returning certificates for {config.Capability}: {errorMessage} for job id {config.JobId}");
- return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:") };
+ return new JobResult
+ {
+ Result = OrchestratorJobStatusJobResult.Failure,
+ JobHistoryId = config.JobHistoryId,
+ FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:")
+ };
}
}
}
diff --git a/RemoteFile/PropertyUtilities.cs b/RemoteFile/PropertyUtilities.cs
new file mode 100644
index 00000000..f69a5d99
--- /dev/null
+++ b/RemoteFile/PropertyUtilities.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace Keyfactor.Extensions.Orchestrator.RemoteFile;
+
+public static class PropertyUtilities
+{
+ public static bool TryEnumParse(string value, out bool isFlagCombination, out T result) where T : struct, Enum
+ {
+ isFlagCombination = false;
+ result = default(T);
+
+ // First, do the normal TryParse
+ if (!Enum.TryParse(value, out result))
+ {
+ return false;
+ }
+
+ // Check if the enum has the Flags attribute
+ bool hasFlags = typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
+
+ // If it doesn't have Flags attribute but the input contains commas,
+ // this might be unintended flag parsing
+ if (!hasFlags && value.Contains(','))
+ {
+ // Check if the parsed result corresponds to a defined enum value
+ if (!Enum.IsDefined(typeof(T), result))
+ {
+ isFlagCombination = true;
+ return true;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs
index cada5195..793e976c 100644
--- a/RemoteFile/RemoteCertificateStore.cs
+++ b/RemoteFile/RemoteCertificateStore.cs
@@ -252,6 +252,7 @@ internal void CreateCertificateStore(ICertificateStoreSerializer certificateStor
internal void AddCertificate(string alias, string certificateEntry, bool overwrite, string pfxPassword, bool removeRootCertificate)
{
logger.MethodEntry(LogLevel.Debug);
+ logger.LogDebug($"Alias: {alias}, Certificate Entry: {certificateEntry}, Overwrite: {overwrite}, RemoveRootCertificate: {removeRootCertificate}");
try
{
diff --git a/RemoteFile/RemoteFileJobTypeBase.cs b/RemoteFile/RemoteFileJobTypeBase.cs
index 61640b0d..02e7f562 100644
--- a/RemoteFile/RemoteFileJobTypeBase.cs
+++ b/RemoteFile/RemoteFileJobTypeBase.cs
@@ -11,6 +11,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
+using Keyfactor.Logging;
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
{
@@ -35,6 +36,8 @@ public abstract class RemoteFileJobTypeBase
internal void SetJobProperties(JobConfiguration config, CertificateStore certificateStoreDetails, ILogger logger)
{
+ logger.MethodEntry(LogLevel.Debug);
+
logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}...");
logger.LogDebug($"Server: {certificateStoreDetails.ClientMachine}");
logger.LogDebug($"Store Path: {certificateStoreDetails.StorePath}");
@@ -73,9 +76,21 @@ internal void SetJobProperties(JobConfiguration config, CertificateStore certifi
FileTransferProtocol = ApplicationSettings.FileTransferProtocol;
if (properties.FileTransferProtocol != null && !string.IsNullOrEmpty(properties.FileTransferProtocol.Value))
{
+ logger.LogDebug($"Attempting to map file transfer protocol from properties. Current Value: {FileTransferProtocol}, Property Value: {properties.FileTransferProtocol.Value}");
ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol;
- if (Enum.TryParse(properties.FileTransferProtocol.Value, out fileTransferProtocol))
+ if (PropertyUtilities.TryEnumParse(properties.FileTransferProtocol.Value, out bool isFlagCombination, out fileTransferProtocol))
+ {
+ logger.LogDebug($"Successfully mapped file transfer protocol from properties. Value: {fileTransferProtocol}");
FileTransferProtocol = fileTransferProtocol;
+ }
+
+ // Issue: If received a comma-delimited list ("SCP,SFTP,Both"), it's treating it as a flag combination (i.e. mapping it to 0+1+2=3)
+ // If this happens, we want to default it to Both so it's resolved as a valid mapping.
+ if (isFlagCombination)
+ {
+ logger.LogWarning($"FileTransferProtocol job property value {properties.FileTransferProtocol.Value} mapped to a flag combination. Setting FileTransferProtocol explicitly to Both.");
+ FileTransferProtocol = ApplicationSettings.FileTransferProtocolEnum.Both;
+ }
}
if (config.JobProperties != null)
@@ -83,7 +98,28 @@ internal void SetJobProperties(JobConfiguration config, CertificateStore certifi
KeyType = !config.JobProperties.ContainsKey("keyType") || config.JobProperties["keyType"] == null || string.IsNullOrEmpty(config.JobProperties["keyType"].ToString()) ? string.Empty : config.JobProperties["keyType"].ToString();
KeySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) || !int.TryParse(config.JobProperties["keySize"].ToString(), out int notUsed2) ? 2048 : Convert.ToInt32(config.JobProperties["keySize"]);
SubjectText = !config.JobProperties.ContainsKey("subjectText") || config.JobProperties["subjectText"] == null || string.IsNullOrEmpty(config.JobProperties["subjectText"].ToString()) ? string.Empty : config.JobProperties["subjectText"].ToString();
- }
+ }
+
+ logger.LogDebug("Store properties have been configured successfully. Property values:");
+ logger.LogDebug($"UserName: {UserName}");
+ logger.LogDebug($"UserPassword: {LogSensitiveField(UserPassword)}");
+ logger.LogDebug($"StorePassword: {LogSensitiveField(StorePassword)}");
+ logger.LogDebug($"SudoImpersonatedUser: {SudoImpersonatedUser}");
+ logger.LogDebug($"RemoveRootCertificate: {RemoveRootCertificate}");
+ logger.LogDebug($"SSHPort: {SSHPort}");
+ logger.LogDebug($"IncludePortInSPN: {IncludePortInSPN}");
+ logger.LogDebug($"FileTransferProtocol: {FileTransferProtocol}");
+ logger.LogDebug($"CreateCSROnDevice: {CreateCSROnDevice}");
+ logger.LogDebug($"KeyType: {KeyType}");
+ logger.LogDebug($"KeySize: {KeySize}");
+ logger.LogDebug($"SubjectText: {SubjectText}");
+
+ logger.MethodExit(LogLevel.Debug);
+ }
+
+ private string LogSensitiveField(string input)
+ {
+ return string.IsNullOrWhiteSpace(input) ? "" : "(hidden)";
}
}
}
diff --git a/RemoteFile/RemoteHandlers/BaseRemoteHandler.cs b/RemoteFile/RemoteHandlers/BaseRemoteHandler.cs
index f8a0ca66..457caed3 100644
--- a/RemoteFile/RemoteHandlers/BaseRemoteHandler.cs
+++ b/RemoteFile/RemoteHandlers/BaseRemoteHandler.cs
@@ -5,6 +5,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
+using System;
using Keyfactor.Logging;
using Microsoft.Extensions.Logging;
@@ -18,6 +19,8 @@ abstract class BaseRemoteHandler : IRemoteHandler
internal const string PASSWORD_MASK_VALUE = "[PASSWORD]";
internal const int PASSWORD_LENGTH_MAX = 100;
internal const string LINUX_PERMISSION_REGEXP = "^[0-7]{3}$";
+
+ public string[] Warnings { get; set; } = Array.Empty();
public string Server { get; set; }
diff --git a/RemoteFile/RemoteHandlers/IRemoteHandler.cs b/RemoteFile/RemoteHandlers/IRemoteHandler.cs
index 619e3b96..32466a97 100644
--- a/RemoteFile/RemoteHandlers/IRemoteHandler.cs
+++ b/RemoteFile/RemoteHandlers/IRemoteHandler.cs
@@ -13,6 +13,8 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers
///
interface IRemoteHandler
{
+
+ string [] Warnings { get; set; }
void Terminate();
string RunCommand(string commandText, object[] arguments, bool withSudo, string[] passwordsToMaskInLog);
diff --git a/RemoteFile/RemoteHandlers/SSHHandler.cs b/RemoteFile/RemoteHandlers/SSHHandler.cs
index d89d255f..6d664338 100644
--- a/RemoteFile/RemoteHandlers/SSHHandler.cs
+++ b/RemoteFile/RemoteHandlers/SSHHandler.cs
@@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Security.Cryptography;
using System.Text;
@@ -251,6 +252,7 @@ public override byte[] DownloadCertificateFile(string path)
if (!string.IsNullOrEmpty(ApplicationSettings.SeparateUploadFilePath) && IsStoreServerLinux)
{
+ _logger.LogDebug("Splitting store path");
SplitStorePathFile(path, out altPathOnly, out altFileNameOnly);
downloadPath = ApplicationSettings.SeparateUploadFilePath + altFileNameOnly;
RunCommand($"cp {path} {downloadPath}", null, ApplicationSettings.UseSudo, null);
@@ -260,8 +262,14 @@ public override byte[] DownloadCertificateFile(string path)
bool scpError = false;
+ _logger.LogDebug($"Download path: {downloadPath}");
+ _logger.LogDebug($"IsStoreServerLinux: {IsStoreServerLinux}");
+ _logger.LogDebug($"FileTransferProtocol: {FileTransferProtocol}");
+ bool attemptedDownload = false;
+
if (FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both || FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.SCP)
{
+ _logger.LogDebug($"Attempting SCP download...");
using (ScpClient client = new ScpClient(Connection))
{
try
@@ -288,6 +296,7 @@ public override byte[] DownloadCertificateFile(string path)
}
finally
{
+ attemptedDownload = true;
client.Disconnect();
}
}
@@ -295,6 +304,7 @@ public override byte[] DownloadCertificateFile(string path)
if ((FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both && scpError) || FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.SFTP)
{
+ _logger.LogDebug($"Attempting SFTP download...");
using (SftpClient client = new SftpClient(Connection))
{
try
@@ -317,10 +327,21 @@ public override byte[] DownloadCertificateFile(string path)
}
finally
{
+ attemptedDownload = true;
client.Disconnect();
}
}
}
+ if (!attemptedDownload)
+ {
+ FileTransferProtocol = ApplicationSettings.FileTransferProtocolEnum.Both;
+ var warningMsg = "No download attempted. Setting FileTransferProtocol to 'Both' and retrying download.";
+ _logger.LogWarning(warningMsg);
+ // append to Warnings global array
+ Warnings = Warnings.Length == 0 ? new[] { warningMsg } : Warnings.Append(warningMsg).ToArray();
+
+ return DownloadCertificateFile(path);
+ }
if (!string.IsNullOrEmpty(ApplicationSettings.SeparateUploadFilePath) && IsStoreServerLinux)
{