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) {