From be5b2212f72a2e9a83e8af7326a535d335b0b2c0 Mon Sep 17 00:00:00 2001
From: Lee Fine <50836957+leefine02@users.noreply.github.com>
Date: Fri, 1 Nov 2024 16:24:55 +0000
Subject: [PATCH] Ab#63477 (#68)
* use dual build action release candidate
* target completed 3.1.1 workflow
---
.../workflows/keyfactor-starter-workflow.yml | 3 +-
CHANGELOG.md | 10 +
README.md | 1483 +++++++++++++----
RemoteFile/Discovery.cs | 4 +-
.../KDB/KDBCertificateStoreSerializer.cs | 8 +-
.../OraWltCertificateStoreSerializer.cs | 8 +-
.../PEM/PEMCertificateStoreSerializer.cs | 44 +-
.../PKCS12CertificateStoreSerializer.cs | 66 +-
RemoteFile/InventoryBase.cs | 1 -
RemoteFile/ManagementBase.cs | 1 -
RemoteFile/Models/SerializedStoreInfo.cs | 2 +-
RemoteFile/RemoteCertificateStore.cs | 12 +-
RemoteFile/RemoteFile.csproj | 31 +-
.../RemoteHandlers/LinuxLocalHandler.cs | 2 +-
RemoteFile/RemoteHandlers/SSHHandler.cs | 41 +-
RemoteFile/RemoteHandlers/SSHHelper.cs | 151 --
RemoteFile/RemoteHandlers/WinRMHandler.cs | 6 +-
docsource/content.md | 222 +++
.../RFDER-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../images/RFDER-basic-store-type-dialog.png | Bin 0 -> 50826 bytes
.../RFDER-custom-fields-store-type-dialog.png | Bin 0 -> 44281 bytes
.../RFJKS-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../images/RFJKS-basic-store-type-dialog.png | Bin 0 -> 50940 bytes
.../RFJKS-custom-fields-store-type-dialog.png | Bin 0 -> 40605 bytes
.../RFKDB-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../images/RFKDB-basic-store-type-dialog.png | Bin 0 -> 51066 bytes
.../RFKDB-custom-fields-store-type-dialog.png | Bin 0 -> 40605 bytes
.../RFORA-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../images/RFORA-basic-store-type-dialog.png | Bin 0 -> 51335 bytes
.../RFORA-custom-fields-store-type-dialog.png | Bin 0 -> 44469 bytes
.../RFPEM-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../images/RFPEM-basic-store-type-dialog.png | Bin 0 -> 50797 bytes
.../RFPEM-custom-fields-store-type-dialog.png | Bin 0 -> 51959 bytes
.../RFPkcs12-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes
.../RFPkcs12-basic-store-type-dialog.png | Bin 0 -> 51842 bytes
...Pkcs12-custom-fields-store-type-dialog.png | Bin 0 -> 40605 bytes
docsource/rfder.md | 7 +
docsource/rfjks.md | 8 +
docsource/rfkdb.md | 8 +
docsource/rfora.md | 8 +
docsource/rfpem.md | 12 +
docsource/rfpkcs12.md | 13 +
integration-manifest.json | 924 +++++-----
readme-src/readme-pam-support.md | 6 -
readme-src/store-types-tables.md | 318 ----
readme_source.md | 535 ------
46 files changed, 2119 insertions(+), 1815 deletions(-)
delete mode 100644 RemoteFile/RemoteHandlers/SSHHelper.cs
create mode 100644 docsource/content.md
create mode 100644 docsource/images/RFDER-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFDER-basic-store-type-dialog.png
create mode 100644 docsource/images/RFDER-custom-fields-store-type-dialog.png
create mode 100644 docsource/images/RFJKS-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFJKS-basic-store-type-dialog.png
create mode 100644 docsource/images/RFJKS-custom-fields-store-type-dialog.png
create mode 100644 docsource/images/RFKDB-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFKDB-basic-store-type-dialog.png
create mode 100644 docsource/images/RFKDB-custom-fields-store-type-dialog.png
create mode 100644 docsource/images/RFORA-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFORA-basic-store-type-dialog.png
create mode 100644 docsource/images/RFORA-custom-fields-store-type-dialog.png
create mode 100644 docsource/images/RFPEM-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFPEM-basic-store-type-dialog.png
create mode 100644 docsource/images/RFPEM-custom-fields-store-type-dialog.png
create mode 100644 docsource/images/RFPkcs12-advanced-store-type-dialog.png
create mode 100644 docsource/images/RFPkcs12-basic-store-type-dialog.png
create mode 100644 docsource/images/RFPkcs12-custom-fields-store-type-dialog.png
create mode 100644 docsource/rfder.md
create mode 100644 docsource/rfjks.md
create mode 100644 docsource/rfkdb.md
create mode 100644 docsource/rfora.md
create mode 100644 docsource/rfpem.md
create mode 100644 docsource/rfpkcs12.md
delete mode 100644 readme-src/readme-pam-support.md
delete mode 100644 readme-src/store-types-tables.md
delete mode 100644 readme_source.md
diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml
index 6d8de532..0093d2cd 100644
--- a/.github/workflows/keyfactor-starter-workflow.yml
+++ b/.github/workflows/keyfactor-starter-workflow.yml
@@ -11,9 +11,10 @@ on:
jobs:
call-starter-workflow:
- uses: keyfactor/actions/.github/workflows/starter.yml@v2
+ uses: keyfactor/actions/.github/workflows/starter.yml@3.1.1
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 }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0696ada3..79bf2739 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+v2.9.0
+- Modify Discovery on Linux servers to filter out ignored folders when searching using the Find command rather than eliminating them after. This was done to eliminate permissions errors.
+- Deprecated isRSAPrivateKey custom property on RFPEM certificate store type. Integration now reads the existing private key to determin if it is formatted as PKCS#1 or PKCS#8 and, on renewal, keeps the format the same. For new PEM certificate stores/certificates, PKCS#8 will always be used. PLEASE NOTE, for existing certificate stores that already have isRSAPrivateKey defined, this setting will be ignored.
+- Modified RFPkcs12 store type to handle single store certificate stores with no friendly name/alias
+- Modify to create 2 builds - one for .net6 and one for .net8
+- Update README to new DocTool format
+
+v2.8.1
+- Fixed issue were sensitive information could be exposed at debug logging level
+
v2.8.0
- Added new custom field - Remove Root Certificate from Chain - to allow adding certificate entries with the root CA certificate removed from the chain.
- Added SSH KeyboardInteractive Authentication support if Password Authentication is not enabled.
diff --git a/README.md b/README.md
index 0159c24d..01c08d71 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,74 @@
-
-# Remote File
-
-The Remote File Orchestrator allows for the remote management of file-based certificate stores. Discovery, Inventory, and Management functions are supported. The orchestrator performs operations by first converting the certificate store into a BouncyCastle PKCS12Store.
-
-#### Integration status: Production - Ready for use in production environments.
-
-## About the Keyfactor Universal Orchestrator Extension
-
-This repository contains a Universal Orchestrator Extension which is a plugin to the Keyfactor Universal Orchestrator. Within the Keyfactor Platform, Orchestrators are used to manage “certificate stores” — collections of certificates and roots of trust that are found within and used by various applications.
-
-The Universal Orchestrator is part of the Keyfactor software distribution and is available via the Keyfactor customer portal. For general instructions on installing Extensions, see the “Keyfactor Command Orchestrator Installation and Configuration Guide” section of the Keyfactor documentation. For configuration details of this specific Extension see below in this readme.
-
-The Universal Orchestrator is the successor to the Windows Orchestrator. This Orchestrator Extension plugin only works with the Universal Orchestrator and does not work with the Windows Orchestrator.
-
-## Support for Remote File
-
-Remote File is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com
-
-###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab.
-
----
-
-
----
-
-
-
-## Keyfactor Version Supported
-
-This extension is compiled against Microsoft's .NET6 Framework and must be installed with the Keyfactor v11.5.1 Universal Orchestrator Framework or earlier. This is not compatible with v11.6+ or v12.x of the Keyfactor Universal Orchestrator Framework.
-
-## Platform Specific Notes
-
-The Keyfactor Universal Orchestrator may be installed on either Windows or Linux based platforms. The certificate operations supported by a capability may vary based what platform the capability is installed on. The table below indicates what capabilities are supported based on which platform the encompassing Universal Orchestrator is running.
-| Operation | Win | Linux |
-|-----|-----|------|
-|Supports Management Add|✓ |✓ |
-|Supports Management Remove|✓ |✓ |
-|Supports Create Store|✓ |✓ |
-|Supports Discovery|✓ |✓ |
-|Supports Reenrollment| | |
-|Supports Inventory|✓ |✓ |
-
-
-## PAM Integration
-
-This orchestrator extension has the ability to connect to a variety of supported PAM providers to allow for the retrieval of various client hosted secrets right from the orchestrator server itself. This eliminates the need to set up the PAM integration on Keyfactor Command which may be in an environment that the client does not want to have access to their PAM provider.
-
-The secrets that this orchestrator extension supports for use with a PAM Provider are:
-
-|Name|Description|
-|----|-----------|
-|ServerUsername|The user id that will be used to authenticate into the server hosting the store|
-|ServerPassword|The password that will be used to authenticate into the server hosting the store|
-|StorePassword|The optional password used to secure the certificate store being managed|
-
-
-It is not necessary to use a PAM Provider for all of the secrets available above. If a PAM Provider should not be used, simply enter in the actual value to be used, as normal.
-
-If a PAM Provider will be used for one of the fields above, start by referencing the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam). The GitHub repo for the PAM Provider to be used contains important information such as the format of the `json` needed. What follows is an example but does not reflect the `json` values for all PAM Providers as they have different "instance" and "initialization" parameter names and values.
-
-General PAM Provider Configuration
-
-
-
-
-### Example PAM Provider Setup
-
-To use a PAM Provider to resolve a field, in this example the __Server Password__ will be resolved by the `Hashicorp-Vault` provider, first install the PAM Provider extension from the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) on the Universal Orchestrator.
-
-Next, complete configuration of the PAM Provider on the UO by editing the `manifest.json` of the __PAM Provider__ (e.g. located at extensions/Hashicorp-Vault/manifest.json). The "initialization" parameters need to be entered here:
-
-~~~ json
- "Keyfactor:PAMProviders:Hashicorp-Vault:InitializationInfo": {
- "Host": "http://127.0.0.1:8200",
- "Path": "v1/secret/data",
- "Token": "xxxxxx"
- }
-~~~
-
-After these values are entered, the Orchestrator needs to be restarted to pick up the configuration. Now the PAM Provider can be used on other Orchestrator Extensions.
-
-### Use the PAM Provider
-With the PAM Provider configured as an extenion on the UO, a `json` object can be passed instead of an actual value to resolve the field with a PAM Provider. Consult the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) for the specific format of the `json` object.
-
-To have the __Server Password__ field resolved by the `Hashicorp-Vault` provider, the corresponding `json` object from the `Hashicorp-Vault` extension needs to be copied and filed in with the correct information:
-
-~~~ json
-{"Secret":"my-kv-secret","Key":"myServerPassword"}
-~~~
-
-This text would be entered in as the value for the __Server Password__, instead of entering in the actual password. The Orchestrator will attempt to use the PAM Provider to retrieve the __Server Password__. If PAM should not be used, just directly enter in the value for the field.
+
+ Remote File Universal Orchestrator Extension
+
+
+
+
+
+
+
+
-
+
+
+
+ Support
+
+ ·
+
+ Installation
+
+ ·
+
+ License
+
+ ·
+
+ Related Integrations
+
+
+## Overview
+The Remote File Orchestrator Extension is a multi-purpose integration that can remotely manage a variety of file-based certificate stores and can easily be extended to manage others. The certificate store types that can be managed in the current version are:
----
+* RFJKS - Java Keystores of types JKS or PKCS12
+* RFPkcs12 - Certificate stores that follow the PKCS#12 standard
+* RFPEM - Files in PEM format
+* RFDER - Files in binary DER format
+* RFORA - Pkcs#12 formatted Oracle Wallets
+* RFKDB - IBM Key Database files
+The Keyfactor Univeral Orchestrator (UO) and RemoteFile Extension can be installed on either Windows or Linux operating systems as well as manage certificates residing on servers of both operating systems. A UO service managing certificates on remote servers is considered to be acting as an Orchestrator, while a UO service managing local certificates on the same server running the service is considered an Agent. When acting as an Orchestrator, connectivity from the orchestrator server hosting the RemoteFile extension to the orchestrated server hosting the certificate store(s) being managed is achieved via either an SSH (for Linux and possibly Windows orchestrated servers) or WinRM (for Windows 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.
-
-## Overview
-The Remote File Orchestrator Extension is a multi-purpose integration that can remotely manage a variety of file-based certificate stores and can easily be extended to manage others. The certificate store types that can be managed in the current version are:
+![](images/orchestrator-agent.png)
-
-RFPkcs12
+Please refer to the READMEs for each supported store type for more information on proper configuration and setup for these different architectures. The supported configurations of Universal Orchestrator hosts and managed orchestrated servers are detailed below:
-The RFPkcs12 store type can be used to manage any PKCS#12 compliant file format INCLUDING java keystores of type PKCS12.
+| | UO Installed on Windows | UO Installed on Linux |
+|-----|-----|------|
+|Orchestrated Server hosting certificate store(s) on remote Windows server|WinRM connection | SSH connection |
+|Orchestrated Server hosting certificate store(s) on remote Linux server| SSH connection | SSH connection |
+|Certificate store(s) on same server as orchestrator service (Agent)| WinRM connection or local file system | SSH connection or local file system |
-Use cases supported:
-1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
-2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate identified with a custom alias.
-3. A mix of trust and key entries.
+The Remote File Universal Orchestrator extension implements 6 Certificate Store Types. Depending on your use case, you may elect to use one, or all of these Certificate Store Types. Descriptions of each are provided below.
-
+RFJKS (RFJKS)
-
-RFJKS
+### RFJKS
The RFJKS store type can be used to manage java keystores of types JKS or PKCS12. If creating a new java keystore and adding a certificate all via Keyfactor Command, the created java keystore will be of type PKCS12, as java keystores of type JKS have been deprecated as of JDK 9.
Use cases supported:
-1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
-2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate identified with a custom alias.
+1. One-to-many trust entries - A trust entry is defined as a single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
+2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate is identified with a custom alias.
3. A mix of trust and key entries.
-
-
-RFPEM
+RFPEM (RFPEM)
+
+### RFPEM
The RFPEM store type can be used to manage PEM encoded files.
@@ -140,66 +79,75 @@ Use cases supported:
4. Single certificate stores with private key in an external file.
5. Single certificate stores with certificate chain in the file and private key in an external file
-NOTE: PEM stores may only have one private key (internal or external) associated with the store, as only one certificate/chain/private key combination can be stored in a PEM store supported by RFPEM.
+NOTE: PEM stores may only have one private key (internal or external) associated with the store, as only one certificate/chain/private key combination can be stored in a PEM store supported by RFPEM. Private keys will be stored in encrypted or unencrypted PKCS#8 format (BEGIN [ENCRYPTED] PRIVATE KEY) based on the Store Password set on the Keyfactor Command Certificate Store unless managing a PEM store that currently contains a private key in PKCS#1 format (BEGIN RSA PRIVATE KEY). Store password MUST be set to "No Password" if managing a store with a PKCS#1 private key.
+
+
+RFPkcs12 (RFPkcs12)
+
+### RFPkcs12
+
+The RFPkcs12 store type can be used to manage any PKCS#12 compliant file format INCLUDING java keystores of type PKCS12.
+Use cases supported:
+1. One-to-many trust entries - A trust entry is defined as a single certificate without a private key in a certificate store. Each trust entry MUST BE identified with a custom friendly name/alias.
+2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate MUST BE identified with a custom friendly name/alias.
+3. A mix of trust and key entries. Each entry MUST BE identified with a custom friendly name/alias.
+4. Single certificate stores with a blank/missing friendly name/alias. Any management add job will replace the current certificate entry and will keep the friendly name/alias blank. The Keyfactor Command certificate store will show the current certificate thumbprint as the entry's alias.
+
+Use cases not supported:
+1. Multiple key and/or trust entries with a mix of existing and non existing friendly names/aliases.
+2. Multiple key and/or trust entries with blank friendly names/aliases
-
+RFDER (RFDER)
-RFDER
+### RFDER
The RFDER store type can be used to manage DER encoded files.
Use cases supported:
1. Single certificate stores with private key in an external file.
-2. Single certificate stores with no private key.
-
+2. Single certificate stores with no private key.
-
-RFKDB
+RFKDB (RFKDB)
+
+### RFKDB
The RFKDB store type can be used to manage IBM Key Database Files (KDB) files. The IBM utility, GSKCAPICMD, is used to read and write certificates from and to the target store and is therefore required to be installed on the server where each KDB certificate store being managed resides, and its location MUST be in the system $Path.
Use cases supported:
-1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
-2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate identified with a custom alias.
+1. One-to-many trust entries - A trust entry is defined as a single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
+2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate is identified with a custom alias.
3. A mix of trust and key entries.
-
-
-RFORA
+RFORA (RFORA)
+
+### RFORA
The RFORA store type can be used to manage Pkcs12 Oracle Wallets. Please note that while this should work for Pkcs12 Oracle Wallets installed on both Windows and Linux servers, this has only been tested on wallets installed on Windows. Please note, when entering the Store Path for an Oracle Wallet in Keyfactor Command, make sure to INCLUDE the eWallet.p12 file name that by convention is the name of the Pkcs12 wallet file that gets created.
Use cases supported:
-1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
-2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate identified with a custom alias.
+1. One-to-many trust entries - A trust entry is defined as a single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias.
+2. One-to-many key entries - One-to-many certificates with private keys and optionally the full certificate chain. Each certificate is identified with a custom alias.
3. A mix of trust and key entries.
-
-The Keyfactor Univeral Orchestrator (UO) and RemoteFile Extension can be installed on either Windows or Linux operating systems as well as manage certificates residing on servers of both operating systems. A UO service managing certificates on remote servers is considered to be acting as an Orchestrator, while a UO service managing local certificates on the same server running the service is considered an Agent. When acting as an Orchestrator, connectivity from the orchestrator server hosting the RemoteFile extension to the orchestrated server hosting the certificate store(s) being managed is achieved via either an SSH (for Linux and possibly Windows orchestrated servers) or WinRM (for Windows 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.
-![](images/orchestrator-agent.png)
+## Compatibility
-Please review the [Prerequisites and Security Considerations](#prerequisites-and-security-considerations) and [Certificate Stores and Discovery Jobs](#certificate-stores-and-discovery-jobs) sections for more information on proper configuration and setup for these different architectures. The supported configurations of Universal Orchestrator hosts and managed orchestrated servers are detailed below:
+This integration is compatible with Keyfactor Universal Orchestrator version 10.4 and later.
-| | UO Installed on Windows | UO Installed on Linux |
-|-----|-----|------|
-|Orchestrated Server hosting certificate store(s) on remote Windows server|WinRM connection | SSH connection |
-|Orchestrated Server hosting certificate store(s) on remote Linux server| SSH connection | SSH connection |
-|Certificate store(s) on same server as orchestrator service (Agent)| WinRM connection or local file system | SSH connection or local file system |
+## Support
+The Remote File Universal Orchestrator extension is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com.
+
+> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab.
-
-## Versioning
+## Requirements & Prerequisites
-The version number of a the Remote File Orchestrator Extension can be verified by right clicking on the RemoteFile.dll file in the Extensions/RemoteFile installation folder, selecting Properties, and then clicking on the Details tab.
-
-
+Before installing the Remote File Universal Orchestrator extension, we recommend that you install [kfutil](https://github.com/Keyfactor/kfutil). Kfutil is a command-line tool that simplifies the process of creating store types, installing extensions, and instantiating certificate stores in Keyfactor Command.
-## Prerequisites and Security Considerations
Certificate stores hosted on Linux servers:
@@ -210,7 +158,8 @@ The version number of a the Remote File Orchestrator Extension can be verified b
|---|---|
|echo|Used to append a newline and terminate all commands sent.|
|find|Used by Discovery jobs to locate potential certificate stores on the file system.|
-|cp|Used by Inventory and Management Add/Remove jobs to copy the certificate store file to a temporary file (only when an alternate download folder has been configured).|
+|cp|Used by Inventory and Management Add/Remove/Create jobs to determine if certificate store file exists.|
+|ls|Used by Management Add/Remove jobs to copy the certificate store file to a temporary file (only when an alternate download folder has been configured).|
|chown|Used by the Inventory and Management Add/Remove jobs to set the permissions on the temporary file (only when an alternate download folder has been configured).|
|tee|Used by Management Add/Remove jobs to copy the temporary uploaded certificate file to the certificate store file (only when an alternate upload folder has been configured).|
|rm|Used by Inventory and Management Add/Remove jobs to remove temporary files (only when an alternate upload/download folder has been configured).|
@@ -225,7 +174,7 @@ The version number of a the Remote File Orchestrator Extension can be verified b
- PKCS#8 (BEGIN PRIVATE KEY)
- ECDSA OPENSSH (BEGIN OPENSSH PRIVATE KEY)
-Please reference [Configuration File Setup](#configuration-file-setup) for more information on setting up the config.json file and [Certificate Stores and Discovery Jobs](#certificate-stores-and-discovery-jobs) for more information on the items above.
+Please reference [Post Installation](#post-installation) for more information on setting up the config.json file and [Defining Certificate Stores](#defining-certificate-stores) and [Discovering Certificate Stores with the Discovery Job](#discovering-certificate-stores-with-the-discovery-job) for more information on defining and configuring certificate stores.
@@ -236,23 +185,467 @@ Please reference [Configuration File Setup](#configuration-file-setup) for more
Please consult with your company's system administrator for more information on configuring SSH/SFTP/SCP or WinRM in your environment.
-
-
-## Remote File Orchestrator Extension Installation
-1. Review the [Prerequisites and Security Considerations](#prerequisites-and-security-considerations) section and make sure your environment is set up as required.
-2. Refer to the [Creating Certificate Store Types](#creating-certificate-store-types) section to create the certificate store types you wish to manage.
-3. Stop the Keyfactor Universal Orchestrator Service on the server you plan to install this extension to run on.
-4. In the Keyfactor Orchestrator installation folder (by convention usually C:\Program Files\Keyfactor\Keyfactor Orchestrator for a Windows install or /opt/keyfactor/orchestrator/ for a Linux install), find the "Extensions" folder. Underneath that, create a new folder named "RemoteFile". You may choose to use a different name if you wish.
-5. Download the latest version of the RemoteFile orchestrator extension from [GitHub](https://github.com/Keyfactor/remote-file-orchestrator). Click on the "Latest" release link on the right hand side of the main page and download the first zip file.
-6. Copy the contents of the download installation zip file to the folder created in step 4.
-7. (Optional) If you decide to create one or more certificate store types with short names different than the suggested values, edit the manifest.json file in the folder you created in step 4, and modify each "ShortName" in each "Certstores.{ShortName}.{Operation}" line with the ShortName you used to create the respective certificate store type.
-8. Modify the config.json file to use the settings you desire. Please go to [Configuration File Setup](#configuration-file-setup) to learn more.
-9. Start the Keyfactor Universal Orchestrator Service.
-
+## Create Certificate Store Types
+
+To use the Remote File Universal Orchestrator extension, you **must** create the Certificate Store Types required for your usecase. This only needs to happen _once_ per Keyfactor Command instance.
+
+The Remote File Universal Orchestrator extension implements 6 Certificate Store Types. Depending on your use case, you may elect to use one, or all of these Certificate Store Types.
+
+RFJKS (RFJKS)
+
+
+* **Create RFJKS using kfutil**:
+
+ ```shell
+ # RFJKS
+ kfutil store-types create RFJKS
+ ```
+
+* **Create RFJKS manually in the Command UI**:
+ Create RFJKS manually in the Command UI
+
+ Create a store type called `RFJKS` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFJKS | Display name for the store type (may be customized) |
+ | Short Name | RFJKS | Short display name for the store type |
+ | Capability | RFJKS | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFJKS Basic Tab](docsource/images/RFJKS-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFJKS Advanced Tab](docsource/images/RFJKS-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+
+ The Custom Fields tab should look like this:
+
+ ![RFJKS Custom Fields Tab](docsource/images/RFJKS-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+RFPEM (RFPEM)
+
+
+* **Create RFPEM using kfutil**:
+
+ ```shell
+ # RFPEM
+ kfutil store-types create RFPEM
+ ```
+
+* **Create RFPEM manually in the Command UI**:
+ Create RFPEM manually in the Command UI
+
+ Create a store type called `RFPEM` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFPEM | Display name for the store type (may be customized) |
+ | Short Name | RFPEM | Short display name for the store type |
+ | Capability | RFPEM | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFPEM Basic Tab](docsource/images/RFPEM-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Forbidden | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFPEM Advanced Tab](docsource/images/RFPEM-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+ | IsTrustStore | Trust Store | The IsTrustStore field should contain a boolean value ('true' or 'false') indicating whether the store will be identified as a trust store, which can hold multiple certificates without private keys. Example: 'true' for a trust store or 'false' for a store with a single certificate and private key. | Bool | false | 🔲 Unchecked |
+ | IncludesChain | Store Includes Chain | The IncludesChain field should contain a boolean value ('true' or 'false') indicating whether the certificate store includes the full certificate chain along with the end entity certificate. Example: 'true' to include the full chain or 'false' to exclude it. | Bool | false | 🔲 Unchecked |
+ | 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.pem'. | String | | 🔲 Unchecked |
+ | IgnorePrivateKeyOnInventory | Ignore Private Key On Inventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to ignore the private key during inventory, which will make the store inventory-only and return all certificates without private key entries. Example: 'true' to ignore the private key or 'false' to include it. | Bool | false | 🔲 Unchecked |
+
+ The Custom Fields tab should look like this:
+
+ ![RFPEM Custom Fields Tab](docsource/images/RFPEM-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+RFPkcs12 (RFPkcs12)
+
+
+* **Create RFPkcs12 using kfutil**:
+
+ ```shell
+ # RFPkcs12
+ kfutil store-types create RFPkcs12
+ ```
+
+* **Create RFPkcs12 manually in the Command UI**:
+ Create RFPkcs12 manually in the Command UI
+
+ Create a store type called `RFPkcs12` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFPkcs12 | Display name for the store type (may be customized) |
+ | Short Name | RFPkcs12 | Short display name for the store type |
+ | Capability | RFPkcs12 | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFPkcs12 Basic Tab](docsource/images/RFPkcs12-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFPkcs12 Advanced Tab](docsource/images/RFPkcs12-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+
+ The Custom Fields tab should look like this:
+
+ ![RFPkcs12 Custom Fields Tab](docsource/images/RFPkcs12-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+RFDER (RFDER)
+
+
+* **Create RFDER using kfutil**:
+
+ ```shell
+ # RFDER
+ kfutil store-types create RFDER
+ ```
+
+* **Create RFDER manually in the Command UI**:
+ Create RFDER manually in the Command UI
+
+ Create a store type called `RFDER` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFDER | Display name for the store type (may be customized) |
+ | Short Name | RFDER | Short display name for the store type |
+ | Capability | RFDER | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFDER Basic Tab](docsource/images/RFDER-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Forbidden | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFDER Advanced Tab](docsource/images/RFDER-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+ | 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 |
+
+ The Custom Fields tab should look like this:
+
+ ![RFDER Custom Fields Tab](docsource/images/RFDER-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+RFKDB (RFKDB)
+
+
+* **Create RFKDB using kfutil**:
+
+ ```shell
+ # RFKDB
+ kfutil store-types create RFKDB
+ ```
+
+* **Create RFKDB manually in the Command UI**:
+ Create RFKDB manually in the Command UI
+
+ Create a store type called `RFKDB` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFKDB | Display name for the store type (may be customized) |
+ | Short Name | RFKDB | Short display name for the store type |
+ | Capability | RFKDB | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFKDB Basic Tab](docsource/images/RFKDB-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFKDB Advanced Tab](docsource/images/RFKDB-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+
+ The Custom Fields tab should look like this:
-
-## Configuration File Setup
+ ![RFKDB Custom Fields Tab](docsource/images/RFKDB-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+RFORA (RFORA)
+
+
+* **Create RFORA using kfutil**:
+
+ ```shell
+ # RFORA
+ kfutil store-types create RFORA
+ ```
+
+* **Create RFORA manually in the Command UI**:
+ Create RFORA manually in the Command UI
+
+ Create a store type called `RFORA` with the attributes in the tables below:
+
+ #### Basic Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Name | RFORA | Display name for the store type (may be customized) |
+ | Short Name | RFORA | Short display name for the store type |
+ | Capability | RFORA | Store type name orchestrator will register with. Check the box to allow entry of value |
+ | 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 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 |
+ | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
+ | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. |
+ | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. |
+
+ The Basic tab should look like this:
+
+ ![RFORA Basic Tab](docsource/images/RFORA-basic-store-type-dialog.png)
+
+ #### Advanced Tab
+ | Attribute | Value | Description |
+ | --------- | ----- | ----- |
+ | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
+
+ The Advanced tab should look like this:
+
+ ![RFORA Advanced Tab](docsource/images/RFORA-advanced-store-type-dialog.png)
+
+ #### Custom Fields Tab
+ Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
+
+ | Name | Display Name | Description | Type | Default Value/Options | Required |
+ | ---- | ------------ | ---- | --------------------- | -------- | ----------- |
+ | ServerUsername | Server Username | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | ServerPassword | Server Password | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* | Secret | | 🔲 Unchecked |
+ | LinuxFilePermissionsOnStoreCreation | Linux File Permissions on Store Creation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. | String | | 🔲 Unchecked |
+ | LinuxFileOwnerOnStoreCreation | Linux File Owner on Store Creation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. | String | | 🔲 Unchecked |
+ | SudoImpersonatingUser | Sudo Impersonating User | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. | String | | 🔲 Unchecked |
+ | 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 |
+
+ The Custom Fields tab should look like this:
+
+ ![RFORA Custom Fields Tab](docsource/images/RFORA-custom-fields-store-type-dialog.png)
+
+
+
+
+
+
+
+## Installation
+
+1. **Download the latest Remote File Universal Orchestrator extension from GitHub.**
+
+ Navigate to the [Remote File Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/remote-file-orchestrator/releases/latest). Refer to the compatibility matrix below to determine whether the `net6.0` or `net8.0` asset should be downloaded. Then, click the corresponding asset to download the zip archive.
+ | Universal Orchestrator Version | Latest .NET version installed on the Universal Orchestrator server | `rollForward` condition in `Orchestrator.runtimeconfig.json` | `remote-file-orchestrator` .NET version to download |
+ | --------- | ----------- | ----------- | ----------- |
+ | Older than `11.0.0` | | | `net6.0` |
+ | Between `11.0.0` and `11.5.1` (inclusive) | `net6.0` | | `net6.0` |
+ | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `Never` | `net6.0` |
+ | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `LatestMajor` | `net8.0` |
+ | `11.6` _and_ newer | `net8.0` | | `net8.0` |
+
+ Unzip the archive containing extension assemblies to a known location.
+
+ > **Note** If you don't see an asset with a corresponding .NET version, you should always assume that it was compiled for `net6.0`.
+
+2. **Locate the Universal Orchestrator extensions directory.**
+
+ * **Default on Windows** - `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions`
+ * **Default on Linux** - `/opt/keyfactor/orchestrator/extensions`
+
+3. **Create a new directory for the Remote File Universal Orchestrator extension inside the extensions directory.**
+
+ Create a new directory called `remote-file-orchestrator`.
+ > The directory name does not need to match any names used elsewhere; it just has to be unique within the extensions directory.
+
+4. **Copy the contents of the downloaded and unzipped assemblies from __step 2__ to the `remote-file-orchestrator` directory.**
+
+5. **Restart the Universal Orchestrator service.**
+
+ Refer to [Starting/Restarting the Universal Orchestrator service](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/StarttheService.htm).
+
+
+6. **(optional) PAM Integration**
+
+ The Remote File Universal Orchestrator extension is compatible with all supported Keyfactor PAM extensions to resolve PAM-eligible secrets. PAM extensions running on Universal Orchestrators enable secure retrieval of secrets from a connected PAM provider.
+
+ To configure a PAM provider, [reference the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) to select an extension, and follow the associated instructions to install it on the Universal Orchestrator (remote).
+
+
+> The above installation steps can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm?Highlight=extensions).
+
+
+## Post Installation
The Remote File Orchestrator Extension uses a JSON configuration file. It is located in the {Keyfactor Orchestrator Installation Folder}\Extensions\RemoteFile. None of the values are required, and a description of each follows below:
{
@@ -338,278 +731,683 @@ The Remote File Orchestrator Extension uses a JSON configuration file. It is lo
-
-## Creating Certificate Store Types
-Below are the various certificate store types that the RemoteFile Orchestator Extension manages. To create a new Certificate Store Type in Keyfactor Command, first click on settings (the gear icon on the top right) => Certificate Store Types => Add. Next, follow the incstructions under each store type you wish to set up.
+## Defining Certificate Stores
-
-RFPkcs12 - Pkcs12 formatted certificate file (including java keystores of type PKCS12)
+The Remote File Universal Orchestrator extension implements 6 Certificate Store Types, each of which implements different functionality. Refer to the individual instructions below for each Certificate Store Type that you deemed necessary for your use case from the installation section.
-- Basic Tab:
+RFJKS (RFJKS)
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFPkcs12**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
-- Advanced Tab:
+* **Manually with the Command UI**
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Required.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+ Create Certificate Stores manually in the UI
-- Custom Fields Tab:
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
+ 2. **Add a Certificate Store.**
-- Entry Parameters Tab:
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFJKS" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions) |
+ | Store Path | The full path and file name, including file extension if one exists where the certificate store file is located. For Linux orchestrated servers, StorePath will begin with a forward slash (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should begin with a drive letter (i.e. c:\folder\path\storename.ext). |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFJKS` certificates. Specifically, one with the `RFJKS` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
- - no additional entry parameters
+
-
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
-
-RFJKS - Java keystore
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+
+
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFJKS certificate store**
-- Basic Tab:
+ ```shell
+ kfutil stores import generate-template --store-type-name RFJKS --outpath RFJKS.csv
+ ```
+ 2. **Populate the generated CSV file**
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFJKS**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFJKS" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions) |
+ | Store Path | The full path and file name, including file extension if one exists where the certificate store file is located. For Linux orchestrated servers, StorePath will begin with a forward slash (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should begin with a drive letter (i.e. c:\folder\path\storename.ext). |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFJKS` certificates. Specifically, one with the `RFJKS` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
-- Advanced Tab:
+
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Required.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
-- Custom Fields Tab:
-
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.**.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
-- Entry Parameters Tab:
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+ 3. **Import the CSV file to create the certificate stores**
+
+ ```shell
+ kfutil stores import csv --store-type-name RFJKS --file RFJKS.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
- - no additional entry parameters
-
-RFPEM - PEM formatted certificate file
+RFPEM (RFPEM)
+
+
+* **Manually with the Command UI**
+
+ Create Certificate Stores manually in the UI
+
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
+
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
+
+ 2. **Add a Certificate Store.**
+
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFPEM" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.ext) for Windows orchestrated servers. Example: '/folder/path/storename.pem' or 'c:\folder\path\storename.pem'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFPEM` certificates. Specifically, one with the `RFPEM` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | IsTrustStore | The IsTrustStore field should contain a boolean value ('true' or 'false') indicating whether the store will be identified as a trust store, which can hold multiple certificates without private keys. Example: 'true' for a trust store or 'false' for a store with a single certificate and private key. |
+ | IncludesChain | The IncludesChain field should contain a boolean value ('true' or 'false') indicating whether the certificate store includes the full certificate chain along with the end entity certificate. Example: 'true' to include the full chain or 'false' to exclude it. |
+ | 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.pem'. |
+ | IgnorePrivateKeyOnInventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to ignore the private key during inventory, which will make the store inventory-only and return all certificates without private key entries. Example: 'true' to ignore the private key or 'false' to include it. |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
+
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+
+
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFPEM certificate store**
+
+ ```shell
+ kfutil stores import generate-template --store-type-name RFPEM --outpath RFPEM.csv
+ ```
+ 2. **Populate the generated CSV file**
-- Basic Tab:
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFPEM" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.ext) for Windows orchestrated servers. Example: '/folder/path/storename.pem' or 'c:\folder\path\storename.pem'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFPEM` certificates. Specifically, one with the `RFPEM` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | IsTrustStore | The IsTrustStore field should contain a boolean value ('true' or 'false') indicating whether the store will be identified as a trust store, which can hold multiple certificates without private keys. Example: 'true' for a trust store or 'false' for a store with a single certificate and private key. |
+ | IncludesChain | The IncludesChain field should contain a boolean value ('true' or 'false') indicating whether the certificate store includes the full certificate chain along with the end entity certificate. Example: 'true' to include the full chain or 'false' to exclude it. |
+ | 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.pem'. |
+ | IgnorePrivateKeyOnInventory | The IgnorePrivateKeyOnInventory field should contain a boolean value ('true' or 'false') indicating whether to ignore the private key during inventory, which will make the store inventory-only and return all certificates without private key entries. Example: 'true' to ignore the private key or 'false' to include it. |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFPEM**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
+
-- Advanced Tab:
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Forbidden.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store. For stores with PKCS#8 private keys, set the password for encrypted private keys (BEGIN ENCRYPTED PRIVATE KEY) or 'No Value' for unencrypted private keys (BEGIN PRIVATE KEY). If managing a store with a PKCS#1 private key (BEGIN RSA PRIVATE KEY), this value MUST be set to 'No Value' |
-- Custom Fields Tab:
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.**.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
- - **Name:** IsTrustStore, **Display Name:** Trust Store, **Type:** Bool, **Default Value:** false. This custom field is **not required**. Default value if not present is 'false'. If 'true', this store will be identified as a trust store. Any certificates attempting to be added via a Management-Add job that contain a private key will raise an error with an accompanying message. Multiple certificates may be added to the store in this use case. If set to 'false', this store can only contain a single certificate with chain and private key. Management-Add jobs attempting to add a certificate without a private key to a store marked as IsTrustStore = 'false' will raise an error with an accompanying message.
- - **Name:** IncludesChain, **Display Name:** Store Includes Chain, **Type:** Bool, **Default Value:** false. This custom field is **not required**. Default value if not present is 'false'. If 'true' the full certificate chain, if sent by Keyfactor Command, will be stored in the file. The order of appearance is always assumed to be 1) end entity certificate, 2) issuing CA certificate, and 3) root certificate. If additional CA tiers are applicable, the order will be end entity certificate up to the root CA certificate. if set to 'false', only the end entity certificate and private key will be stored in this store. This setting is only valid when IsTrustStore = false.
- - **Name:** SeparatePrivateKeyFilePath, **Display Name:** Separate Private Key File Location, **Type:** String, **Default Value:** empty. This custom field is **not required**. If empty, or not provided, it will be assumed that the private key for the certificate stored in this file will be inside the same file as the certificate. If the full path AND file name is put here, that location will be used to store the private key as an external file. This setting is only valid when IsTrustStore = false.
- - **Name:** IsRSAPrivateKey, **Display Name:** Is RSA Private Key, **Type:** Bool, **Default Value:** false. This custom field is **not required**. Default value if not present is 'false'. If 'true' it will be assumed that the private key for the certificate is a PKCS#1 RSA formatted private key (BEGIN RSA PRIVATE KEY). If 'false' (default), encrypted/non-encrypted PKCS#8 (BEGIN [ENCRYPTED] PRIVATE KEY) is assumed If set to 'true' the store password **must** be set to "no password", as PKCS#1 does not support encrypted keys. This setting is only valid when IsTrustStore = false.
- - **Name:** IgnorePrivateKeyOnInventory, **Display Name:** Ignore Private Key On Inventory, **Type:** Bool, **Default Value:** false. This custom field is **not required**. Default value if not present is 'false'. If 'true', inventory for this certificate store will be performed without accessing the certificate's private key or the store password. This will functionally make the store INVENTORY ONLY, as all certificates will be returned with "Private Key Entry" = false. Also, no certificate chain relationships will be maintained, and all certificates will be considered separate entries (basically a trust store). This may be useful in situations where the client does not know the store password at inventory run time, but would still like the certificates to be imported into Keyfactor Command. Once the correct store password is entered for the store, the client may de-select this option (change the value to False), schedule an inventory job, and then the appropriate private key entry and chain information should be properly stored in the Keyfactor Command location, allowing for renewal/removal of the certificate at a later time.
+ 3. **Import the CSV file to create the certificate stores**
-- Entry Parameters Tab:
+ ```shell
+ kfutil stores import csv --store-type-name RFPEM --file RFPEM.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
- - no additional entry parameters
-
-RFDER - DER formatted certificate file
+RFPkcs12 (RFPkcs12)
+
+
+* **Manually with the Command UI**
+
+ Create Certificate Stores manually in the UI
+
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
+
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
+
+ 2. **Add a Certificate Store.**
+
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFPkcs12" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.p12) for Windows orchestrated servers. Example: '/folder/path/storename.p12' or 'c:\folder\path\storename.p12'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFPkcs12` certificates. Specifically, one with the `RFPkcs12` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
-- Basic Tab:
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFDER**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
-- Advanced Tab:
+
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Forbidden.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFPkcs12 certificate store**
-- Custom Fields Tab:
+ ```shell
+ kfutil stores import generate-template --store-type-name RFPkcs12 --outpath RFPkcs12.csv
+ ```
+ 2. **Populate the generated CSV file**
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
- - **Name:** SeparatePrivateKeyFilePath, **Display Name:** Separate Private Key File Location, **Type:** String, **Default Value:** empty. This custom field is **not required**. If empty, or not provided, it will be assumed that there is no private key associated with this DER store. If the full path AND file name is entered here, that location will be used to store the private key as an external file in DER format.
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFPkcs12" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.p12) for Windows orchestrated servers. Example: '/folder/path/storename.p12' or 'c:\folder\path\storename.p12'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFPkcs12` certificates. Specifically, one with the `RFPkcs12` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
-- Entry Parameters Tab:
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+ 3. **Import the CSV file to create the certificate stores**
+
+ ```shell
+ kfutil stores import csv --store-type-name RFPkcs12 --file RFPkcs12.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
- - no additional entry parameters
-
-RFKDB - IBM Key Database File
+RFDER (RFDER)
+
+
+* **Manually with the Command UI**
+
+ Create Certificate Stores manually in the UI
-- Basic Tab:
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFKDB**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
-- Advanced Tab:
+ 2. **Add a Certificate Store.**
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Required.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFDER" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.der) for Windows orchestrated servers. Example: '/folder/path/storename.der' or 'c:\folder\path\storename.der'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFDER` certificates. Specifically, one with the `RFDER` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | 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'. |
+ | Store Password | Password used to secure the Certificate Store |
-- Custom Fields Tab:
-
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
+
-- Entry Parameters Tab:
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+
+
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFDER certificate store**
+
+ ```shell
+ kfutil stores import generate-template --store-type-name RFDER --outpath RFDER.csv
+ ```
+ 2. **Populate the generated CSV file**
+
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFDER" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.der) for Windows orchestrated servers. Example: '/folder/path/storename.der' or 'c:\folder\path\storename.der'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFDER` certificates. Specifically, one with the `RFDER` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | 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'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+ 3. **Import the CSV file to create the certificate stores**
+
+ ```shell
+ kfutil stores import csv --store-type-name RFDER --file RFDER.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
- - no additional entry parameters
-
-RFORA - Oracle Wallet
+RFKDB (RFKDB)
-- Basic Tab:
- - **Name** – Required. The display name you wish to use for the new Certificate Store Type.
- - **Short Name** – Required. Suggested value - **RFORA**. If you choose to use a different value you must make the corresponding modification to the manifest.json file. See [Remote File Orchestrator Extension Installation](#remote-file-orchestrator-extension-installation), step 7 above.
- - **Custom Capability** - Unchecked
- - **Supported Job Types** - Inventory, Add, Remove, Create, and Discovery should all be checked.
- - **Needs Server** - Checked
- - **Blueprint Allowed** - Checked if you wish to make use of blueprinting. Please refer to the Keyfactor Command Reference Guide for more details on this feature.
- - **Uses PowerShell** - Unchecked
- - **Requires Store Password** - Checked. NOTE: This does not require that a certificate store have a password, but merely ensures that a user who creates a Keyfactor Command Certificate Store MUST click the Store Password button and either enter a password or check No Password. Certificate stores with no passwords are still possible for certain certificate store types when checking this option.
- - **Supports Entry Password** - Unchecked.
+* **Manually with the Command UI**
-- Advanced Tab:
+ Create Certificate Stores manually in the UI
- - **Store Path Type** - Freeform
- - **Supports Custom Alias** - Required.
- - **Private Key Handling** - Optional.
- - **PFX Password Style** - Default
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
-- Custom Fields Tab:
-
- - **Name:** LinuxFilePermissionsOnStoreCreation, **Display Name:** Linux File Permissions on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultLinuxPermissionsOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, must be 3 digits all between 0-7. This represents the Linux file permissions that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y".
- - **Name:** LinuxFileOwnerOnStoreCreation, **Display Name:** Linux File Owner on Store Creation, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultOwnerOnStoreCreation setting in config.json (see Configuration File Setup section above). This value, applicable to certificate stores hosted on Linux orchestrated servers only, represents the alternate Linux file owner:group that will be set for this certificate store if created via a Management Create job or a Management Add job where the config.json option CreateStoreOnAddIsMissing is set to "Y". If the group needs to be set as well, use a ":" as a delimitter between the owner and group values, such as ownerId:groupId. If the group is NOT supplied, the group value will be set per normal behavior of the Linux "Install" command.
- - **Name:** SudoImpersonatedUser, **Display Name:** Sudo Impersonated User Id, **Type:** String, **Default Value:** none. This custom field is **not required**. If not present, value reverts back to the DefaultSudoImpersonatedUser setting in config.json (see Configuration File Setup section above). Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is empty, and nothing is set for DefaultSudoImpersonatedUser in your config.json, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see Configuration File Setup section above) as well as permissions to execute the commands listed in the "Security Considerations" section above.
- - **Name:** RemoveRootCertificate, **Display Name:** Remove Root Certificate from Chain, **Type:** Bool, **Default Value:** False. This custom field is **not required**. If not present, value is set to the Default Value. This value determines whether root CA certificates should be included in the certificate chain when adding/renewing certificates in Management Add jobs. If set to False, the root CA certificate is included in the chain. If True, it is removed and only the non-root CA certificates are included in the chain when adding the entry to the certificate store.
- - **Name:** WorkFolder, **Display Name:** Work Folder, **Type:** String, **Default Value:** empty. This custom field is **required**. This required field should contain the path on the managed server where temporary work files can be created during Inventory and Management jobs. These files will be removed at the end of each job Please make sure that user id you have assigned to this certificate store will have access to create, modify, and delete files from this folder.
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
+
+ 2. **Add a Certificate Store.**
+
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFKDB" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.kdb) for Windows orchestrated servers. Example: '/folder/path/storename.kdb' or 'c:\folder\path\storename.kdb'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFKDB` certificates. Specifically, one with the `RFKDB` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
-- Entry Parameters Tab:
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+
+
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFKDB certificate store**
+
+ ```shell
+ kfutil stores import generate-template --store-type-name RFKDB --outpath RFKDB.csv
+ ```
+ 2. **Populate the generated CSV file**
+
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFKDB" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name, including file extension if applicable, beginning with a forward slash (/) for Linux orchestrated servers or a drive letter (i.e., c:\folder\path\storename.kdb) for Windows orchestrated servers. Example: '/folder/path/storename.kdb' or 'c:\folder\path\storename.kdb'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFKDB` certificates. Specifically, one with the `RFKDB` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+ 3. **Import the CSV file to create the certificate stores**
+
+ ```shell
+ kfutil stores import csv --store-type-name RFKDB --file RFKDB.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
- - no additional entry parameters
-
-## Certificate Stores and Discovery Jobs
+RFORA (RFORA)
-When creating new certificate stores or scheduling discovery jobs in Keyfactor Command, there are a few fields that are important to highlight here:
-
-Client Machine (certificate stores and discovery jobs)
+* **Manually with the Command UI**
-For Linux orchestrated servers, "Client Machine" should be the DNS name or IP address of the remote orchestrated server, while for Windows orchestratred servers, it should be the following URL format: protocol://dns-or-ip:port, where
-* protocol is http or https, whatever your WinRM configuration uses
-* dns-or-ip is the DNS name or IP address of the server
-* port is the port WinRM is running under, usually 5985 for http and 5986 for https.
+ Create Certificate Stores manually in the UI
-Example: https://myserver.mydomain.com:5986
+ 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.**
+
+ Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_.
+
+ 2. **Add a Certificate Store.**
+
+ Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFORA" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name of the Oracle Wallet, including the 'eWallet.p12' file name by convention. Example: '/path/to/eWallet.p12' or 'c:\path\to\eWallet.p12'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFORA` certificates. Specifically, one with the `RFORA` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | 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'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side.
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+
+
+* **Using kfutil**
+
+ Create Certificate Stores with kfutil
+
+ 1. **Generate a CSV template for the RFORA certificate store**
+
+ ```shell
+ kfutil stores import generate-template --store-type-name RFORA --outpath RFORA.csv
+ ```
+ 2. **Populate the generated CSV file**
+
+ Open the CSV file, and reference the table below to populate parameters for each **Attribute**.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | Category | Select "RFORA" or the customized certificate store name from the previous step. |
+ | Container | Optional container to associate certificate store with. |
+ | Client Machine | The Client Machine field should contain the DNS name or IP address of the remote orchestrated server for Linux orchestrated servers, formatted as a URL (protocol://dns-or-ip:port) for Windows orchestrated servers, or '1.1.1.1|LocalMachine' for local agents. Example: 'https://myserver.mydomain.com:5986' or '1.1.1.1|LocalMachine' for local access. |
+ | Store Path | The Store Path field should contain the full path and file name of the Oracle Wallet, including the 'eWallet.p12' file name by convention. Example: '/path/to/eWallet.p12' or 'c:\path\to\eWallet.p12'. |
+ | Orchestrator | Select an approved orchestrator capable of managing `RFORA` certificates. Specifically, one with the `RFORA` capability. |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | LinuxFilePermissionsOnStoreCreation | The LinuxFilePermissionsOnStoreCreation field should contain a three-digit value between 000 and 777 representing the Linux file permissions to be set for the certificate store upon creation. Example: '600' or '755'. |
+ | LinuxFileOwnerOnStoreCreation | The LinuxFileOwnerOnStoreCreation field should contain a valid user ID recognized by the destination Linux server, optionally followed by a colon and a group ID if the group owner differs. Example: 'userID' or 'userID:groupID'. |
+ | SudoImpersonatingUser | The SudoImpersonatingUser field should contain a valid user ID to impersonate using sudo on the destination Linux server. Example: 'impersonatedUserID'. |
+ | 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'. |
+ | Store Password | Password used to secure the Certificate Store |
+
+
+
+ Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
+
+ If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_.
+ | Attribute | Description |
+ | --------- | ----------- |
+ | ServerUsername | A username (or valid PAM key if the username is stored in a KF Command configured PAM integration). If acting as an *agent* using local file access, just check *No Value* |
+ | ServerPassword | A password (or valid PAM key if the password is stored in a KF Command configured PAM integration). The password can also be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check *No Value* |
+ | Store Password | Password used to secure the Certificate Store |
+
+ > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself.
+
+
+
+ 3. **Import the CSV file to create the certificate stores**
+
+ ```shell
+ kfutil stores import csv --store-type-name RFORA --file RFORA.csv
+ ```
+
+
+> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
-If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), Client Machine can be entered as stated above, OR you can bypass SSH/WinRM and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a cerificate store that will accessed without a WinRM/SSH connection.
+## Discovering Certificate Stores with the Discovery Job
+When scheduling discovery jobs in Keyfactor Command, there are a few fields that are important to highlight here:
+
-Store Path (certificate stores only)
+Client Machine
+
+The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions)
+
+
+
+
+Store Path
For Linux orchestrated servers, "StorePath" will begin with a forward slash (/) and contain the full path and file name, including file extension if one exists (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should be the full path and file name, including file extension if one exists, beginning with a drive letter (i.e. c:\folder\path\storename.ext).
-Server Username/Password (certificate stores and discovery jobs)
+Server Username/Password
A username and password (or valid PAM key if the username and/or password is stored in a KF Command configured PAM integration). The password can be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check "No Value" for the username and password.
-Directories to Search (discovery jobs only)
+Directories to Search
Enter one or more comma delimitted file paths to search (please reference the Keyfactor Command Reference Guide for more information), but there is also a special value that can be used on Windows orchestrated servers instead - "fullscan". Entering fullscan in this field will tell the RemoteFile discovery job to search all available drive letters at the root and recursively search all of them for files matching the other search criteria.
-Extensions (discovery jobs only)
+Extensions
In addition to entering one or more comma delimitted extensions to search for (please reference the Keyfactor Command Reference Guide for more information), a reserved value of "noext" can be used that will cause the RemoteFile discovery job to search for files that do not have an extension. This value can be chained with other extensions using the comma delimiter. For example, entering pem,jks,noext will cause the RemoteFile discovery job to return file locations with extensions of "pem", "jks", *and* files that do not have extensions.
-Please refer to the Keyfactor Command Reference Guide for complete information on creating certificate stores and scheduling discovery jobs in Keyfactor Command.
-
-
+Please refer to the Keyfactor Command Reference Guide for complete information on creating certificate stores and scheduling discovery jobs in Keyfactor Command.
+
+
+
+
+
+
+
+
+
+## Client Machine Instructions
+
+When creating a Certificate Store or scheduling a Discovery Job, you will be asked to provide a "Client Machine".
+
+For Linux orchestrated servers, "Client Machine" should be the DNS name or IP address of the remote orchestrated server, while for Windows orchestratred servers, it should be the following URL format: protocol://dns-or-ip:port, where
+* protocol is http or https, whatever your WinRM configuration uses
+* dns-or-ip is the DNS name or IP address of the server
+* port is the port WinRM is running under, usually 5985 for http and 5986 for https.
+
+Example: https://myserver.mydomain.com:5986
+
+If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), Client Machine can be entered as stated above, OR you can bypass SSH/WinRM and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a cerificate store that will accessed without a WinRM/SSH connection.
+
## Developer Notes
The Remote File Orchestrator Extension is meant to be extended to be used for other file based certificate store types than the ones referenced above. The advantage to extending this integration rather than creating a new one is that the configuration, remoting, and Inventory/Management/Discovery logic is already written. The developer needs to only implement a few classes and write code to convert the destired file based store to a common format. This section describes the steps necessary to add additional store/file types. Please note that familiarity with the [.Net Core BouncyCastle cryptography library](https://github.com/bcgit/bc-csharp) is a prerequisite for adding additional supported file/store types.
@@ -626,16 +1424,13 @@ Steps to create a new supported file based certificate store type:
5. Create an Inventory.cs class (with namespace of Keyfactor.Extensions.Orchestrator.RemoteFile.{NewType}) under the new folder and have it inherit InventoryBase. Override the internal GetCertificateStoreSerializer() method with a one line implementation returning a new instantiation of the class created in step 4.
6. Create a Management.cs class (with namespace of Keyfactor.Extensions.Orchestrator.RemoteFile.{NewType}) under the new folder and have it inherit ManagementBase. Override the internal GetCertificateStoreSerializer() method with a one line implementation returning a new instantiation of the class created in step 4.
7. Modify the manifest.json file to add three new sections (for Inventory, Management, and Discovery). Make sure for each, the "NewType" in Certstores.{NewType}.{Operation}, matches what you will use for the certificate store type short name in Keyfactor Command. On the "TypeFullName" line for all three sections, make sure the namespace matches what you used for your new classes. Note that the namespace for Discovery uses a common class for all supported types. Discovery is a common implementation for all supported store types.
-8. After compiling, move all compiled files, including the config.json and manifest.json to {Keyfactor Orchestrator Installation Folder}\Extensions\RemoteFile.
-9. Create the certificate store type in Keyfactor Command
-10. Add a new CURL script to build the proper Keyfactor Command certificate store type and place it under "Certificate Store Type CURL Scripts". The name of the file should match the ShortName you are using for the new store type.
-11. Update the documenation in readme_source.md by adding a new section under [Creating Certificate Store Types](#creating-certificate-store-types) for this new supported file based store type. Include a pointer to the CURL script created in step 10.
-
-
-## License
-[Apache](https://apache.org/licenses/LICENSE-2.0)
+8. Modify the integration-manifest.json file to add the new store type under the store_types element.
-When creating cert store type manually, that store property names and entry parameter names are case sensitive
+## License
+
+Apache License 2.0, see [LICENSE](LICENSE).
+## Related Integrations
+See all [Keyfactor Universal Orchestrator extensions](https://github.com/orgs/Keyfactor/repositories?q=orchestrator).
\ No newline at end of file
diff --git a/RemoteFile/Discovery.cs b/RemoteFile/Discovery.cs
index 42e78e7e..fe4d40b8 100644
--- a/RemoteFile/Discovery.cs
+++ b/RemoteFile/Discovery.cs
@@ -16,6 +16,7 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
+using static Keyfactor.PKI.PKIConstants.Microsoft;
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
{
@@ -67,7 +68,8 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd
if (filesTosearch.Length == 0)
filesTosearch = new string[] { "*" };
- locations = certificateStore.FindStores(directoriesToSearch, extensionsToSearch, filesTosearch, includeSymLinks);
+ locations = certificateStore.FindStores(directoriesToSearch, extensionsToSearch, filesTosearch, ignoredDirs, includeSymLinks);
+
foreach (string ignoredDir in ignoredDirs)
{
locations = locations.Where(p => !p.StartsWith(ignoredDir) && !p.ToLower().StartsWith("find:")).ToList();
diff --git a/RemoteFile/ImplementedStoreTypes/KDB/KDBCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/KDB/KDBCertificateStoreSerializer.cs
index 177efaa8..a7f1dcf4 100644
--- a/RemoteFile/ImplementedStoreTypes/KDB/KDBCertificateStoreSerializer.cs
+++ b/RemoteFile/ImplementedStoreTypes/KDB/KDBCertificateStoreSerializer.cs
@@ -51,9 +51,9 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
byte[] storeBytes = remoteHandler.DownloadCertificateFile($"{storePath}{tempCertFile}");
store.Load(new MemoryStream(storeBytes), string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
}
- catch (Exception ex)
+ catch (Exception)
{
- throw ex;
+ throw;
}
finally
{
@@ -90,9 +90,9 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer
storeInfo.Add(new SerializedStoreInfo() { Contents = storeContents, FilePath = storePath+storeFileName });
return storeInfo;
}
- catch (Exception ex)
+ catch (Exception)
{
- throw ex;
+ throw;
}
finally
{
diff --git a/RemoteFile/ImplementedStoreTypes/OraWlt/OraWltCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/OraWlt/OraWltCertificateStoreSerializer.cs
index c460b67c..2e1002fd 100644
--- a/RemoteFile/ImplementedStoreTypes/OraWlt/OraWltCertificateStoreSerializer.cs
+++ b/RemoteFile/ImplementedStoreTypes/OraWlt/OraWltCertificateStoreSerializer.cs
@@ -59,9 +59,9 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
JKSCertificateStoreSerializer serializer = new JKSCertificateStoreSerializer(String.Empty);
store = serializer.DeserializeRemoteCertificateStore(storeBytes, $"{WorkFolder}{tempStoreFileJKS}", storePassword, remoteHandler, isInventory);
}
- catch (Exception ex)
+ catch (Exception)
{
- throw ex;
+ throw;
}
finally
{
@@ -101,9 +101,9 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer
storeInfo.Add(new SerializedStoreInfo() { Contents = storeContents, FilePath = storePath+storeFileName });
return storeInfo;
}
- catch (Exception ex)
+ catch (Exception)
{
- throw ex;
+ throw;
}
finally
{
diff --git a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
index 7515cf40..4f3e3cca 100644
--- a/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
+++ b/RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs
@@ -39,7 +39,6 @@ class PEMCertificateStoreSerializer : ICertificateStoreSerializer
private bool IsTrustStore { get; set; }
private bool IncludesChain { get; set; }
private string SeparatePrivateKeyFilePath { get; set; }
- private bool IsRSAPrivateKey { get; set; }
private bool IgnorePrivateKeyOnInventory { get; set; }
private ILogger logger;
@@ -53,9 +52,6 @@ public PEMCertificateStoreSerializer(string storeProperties)
public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory)
{
logger.MethodEntry(LogLevel.Debug);
-
- if (IsRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
- throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
Pkcs12Store store = storeBuilder.Build();
@@ -72,7 +68,12 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
}
else
{
- AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler);
+ bool isRSAPrivateKey = false;
+ AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out isRSAPrivateKey);
+
+ if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
+ throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
+
store.SetKeyEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificates[0].Certificate).ToX509Certificate2().Thumbprint, keyEntry, certificates);
}
@@ -93,9 +94,6 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer
{
logger.MethodEntry(LogLevel.Debug);
- if (IsRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
- throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
-
string pemString = string.Empty;
string keyString = string.Empty;
List storeInfo = new List();
@@ -113,6 +111,17 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer
}
else
{
+ string storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(storePath + storeFileName));
+ bool isRSAPrivateKey = false;
+ try
+ {
+ GetPrivateKey(storeContents, storePassword, remoteHandler, out isRSAPrivateKey);
+ }
+ catch (RemoteFileException) { }
+
+ if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
+ throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
+
bool keyEntryProcessed = false;
foreach (string alias in certificateStore.Aliases)
{
@@ -131,7 +140,7 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer
X509CertificateEntry[] certEntries = certificateStore.GetCertificateChain(alias);
AsymmetricKeyParameter publicKey = certEntries[0].Certificate.GetPublicKey();
- if (IsRSAPrivateKey)
+ if (isRSAPrivateKey)
{
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
@@ -185,7 +194,6 @@ private void LoadCustomProperties(string storeProperties)
IsTrustStore = properties.IsTrustStore == null || string.IsNullOrEmpty(properties.IsTrustStore.Value) ? false : bool.Parse(properties.IsTrustStore.Value);
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;
- IsRSAPrivateKey = properties.IsRSAPrivateKey == null || string.IsNullOrEmpty(properties.IsRSAPrivateKey.Value) ? false : bool.Parse(properties.IsRSAPrivateKey.Value);
IgnorePrivateKeyOnInventory = properties.IgnorePrivateKeyOnInventory == null || string.IsNullOrEmpty(properties.IgnorePrivateKeyOnInventory.Value) ? false : bool.Parse(properties.IgnorePrivateKeyOnInventory.Value);
logger.MethodExit(LogLevel.Debug);
@@ -222,7 +230,7 @@ private X509CertificateEntry[] GetCertificates(string certificates)
return certificateEntries.ToArray();
}
- private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler)
+ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler, out bool isRSA)
{
logger.MethodEntry(LogLevel.Debug);
@@ -231,8 +239,18 @@ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassw
storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(SeparatePrivateKeyFilePath));
}
+ isRSA = false;
+ foreach (string begDelim in PrivateKeyDelimetersPkcs1)
+ {
+ if (storeContents.Contains(begDelim))
+ {
+ isRSA = true;
+ break;
+ }
+ }
+
string privateKey = string.Empty;
- foreach (string begDelim in IsRSAPrivateKey ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8)
+ foreach (string begDelim in isRSA ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8)
{
string endDelim = begDelim.Replace("BEGIN", "END");
@@ -252,7 +270,7 @@ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassw
throw new RemoteFileException("Invalid private key: No private key or invalid private key format found.");
PrivateKeyConverter c;
- if (IsRSAPrivateKey)
+ if (isRSA)
{
RSA rsa = RSA.Create();
int bytesRead;
diff --git a/RemoteFile/ImplementedStoreTypes/PKCS12/PKCS12CertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/PKCS12/PKCS12CertificateStoreSerializer.cs
index f78f8114..2b159f98 100644
--- a/RemoteFile/ImplementedStoreTypes/PKCS12/PKCS12CertificateStoreSerializer.cs
+++ b/RemoteFile/ImplementedStoreTypes/PKCS12/PKCS12CertificateStoreSerializer.cs
@@ -5,6 +5,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
+using System;
using System.IO;
using System.Collections.Generic;
using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers;
@@ -13,12 +14,15 @@
using Org.BouncyCastle.Pkcs;
using Keyfactor.Logging;
using Microsoft.Extensions.Logging;
+using System.Linq;
+using Keyfactor.PKI.Extensions;
namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12
{
class PKCS12CertificateStoreSerializer : ICertificateStoreSerializer
{
private ILogger logger;
+ private bool HasEmptyAliases { get; set; }
public PKCS12CertificateStoreSerializer(string storeProperties)
{
@@ -28,21 +32,46 @@ public PKCS12CertificateStoreSerializer(string storeProperties)
public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory)
{
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
- Pkcs12Store store = storeBuilder.Build();
+ Pkcs12Store workingStore = storeBuilder.Build();
+ Pkcs12Store returnStore = storeBuilder.Build();
using (MemoryStream ms = new MemoryStream(storeContents))
{
- store.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
+ workingStore.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
}
- return store;
+ if (workingStore.Aliases.Where(p => string.IsNullOrEmpty(p)).Count() > 0 && workingStore.Aliases.Where(p => !string.IsNullOrEmpty(p)).Count() > 0)
+ throw new Exception("Certificate store contains entries with both empty and non-empty friendly names. This configuration is not supported in this store type.");
+
+ HasEmptyAliases = workingStore.Aliases.Where(p => string.IsNullOrEmpty(p)).Count() > 0;
+
+ returnStore = ConvertAliases(workingStore, true);
+
+ return returnStore;
}
public List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, string storeFileName, string storePassword, IRemoteHandler remoteHandler)
{
+ Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
+ Pkcs12Store workingStore = storeBuilder.Build();
+
+ foreach (string alias in certificateStore.Aliases)
+ {
+ if (certificateStore.IsKeyEntry(alias))
+ {
+ workingStore.SetKeyEntry(alias, certificateStore.GetKey(alias), certificateStore.GetCertificateChain(alias));
+ }
+ else
+ {
+ workingStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias));
+ }
+ }
+
+ Pkcs12Store outputCertificateStore = ConvertAliases(workingStore, false);
+
using (MemoryStream outStream = new MemoryStream())
{
- certificateStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom());
+ outputCertificateStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom());
List storeInfo = new List();
storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath+storeFileName, Contents = outStream.ToArray() });
@@ -55,5 +84,34 @@ public string GetPrivateKeyPath()
{
return null;
}
+
+ private Pkcs12Store ConvertAliases(Pkcs12Store workingStore, bool useThumbprintAsAlias)
+ {
+ Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
+ Pkcs12Store returnStore = storeBuilder.Build();
+
+ if (HasEmptyAliases)
+ {
+ foreach (string alias in workingStore.Aliases)
+ {
+ if (workingStore.IsKeyEntry(alias))
+ {
+ X509CertificateEntry cert = workingStore.GetCertificate(alias);
+ returnStore.SetKeyEntry(useThumbprintAsAlias ? cert.Certificate.Thumbprint() : string.Empty, workingStore.GetKey(alias), workingStore.GetCertificateChain(alias));
+ }
+ else
+ {
+ X509CertificateEntry cert = workingStore.GetCertificate(alias);
+ returnStore.SetCertificateEntry(cert.Certificate.Thumbprint(), cert);
+ }
+ }
+ }
+ else
+ {
+ returnStore = workingStore;
+ }
+
+ return returnStore;
+ }
}
}
diff --git a/RemoteFile/InventoryBase.cs b/RemoteFile/InventoryBase.cs
index 327e9457..0d1a04c0 100644
--- a/RemoteFile/InventoryBase.cs
+++ b/RemoteFile/InventoryBase.cs
@@ -31,7 +31,6 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}...");
logger.LogDebug($"Server: { config.CertificateStoreDetails.ClientMachine }");
logger.LogDebug($"Store Path: { config.CertificateStoreDetails.StorePath }");
- logger.LogDebug($"Store Properties: {config.CertificateStoreDetails.Properties.ToString()}");
logger.LogDebug($"Job Properties:");
foreach (KeyValuePair keyValue in config.JobProperties ?? new Dictionary())
{
diff --git a/RemoteFile/ManagementBase.cs b/RemoteFile/ManagementBase.cs
index 9645068e..c2c992e7 100644
--- a/RemoteFile/ManagementBase.cs
+++ b/RemoteFile/ManagementBase.cs
@@ -31,7 +31,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}...");
logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}");
logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}");
- logger.LogDebug($"Store Properties: {config.CertificateStoreDetails.Properties.ToString()}");
logger.LogDebug($"Job Properties:");
foreach (KeyValuePair keyValue in config.JobProperties == null ? new Dictionary() : config.JobProperties)
{
diff --git a/RemoteFile/Models/SerializedStoreInfo.cs b/RemoteFile/Models/SerializedStoreInfo.cs
index 9eba4fdf..6b13d1ce 100644
--- a/RemoteFile/Models/SerializedStoreInfo.cs
+++ b/RemoteFile/Models/SerializedStoreInfo.cs
@@ -9,7 +9,7 @@
namespace Keyfactor.Extensions.Orchestrator.RemoteFile.Models
{
- class SerializedStoreInfo : X509Certificate2
+ internal class SerializedStoreInfo
{
public string FilePath { get; set; }
diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs
index 9de5ccff..77aba785 100644
--- a/RemoteFile/RemoteCertificateStore.cs
+++ b/RemoteFile/RemoteCertificateStore.cs
@@ -137,7 +137,7 @@ internal void Terminate()
logger.MethodExit(LogLevel.Debug);
}
- internal List FindStores(string[] paths, string[] extensions, string[] files, bool includeSymLinks)
+ internal List FindStores(string[] paths, string[] extensions, string[] files, string[] ignoredDirs, bool includeSymLinks)
{
logger.MethodEntry(LogLevel.Debug);
@@ -153,7 +153,7 @@ internal List FindStores(string[] paths, string[] extensions, string[] f
if (DiscoveredStores != null)
return DiscoveredStores;
- return ServerType == ServerTypeEnum.Linux ? FindStoresLinux(paths, extensions, files, includeSymLinks) : FindStoresWindows(paths, extensions, files);
+ return ServerType == ServerTypeEnum.Linux ? FindStoresLinux(paths, extensions, files, ignoredDirs, includeSymLinks) : FindStoresWindows(paths, extensions, files);
}
internal List GetCertificateChains()
@@ -511,7 +511,7 @@ private bool IsValueSafeRegex(string value)
return regex.IsMatch(value);
}
- private List FindStoresLinux(string[] paths, string[] extensions, string[] fileNames, bool includeSymLinks)
+ private List FindStoresLinux(string[] paths, string[] extensions, string[] fileNames, string[] ignoredDirs, bool includeSymLinks)
{
logger.MethodEntry(LogLevel.Debug);
@@ -519,6 +519,12 @@ private List FindStoresLinux(string[] paths, string[] extensions, string
{
string concatPaths = string.Join(" ", paths);
string command = $"find {concatPaths} -path /proc -prune -o ";
+
+ foreach (string ignoredDir in ignoredDirs)
+ {
+ command += $"-path {ignoredDir} -prune -o ";
+ }
+
if (!includeSymLinks)
command += " -type f ";
diff --git a/RemoteFile/RemoteFile.csproj b/RemoteFile/RemoteFile.csproj
index c2fd7c0b..f577e4e4 100644
--- a/RemoteFile/RemoteFile.csproj
+++ b/RemoteFile/RemoteFile.csproj
@@ -1,37 +1,28 @@
- false
- net6.0
+ true
+ net6.0;net8.0
true
+ disable
-
-
-
-
-
-
+
+
-
-
-
- External References\Renci.SshNet.dll
-
-
- External References\SshNet.Security.Cryptography.dll
-
+
+ Always
+
+
+ Always
+
-
-
-
-
diff --git a/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs b/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs
index f7b45ac9..4abb4a08 100644
--- a/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs
+++ b/RemoteFile/RemoteHandlers/LinuxLocalHandler.cs
@@ -76,7 +76,7 @@ public override string RunCommand(string commandText, object[] arguments, bool w
catch (Exception ex)
{
_logger.LogError($"Exception during RunCommand...{RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}");
- throw ex;
+ throw;
}
}
diff --git a/RemoteFile/RemoteHandlers/SSHHandler.cs b/RemoteFile/RemoteHandlers/SSHHandler.cs
index 81563b51..5347964a 100644
--- a/RemoteFile/RemoteHandlers/SSHHandler.cs
+++ b/RemoteFile/RemoteHandlers/SSHHandler.cs
@@ -60,7 +60,7 @@ internal SSHHandler(string server, string serverLogin, string serverPassword, bo
privateKeyFile = new PrivateKeyFile(ms);
}
}
- catch (Exception ex)
+ catch (Exception)
{
using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(ConvertToPKCS1(serverPassword))))
{
@@ -354,24 +354,27 @@ public override bool DoesFileExist(string path)
_logger.MethodEntry(LogLevel.Debug);
_logger.LogDebug($"DoesFileExist: {path}");
- 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();
- }
- }
+ 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();
+ // }
+ //}
}
public override void RemoveCertificateFile(string path, string fileName)
diff --git a/RemoteFile/RemoteHandlers/SSHHelper.cs b/RemoteFile/RemoteHandlers/SSHHelper.cs
deleted file mode 100644
index d93be6b1..00000000
--- a/RemoteFile/RemoteHandlers/SSHHelper.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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 Renci.SshNet.Common;
-using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography;
-using System;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text;
-using Renci.SshNet.Security;
-using Renci.SshNet;
-using System.Reflection;
-
-namespace Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers
-{
- ///
- /// Based on https://github.com/sshnet/SSH.NET/blob/1d5d58e17c68a2f319c51e7f938ce6e964498bcc/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs#L12
- ///
- /// With following changes:
- ///
- /// - OID changed to sha2-256
- /// - hash changed from sha1 to sha2-256
- ///
- public class RsaSha256DigitalSignature : CipherDigitalSignature, IDisposable
- {
- private HashAlgorithm _hash;
-
- public RsaSha256DigitalSignature(RsaWithSha256SignatureKey rsaKey)
- // custom OID
- : base(new ObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 1), new RsaCipher(rsaKey))
- {
- // custom
- _hash = SHA256.Create();
- }
-
- protected override byte[] Hash(byte[] input)
- {
- return _hash.ComputeHash(input);
- }
-
- private bool _isDisposed;
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (_isDisposed)
- return;
-
- if (disposing)
- {
- var hash = _hash;
- if (hash != null)
- {
- hash.Dispose();
- _hash = null;
- }
-
- _isDisposed = true;
- }
- }
-
- ~RsaSha256DigitalSignature()
- {
- Dispose(false);
- }
- }
- ///
- /// Utility class which allows ssh.net to connect to servers using ras-sha2-256
- ///
- public static class RsaSha256Util
- {
- public static void SetupConnection(ConnectionInfo connection)
- {
- connection.HostKeyAlgorithms["rsa-sha2-256"] = data => new KeyHostAlgorithm("rsa-sha2-256", new RsaKey(), data);
- }
-
- ///
- /// Converts key file to rsa key with sha2-256 signature
- /// Due to lack of constructor: https://github.com/sshnet/SSH.NET/blob/bc99ada7da3f05f50d9379f2644941d91d5bf05a/src/Renci.SshNet/PrivateKeyFile.cs#L86
- /// We do that in place
- ///
- ///
- ///
- public static void ConvertToKeyWithSha256Signature(PrivateKeyFile keyFile)
- {
- var oldKeyHostAlgorithm = keyFile.HostKey as KeyHostAlgorithm;
- if (oldKeyHostAlgorithm == null)
- {
- throw new ArgumentNullException(nameof(oldKeyHostAlgorithm));
- }
- var oldRsaKey = oldKeyHostAlgorithm.Key as RsaKey;
- if (oldRsaKey == null)
- {
- throw new ArgumentNullException(nameof(oldRsaKey));
- }
-
- var newRsaKey = new RsaWithSha256SignatureKey(oldRsaKey.Modulus, oldRsaKey.Exponent, oldRsaKey.D, oldRsaKey.P, oldRsaKey.Q,
- oldRsaKey.InverseQ);
-
- UpdatePrivateKeyFile(keyFile, newRsaKey);
- }
-
- private static void UpdatePrivateKeyFile(PrivateKeyFile keyFile, RsaWithSha256SignatureKey key)
- {
- var keyHostAlgorithm = new KeyHostAlgorithm(key.ToString(), key);
-
- var hostKeyProperty = typeof(PrivateKeyFile).GetProperty(nameof(PrivateKeyFile.HostKey));
- hostKeyProperty.SetValue(keyFile, keyHostAlgorithm);
-
- var keyField = typeof(PrivateKeyFile).GetField("_key", BindingFlags.NonPublic | BindingFlags.Instance);
- keyField.SetValue(keyFile, key);
- }
- }
- public class RsaWithSha256SignatureKey : RsaKey
- {
- public RsaWithSha256SignatureKey(BigInteger modulus, BigInteger exponent, BigInteger d, BigInteger p, BigInteger q,
- BigInteger inverseQ) : base(modulus, exponent, d, p, q, inverseQ)
- {
- }
-
- private RsaSha256DigitalSignature _digitalSignature;
-
- protected override DigitalSignature DigitalSignature
- {
- get
- {
- if (_digitalSignature == null)
- {
- _digitalSignature = new RsaSha256DigitalSignature(this);
- }
-
- return _digitalSignature;
- }
- }
-
- public override string ToString()
- {
- return "rsa-sha2-256";
- }
- }
-}
diff --git a/RemoteFile/RemoteHandlers/WinRMHandler.cs b/RemoteFile/RemoteHandlers/WinRMHandler.cs
index 1d1a0418..54689250 100644
--- a/RemoteFile/RemoteHandlers/WinRMHandler.cs
+++ b/RemoteFile/RemoteHandlers/WinRMHandler.cs
@@ -138,7 +138,7 @@ public override string RunCommand(string commandText, object[] parameters, bool
catch (Exception ex)
{
_logger.LogError($"Exception during RunCommand...{RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}");
- throw ex;
+ throw;
}
}
@@ -182,8 +182,8 @@ private byte[] RunCommandBinary(string commandText)
catch (Exception ex)
{
- _logger.LogError("Exception during RunCommandBinary...{RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}");
- throw ex;
+ _logger.LogError($"Exception during RunCommandBinary...{RemoteFileException.FlattenExceptionMessages(ex, ex.Message)}");
+ throw;
}
}
diff --git a/docsource/content.md b/docsource/content.md
new file mode 100644
index 00000000..7220e7e9
--- /dev/null
+++ b/docsource/content.md
@@ -0,0 +1,222 @@
+## Overview
+
+The Remote File Orchestrator Extension is a multi-purpose integration that can remotely manage a variety of file-based certificate stores and can easily be extended to manage others. The certificate store types that can be managed in the current version are:
+
+* RFJKS - Java Keystores of types JKS or PKCS12
+* RFPkcs12 - Certificate stores that follow the PKCS#12 standard
+* RFPEM - Files in PEM format
+* RFDER - Files in binary DER format
+* RFORA - Pkcs#12 formatted Oracle Wallets
+* RFKDB - IBM Key Database files
+
+The Keyfactor Univeral Orchestrator (UO) and RemoteFile Extension can be installed on either Windows or Linux operating systems as well as manage certificates residing on servers of both operating systems. A UO service managing certificates on remote servers is considered to be acting as an Orchestrator, while a UO service managing local certificates on the same server running the service is considered an Agent. When acting as an Orchestrator, connectivity from the orchestrator server hosting the RemoteFile extension to the orchestrated server hosting the certificate store(s) being managed is achieved via either an SSH (for Linux and possibly Windows orchestrated servers) or WinRM (for Windows 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.
+
+![](images/orchestrator-agent.png)
+
+Please refer to the READMEs for each supported store type for more information on proper configuration and setup for these different architectures. The supported configurations of Universal Orchestrator hosts and managed orchestrated servers are detailed below:
+
+| | UO Installed on Windows | UO Installed on Linux |
+|-----|-----|------|
+|Orchestrated Server hosting certificate store(s) on remote Windows server|WinRM connection | SSH connection |
+|Orchestrated Server hosting certificate store(s) on remote Linux server| SSH connection | SSH connection |
+|Certificate store(s) on same server as orchestrator service (Agent)| WinRM connection or local file system | SSH connection or local file system |
+
+
+## Requirements
+
+
+Certificate stores hosted on Linux servers:
+
+1. The Remote File Orchestrator Extension makes use of a few common Linux commands when managing stores on Linux servers. If the credentials you will be connecting with need elevated access to run these commands or to access the certificate store files these commands operate against, you must set up the user id as a sudoer with no password necessary and set the config.json "UseSudo" value to "Y". When RemoteFile is using orchestration, managing local or external certificate stores using SSH or WinRM, the security context is determined by the user id entered in the Keyfactor Command certificate store or discovery job screens. When RemoteFile is running as an agent, managing local stores only, the security context is the user id running the Keyfactor Command Universal Orchestrator service account. The full list of these commands below:
+
+|Shell Command|Used For|
+|---|---|
+|echo|Used to append a newline and terminate all commands sent.|
+|find|Used by Discovery jobs to locate potential certificate stores on the file system.|
+|cp|Used by Inventory and Management Add/Remove/Create jobs to determine if certificate store file exists.|
+|ls|Used by Management Add/Remove jobs to copy the certificate store file to a temporary file (only when an alternate download folder has been configured).|
+|chown|Used by the Inventory and Management Add/Remove jobs to set the permissions on the temporary file (only when an alternate download folder has been configured).|
+|tee|Used by Management Add/Remove jobs to copy the temporary uploaded certificate file to the certificate store file (only when an alternate upload folder has been configured).|
+|rm|Used by Inventory and Management Add/Remove jobs to remove temporary files (only when an alternate upload/download folder has been configured).|
+|install|Used by the Management Create Store job when initializing a certificate store file.|
+|orapki|Oracle Wallet CLI utility used by Inventory and Management Add/Remove jobs to manipulate an Oracle Wallet certificate store. Used for the RFORA store type only.|
+|gskcapicmd|IBM Key Database CLI utility used by Inventory and Management Add/Remove jobs to manipulate an IBM Key Database certificate store. Used for the 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 certificate store/discovery job. If this is not possible, set the values in the config.json apprpriately to use an alternative upload/download folder that does allow SFTP/SCP 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.
+
+3. SSH Authentication: When creating a Keyfactor certificate store for the remote file orchestrator extension, you may supply either a user id and password for the certificate store credentials (directly or through one of Keyfactor Command's PAM integrations), or supply a user id and SSH private key. When using a password, the connection is attempted using SSH Password Authentication. If that fails, Keyboard Interactive Authentication is automatically attempted. One or both of these must be enabled on the Linux box being managed. If private key authentication is desired, copy and paste the full SSH private key into the Password textbox (or pointer to the private key if using a PAM provider). Please note that SSH Private Key Authentication is not available when running locally as an agent. The following private key formats are supported:
+- PKCS#1 (BEGIN RSA PRIVATE KEY)
+- PKCS#8 (BEGIN PRIVATE KEY)
+- ECDSA OPENSSH (BEGIN OPENSSH PRIVATE KEY)
+
+Please reference [Post Installation](#post-installation) for more information on setting up the config.json file and [Defining Certificate Stores](#defining-certificate-stores) and [Discovering Certificate Stores with the Discovery Job](#discovering-certificate-stores-with-the-discovery-job) for more information on defining and configuring certificate stores.
+
+
+
+Certificate stores hosted on Windows servers:
+1. When orchestrating management of external (and potentially local) certificate stores, the RemoteFile Orchestrator Extension makes use of WinRM to connect to external certificate store servers. The security context used is the user id entered in the Keyfactor Command certificate store or discovery job screen. Make sure that WinRM is set up on the orchestrated server and that the WinRM port (by convention, 5585 for HTTP and 5586 for HTTPS) is part of the certificate store path when setting up your certificate stores/discovery jobs. If running as an agent, managing local certificate stores, local commands are run under the security context of the user account running the Keyfactor Universal Orchestrator Service. Please reference [Certificate Stores and Discovery Jobs](#certificate-stores-and-discovery-jobs) for more information on creating certificate stores for the RemoteFile Orchestrator Extension.
+
+
+
+Please consult with your company's system administrator for more information on configuring SSH/SFTP/SCP or WinRM in your environment.
+
+
+## Post Installation
+
+The Remote File Orchestrator Extension uses a JSON configuration file. It is located in the {Keyfactor Orchestrator Installation Folder}\Extensions\RemoteFile. None of the values are required, and a description of each follows below:
+{
+ "UseSudo": "N",
+ "DefaultSudoImpersonatedUser": "",
+ "CreateStoreIfMissing": "N",
+ "UseNegotiate": "N",
+ "SeparateUploadFilePath": "",
+ "FileTransferProtocol": "SCP",
+ "DefaultLinuxPermissionsOnStoreCreation": "600",
+ "DefaultOwnerOnStoreCreation": ""
+}
+
+
+UseSudo (Applicable for Linux hosted certificate stores only)
+
+* Determines whether to prefix Linux command with "sudo". This can be very helpful in ensuring that the user id running commands over an ssh connection uses "least permissions necessary" to process each task. Setting this value to "Y" will prefix all Linux commands with "sudo" with the expectation that the command being executed on the orchestrated Linux server will look in the sudoers file to determine whether the logged in ID has elevated permissions for that specific command. Setting this value to "N" will result in "sudo" not being added to Linux commands.
+* Allowed values - Y/N
+* Default value - N
+
+
+
+
+DefaultSudoImpersonatedUser (Applicable for Linux hosted certificate stores only)
+
+* Used in conjunction with UseSudo="Y", this optional setting can be used to set an alternate user id you wish to impersonate with sudo. If this option does not exist or is set to an empty string, the default user of "root" will be used. Any user id used here must have permissions to SCP/SFTP files to/from each certificate store location OR the SeparateUploadFilePath (see later in this section) as well as permissions to execute the commands listed in the "Prerequisites and Security Considerations" section above. This value will be used for all certificate stores managed by this orchestrator extension implementation UNLESS overriden by the SudoImpersonatedUser certificate store type custom field setting described later in the [Creating Certificate Store Types](#creating-certificate-store-types) section.
+* Allowed values - Any valid user id that the destination Linux server will recognize
+* Default value - blank (root will be used)
+
+
+
+
+CreateStoreOnAddIfMissing
+
+* Determines, during a Management-Add job, if a certificate store should be created if it does not already exist. If set to "N", and the store referenced in the Management-Add job is not found, the job will return an error with a message stating that the store does not exist. If set to "Y", the store will be created and the certificate added to the certificate store.
+* Allowed values - Y/N
+* Default value - N
+
+
+
+
+UseNegotiateAuth (Applicable for Windows hosted certificate stores only)
+
+* Determines if WinRM should use Negotiate (Y) when connecting to the remote server.
+* Allowed values - Y/N
+* Default value - N
+
+
+
+
+SeparateUploadFilePath (Applicable for Linux hosted certificate stores only)
+
+* Set this to the path you wish to use as the location on the orchestrated server to upload/download and later remove temporary work files when processing jobs. If set to "" or not provided, the location of the certificate store itself will be used. File transfer is performed using the SCP or SFTP protocols (see the File TransferProtocol setting).
+* Allowed values - Any valid, existing Linux path configured to allow SCP/SFTP file upload/download tranfers.
+* Default value - blank (actual store path will be used)
+
+
+
+
+FileTransferProtocol (Applicable for Linux hosted certificate stores only)
+
+* Determines the protocol to use when uploading/downloading files while processing a job.
+* Allowed values - SCP, SFTP or Both. If "Both" is entered, SCP will be attempted first, and if that does not work, SFTP will be tried.
+* Default value - SCP.
+
+
+
+
+DefaultLinuxPermissionsOnStoreCreation (Applicable for Linux hosted certificate stores only)
+
+* The Linux file permissions that will be set on a new certificate store created via a Management Create job or a Management Add job where CreateStoreOnAddIsMissing is set to "Y". This value will be used for all certificate stores managed by this orchestrator instance unless overridden by the optional "Linux File Permissions on Store Creation" custom parameter setting on a specific certificate store. See the [Creating Certificate Store Types](#creating-certificate-store-types) section for more information on creating RemoteFile certificate store types.
+* Allowed values - Any 3 digit value from 000-777.
+* Default Value - 600.
+
+
+
+
+DefaultOwnerOnStoreCreation (Applicable for Linux hosted certificate stores only)
+
+* When a Management job is run to remotely create the physical certificate store on a remote server, by default the file owner and group will be set to the user name associated with the Keyfactor certificate store. Setting DefaultOwnerOnStoreCreation to an alternate valid Linux user name will set that as the owner instead. The owner AND group may be supplied by adding a ":" as a delimitter between the owner and group values, such as ownerId:groupId. Supplying only the ownerId will set that value as the file owner. The group name will default to how the Linux "install" command handles assigning the group. The optional "Linux File Owner on Store Creation" custom parameter setting for a specific certificate store can override this value for a specific store. See the [Creating Certificate Store Types](#creating-certificate-store-types) section for more information on creating RemoteFile certificate store types.
+* Allowed values - Any valid user id that the destination Linux server will recognize
+* Default Value - blank (the ID associated with the Keyfactor certificate store will be used).
+
+
+
+
+## Discovery
+
+When scheduling discovery jobs in Keyfactor Command, there are a few fields that are important to highlight here:
+
+
+Client Machine
+
+The IP address or DNS of the server hosting the certificate store. For more information, see [Client Machine ](#client-machine-instructions)
+
+
+
+
+Store Path
+
+For Linux orchestrated servers, "StorePath" will begin with a forward slash (/) and contain the full path and file name, including file extension if one exists (i.e. /folder/path/storename.ext). For Windows orchestrated servers, it should be the full path and file name, including file extension if one exists, beginning with a drive letter (i.e. c:\folder\path\storename.ext).
+
+
+
+
+Server Username/Password
+
+A username and password (or valid PAM key if the username and/or password is stored in a KF Command configured PAM integration). The password can be an SSH private key if connecting via SSH to a server using SSH private key authentication. If acting as an *agent* using local file access, just check "No Value" for the username and password.
+
+
+
+Directories to Search
+
+Enter one or more comma delimitted file paths to search (please reference the Keyfactor Command Reference Guide for more information), but there is also a special value that can be used on Windows orchestrated servers instead - "fullscan". Entering fullscan in this field will tell the RemoteFile discovery job to search all available drive letters at the root and recursively search all of them for files matching the other search criteria.
+
+
+
+
+Extensions
+
+In addition to entering one or more comma delimitted extensions to search for (please reference the Keyfactor Command Reference Guide for more information), a reserved value of "noext" can be used that will cause the RemoteFile discovery job to search for files that do not have an extension. This value can be chained with other extensions using the comma delimiter. For example, entering pem,jks,noext will cause the RemoteFile discovery job to return file locations with extensions of "pem", "jks", *and* files that do not have extensions.
+
+
+
+Please refer to the Keyfactor Command Reference Guide for complete information on creating certificate stores and scheduling discovery jobs in Keyfactor Command.
+
+
+## Client Machine Instructions
+
+When creating a Certificate Store or scheduling a Discovery Job, you will be asked to provide a "Client Machine".
+
+For Linux orchestrated servers, "Client Machine" should be the DNS name or IP address of the remote orchestrated server, while for Windows orchestratred servers, it should be the following URL format: protocol://dns-or-ip:port, where
+* protocol is http or https, whatever your WinRM configuration uses
+* dns-or-ip is the DNS name or IP address of the server
+* port is the port WinRM is running under, usually 5985 for http and 5986 for https.
+
+Example: https://myserver.mydomain.com:5986
+
+If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), Client Machine can be entered as stated above, OR you can bypass SSH/WinRM and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a cerificate store that will accessed without a WinRM/SSH connection.
+
+
+## Developer Notes
+
+The Remote File Orchestrator Extension is designed to be highly extensible, enabling its use with various file-based certificate stores beyond the specific implementations currently referenced above. The advantage to extending this integration rather than creating a new one is that the configuration, remoting, and Inventory/Management/Discovery logic is already written. The developer needs to only implement a few classes and write code to convert the destired file based store to a common format. This section describes the steps necessary to add additional store/file types. Please note that familiarity with the [.Net Core BouncyCastle cryptography library](https://github.com/bcgit/bc-csharp) is a prerequisite for adding additional supported file/store types.
+
+Steps to create a new supported file based certificate store type:
+
+1. Clone this repository from GitHub
+2. Open the .net core solution in the IDE of your choice
+3. Under the ImplementationStoreTypes folder, create a new folder named for the new certificate store type
+4. Create a new class (with namespace of Keyfactor.Extensions.Orchestrator.RemoteFile.{NewType}) in the new folder that will implement ICertificateStoreSerializer. By convention, {StoreTypeName}CertificateSerializer would be a good choice for the class name. This interface requires you to implement three methods:
+ - DesrializeRemoteCertificateStore - This method takes in a byte array containing the contents of file based store you are managing. The developer will need to convert that to an Org.BouncyCastle.Pkcs.Pkcs12Store class and return it.
+ - SerializeRemoteCertificateStore - This method takes in an Org.BouncyCastle.Pkcs.Pkcs12Store and converts it to a collection of custom file representations.
+ - GetPrivateKeyPath - This method returns the location of the external private key file for single certificate stores. Currently this is only used for RFPEM, and all other implementations return NULL for this method. If this is not applicable to your implementation just return a NULL value for this method.
+5. Create an Inventory.cs class (with namespace of Keyfactor.Extensions.Orchestrator.RemoteFile.{NewType}) under the new folder and have it inherit InventoryBase. Override the internal GetCertificateStoreSerializer() method with a one line implementation returning a new instantiation of the class created in step 4.
+6. Create a Management.cs class (with namespace of Keyfactor.Extensions.Orchestrator.RemoteFile.{NewType}) under the new folder and have it inherit ManagementBase. Override the internal GetCertificateStoreSerializer() method with a one line implementation returning a new instantiation of the class created in step 4.
+7. Modify the manifest.json file to add three new sections (for Inventory, Management, and Discovery). Make sure for each, the "NewType" in Certstores.{NewType}.{Operation}, matches what you will use for the certificate store type short name in Keyfactor Command. On the "TypeFullName" line for all three sections, make sure the namespace matches what you used for your new classes. Note that the namespace for Discovery uses a common class for all supported types. Discovery is a common implementation for all supported store types.
+8. Modify the integration-manifest.json file to add the new store type under the store_types element.
\ No newline at end of file
diff --git a/docsource/images/RFDER-advanced-store-type-dialog.png b/docsource/images/RFDER-advanced-store-type-dialog.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb418e6f83790cd2e6a6d55a8bf9b1e49c25571c
GIT binary patch
literal 41690
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 CCBg-qMfLQcYP*4zP2JpfL*o%}{=ley
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
U2-Y$>_#s95oWj|p)0hAFZym3l)c^nh
literal 0
HcmV?d00001
diff --git a/docsource/images/RFDER-basic-store-type-dialog.png b/docsource/images/RFDER-basic-store-type-dialog.png
new file mode 100644
index 0000000000000000000000000000000000000000..451ac345440ba7a559af1785d8f1e1d233bacbae
GIT binary patch
literal 50826
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 |