diff --git a/.github/workflows/keyfactor-merge-store-types.yml b/.github/workflows/keyfactor-merge-store-types.yml deleted file mode 100644 index c70659f8..00000000 --- a/.github/workflows/keyfactor-merge-store-types.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Keyfactor Merge Cert Store Types -on: [workflow_dispatch] - -jobs: - get-manifest-properties: - runs-on: windows-latest - outputs: - update_catalog: ${{ steps.read-json.outputs.update_catalog }} - integration_type: ${{ steps.read-json.outputs.integration_type }} - steps: - - uses: actions/checkout@v3 - - name: Store json - id: read-json - shell: pwsh - run: | - $json = Get-Content integration-manifest.json | ConvertFrom-Json - $myvar = $json.update_catalog - echo "update_catalog=$myvar" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - $myvar = $json.integration_type - echo "integration_type=$myvar" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - - call-update-store-types-workflow: - needs: get-manifest-properties - if: needs.get-manifest-properties.outputs.integration_type == 'orchestrator' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - uses: Keyfactor/actions/.github/workflows/update-store-types.yml@main - secrets: - token: ${{ secrets.UPDATE_STORE_TYPES }} diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index a4649f2b..c9b180cc 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -11,10 +11,17 @@ on: jobs: call-starter-workflow: - uses: keyfactor/actions/.github/workflows/starter.yml@3.1.2 + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + with: + command_token_url: ${{ vars.COMMAND_TOKEN_URL }} # Only required for doctool generated screenshots + command_hostname: ${{ vars.COMMAND_HOSTNAME }} # Only required for doctool generated screenshots + command_base_api_path: ${{ vars.COMMAND_API_PATH }} # Only required for doctool generated screenshots secrets: - token: ${{ secrets.V2BUILDTOKEN}} - APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} - gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} - gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} - scan_token: ${{ secrets.SAST_TOKEN }} + token: ${{ secrets.V2BUILDTOKEN}} # REQUIRED + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} # Only required for golang builds + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} # Only required for golang builds + scan_token: ${{ secrets.SAST_TOKEN }} # REQUIRED + entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} # Only required for doctool generated screenshots + entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} # Only required for doctool generated screenshots + command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} # Only required for doctool generated screenshots + command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} # Only required for doctool generated screenshots \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f070bdae..21f10966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +v3.0.0 +- Added support for post quantum ML-DSA certificates for store types RFPEM, RFJKS, RFPkcs12, and RFDER +- Added support for On Device Key Generation (ODKG) +- Removed FileTransferProtocol option from the config.json and store type custom fields. Integration will now always attempt SCP first and then SFTP (if SCP fails) for all file transfers. +- Removed .net8/.net6 dual build capabilities. 3.0 and later releases .net8 only due to mandatory use of later Keyfactor libraries that have minimum requirements of .net8. + +v2.12.0 +- Added config.json setting and its override store level custom field - AllowShellCommands. If "N" (default "Y"), SFTP will be used to create stores and move files on Linux-based certificate store servers. No Linux shell commands will be used in the integration. + v2.11.5 - Bug Fix: Rare race condition loading config settings when multiple RemoteFile jobs are running simultaneously on the same orchestrator - Documentation update to better list out what Linux commands get executed under what situations in Requirements & Prerequisites section diff --git a/README.md b/README.md index 2d65a67e..c381f085 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ managed is achieved via either an `SSH` (for Linux and possibly Windows orchestr orchestrated servers) connection. When acting as an agent, `SSH/WinRM` may still be used, OR the certificate store can be configured to bypass these and instead directly access the orchestrator server's file system. +RemoteFile Management and Inventory capabilities support the handling of certificates with RSA, ECC, and ML-DSA ditital signature algorithms. However, +ODKG (On Device Key Generation) introduced in release 3.0 for store types RFJKS, RFPEM, RFPkcs12, and RFDER, currently only supports +RSA and ECC. Also, please be aware that the CSR and key pairs generated by the RemoteFile Orchestrator extension when utilizing +the ODKG capability are technically created on the orchestrator server itself and not the target device if the orchestrator is +managing a store or stores on separate servers than where the Universal Orchestrator is installed. + ![](images/orchestrator-agent.png) The supported configurations of Universal Orchestrator hosts and managed orchestrated servers are detailed below: @@ -109,6 +115,7 @@ Before installing the Remote File Universal Orchestrator extension, we recommend | `tee` | | X(c) | X(a) | X(a) | | | `rm` | | X(d) | X(d) | X(d) | | | `install` | | | | | X | +| `stat` | | | | | X | | `orapki` | | X(e) | X(e) | X(e) | | | `gskcapicmd` | | X(f) | X(f) | X(f) | | @@ -120,10 +127,11 @@ Before installing the Remote File Universal Orchestrator extension, we recommend (f) - RFKDB store type only 2. When orchestrating management of local or external certificate stores, the Remote File Orchestrator Extension makes - use of SFTP and/or SCP to transfer files to and from the orchestrated server. `SFTP/SCP` cannot make use of `sudo`, so - all folders containing certificate stores will need to allow SFTP/SCP file transfer for the user assigned to the + use of SCP or SFTP to transfer files to and from the orchestrated server. SCP is attempted first, and if that + fails, SFTP is attempted. `SCP/SFTP` cannot make use of `sudo`, so + all folders containing certificate stores will need to allow SCP/SFTP file transfer for the user assigned to the certificate store/discovery job. If this is not possible, set the values in the `config.json` appropriately to use an - alternative upload/download folder that does allow `SFTP/SCP` file transfer. If the certificate store/discovery job is + alternative upload/download folder that does allow `SCP/SFTP` file transfer. If the certificate store/discovery job is configured for local (agent) access, the account running the Keyfactor Universal Orchestrator service must have access to read/write to the certificate store location, OR the `config.json` file must be set up to use the alternative upload/download file. @@ -160,8 +168,8 @@ Please reference [Certificate Stores and Discovery Jobs](#certificate-stores-and creating certificate stores for the `RemoteFile` Orchestrator Extension. - -Please consult with your system administrator for more information on configuring `SSH/SFTP/SCP` or `WinRM` in your environment. +C +Please consult with your system administrator for more information on configuring `SSH/SCP/SFTP` or `WinRM` in your environment. ## Certificate Store Types @@ -194,7 +202,7 @@ of type `JKS` have been deprecated as of `JDK 9`. | Add | ✅ Checked | | Remove | ✅ Checked | | Discovery | ✅ Checked | -| Reenrollment | 🔲 Unchecked | +| Reenrollment | ✅ Checked | | Create | ✅ Checked | #### Store Type Creation @@ -237,7 +245,7 @@ the Keyfactor Command Portal | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add | | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove | | Supports Discovery | ✅ Checked | Check the box. Indicates that the Store Type supports Discovery | - | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment | + | Supports Reenrollment | ✅ Checked | Indicates that the Store Type supports Reenrollment | | Supports Create | ✅ Checked | Check the box. Indicates that the Store Type supports store creation | | Needs Server | ✅ Checked | Determines if a target server name is required when creating store | | Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint | @@ -274,8 +282,8 @@ the Keyfactor Command Portal | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | String | | 🔲 Unchecked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -313,7 +321,7 @@ The `RFPEM` store type can be used to manage `PEM` encoded files. | Add | ✅ Checked | | Remove | ✅ Checked | | Discovery | ✅ Checked | -| Reenrollment | 🔲 Unchecked | +| Reenrollment | ✅ Checked | | Create | ✅ Checked | #### Store Type Creation @@ -356,7 +364,7 @@ the Keyfactor Command Portal | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add | | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove | | Supports Discovery | ✅ Checked | Check the box. Indicates that the Store Type supports Discovery | - | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment | + | Supports Reenrollment | ✅ Checked | Indicates that the Store Type supports Reenrollment | | Supports Create | ✅ Checked | Check the box. Indicates that the Store Type supports store creation | | Needs Server | ✅ Checked | Determines if a target server name is required when creating store | | Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint | @@ -397,8 +405,8 @@ the Keyfactor Command Portal | IgnorePrivateKeyOnInventory | Ignore Private Key On Inventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to disregard the private key during inventory. Setting this to 'true' will allow inventory for the store without needing to supply the location of the private key or the password if the key is encrypted. However, doing this makes the store in effect inventory-only and no management jobs will be able to be run for this store. Example: 'true' to ignore the private key or 'false' to include it. | Bool | false | 🔲 Unchecked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -434,7 +442,7 @@ The `RFPkcs12` store type can be used to manage any `PKCS#12` compliant file for | Add | ✅ Checked | | Remove | ✅ Checked | | Discovery | ✅ Checked | -| Reenrollment | 🔲 Unchecked | +| Reenrollment | ✅ Checked | | Create | ✅ Checked | #### Store Type Creation @@ -477,7 +485,7 @@ the Keyfactor Command Portal | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add | | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove | | Supports Discovery | ✅ Checked | Check the box. Indicates that the Store Type supports Discovery | - | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment | + | Supports Reenrollment | ✅ Checked | Indicates that the Store Type supports Reenrollment | | Supports Create | ✅ Checked | Check the box. Indicates that the Store Type supports store creation | | Needs Server | ✅ Checked | Determines if a target server name is required when creating store | | Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint | @@ -514,8 +522,8 @@ the Keyfactor Command Portal | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | String | | 🔲 Unchecked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -545,7 +553,7 @@ The `RFDER` store type can be used to manage DER encoded files. | Add | ✅ Checked | | Remove | ✅ Checked | | Discovery | ✅ Checked | -| Reenrollment | 🔲 Unchecked | +| Reenrollment | ✅ Checked | | Create | ✅ Checked | #### Store Type Creation @@ -588,7 +596,7 @@ the Keyfactor Command Portal | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add | | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove | | Supports Discovery | ✅ Checked | Check the box. Indicates that the Store Type supports Discovery | - | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment | + | Supports Reenrollment | ✅ Checked | Indicates that the Store Type supports Reenrollment | | Supports Create | ✅ Checked | Check the box. Indicates that the Store Type supports store creation | | Needs Server | ✅ Checked | Determines if a target server name is required when creating store | | Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint | @@ -626,8 +634,8 @@ the Keyfactor Command Portal | SeparatePrivateKeyFilePath | Separate Private Key File Location | The SeparatePrivateKeyFilePath field should contain the full path and file name where the separate private key file will be stored if it is to be kept outside the main certificate file. Example: '/path/to/privatekey.der'. | String | | 🔲 Unchecked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -740,8 +748,8 @@ the Keyfactor Command Portal | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides [config.json](#post-installation) DefaultSudoImpersonatedUser setting. | String | | 🔲 Unchecked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -856,8 +864,8 @@ the Keyfactor Command Portal | WorkFolder | Location to use for creation/removal of work files | The WorkFolder field should contain the path on the managed server where temporary work files can be created, modified, and deleted during Inventory and Management jobs. Example: '/path/to/workfolder'. | String | | ✅ Checked | | RemoveRootCertificate | Remove Root Certificate from Chain | Remove root certificate from chain when adding/renewing a certificate in a store. | Bool | False | 🔲 Unchecked | | IncludePortInSPN | Include Port in SPN for WinRM | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | Bool | False | 🔲 Unchecked | - | FileTransferProtocol | File Transfer Protocol to Use | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | MultipleChoice | ,SCP,SFTP,Both | 🔲 Unchecked | | SSHPort | SSH Port | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | String | | 🔲 Unchecked | + | UseShellCommands | Use Shell Commands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -923,10 +931,10 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is loc "CreateStoreIfMissing": "N", "UseNegotiate": "N", "SeparateUploadFilePath": "", - "FileTransferProtocol": "SCP", "DefaultLinuxPermissionsOnStoreCreation": "600", "DefaultOwnerOnStoreCreation": "", - "SSHPort": "" + "SSHPort": "", + "UseShellCommands": "Y" } ``` @@ -937,10 +945,10 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is loc | `CreateStoreIfMissing` | `N` | `Y/N` | Determines if a certificate store should be created during a Management-Add job if it doesn't exist. If `N`, the job will return an error. If `Y`, the store will be created and the certificate added. | | `UseNegotiate` | `N` | `Y/N` | Determines if WinRM should use Negotiate (Y) when connecting to the remote server. Only applicable for Windows hosted certificate stores. | | `SeparateUploadFilePath` | | Any valid, existing Linux path | Path on the orchestrated server for uploading/downloading temporary work files. If empty, the certificate store location will be used. Only applicable for Linux hosted certificate stores. | -| `FileTransferProtocol` | `SCP` | `SCP, SFTP, Both` | Protocol used for uploading/downloading files. If `Both`, `SCP` will be tried first, then `SFTP`. Only applicable for Linux hosted certificate stores. | | `DefaultLinuxPermissionsOnStoreCreation` | `600` | Any 3-digit value from 000-777 | Linux file permissions set on new certificate stores. If blank, permissions from the parent folder will be used. Only applicable for Linux hosted certificate stores. | | `DefaultOwnerOnStoreCreation` | | Any valid user id | Sets the owner for newly created certificate stores. Can include group with format `ownerId:groupId`. If blank, the owner of the parent folder will be used. Only applicable for Linux hosted certificate stores. | | `SSHPort` | | Any valid integer representing a port | The port that SSH is listening on. Default is 22. Only applicable for Linux hosted certificate stores. | +| `UseShellCommands` | `Y` | `Y/N` | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | ## Defining Certificate Stores @@ -979,8 +987,8 @@ The Remote File Universal Orchestrator extension implements 6 Certificate Store | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1014,8 +1022,8 @@ The Remote File Universal Orchestrator extension implements 6 Certificate Store | Properties.SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1084,8 +1092,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | IgnorePrivateKeyOnInventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to disregard the private key during inventory. Setting this to 'true' will allow inventory for the store without needing to supply the location of the private key or the password if the key is encrypted. However, doing this makes the store in effect inventory-only and no management jobs will be able to be run for this store. Example: 'true' to ignore the private key or 'false' to include it. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1123,8 +1131,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | Properties.IgnorePrivateKeyOnInventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to disregard the private key during inventory. Setting this to 'true' will allow inventory for the store without needing to supply the location of the private key or the password if the key is encrypted. However, doing this makes the store in effect inventory-only and no management jobs will be able to be run for this store. Example: 'true' to ignore the private key or 'false' to include it. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1189,8 +1197,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1224,8 +1232,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | Properties.SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides DefaultSudoImpersonatedUser [config.json](#post-installation) setting. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1291,8 +1299,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | SeparatePrivateKeyFilePath | The SeparatePrivateKeyFilePath field should contain the full path and file name where the separate private key file will be stored if it is to be kept outside the main certificate file. Example: '/path/to/privatekey.der'. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1327,8 +1335,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | Properties.SeparatePrivateKeyFilePath | The SeparatePrivateKeyFilePath field should contain the full path and file name where the separate private key file will be stored if it is to be kept outside the main certificate file. Example: '/path/to/privatekey.der'. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1393,8 +1401,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides [config.json](#post-installation) DefaultSudoImpersonatedUser setting. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1428,8 +1436,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | Properties.SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. Overrides [config.json](#post-installation) DefaultSudoImpersonatedUser setting. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1495,8 +1503,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | WorkFolder | The WorkFolder field should contain the path on the managed server where temporary work files can be created, modified, and deleted during Inventory and Management jobs. Example: '/path/to/workfolder'. | | RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | @@ -1531,8 +1539,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov | Properties.WorkFolder | The WorkFolder field should contain the path on the managed server where temporary work files can be created, modified, and deleted during Inventory and Management jobs. Example: '/path/to/workfolder'. | | Properties.RemoveRootCertificate | Remove root certificate from chain when adding/renewing a certificate in a store. | | Properties.IncludePortInSPN | Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations. | - | Properties.FileTransferProtocol | Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting. | | Properties.SSHPort | Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting. | + | Properties.UseShellCommands | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | 3. **Import the CSV file to create the certificate stores** @@ -1619,6 +1627,29 @@ For agent mode (accessing stores on the same server where Universal Orchestrator - `Store Type` + `Client Machine` + `Store Path` must be unique in Keyfactor Command - Best practice: Use the full DNS or IP Address to the left of the `|` character +## Use Shell Commands Setting + +The Use Shell Commands Setting (orchestrator level in config.json and per store override of this value as a custom field value) +determines whether or not Linux shell commands will be used when managing certificate stores on Linux-based servers. +This is useful for environments where shell access is limited or even not allowed. In those scenarios setting this value to 'N' +will substitute SFTP commands for certain specific Linux shell commands. The following restrictions will be in place when +using RemoteFile in this mode: +1. The config.json option SeparateUploadFilePath must NOT be used (option missing from the config.json file or set to empty) for shell +commands to be suppressed for all use cases. +2. The config.json and custom field options DefaultLinuxPermissionsOnStoreCreation, DefaultOwnerOnStoreCreation, +LinuxFilePermissionsOnStoreCreation, and LinuxFileOwnerOnStoreCreation are not supported and will be ignored. As a result, file +permissions and ownership when creating certificate stores will be based on the user assigned to the Command certificate store and +other Linux environmental settings. +3. Discovery jobs are excluded and will still use the `find` shell command +4. A rare issue exists where the user id assigned to a certificate store has an expired password causing the orchestrator to hang +when attempting an SCP/SFTP connection. A modification was added to RemoteFile to check for this condition. Running RemoteFile +with Use Shell Commands = N will cause this validation check to NOT occur. +5. Both RFORA and RFKDB use proprietary CLI commands in order to manage their respective certificate stores. These commands +will still be executed when Use Shell Commands is set to Y. +6. If executing in local mode ('|LocalMachine' at the end of your client machine name for your certificate store), Use Shell +Commands = 'N' will have no effect. Shell commands will continue to be used because there will be no SSH connection +available from which to execute SFTP commands. + ## Developer Notes The Remote File Orchestrator Extension is designed to be highly extensible, enabling its use with various file-based diff --git a/RemoteFile.UnitTests/ApplicationSettingsTests.cs b/RemoteFile.UnitTests/ApplicationSettingsTests.cs deleted file mode 100644 index 96201e78..00000000 --- a/RemoteFile.UnitTests/ApplicationSettingsTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Xunit; -using Keyfactor.Extensions.Orchestrator.RemoteFile; - -namespace RemoteFile.UnitTests; - -public class ApplicationSettingsTests -{ - [Fact] - public void FileTransferProtocol_WhenPopulatedWithValidValue_ReturnsValue() - { - Assert.Equal(ApplicationSettings.FileTransferProtocolEnum.SCP, ApplicationSettings.FileTransferProtocol); - } - - [Fact] - public void FileTransferProtocol_WhenAllThreePopulated_DefaultsToBoth() - { - Assert.Equal(ApplicationSettings.FileTransferProtocolEnum.Both, ApplicationSettings.FileTransferProtocol); - } -} diff --git a/RemoteFile.UnitTests/PropertyUtilitiesTests.cs b/RemoteFile.UnitTests/PropertyUtilitiesTests.cs deleted file mode 100644 index 42b63cd8..00000000 --- a/RemoteFile.UnitTests/PropertyUtilitiesTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -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 index b71b933b..8131d038 100644 --- a/RemoteFile.UnitTests/RemoteFile.UnitTests.csproj +++ b/RemoteFile.UnitTests/RemoteFile.UnitTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable @@ -9,8 +9,11 @@ - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/RemoteFile/ApplicationSettings.cs b/RemoteFile/ApplicationSettings.cs index e22bfdad..d0ff264f 100644 --- a/RemoteFile/ApplicationSettings.cs +++ b/RemoteFile/ApplicationSettings.cs @@ -19,13 +19,6 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile { public class ApplicationSettings { - public enum FileTransferProtocolEnum - { - SCP, - SFTP, - Both - } - private const string DEFAULT_LINUX_PERMISSION_SETTING = ""; private const string DEFAULT_OWNER_SETTING = ""; private const string DEFAULT_SUDO_IMPERSONATION_SETTING = ""; @@ -40,8 +33,8 @@ public enum FileTransferProtocolEnum public static string DefaultLinuxPermissionsOnStoreCreation { get { return configuration.ContainsKey("DefaultLinuxPermissionsOnStoreCreation") ? configuration["DefaultLinuxPermissionsOnStoreCreation"] : DEFAULT_LINUX_PERMISSION_SETTING; } } public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"] : DEFAULT_OWNER_SETTING; } } public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } } - public static bool CreateCSROnDevice { get { return configuration.ContainsKey("CreateCSROnDevice") ? configuration["CreateCSROnDevice"]?.ToUpper() == "Y" : false; } } public static string TempFilePathForODKG { get { return configuration.ContainsKey("TempFilePathForODKG") ? configuration["TempFilePathForODKG"] : string.Empty; } } + public static bool UseShellCommands { get { return configuration.ContainsKey("UseShellCommands") ? configuration["UseShellCommands"]?.ToUpper() == "Y" : true; } } public static int SSHPort { get @@ -60,34 +53,6 @@ public static int SSHPort } } } - 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 (!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; - } - } static ApplicationSettings() { @@ -142,8 +107,6 @@ private static void ValidateConfiguration(ILogger logger) logger.LogDebug($"Missing configuration parameter - DefaultLinuxPermissionsOnStoreCreation. Will set to default value of '{DEFAULT_LINUX_PERMISSION_SETTING}'"); if (!configuration.ContainsKey("DefaultOwnerOnStoreCreation")) logger.LogDebug($"Missing configuration parameter - DefaultOwnerOnStoreCreation. Will set to default value of '{DEFAULT_OWNER_SETTING}'"); - if (!configuration.ContainsKey("FileTransferProtocol")) - logger.LogDebug($"Missing configuration parameter - FileTransferProtocol. Will set to default value of 'SCP'"); } private static string AddTrailingSlash(string path) diff --git a/RemoteFile/Discovery.cs b/RemoteFile/Discovery.cs index c468d41b..920407c2 100644 --- a/RemoteFile/Discovery.cs +++ b/RemoteFile/Discovery.cs @@ -57,7 +57,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd string userPassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Server Password", config.ServerPassword); certificateStore = new RemoteCertificateStore(config.ClientMachine, userName, userPassword, directoriesToSearch[0].Substring(0, 1) == "/" ? RemoteCertificateStore.ServerTypeEnum.Linux : RemoteCertificateStore.ServerTypeEnum.Windows, ApplicationSettings.SSHPort); - certificateStore.Initialize(ApplicationSettings.DefaultSudoImpersonatedUser); + certificateStore.Initialize(ApplicationSettings.DefaultSudoImpersonatedUser, true); if (directoriesToSearch.Length == 0) throw new RemoteFileException("Blank or missing search directories for Discovery."); diff --git a/RemoteFile/ImplementedStoreTypes/DER/DERCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/DER/DERCertificateStoreSerializer.cs index ef70a6e2..1d786216 100644 --- a/RemoteFile/ImplementedStoreTypes/DER/DERCertificateStoreSerializer.cs +++ b/RemoteFile/ImplementedStoreTypes/DER/DERCertificateStoreSerializer.cs @@ -5,24 +5,22 @@ // 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 System.Collections.Generic; -using System.IO; -using System.Linq; - -using Newtonsoft.Json; - +using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; +using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; using Keyfactor.Logging; +using Keyfactor.PKI.CryptographicObjects.Formatters; using Keyfactor.PKI.PrivateKeys; using Keyfactor.PKI.X509; -using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; -using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; - using Microsoft.Extensions.Logging; - +using Newtonsoft.Json; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Tls; using Org.BouncyCastle.X509; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Keyfactor.Extensions.Orchestrator.RemoteFile.DER { @@ -94,8 +92,7 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer throw new RemoteFileException($"DER certificate store has a private key at {SeparatePrivateKeyFilePath}, but no private key was passed with the certificate to this job."); } - CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(certificateStore.GetCertificate(alias).Certificate); - certificateBytes = certConverter.ToDER(string.IsNullOrEmpty(storePassword) ? string.Empty : storePassword); + certificateBytes = CryptographicObjectFormatter.DER.Format(certificateStore.GetCertificate(alias).Certificate); if (!string.IsNullOrEmpty(SeparatePrivateKeyFilePath)) { diff --git a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs index aa737d27..936e4583 100644 --- a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs +++ b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs @@ -5,30 +5,31 @@ // 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 System.Collections.Generic; -using System.Text; -using System.IO; - -using Newtonsoft.Json; - +using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; +using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; using Keyfactor.Logging; +using Keyfactor.PKI.CryptographicObjects.Formatters; +using Keyfactor.PKI.Extensions; +using Keyfactor.PKI.PEM; using Keyfactor.PKI.PrivateKeys; using Keyfactor.PKI.X509; -using Keyfactor.PKI.PEM; -using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; -using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; - using Microsoft.Extensions.Logging; - -using Org.BouncyCastle.Math; +using Newtonsoft.Json; +using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; +using System; +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.IO; +using System.Linq; using System.Security.Cryptography; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Asn1.X9; +using System.Text; +using static Keyfactor.PKI.PEM.PemUtilities; namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PEM { @@ -37,8 +38,6 @@ class PEMCertificateStoreSerializer : ICertificateStoreSerializer string[] PrivateKeyDelimetersPkcs8 = new string[] { "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----" }; string[] PrivateKeyDelimetersRSA = new string[] { "-----BEGIN RSA PRIVATE KEY-----" }; string[] PrivateKeyDelimetersEC = new string[] { "-----BEGIN EC PRIVATE KEY-----" }; - string CertDelimBeg = "-----BEGIN CERTIFICATE-----"; - string CertDelimEnd = "-----END CERTIFICATE-----"; private enum PrivateKeyTypeEnum { @@ -54,7 +53,7 @@ private enum PrivateKeyTypeEnum private ILogger logger; - public PEMCertificateStoreSerializer(string storeProperties) + public PEMCertificateStoreSerializer(string storeProperties) { logger = LogHandler.GetClassLogger(this.GetType()); LoadCustomProperties(storeProperties); @@ -63,7 +62,7 @@ public PEMCertificateStoreSerializer(string storeProperties) public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory) { logger.MethodEntry(LogLevel.Debug); - + Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder(); Pkcs12Store store = storeBuilder.Build(); @@ -72,9 +71,9 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s if (IsTrustStore || (isInventory && IgnorePrivateKeyOnInventory)) { - foreach(X509CertificateEntry certificate in certificates) + foreach (X509CertificateEntry certificate in certificates) { - store.SetCertificateEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificate.Certificate).ToX509Certificate2().Thumbprint, certificate); + store.SetCertificateEntry(certificate.Certificate.Thumbprint(), certificate); } } else @@ -82,7 +81,7 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s PrivateKeyTypeEnum privateKeyType; AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out privateKeyType); - store.SetKeyEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificates[0].Certificate).ToX509Certificate2().Thumbprint, keyEntry, certificates); + store.SetKeyEntry(certificates[0].Certificate.Thumbprint(), keyEntry, certificates); } // Second Pkcs12Store necessary because of an obscure BC bug where creating a Pkcs12Store without .Load (code above using "Set" methods only) does not set all internal hashtables necessary to avoid an error later @@ -113,8 +112,7 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer if (certificateStore.IsKeyEntry(alias)) throw new RemoteFileException("Cannot add a certificate with a private key to a PEM trust store."); - CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(certificateStore.GetCertificate(alias).Certificate); - pemString += certConverter.ToPEM(true); + pemString += CryptographicObjectFormatter.PEM.Format(certificateStore.GetCertificate(alias).Certificate, false); } } else @@ -127,7 +125,7 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer if (!string.IsNullOrEmpty(storePassword) && privateKeyType != PrivateKeyTypeEnum.PKCS8) throw new RemoteFileException("Error retrieving private key. Certificate store password cannot have a non empty value if the private key is in PKCS#1 format (BEGIN [RSA|EC] PRIVATE KEY)"); - + bool keyEntryProcessed = false; foreach (string alias in certificateStore.Aliases) { @@ -140,17 +138,19 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer throw new RemoteFileException("No private key found. Private key must be present to add entry to a non-Trust PEM certificate store."); X509CertificateEntry[] chainEntries = certificateStore.GetCertificateChain(alias); - CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[0].Certificate); + X509Certificate endCertificate = chainEntries[0].Certificate; AsymmetricKeyParameter privateKey = certificateStore.GetKey(alias).Key; - AsymmetricKeyParameter publicKey = chainEntries[0].Certificate.GetPublicKey(); + PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCPrivateKeyAndCert(privateKey, endCertificate); + + pemString = CryptographicObjectFormatter.PEM.Format(endCertificate, false); if (privateKeyType == PrivateKeyTypeEnum.PKCS8) { - PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false); - - byte[] privateKeyBytes = string.IsNullOrEmpty(storePassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(storePassword); - keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(storePassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey); + if (string.IsNullOrEmpty(storePassword)) + keyString = PemUtilities.DERToPEM(keyConverter.ToPkcs8BlobUnencrypted(), PemObjectType.PrivateKey); + else + keyString = CryptographicObjectFormatter.PEM.Format(keyConverter, storePassword); } else { @@ -162,22 +162,22 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer keyString = textWriter.ToString(); } - pemString = certConverter.ToPEM(true); if (string.IsNullOrEmpty(SeparatePrivateKeyFilePath)) pemString += keyString; - if (IncludesChain) + if (!IncludesChain) + { + continue; + } + + for (int i = 1; i < chainEntries.Length; i++) { - for (int i = 1; i < chainEntries.Length; i++) - { - CertificateConverter chainConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[i].Certificate); - pemString += chainConverter.ToPEM(true); - } + pemString += CryptographicObjectFormatter.PEM.Format(chainEntries[i].Certificate, false); } } } - storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath+storeFileName, Contents = Encoding.ASCII.GetBytes(pemString) }); + storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = Encoding.ASCII.GetBytes(pemString) }); if (!string.IsNullOrEmpty(SeparatePrivateKeyFilePath)) storeInfo.Add(new SerializedStoreInfo() { FilePath = SeparatePrivateKeyFilePath, Contents = Encoding.ASCII.GetBytes(keyString) }); @@ -200,7 +200,7 @@ 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}"); @@ -215,18 +215,10 @@ private X509CertificateEntry[] GetCertificates(string certificates) try { - while (certificates.Contains(CertDelimBeg)) - { - int certStart = certificates.IndexOf(CertDelimBeg); - int certLength = certificates.IndexOf(CertDelimEnd) + CertDelimEnd.Length - certStart; - string certificate = certificates.Substring(certStart, certLength); - - CertificateConverter c2 = CertificateConverterFactory.FromPEM(Encoding.ASCII.GetBytes(certificate.Replace(CertDelimBeg, string.Empty).Replace(CertDelimEnd, string.Empty))); - X509Certificate bcCert = c2.ToBouncyCastleCertificate(); - certificateEntries.Add(new X509CertificateEntry(bcCert)); - - certificates = certificates.Substring(certStart + certLength - 1); - } + IEnumerable pemCertificates = PemUtilities.SplitCollection(RemovePrivateKey(certificates)); + certificateEntries.AddRange(pemCertificates.Select(cert => + new X509CertificateEntry(new X509Certificate(CryptographicObjectFormatter.DER.Format(cert)))) + ); } catch (Exception ex) { @@ -290,7 +282,7 @@ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassw logger.MethodExit(LogLevel.Debug); - return keyEntry; + return keyEntry; } private PrivateKeyTypeEnum GetPrivateKeyType(string storeContents, out string privateKeyBegDelim) @@ -353,5 +345,29 @@ private AsymmetricKeyParameter ToBCPrivateKey(ECDiffieHellman ecdh) throw new RemoteFileException("Error converting to BouncyCastle private key - Invalid parameter."); } } + + private string RemovePrivateKey(string pemString) + { + List delimiters = new List(); + + foreach (string delim in PrivateKeyDelimetersPkcs8) + delimiters.Add(delim); + foreach (string delim in PrivateKeyDelimetersRSA) + delimiters.Add(delim); + foreach (string delim in PrivateKeyDelimetersEC) + delimiters.Add(delim); + + foreach (string delim in delimiters) + { + string delimEnd = delim.Replace("BEGIN", "END"); + int certStart = pemString.IndexOf(delim); + if (certStart == -1) + continue; + int certLength = pemString.IndexOf(delimEnd) + delimEnd.Length - certStart; + pemString = pemString.Remove(certStart, certLength); + } + + return pemString.Trim(); + } } } diff --git a/RemoteFile/InventoryBase.cs b/RemoteFile/InventoryBase.cs index 6042b076..67704213 100644 --- a/RemoteFile/InventoryBase.cs +++ b/RemoteFile/InventoryBase.cs @@ -5,19 +5,21 @@ // 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 Keyfactor.Extensions.Orchestrator.RemoteFile.Models; +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.PKI.CryptographicObjects.Formatters; +using Keyfactor.PKI.Extensions; +using Keyfactor.PKI.X509; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Org.BouncyCastle.Pkcs; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; -using Keyfactor.Orchestrators.Extensions; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Logging; -using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; - -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - namespace Keyfactor.Extensions.Orchestrator.RemoteFile { public abstract class InventoryBase : RemoteFileJobTypeBase, IInventoryJobExtension @@ -37,33 +39,32 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd { SetJobProperties(config, config.CertificateStoreDetails, logger); - certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, FileTransferProtocol, SSHPort, IncludePortInSPN); - certificateStore.Initialize(SudoImpersonatedUser); + certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, SSHPort, IncludePortInSPN); + certificateStore.Initialize(SudoImpersonatedUser, UseShellCommands); certificateStore.LoadCertificateStore(certificateStoreSerializer, true); - List collections = certificateStore.GetCertificateChains(); + List collection = certificateStore.GetCertificateChains(); logger.LogDebug($"Format returned certificates BEGIN"); - foreach (X509Certificate2Collection collection in collections) + foreach (X509CertificateEntryCollection entry in collection) { if (collection.Count == 0) continue; - X509Certificate2Ext issuedCertificate = (X509Certificate2Ext)collection[0]; + X509CertificateEntry issuedCertificate = entry.CertificateChain[0]; List certChain = new List(); - foreach (X509Certificate2 certificate in collection) + foreach (X509CertificateEntry certificateEntry in entry.CertificateChain) { - certChain.Add(Convert.ToBase64String(certificate.Export(X509ContentType.Cert))); - logger.LogDebug(Convert.ToBase64String(certificate.Export(X509ContentType.Cert))); + certChain.Add(CryptographicObjectFormatter.PEM.Format(certificateEntry.Certificate, false)); } inventoryItems.Add(new CurrentInventoryItem() { ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Alias = string.IsNullOrEmpty(issuedCertificate.FriendlyNameExt) ? issuedCertificate.Thumbprint : issuedCertificate.FriendlyNameExt, - PrivateKeyEntry = issuedCertificate.HasPrivateKey, - UseChainLevel = collection.Count > 1, + Alias = string.IsNullOrEmpty(entry.Alias) ? BouncyCastleX509Extensions.Thumbprint(issuedCertificate.Certificate) : entry.Alias, + PrivateKeyEntry = entry.HasPrivateKey, + UseChainLevel = entry.CertificateChain.Count > 1, Certificates = certChain.ToArray() }); } diff --git a/RemoteFile/ManagementBase.cs b/RemoteFile/ManagementBase.cs index d27e95c4..2a289e50 100644 --- a/RemoteFile/ManagementBase.cs +++ b/RemoteFile/ManagementBase.cs @@ -5,17 +5,17 @@ // 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 System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; - using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Common.Enums; - +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.PKI.Extensions; using Microsoft.Extensions.Logging; - using Newtonsoft.Json; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509; +using System; +using System.IO; +using static Org.BouncyCastle.Math.EC.ECCurve; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -28,15 +28,15 @@ public abstract class ManagementBase : RemoteFileJobTypeBase, IManagementJobExte public JobResult ProcessJob(ManagementJobConfiguration config) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); - + ICertificateStoreSerializer certificateStoreSerializer = GetCertificateStoreSerializer(config.CertificateStoreDetails.Properties); try { SetJobProperties(config, config.CertificateStoreDetails, logger); - - certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, FileTransferProtocol, SSHPort, IncludePortInSPN); - certificateStore.Initialize(SudoImpersonatedUser); + + certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, SSHPort, IncludePortInSPN); + certificateStore.Initialize(SudoImpersonatedUser, UseShellCommands); PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); @@ -47,12 +47,12 @@ public JobResult ProcessJob(ManagementJobConfiguration config) if (!certificateStore.DoesStoreExist()) { if (ApplicationSettings.CreateStoreIfMissing) - CreateStore(certificateStoreSerializer, config); + certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, config.CertificateStoreDetails.StorePath, logger); else throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); } certificateStore.LoadCertificateStore(certificateStoreSerializer, false); - certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword, RemoveRootCertificate); + certificateStore.AddCertificate(config.JobCertificate.Alias ?? GetThumbprint(config.JobCertificate, logger), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword, RemoveRootCertificate); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, StorePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); @@ -82,7 +82,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } else { - CreateStore(certificateStoreSerializer, config); + certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, config.CertificateStoreDetails.StorePath, logger); } logger.LogDebug($"END create Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); break; @@ -106,18 +106,28 @@ public JobResult ProcessJob(ManagementJobConfiguration config) return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } - private void CreateStore(ICertificateStoreSerializer certificateStoreSerializer, ManagementJobConfiguration config) + private string GetThumbprint (ManagementJobCertificate jobCertificate, ILogger logger) { - dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); - string linuxFilePermissions = properties.LinuxFilePermissionsOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFilePermissionsOnStoreCreation.Value) ? - ApplicationSettings.DefaultLinuxPermissionsOnStoreCreation : - properties.LinuxFilePermissionsOnStoreCreation.Value; + logger.MethodEntry(LogLevel.Debug); + + string thumbprint = string.Empty; + + using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(jobCertificate.Contents))) + { + Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder(); + Pkcs12Store store = storeBuilder.Build(); - string linuxFileOwner = properties.LinuxFileOwnerOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFileOwnerOnStoreCreation.Value) ? - ApplicationSettings.DefaultOwnerOnStoreCreation : - properties.LinuxFileOwnerOnStoreCreation.Value; + store.Load(ms, jobCertificate.PrivateKeyPassword.ToCharArray()); + + foreach (string alias in store.Aliases) + { + thumbprint = store.GetCertificate(alias).Certificate.Thumbprint(); + break; + } + } - certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.StorePath, linuxFilePermissions, linuxFileOwner); + logger.MethodExit(LogLevel.Debug); + return thumbprint; } } } diff --git a/RemoteFile/Models/SerializedStoreInfo.cs b/RemoteFile/Models/SerializedStoreInfo.cs index 6b13d1ce..29360700 100644 --- a/RemoteFile/Models/SerializedStoreInfo.cs +++ b/RemoteFile/Models/SerializedStoreInfo.cs @@ -5,8 +5,6 @@ // 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.Security.Cryptography.X509Certificates; - namespace Keyfactor.Extensions.Orchestrator.RemoteFile.Models { internal class SerializedStoreInfo diff --git a/RemoteFile/Models/X509Certificate2Ext.cs b/RemoteFile/Models/X509CertificateEntryCollection.cs similarity index 66% rename from RemoteFile/Models/X509Certificate2Ext.cs rename to RemoteFile/Models/X509CertificateEntryCollection.cs index 25df1050..ea1ea18c 100644 --- a/RemoteFile/Models/X509Certificate2Ext.cs +++ b/RemoteFile/Models/X509CertificateEntryCollection.cs @@ -1,20 +1,21 @@ -// Copyright 2021 Keyfactor +// Copyright 2021 Keyfactor // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, // 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.Security.Cryptography.X509Certificates; +using Org.BouncyCastle.Pkcs; +using System.Collections.Generic; namespace Keyfactor.Extensions.Orchestrator.RemoteFile.Models { - class X509Certificate2Ext : X509Certificate2 + internal class X509CertificateEntryCollection { - public string FriendlyNameExt { get; set; } + public string Alias { get; set; } - public new bool HasPrivateKey { get; set; } + public bool HasPrivateKey { get; set; } - public X509Certificate2Ext(byte[] bytes): base(bytes) { } + public List CertificateChain { get; set; } } -} +} \ No newline at end of file diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 0eb2ddd9..667c97eb 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -7,21 +7,20 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.PKI.PEM; using Microsoft.Extensions.Logging; +using static Keyfactor.PKI.PKIConstants.X509; -using Newtonsoft.Json; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { - public abstract class ReenrollmentBase : RemoteFileJobTypeBase + public abstract class ReenrollmentBase : RemoteFileJobTypeBase, IReenrollmentJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile"; @@ -42,7 +41,7 @@ internal enum SupportedKeyTypeEnum // 6) Modify ReenrollmentBase to implement IReenrollmentJobExtension // 6) Update README. Remember to explain the differences between ODKG and OOKG - public JobResult ProcessJobToDo(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); @@ -52,9 +51,17 @@ public JobResult ProcessJobToDo(ReenrollmentJobConfiguration config, SubmitReenr { SetJobProperties(config, config.CertificateStoreDetails, logger); - string alias = "abcd"; - string sans = "reenroll2.Keyfactor.com&reenroll1.keyfactor.com&reenroll3.Keyfactor.com"; - bool overwrite = true; + certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, SSHPort, IncludePortInSPN); + certificateStore.Initialize(SudoImpersonatedUser, UseShellCommands); + certificateStore.LoadCertificateStore(certificateStoreSerializer, false); + + if (!certificateStore.DoesStoreExist()) + { + if (ApplicationSettings.CreateStoreIfMissing) + certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, config.CertificateStoreDetails.StorePath, logger); + else + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + } // validate parameters string KeyTypes = string.Join(",", Enum.GetNames(typeof(SupportedKeyTypeEnum))); @@ -62,42 +69,39 @@ public JobResult ProcessJobToDo(ReenrollmentJobConfiguration config, SubmitReenr { throw new RemoteFileException($"Unsupported KeyType value {KeyType}. Supported types are {KeyTypes}."); } - - ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol = ApplicationSettings.FileTransferProtocol; - - certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, fileTransferProtocol, SSHPort, IncludePortInSPN); - certificateStore.Initialize(SudoImpersonatedUser); - + PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); - if (!certificateStore.DoesStoreExist()) - { - throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); - } - // generate CSR and call back to enroll certificate string csr = string.Empty; - string pemPrivateKey = string.Empty; - if (CreateCSROnDevice) - { - csr = certificateStore.GenerateCSROnDevice(SubjectText, KeyTypeEnum, KeySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries)), out pemPrivateKey); - } - else - { - csr = certificateStore.GenerateCSR(SubjectText, KeyTypeEnum, KeySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries))); - } + AsymmetricAlgorithm privateKey; + csr = certificateStore.GenerateCSR(SubjectText, config.Overwrite, config.Alias, KeyTypeEnum, KeySize, config.SANs, out privateKey); X509Certificate2 cert = submitReenrollment.Invoke(csr); - if (cert == null || String.IsNullOrEmpty(pemPrivateKey)) + + if (cert == null) throw new RemoteFileException("Enrollment of CSR failed. Please check Keyfactor Command logs for more information on potential enrollment errors."); - AsymmetricAlgorithm alg = KeyTypeEnum == SupportedKeyTypeEnum.RSA ? RSA.Create() : ECDsa.Create(); - alg.ImportEncryptedPkcs8PrivateKey(string.Empty, Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); - cert = KeyTypeEnum == SupportedKeyTypeEnum.RSA ? cert.CopyWithPrivateKey((RSA)alg) : cert.CopyWithPrivateKey((ECDsa)alg); + switch (privateKey) + { + case RSA rsa: + cert = cert.CopyWithPrivateKey(rsa); + break; + + case ECDsa ecdsa: + cert = cert.CopyWithPrivateKey(ecdsa); + break; + + case DSA dsa: + cert = cert.CopyWithPrivateKey(dsa); + break; + + default: + throw new NotSupportedException($"Unsupported key type: {privateKey?.GetType().Name}"); + } // save certificate - certificateStore.LoadCertificateStore(certificateStoreSerializer, false); - certificateStore.AddCertificate((alias ?? cert.Thumbprint), Convert.ToBase64String(cert.Export(X509ContentType.Pfx)), overwrite, null, RemoveRootCertificate); + certificateStore.AddCertificate(config.Alias ?? cert.Thumbprint, Convert.ToBase64String(cert.Export(X509ContentType.Pfx)), config.Overwrite, null, RemoveRootCertificate); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, StorePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index 793e976c..4829c7a8 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -5,30 +5,26 @@ // 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 Keyfactor.Extensions.Orchestrator.RemoteFile.Models; +using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; +using Keyfactor.Logging; +using Keyfactor.PKI.X509; +using Keyfactor.PKI.PEM; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Security.Cryptography.X509Certificates; +using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; - -using Microsoft.Extensions.Logging; - -using Org.BouncyCastle.Pkcs; -using Org.BouncyCastle.Security; - -using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; -using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; -using Keyfactor.Logging; -using System.Runtime.InteropServices; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Crypto; using static Keyfactor.Extensions.Orchestrator.RemoteFile.ReenrollmentBase; +using static Keyfactor.PKI.PKIConstants.X509; +using Keyfactor.PKI.PrivateKeys; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -54,7 +50,6 @@ internal enum ServerTypeEnum internal ServerTypeEnum ServerType { get; set; } internal List DiscoveredStores { get; set; } internal string UploadFilePath { get; set; } - internal ApplicationSettings.FileTransferProtocolEnum FileTransferProtocol { get; set; } internal bool IncludePortInSPN { get; set; } internal int SSHPort { get; set; } @@ -64,7 +59,7 @@ internal enum ServerTypeEnum internal RemoteCertificateStore() { } - internal RemoteCertificateStore(string server, string serverId, string serverPassword, string storeFileAndPath, string storePassword, ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol, int sshPort, bool includePortInSPN) + internal RemoteCertificateStore(string server, string serverId, string serverPassword, string storeFileAndPath, string storePassword, int sshPort, bool includePortInSPN) { logger = LogHandler.GetClassLogger(this.GetType()); logger.MethodEntry(LogLevel.Debug); @@ -80,7 +75,6 @@ internal RemoteCertificateStore(string server, string serverId, string serverPas StorePassword = storePassword; ServerType = StorePath.Substring(0, 1) == "/" ? ServerTypeEnum.Linux : ServerTypeEnum.Windows; UploadFilePath = !string.IsNullOrEmpty(ApplicationSettings.SeparateUploadFilePath) && ServerType == ServerTypeEnum.Linux ? ApplicationSettings.SeparateUploadFilePath : StorePath; - FileTransferProtocol = fileTransferProtocol; SSHPort = sshPort; IncludePortInSPN = includePortInSPN; logger.LogDebug($"UploadFilePath: {UploadFilePath}"); @@ -163,36 +157,25 @@ internal List FindStores(string[] paths, string[] extensions, string[] f return ServerType == ServerTypeEnum.Linux ? FindStoresLinux(paths, extensions, files, ignoredDirs, includeSymLinks) : FindStoresWindows(paths, extensions, files); } - internal List GetCertificateChains() + internal List GetCertificateChains() { logger.MethodEntry(LogLevel.Debug); - - List certificateChains = new List(); + + List certificateChains = new List(); foreach(string alias in CertificateStore.Aliases) { - X509Certificate2Collection chain = new X509Certificate2Collection(); - X509CertificateEntry[] entries; - - if (CertificateStore.IsKeyEntry(alias)) + bool hasPrivateKey = CertificateStore.IsKeyEntry(alias); + X509CertificateEntryCollection entries = new X509CertificateEntryCollection() { - entries = CertificateStore.GetCertificateChain(alias); - } - else - { - X509CertificateEntry entry = CertificateStore.GetCertificate(alias); - entries = new X509CertificateEntry[] { entry }; - } - - foreach(X509CertificateEntry entry in entries) - { - X509Certificate2Ext cert = new X509Certificate2Ext(entry.Certificate.GetEncoded()); - cert.FriendlyNameExt = alias; - cert.HasPrivateKey = CertificateStore.IsKeyEntry(alias); - chain.Add(cert); - } - - certificateChains.Add(chain); + Alias = alias, + HasPrivateKey = hasPrivateKey, + CertificateChain = hasPrivateKey ? + CertificateStore.GetCertificateChain(alias).ToList() : + new List() { CertificateStore.GetCertificate(alias) } + }; + + certificateChains.Add(entries); } logger.MethodExit(LogLevel.Debug); @@ -236,15 +219,32 @@ internal void DeleteCertificateByAlias(string alias) logger.MethodExit(LogLevel.Debug); } - internal void CreateCertificateStore(ICertificateStoreSerializer certificateStoreSerializer, string storePath, string linuxFilePermissions, string linuxFileOwner) + internal void CreateCertificateStore(ICertificateStoreSerializer certificateStoreSerializer, string properties, string storePath, ILogger logger) { logger.MethodEntry(LogLevel.Debug); + dynamic propertiesCollection = JsonConvert.DeserializeObject(properties); + string linuxFilePermissions = propertiesCollection.LinuxFilePermissionsOnStoreCreation == null || string.IsNullOrEmpty(propertiesCollection.LinuxFilePermissionsOnStoreCreation.Value) ? + ApplicationSettings.DefaultLinuxPermissionsOnStoreCreation : + propertiesCollection.LinuxFilePermissionsOnStoreCreation.Value; + + string linuxFileOwner = propertiesCollection.LinuxFileOwnerOnStoreCreation == null || string.IsNullOrEmpty(propertiesCollection.LinuxFileOwnerOnStoreCreation.Value) ? + ApplicationSettings.DefaultOwnerOnStoreCreation : + propertiesCollection.LinuxFileOwnerOnStoreCreation.Value; + RemoteHandler.CreateEmptyStoreFile(storePath, linuxFilePermissions, linuxFileOwner); string privateKeyPath = certificateStoreSerializer.GetPrivateKeyPath(); if (!string.IsNullOrEmpty(privateKeyPath)) RemoteHandler.CreateEmptyStoreFile(privateKeyPath, linuxFilePermissions, linuxFileOwner); + logger.MethodExit(LogLevel.Debug); + } + + internal void CreateCertificateStore(ICertificateStoreSerializer certificateStoreSerializer, string storePath, string linuxFilePermissions, string linuxFileOwner) + { + logger.MethodEntry(LogLevel.Debug); + + logger.MethodExit(LogLevel.Debug); } @@ -257,18 +257,13 @@ internal void AddCertificate(string alias, string certificateEntry, bool overwri try { Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder(); - Pkcs12Store certs = storeBuilder.Build(); + Pkcs12Store newEntry = storeBuilder.Build(); byte[] newCertBytes = removeRootCertificate && !string.IsNullOrEmpty(pfxPassword) ? RemoveRootCertificate(Convert.FromBase64String(certificateEntry), pfxPassword) : Convert.FromBase64String(certificateEntry); - Pkcs12Store newEntry = storeBuilder.Build(); - - X509Certificate2 cert = new X509Certificate2(newCertBytes, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet); - byte[] binaryCert = cert.Export(X509ContentType.Pkcs12, pfxPassword); - - using (MemoryStream ms = new MemoryStream(string.IsNullOrEmpty(pfxPassword) ? binaryCert : newCertBytes)) + using (MemoryStream ms = new MemoryStream(newCertBytes)) { newEntry.Load(ms, string.IsNullOrEmpty(pfxPassword) ? new char[0] : pfxPassword.ToCharArray()); } @@ -295,7 +290,8 @@ internal void AddCertificate(string alias, string certificateEntry, bool overwri if (string.IsNullOrEmpty(checkAliasExists)) { - Org.BouncyCastle.X509.X509Certificate bcCert = DotNetUtilities.FromX509Certificate(cert); + //Org.BouncyCastle.X509.X509Certificate bcCert = DotNetUtilities.FromX509Certificate(cert); + Org.BouncyCastle.X509.X509Certificate bcCert = new Org.BouncyCastle.X509.X509Certificate(newCertBytes); X509CertificateEntry bcEntry = new X509CertificateEntry(bcCert); if (CertificateStore.ContainsAlias(alias)) { @@ -353,114 +349,35 @@ internal static PathFile SplitStorePathFile(string pathFileName) } } - internal string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) + internal string GenerateCSR(string subjectText, bool overwrite, string alias, SupportedKeyTypeEnum keyType, int keySize, Dictionary sans, out AsymmetricAlgorithm privateKey) { - IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; - string algorithm = string.Empty; - switch (keyType) + if (CertificateStore.ContainsAlias(alias) && !overwrite) { - case SupportedKeyTypeEnum.RSA: - keyPairGenerator = new RsaKeyPairGenerator(); - algorithm = "SHA256withRSA"; - break; - case SupportedKeyTypeEnum.ECC: - keyPairGenerator = new ECKeyPairGenerator(); - algorithm = "SHA256withECDSA"; - if (keySize == 384) algorithm = "SHA384withECDSA"; - if (keySize == 521) algorithm = "SHA512withECDSA"; - break; + throw new RemoteFileException($"Alias {alias} already exists in store {StorePath + StoreFileName} and overwrite is set to False. Please try again with overwrite set to True if you wish to replace this entry."); } - var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); - keyPairGenerator.Init(keyGenParams); - var keyPair = keyPairGenerator.GenerateKeyPair(); - - var subject = new X509Name(subjectText); + List sansList = sans + .SelectMany(san => san.Value.Select(value => $"{san.Key}={value}")) + .ToList(); - // Add SAN entries - var subAltNameList = new List(); - sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); - var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); - - var extensionsGenerator = new X509ExtensionsGenerator(); - extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); - var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); - - Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(algorithm, subject, keyPair.Public, (DerSet)attributeSet, keyPair.Private); - - // encode the CSR as base64 - var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); - return encodedCsr; - } - - internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans, out string privateKey) - { - string path = ApplicationSettings.TempFilePathForODKG; - if (path.Substring(path.Length - 1, 1) != "/") path += "/"; - string fileName = Guid.NewGuid().ToString(); - - X500DistinguishedName dn = new X500DistinguishedName(subjectText); - string opensslSubject = dn.Format(true).Replace("S=","ST="); - opensslSubject = opensslSubject.Replace(System.Environment.NewLine, "/"); - opensslSubject = "/" + opensslSubject.Substring(0, opensslSubject.Length - 1); - - string cmd = $"openssl req -new -newkey REPLACE -nodes -keyout {path}{fileName}.key -out {path}{fileName}.csr -subj '{opensslSubject}'"; - switch (keyType) - { - case SupportedKeyTypeEnum.RSA: - cmd = cmd.Replace("REPLACE", $"rsa:{keySize.ToString()}"); - break; - case SupportedKeyTypeEnum.ECC: - string algName = "prime256v1"; - switch (keySize) - { - case 384: - algName = "secp384r1"; - break; - case 521: - algName = "secp521r1"; - break; - } - cmd = cmd.Replace("REPLACE", $"ec:<(openssl ecparam -name {algName})"); - break; - } - - string csr = string.Empty; - privateKey = string.Empty; - try - { - try - { - RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); - } - catch (Exception ex) - { - if (!ex.Message.Contains("----")) - throw; - } - - privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + ".key")); - csr = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + ".csr")); - } - finally - { - if (RemoteHandler.DoesFileExist(path + fileName + ".key")) - RemoteHandler.RemoveCertificateFile(path, fileName + ".key"); - if (RemoteHandler.DoesFileExist(path + fileName + ".csr")) - RemoteHandler.RemoveCertificateFile(path, fileName + ".csr"); - } + RequestGenerator generator = new RequestGenerator(keyType.ToString(), keySize); + generator.SANs = X509Utilities.ParseSANs(sansList); + generator.Subject = subjectText; + string csr = PemUtilities.DERToPEM(generator.CreatePKCS10Request(), PKI.PEM.PemUtilities.PemObjectType.CertRequest); + privateKey = generator.GetRequestPrivateKey().ToNetPrivateKey(); + return csr; } - internal void Initialize(string sudoImpersonatedUser) + internal void Initialize(string sudoImpersonatedUser, bool useShellCommands) { logger.MethodEntry(LogLevel.Debug); bool treatAsLocal = Server.ToLower().EndsWith(LOCAL_MACHINE_SUFFIX); if (ServerType == ServerTypeEnum.Linux || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - RemoteHandler = treatAsLocal ? new LinuxLocalHandler() as IRemoteHandler : new SSHHandler(Server, ServerId, ServerPassword, ServerType == ServerTypeEnum.Linux, FileTransferProtocol, SSHPort, sudoImpersonatedUser) as IRemoteHandler; + RemoteHandler = treatAsLocal ? new LinuxLocalHandler() as IRemoteHandler : new SSHHandler(Server, ServerId, ServerPassword, ServerType == ServerTypeEnum.Linux, SSHPort, sudoImpersonatedUser, useShellCommands) as IRemoteHandler; else RemoteHandler = new WinRMHandler(Server, ServerId, ServerPassword, treatAsLocal, IncludePortInSPN); diff --git a/RemoteFile/RemoteFile.csproj b/RemoteFile/RemoteFile.csproj index f577e4e4..2b13f6e2 100644 --- a/RemoteFile/RemoteFile.csproj +++ b/RemoteFile/RemoteFile.csproj @@ -2,19 +2,17 @@ true - net6.0;net8.0 + net8.0 true disable - + - - - - - + + + diff --git a/RemoteFile/RemoteFileJobTypeBase.cs b/RemoteFile/RemoteFileJobTypeBase.cs index 02e7f562..ec2f7e76 100644 --- a/RemoteFile/RemoteFileJobTypeBase.cs +++ b/RemoteFile/RemoteFileJobTypeBase.cs @@ -27,8 +27,8 @@ public abstract class RemoteFileJobTypeBase internal bool RemoveRootCertificate { get; set; } internal int SSHPort { get; set; } internal bool IncludePortInSPN { get; set; } - internal ApplicationSettings.FileTransferProtocolEnum FileTransferProtocol { get; set; } internal bool CreateCSROnDevice { get; set; } + internal bool UseShellCommands { get; set; } internal string KeyType { get; set; } internal int KeySize { get; set; } internal string SubjectText { get; set; } @@ -57,7 +57,7 @@ internal void SetJobProperties(JobConfiguration config, CertificateStore certifi ApplicationSettings.DefaultSudoImpersonatedUser : properties.SudoImpersonatedUser.Value; - SSHPort = properties.SSHPort == null || string.IsNullOrEmpty(properties.SSHPort.Value) || !int.TryParse(properties.SSHPort.Value, out int notUsed) ? + SSHPort = properties.SSHPort == null || string.IsNullOrEmpty(properties.SSHPort.Value) || !int.TryParse(properties.SSHPort.Value, out int _) ? ApplicationSettings.SSHPort : properties.SSHPort; @@ -69,33 +69,14 @@ internal void SetJobProperties(JobConfiguration config, CertificateStore certifi false : Convert.ToBoolean(properties.IncludePortInSPN.Value); - CreateCSROnDevice = properties.CreateCSROnDevice == null || string.IsNullOrEmpty(properties.CreateCSROnDevice.Value) ? - ApplicationSettings.CreateCSROnDevice : - Convert.ToBoolean(properties.CreateCSROnDevice.Value); - - 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 (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; - } - } + UseShellCommands = properties.UseShellCommands == null || string.IsNullOrEmpty(properties.UseShellCommands.Value) ? + ApplicationSettings.UseShellCommands : + properties.UseShellCommands; if (config.JobProperties != null) { KeyType = !config.JobProperties.ContainsKey("keyType") || config.JobProperties["keyType"] == null || string.IsNullOrEmpty(config.JobProperties["keyType"].ToString()) ? string.Empty : config.JobProperties["keyType"].ToString(); + if (KeyType == "ECDSA") KeyType = "ECC"; 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(); } @@ -108,8 +89,6 @@ internal void SetJobProperties(JobConfiguration config, CertificateStore certifi 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}"); diff --git a/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs b/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs index 4abb4a08..a02918c6 100644 --- a/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs +++ b/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs @@ -7,18 +7,13 @@ using System; using System.IO; -using System.Security.Cryptography; using CliWrap; using CliWrap.Buffered; -using Renci.SshNet; - using Microsoft.Extensions.Logging; using Keyfactor.Logging; -using Keyfactor.PKI.PrivateKeys; -using Keyfactor.PKI.PEM; namespace Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers { diff --git a/RemoteFile/RemoteHandlers/SSHHandler.cs b/RemoteFile/RemoteHandlers/SSHHandler.cs index 6d664338..180dfdac 100644 --- a/RemoteFile/RemoteHandlers/SSHHandler.cs +++ b/RemoteFile/RemoteHandlers/SSHHandler.cs @@ -27,22 +27,23 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers { class SSHHandler : BaseRemoteHandler { + private readonly string[] IgnoreErrors = { "Could not chdir to home directory" }; private ConnectionInfo Connection { get; set; } private string SudoImpersonatedUser { get; set; } - private ApplicationSettings.FileTransferProtocolEnum FileTransferProtocol { get; set; } private bool IsStoreServerLinux { get; set; } + private bool UseShellCommands { get; set; } private string UserId { get; set; } private string Password { get; set; } private SshClient sshClient; - internal SSHHandler(string server, string serverLogin, string serverPassword, bool isStoreServerLinux, ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol, int sshPort, string sudoImpersonatedUser) + internal SSHHandler(string server, string serverLogin, string serverPassword, bool isStoreServerLinux, int sshPort, string sudoImpersonatedUser, bool useShellCommands) { _logger.MethodEntry(LogLevel.Debug); Server = server; SudoImpersonatedUser = sudoImpersonatedUser; - FileTransferProtocol = fileTransferProtocol; IsStoreServerLinux = isStoreServerLinux; + UseShellCommands = useShellCommands; UserId = serverLogin; Password = serverPassword; @@ -80,7 +81,8 @@ internal SSHHandler(string server, string serverLogin, string serverPassword, bo sshClient.Connect(); //method call below necessary to check edge condition where password for user id has expired. SCP (and possibly SFTP) download hangs in that scenario - CheckConnection(); + if (useShellCommands) + CheckConnection(); } catch (Exception ex) { @@ -140,7 +142,7 @@ public override string RunCommand(string commandText, object[] arguments, bool w command.Execute(); _logger.LogDebug($"SSH Results: {displayCommand}::: {command.Result}::: {command.Error}"); - if (!String.IsNullOrEmpty(command.Error)) + if (!String.IsNullOrEmpty(command.Error)/* && !IgnoreError(command.Error)*/) throw new ApplicationException(command.Error); _logger.MethodExit(LogLevel.Debug); @@ -170,39 +172,31 @@ public override void UploadCertificateFile(string path, string fileName, byte[] bool scpError = false; - if (FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both || FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.SCP) + using (ScpClient client = new ScpClient(Connection)) { - using (ScpClient client = new ScpClient(Connection)) + try { - try - { - _logger.LogDebug($"SCP connection attempt to {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}"); - client.OperationTimeout = System.TimeSpan.FromSeconds(60); - client.Connect(); + _logger.LogDebug($"SCP connection attempt to {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}"); + client.OperationTimeout = System.TimeSpan.FromSeconds(60); + client.Connect(); - using (MemoryStream stream = new MemoryStream(certBytes)) - { - client.Upload(stream, FormatFTPPath(uploadPath, false)); - } - } - catch (Exception ex) + using (MemoryStream stream = new MemoryStream(certBytes)) { - scpError = true; - _logger.LogError("Exception during SCP upload..."); - _logger.LogError($"Upload Exception: {RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}"); - if (FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both) - _logger.LogDebug($"SCP upload failed. Attempting with SFTP protocol..."); - else - throw new RemoteFileException("Error attempting SCP file transfer to {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}. Please contact your company's system administrator to verify connection and permission settings.", ex); - } - finally - { - client.Disconnect(); + client.Upload(stream, FormatFTPPath(uploadPath, false)); } } + catch (Exception ex) + { + scpError = true; + _logger.LogDebug($"SCP upload failed. Attempting with SFTP protocol..."); + } + finally + { + client.Disconnect(); + } } - if ((FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both && scpError) || FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.SFTP) + if (scpError) { using (SftpClient client = new SftpClient(Connection)) { @@ -219,9 +213,8 @@ public override void UploadCertificateFile(string path, string fileName, byte[] } catch (Exception ex) { - _logger.LogError("Exception during SFTP upload..."); - _logger.LogError($"Upload Exception: {RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}"); - throw new RemoteFileException("Error attempting SFTP file transfer to {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}. Please contact your company's system administrator to verify connection and permission settings.", ex); + _logger.LogError($"Upload Exception: {RemoteFileException.FlattenExceptionMessages(ex, "Exception during SFTP download...")}"); + throw new RemoteFileException($"Error attempting SFTP file transfer to {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}. Please contact your company's system administrator to verify connection and permission settings.", ex); } finally { @@ -264,45 +257,35 @@ public override byte[] DownloadCertificateFile(string path) _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)) { - _logger.LogDebug($"Attempting SCP download..."); - using (ScpClient client = new ScpClient(Connection)) + try { - try - { - _logger.LogDebug($"SCP connection attempt from {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}"); - client.OperationTimeout = System.TimeSpan.FromSeconds(60); - client.Connect(); + _logger.LogDebug($"SCP connection attempt from {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}"); + client.OperationTimeout = System.TimeSpan.FromSeconds(60); + client.Connect(); - using (MemoryStream stream = new MemoryStream()) - { - client.Download(FormatFTPPath(downloadPath, false), stream); - rtnStore = stream.ToArray(); - } - } - catch (Exception ex) - { - scpError = true; - _logger.LogError("Exception during SCP download..."); - _logger.LogError($"Upload Exception: {RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}"); - if (FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both) - _logger.LogDebug($"SCP download failed. Attempting with SFTP protocol..."); - else - throw new RemoteFileException($"Error attempting SCP file transfer from {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}. Please contact your company's system administrator to verify connection and permission settings.", ex); - } - finally + using (MemoryStream stream = new MemoryStream()) { - attemptedDownload = true; - client.Disconnect(); + client.Download(FormatFTPPath(downloadPath, false), stream); + rtnStore = stream.ToArray(); } } + catch (Exception ex) + { + scpError = true; + _logger.LogError($"Upload Exception: {RemoteFileException.FlattenExceptionMessages(ex, "Exception during SCP download...")}"); + _logger.LogDebug($"SCP download failed. Attempting with SFTP protocol..."); + } + finally + { + client.Disconnect(); + } } - if ((FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.Both && scpError) || FileTransferProtocol == ApplicationSettings.FileTransferProtocolEnum.SFTP) + if (scpError) { _logger.LogDebug($"Attempting SFTP download..."); using (SftpClient client = new SftpClient(Connection)) @@ -321,27 +304,15 @@ public override byte[] DownloadCertificateFile(string path) } catch (Exception ex) { - _logger.LogError("Exception during SFTP download..."); - _logger.LogError($"Download Exception: {RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}"); + _logger.LogError($"Download Exception: {RemoteFileException.FlattenExceptionMessages(ex, "Exception during SFTP download...")}"); throw new RemoteFileException($"Error attempting SFTP file transfer from {Connection.Host} using login {Connection.Username} and connection method {Connection.AuthenticationMethods[0].Name}. Please contact your company's system administrator to verify connection and permission settings.", ex); } 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) { @@ -368,13 +339,20 @@ public override void CreateEmptyStoreFile(string path, string linuxFilePermissio if (IsStoreServerLinux) { string pathOnly = string.Empty; - SplitStorePathFile(path, out pathOnly, out _); + string fileName = string.Empty; + SplitStorePathFile(path, out pathOnly, out fileName); + + if (UseShellCommands) + { + linuxFilePermissions = string.IsNullOrEmpty(linuxFilePermissions) ? GetFolderPermissions(pathOnly) : linuxFilePermissions; + linuxFileOwner = string.IsNullOrEmpty(linuxFileOwner) ? GetFolderOwner(pathOnly) : linuxFileOwner; - linuxFilePermissions = string.IsNullOrEmpty(linuxFilePermissions) ? GetFolderPermissions(pathOnly) : linuxFilePermissions; - linuxFileOwner = string.IsNullOrEmpty(linuxFileOwner) ? GetFolderOwner(pathOnly) : linuxFileOwner; + AreLinuxPermissionsValid(linuxFilePermissions); - AreLinuxPermissionsValid(linuxFilePermissions); - RunCommand($"install -m {linuxFilePermissions} -o {linuxFileOwner} {linuxFileGroup} /dev/null {path}", null, ApplicationSettings.UseSudo, null); + RunCommand($"install -m {linuxFilePermissions} -o {linuxFileOwner} {linuxFileGroup} /dev/null {path}", null, ApplicationSettings.UseSudo, null); + } + else + UploadCertificateFile(pathOnly, fileName, Array.Empty()); } else RunCommand($@"Out-File -FilePath ""{path}""", null, false, null); @@ -386,28 +364,38 @@ public override bool DoesFileExist(string path) { _logger.MethodEntry(LogLevel.Debug); _logger.LogDebug($"DoesFileExist: {path}"); - - string rtn = RunCommand($"ls {path} >> /dev/null 2>&1 && echo True || echo False", null, ApplicationSettings.UseSudo, null); - return Convert.ToBoolean(rtn); - - //using (SftpClient client = new SftpClient(Connection)) - //{ - // try - // { - // client.Connect(); - // string existsPath = FormatFTPPath(path, !IsStoreServerLinux); - // bool exists = client.Exists(existsPath); - // _logger.LogDebug(existsPath); - - // _logger.MethodExit(LogLevel.Debug); - - // return exists; - // } - // finally - // { - // client.Disconnect(); - // } - //} + + bool exists = false; + + if (UseShellCommands) + { + exists = Convert.ToBoolean(RunCommand($"ls {path} >> /dev/null 2>&1 && echo True || echo False", null, ApplicationSettings.UseSudo, null)); + } + else + { + using (SftpClient client = new SftpClient(Connection)) + { + try + { + client.Connect(); + string existsPath = FormatFTPPath(path, !IsStoreServerLinux); + exists = client.Exists(existsPath); + _logger.LogDebug(existsPath); + } + catch (Exception ex) + { + _logger.LogError(RemoteFileException.FlattenExceptionMessages(ex, "Error checking existence of file {path} using SFTP")); + throw; + } + finally + { + _logger.MethodExit(LogLevel.Debug); + client.Disconnect(); + } + } + } + + return exists; } public override void RemoveCertificateFile(string path, string fileName) @@ -527,5 +515,10 @@ private void CheckConnection() throw; } } + + private bool IgnoreError(string err) + { + return IgnoreErrors.Any(p => err.Contains(p, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/RemoteFile/config.json b/RemoteFile/config.json index 93c21131..c74ec029 100644 --- a/RemoteFile/config.json +++ b/RemoteFile/config.json @@ -4,7 +4,6 @@ "CreateStoreIfMissing": "N", "UseNegotiate": "N", "SeparateUploadFilePath": "", - "FileTransferProtocol": "SCP", "DefaultLinuxPermissionsOnStoreCreation": "600", "DefaultOwnerOnStoreCreation": "", "SSHPort": "" diff --git a/RemoteFile/manifest.json b/RemoteFile/manifest.json index c4e40bd5..42930912 100644 --- a/RemoteFile/manifest.json +++ b/RemoteFile/manifest.json @@ -9,6 +9,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12.Management" }, + "CertStores.RFPkcs12.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12.Reenrollment" + }, "CertStores.RFPkcs12.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -21,6 +25,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Management" }, + "CertStores.RFPEM.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Reenrollment" + }, "CertStores.RFPEM.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -33,6 +41,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Management" }, + "CertStores.RFJKS.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Reenrollment" + }, "CertStores.RFJKS.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -57,6 +69,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Management" }, + "CertStores.RFDER.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Reenrollment" + }, "CertStores.RFDER.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" diff --git a/docsource/content.md b/docsource/content.md index dc7f74e4..c883307b 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -12,6 +12,12 @@ managed is achieved via either an `SSH` (for Linux and possibly Windows orchestr orchestrated servers) connection. When acting as an agent, `SSH/WinRM` may still be used, OR the certificate store can be configured to bypass these and instead directly access the orchestrator server's file system. +RemoteFile Management and Inventory capabilities support the handling of certificates with RSA, ECC, and ML-DSA ditital signature algorithms. However, +ODKG (On Device Key Generation) introduced in release 3.0 for store types RFJKS, RFPEM, RFPkcs12, and RFDER, currently only supports +RSA and ECC. Also, please be aware that the CSR and key pairs generated by the RemoteFile Orchestrator extension when utilizing +the ODKG capability are technically created on the orchestrator server itself and not the target device if the orchestrator is +managing a store or stores on separate servers than where the Universal Orchestrator is installed. + ![](images/orchestrator-agent.png) The supported configurations of Universal Orchestrator hosts and managed orchestrated servers are detailed below: @@ -51,6 +57,7 @@ certificates and certificate store files. | `tee` | | X(c) | X(a) | X(a) | | | `rm` | | X(d) | X(d) | X(d) | | | `install` | | | | | X | +| `stat` | | | | | X | | `orapki` | | X(e) | X(e) | X(e) | | | `gskcapicmd` | | X(f) | X(f) | X(f) | | @@ -62,10 +69,11 @@ certificates and certificate store files. (f) - RFKDB store type only 2. When orchestrating management of local or external certificate stores, the Remote File Orchestrator Extension makes - use of SFTP and/or SCP to transfer files to and from the orchestrated server. `SFTP/SCP` cannot make use of `sudo`, so - all folders containing certificate stores will need to allow SFTP/SCP file transfer for the user assigned to the + use of SCP or SFTP to transfer files to and from the orchestrated server. SCP is attempted first, and if that + fails, SFTP is attempted. `SCP/SFTP` cannot make use of `sudo`, so + all folders containing certificate stores will need to allow SCP/SFTP file transfer for the user assigned to the certificate store/discovery job. If this is not possible, set the values in the `config.json` appropriately to use an - alternative upload/download folder that does allow `SFTP/SCP` file transfer. If the certificate store/discovery job is + alternative upload/download folder that does allow `SCP/SFTP` file transfer. If the certificate store/discovery job is configured for local (agent) access, the account running the Keyfactor Universal Orchestrator service must have access to read/write to the certificate store location, OR the `config.json` file must be set up to use the alternative upload/download file. @@ -102,8 +110,8 @@ Please reference [Certificate Stores and Discovery Jobs](#certificate-stores-and creating certificate stores for the `RemoteFile` Orchestrator Extension. - -Please consult with your system administrator for more information on configuring `SSH/SFTP/SCP` or `WinRM` in your environment. +C +Please consult with your system administrator for more information on configuring `SSH/SCP/SFTP` or `WinRM` in your environment. ## Post Installation @@ -116,10 +124,10 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is loc "CreateStoreIfMissing": "N", "UseNegotiate": "N", "SeparateUploadFilePath": "", - "FileTransferProtocol": "SCP", "DefaultLinuxPermissionsOnStoreCreation": "600", "DefaultOwnerOnStoreCreation": "", - "SSHPort": "" + "SSHPort": "", + "UseShellCommands": "Y" } ``` @@ -130,10 +138,10 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is loc | `CreateStoreIfMissing` | `N` | `Y/N` | Determines if a certificate store should be created during a Management-Add job if it doesn't exist. If `N`, the job will return an error. If `Y`, the store will be created and the certificate added. | | `UseNegotiate` | `N` | `Y/N` | Determines if WinRM should use Negotiate (Y) when connecting to the remote server. Only applicable for Windows hosted certificate stores. | | `SeparateUploadFilePath` | | Any valid, existing Linux path | Path on the orchestrated server for uploading/downloading temporary work files. If empty, the certificate store location will be used. Only applicable for Linux hosted certificate stores. | -| `FileTransferProtocol` | `SCP` | `SCP, SFTP, Both` | Protocol used for uploading/downloading files. If `Both`, `SCP` will be tried first, then `SFTP`. Only applicable for Linux hosted certificate stores. | | `DefaultLinuxPermissionsOnStoreCreation` | `600` | Any 3-digit value from 000-777 | Linux file permissions set on new certificate stores. If blank, permissions from the parent folder will be used. Only applicable for Linux hosted certificate stores. | | `DefaultOwnerOnStoreCreation` | | Any valid user id | Sets the owner for newly created certificate stores. Can include group with format `ownerId:groupId`. If blank, the owner of the parent folder will be used. Only applicable for Linux hosted certificate stores. | | `SSHPort` | | Any valid integer representing a port | The port that SSH is listening on. Default is 22. Only applicable for Linux hosted certificate stores. | +| `UseShellCommands` | `Y` | `Y/N` | Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting) | ## Discovery @@ -182,6 +190,31 @@ For agent mode (accessing stores on the same server where Universal Orchestrator - `Store Type` + `Client Machine` + `Store Path` must be unique in Keyfactor Command - Best practice: Use the full DNS or IP Address to the left of the `|` character + +## Use Shell Commands Setting + +The Use Shell Commands Setting (orchestrator level in config.json and per store override of this value as a custom field value) +determines whether or not Linux shell commands will be used when managing certificate stores on Linux-based servers. +This is useful for environments where shell access is limited or even not allowed. In those scenarios setting this value to 'N' +will substitute SFTP commands for certain specific Linux shell commands. The following restrictions will be in place when +using RemoteFile in this mode: +1. The config.json option SeparateUploadFilePath must NOT be used (option missing from the config.json file or set to empty) for shell +commands to be suppressed for all use cases. +2. The config.json and custom field options DefaultLinuxPermissionsOnStoreCreation, DefaultOwnerOnStoreCreation, +LinuxFilePermissionsOnStoreCreation, and LinuxFileOwnerOnStoreCreation are not supported and will be ignored. As a result, file +permissions and ownership when creating certificate stores will be based on the user assigned to the Command certificate store and +other Linux environmental settings. +3. Discovery jobs are excluded and will still use the `find` shell command +4. A rare issue exists where the user id assigned to a certificate store has an expired password causing the orchestrator to hang +when attempting an SCP/SFTP connection. A modification was added to RemoteFile to check for this condition. Running RemoteFile +with Use Shell Commands = N will cause this validation check to NOT occur. +5. Both RFORA and RFKDB use proprietary CLI commands in order to manage their respective certificate stores. These commands +will still be executed when Use Shell Commands is set to Y. +6. If executing in local mode ('|LocalMachine' at the end of your client machine name for your certificate store), Use Shell +Commands = 'N' will have no effect. Shell commands will continue to be used because there will be no SSH connection +available from which to execute SFTP commands. + + ## Developer Notes The Remote File Orchestrator Extension is designed to be highly extensible, enabling its use with various file-based diff --git a/docsource/images/RFDER-advanced-store-type-dialog.png b/docsource/images/RFDER-advanced-store-type-dialog.png index bf8a8a39..90001473 100644 Binary files a/docsource/images/RFDER-advanced-store-type-dialog.png and b/docsource/images/RFDER-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFDER-basic-store-type-dialog.png b/docsource/images/RFDER-basic-store-type-dialog.png index 9e36ef88..a2d7236a 100644 Binary files a/docsource/images/RFDER-basic-store-type-dialog.png and b/docsource/images/RFDER-basic-store-type-dialog.png differ diff --git a/docsource/images/RFDER-custom-fields-store-type-dialog.png b/docsource/images/RFDER-custom-fields-store-type-dialog.png index f0f451b5..16db1d03 100644 Binary files a/docsource/images/RFDER-custom-fields-store-type-dialog.png and b/docsource/images/RFDER-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/RFJKS-advanced-store-type-dialog.png b/docsource/images/RFJKS-advanced-store-type-dialog.png index 3e724abc..4827627b 100644 Binary files a/docsource/images/RFJKS-advanced-store-type-dialog.png and b/docsource/images/RFJKS-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFJKS-basic-store-type-dialog.png b/docsource/images/RFJKS-basic-store-type-dialog.png index 88b86604..68a4881e 100644 Binary files a/docsource/images/RFJKS-basic-store-type-dialog.png and b/docsource/images/RFJKS-basic-store-type-dialog.png differ diff --git a/docsource/images/RFJKS-custom-fields-store-type-dialog.png b/docsource/images/RFJKS-custom-fields-store-type-dialog.png index 81638289..d003aeb1 100644 Binary files a/docsource/images/RFJKS-custom-fields-store-type-dialog.png and b/docsource/images/RFJKS-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/RFKDB-advanced-store-type-dialog.png b/docsource/images/RFKDB-advanced-store-type-dialog.png index 3e724abc..4827627b 100644 Binary files a/docsource/images/RFKDB-advanced-store-type-dialog.png and b/docsource/images/RFKDB-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFKDB-basic-store-type-dialog.png b/docsource/images/RFKDB-basic-store-type-dialog.png index f82fd0de..9580898e 100644 Binary files a/docsource/images/RFKDB-basic-store-type-dialog.png and b/docsource/images/RFKDB-basic-store-type-dialog.png differ diff --git a/docsource/images/RFKDB-custom-fields-store-type-dialog.png b/docsource/images/RFKDB-custom-fields-store-type-dialog.png index 81638289..14b8d903 100644 Binary files a/docsource/images/RFKDB-custom-fields-store-type-dialog.png and b/docsource/images/RFKDB-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/RFORA-advanced-store-type-dialog.png b/docsource/images/RFORA-advanced-store-type-dialog.png index 3e724abc..4827627b 100644 Binary files a/docsource/images/RFORA-advanced-store-type-dialog.png and b/docsource/images/RFORA-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFORA-basic-store-type-dialog.png b/docsource/images/RFORA-basic-store-type-dialog.png index e3f83edd..77a6ef00 100644 Binary files a/docsource/images/RFORA-basic-store-type-dialog.png and b/docsource/images/RFORA-basic-store-type-dialog.png differ diff --git a/docsource/images/RFPEM-advanced-store-type-dialog.png b/docsource/images/RFPEM-advanced-store-type-dialog.png index bf8a8a39..90001473 100644 Binary files a/docsource/images/RFPEM-advanced-store-type-dialog.png and b/docsource/images/RFPEM-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFPEM-basic-store-type-dialog.png b/docsource/images/RFPEM-basic-store-type-dialog.png index c1c221bf..fbe56b2f 100644 Binary files a/docsource/images/RFPEM-basic-store-type-dialog.png and b/docsource/images/RFPEM-basic-store-type-dialog.png differ diff --git a/docsource/images/RFPEM-custom-fields-store-type-dialog.png b/docsource/images/RFPEM-custom-fields-store-type-dialog.png index ada9a5e2..ba1edb2f 100644 Binary files a/docsource/images/RFPEM-custom-fields-store-type-dialog.png and b/docsource/images/RFPEM-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/RFPkcs12-advanced-store-type-dialog.png b/docsource/images/RFPkcs12-advanced-store-type-dialog.png index 3e724abc..4827627b 100644 Binary files a/docsource/images/RFPkcs12-advanced-store-type-dialog.png and b/docsource/images/RFPkcs12-advanced-store-type-dialog.png differ diff --git a/docsource/images/RFPkcs12-basic-store-type-dialog.png b/docsource/images/RFPkcs12-basic-store-type-dialog.png index 45a28c8a..db07a572 100644 Binary files a/docsource/images/RFPkcs12-basic-store-type-dialog.png and b/docsource/images/RFPkcs12-basic-store-type-dialog.png differ diff --git a/docsource/images/RFPkcs12-custom-fields-store-type-dialog.png b/docsource/images/RFPkcs12-custom-fields-store-type-dialog.png index 81638289..14b8d903 100644 Binary files a/docsource/images/RFPkcs12-custom-fields-store-type-dialog.png and b/docsource/images/RFPkcs12-custom-fields-store-type-dialog.png differ diff --git a/integration-manifest.json b/integration-manifest.json index 526d750e..5601c635 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -28,7 +28,7 @@ "Add": true, "Create": true, "Discovery": true, - "Enrollment": false, + "Enrollment": true, "Remove": true }, "PasswordOptions": { @@ -106,15 +106,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -123,6 +114,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [], @@ -142,7 +142,7 @@ "Add": true, "Create": true, "Discovery": true, - "Enrollment": false, + "Enrollment": true, "Remove": true }, "PasswordOptions": { @@ -256,15 +256,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -273,6 +264,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [], @@ -292,7 +292,7 @@ "Add": true, "Create": true, "Discovery": true, - "Enrollment": false, + "Enrollment": true, "Remove": true }, "PasswordOptions": { @@ -370,15 +370,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -387,6 +378,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [], @@ -406,7 +406,7 @@ "Add": true, "Create": true, "Discovery": true, - "Enrollment": false, + "Enrollment": true, "Remove": true }, "PasswordOptions": { @@ -493,15 +493,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -510,6 +501,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [], @@ -607,15 +607,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -624,6 +615,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [], @@ -730,15 +730,6 @@ "DefaultValue": "False", "Description": "Internally set the -IncludePortInSPN option when creating the remote PowerShell connection. Needed for some Kerberos configurations." }, - { - "Name": "FileTransferProtocol", - "DisplayName": "File Transfer Protocol to Use", - "Required": false, - "DependsOn": "", - "Type": "MultipleChoice", - "DefaultValue": ",SCP,SFTP,Both", - "Description": "Which protocol should be used when uploading/downloading files - SCP, SFTP, or Both (try one, and then if necessary, the other). Overrides FileTransferProtocol [config.json](#post-installation) setting." - }, { "Name": "SSHPort", "DisplayName": "SSH Port", @@ -747,6 +738,15 @@ "Type": "String", "DefaultValue": "", "Description": "Integer value representing the port that should be used when connecting to Linux servers over SSH. Overrides SSHPort [config.json](#post-installation) setting." + }, + { + "Name": "UseShellCommands", + "DisplayName": "Use Shell Commands", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": "True", + "Description": "Recommended to be set to the default value of 'Y'. For a detailed explanation of this setting, please refer to [Use Shell Commands Setting](#use-shell-commands-setting)" } ], "EntryParameters": [],