diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index 6d8de53..bd5f384 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -11,9 +11,17 @@ on: jobs: call-starter-workflow: - uses: keyfactor/actions/.github/workflows/starter.yml@v2 + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + with: + command_token_url: ${{ vars.COMMAND_TOKEN_URL }} + command_hostname: ${{ vars.COMMAND_HOSTNAME }} + command_base_api_path: ${{ vars.COMMAND_API_PATH }} 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 }} + entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} + entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} + command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} + command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8544046..78b3932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ +# 1.2.2 + +## Bug Fixes +- fix(storetypes): `K8SJKS` and `K8SPKCS12` storetypes using a separate `k8s` secret for store password does not crash +on missing or invalid secret field name. +- fix(storetypes): `K8SJKS` where JKS files created using Keytool v20+ will be recognized as JKS files. +- fix(storetypes): `K8SJKS` and `K8SPKCS12` store/buddy passwords ending with a `\n` character will be trimmed to not include the newline. + +## Chores: +- chore(docs): Update documentation format +- chore(deps): Bump `BouncyCastle.Cryptography` to `v2.6.2`. + # 1.2.1 + +## Bug Fixes - fix(management): `K8SNS` management jobs handle `storepath` parsed length is less than expected. # 1.2.0 diff --git a/README.md b/README.md index dfff94a..977454c 100644 --- a/README.md +++ b/README.md @@ -1,928 +1,1709 @@ +

+ Kubernetes Universal Orchestrator Extension +

+ +

+ +Integration Status: production +Release +Issues +GitHub Downloads (all assets, all releases) +

-# Kubernetes Orchestrator Extension +

+ + + Support + + · + + Installation + + · + + License + + · + + Related Integrations + +

-The Kubernetes Orchestrator allows for the remote management of certificate stores defined in a Kubernetes cluster. The following types of Kubernetes resources are supported: kubernetes secrets of `kubernetes.io/tls` or `Opaque` and kubernetes certificates `certificates.k8s.io/v1` +## Overview -#### Integration status: Production - Ready for use in production environments. +The Kubernetes Orchestrator allows for the remote management of certificate stores defined in a Kubernetes cluster. +The following types of Kubernetes resources are supported: kubernetes secrets of `kubernetes.io/tls` or `Opaque` and +kubernetes certificates `certificates.k8s.io/v1` + +The certificate store types that can be managed in the current version are: +- `K8SCert` - Kubernetes certificates of type `certificates.k8s.io/v1` +- `K8SSecret` - Kubernetes secrets of type `Opaque` +- `K8STLSSecret` - Kubernetes secrets of type `kubernetes.io/tls` +- `K8SCluster` - This allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. + This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores across all k8s namespaces. +- `K8SNS` - This allows for a single store to manage a k8s namespace's secrets or type `Opaque` and `kubernetes.io/tls`. + This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores for a single k8s namespace. +- `K8SJKS` - Kubernetes secrets of type `Opaque` that contain one or more Java Keystore(s). These cannot be managed at the + cluster or namespace level as they should all require unique credentials. +- `K8SPKCS12` - Kubernetes secrets of type `Opaque` that contain one or more PKCS12(s). These cannot be managed at the + cluster or namespace level as they should all require unique credentials. + +This orchestrator extension makes use of the Kubernetes API by using a service account +to communicate remotely with certificate stores. The service account must have the correct permissions +in order to perform the desired operations. For more information on the required permissions, see the +[service account setup guide](#service-account-setup). -## About the Keyfactor Universal Orchestrator Extension +The Kubernetes Universal Orchestrator extension implements 7 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. -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. +- [K8SCert](#K8SCert) -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. +- [K8SCluster](#K8SCluster) -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. +- [K8SJKS](#K8SJKS) -## Support for Kubernetes Orchestrator Extension +- [K8SNS](#K8SNS) -Kubernetes Orchestrator Extension 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 +- [K8SPKCS12](#K8SPKCS12) -###### 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. +- [K8SSecret](#K8SSecret) ---- +- [K8STLSSecr](#K8STLSSecr) ---- +## Compatibility +This integration is compatible with Keyfactor Universal Orchestrator version 12.4 and later. +## Support +The Kubernetes Universal Orchestrator extension is community open source and there is **no SLA**. Keyfactor will address issues as resources become available. -## Keyfactor Version Supported +> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute bug fixes or additional enhancements, use the **[Pull requests](../../pulls)** tab. -The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.x -## Platform Specific Notes +## Requirements & Prerequisites -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|✓ |✓ | +Before installing the Kubernetes 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. -## PAM Integration +### Kubernetes API Access +This orchestrator extension makes use of the Kubernetes API by using a service account +to communicate remotely with certificate stores. The service account must exist and have the appropriate permissions. +The service account token can be provided to the extension in one of two ways: +- As a raw JSON file that contains the service account credentials +- As a base64 encoded string that contains the service account credentials -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. +#### Service Account Setup +To set up a service account user on your Kubernetes cluster to be used by the Kubernetes Orchestrator Extension. For full +information on the required permissions, see the [service account setup guide](./scripts/kubernetes/README.md). -The secrets that this orchestrator extension supports for use with a PAM Provider are: -| Name | Description | -|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ServerUsername | Must be set to `kubeconfig` if used. If you do not set it to `kubeconfig` the `ServerPassword` will be ignored. | -| ServerPassword | Must be set if `ServerUsername` is provided. The service account credentials for the Universal Orchestrator to use. Must be in `kubeconfig` format. For more information review [Kubernetes service account](https://github.com/Keyfactor/kubernetes-orchestrator/blob/main/scripts/kubernetes/README.md) docs and scripts. | - +## Certificate Store Types -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. +To use the Kubernetes Universal Orchestrator extension, you **must** create the Certificate Store Types required for your use-case. This only needs to happen _once_ per Keyfactor Command instance. -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. +The Kubernetes Universal Orchestrator extension implements 7 Certificate Store Types. Depending on your use case, you may elect to use one, or all of these Certificate Store Types. -
General PAM Provider Configuration -

+### K8SCert +

Click to expand details -### Example PAM Provider Setup +The `K8SCert` store type is used to manage Kubernetes certificates of type `certificates.k8s.io/v1`. -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. +**NOTE**: only `inventory` and `discovery` of these resources is supported with this extension. To provision these certs use the +[k8s-csr-signer](https://github.com/Keyfactor/k8s-csr-signer). -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. +#### Supported Operations -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: +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | 🔲 Unchecked | +| Remove | 🔲 Unchecked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | 🔲 Unchecked | -~~~ json -{"Secret":"my-kv-secret","Key":"myServerPassword"} -~~~ +#### Store Type Creation -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. -

-
- - - - ---- - - -## Table of Contents -- [Keyfactor Version Supported](#keyfactor-version-supported) -- [Platform Specific Notes](#platform-specific-notes) -- [PAM Integration](#pam-integration) -- [Overview](#overview) - * [K8SCert](#k8scert) - * [K8SSecret](#k8ssecret) - * [K8STLSSecret](#k8stlssecret) - * [K8SJKS](#k8sjks) -- [Versioning](#versioning) -- [Security Considerations](#security-considerations) - * [Service Account Setup](#service-account-setup) -- [Kubernetes Orchestrator Extension Installation](#kubernetes-orchestrator-extension-installation) -- [Certificate Store Types](#certificate-store-types) - * [Configuration Information](#configuration-information) - + [Note about StorePath](#note-about-storepath) - + [Common Values](#common-values) - - [UI Basic Tab](#ui-basic-tab) - - [UI Advanced Tab](#ui-advanced-tab) - - [Custom Fields Tab](#custom-fields-tab) - - [Kube Secret Types](#kube-secret-types) - - [Entry Parameters Tab:](#entry-parameters-tab-) - * [K8SSecret Store Type](#k8ssecret-store-type) - + [kfutil Create K8SSecret Store Type](#kfutil-create-k8ssecret-store-type) - + [UI Configuration](#ui-configuration) - - [UI Basic Tab](#ui-basic-tab-1) - - [UI Advanced Tab](#ui-advanced-tab-1) - - [UI Custom Fields Tab](#ui-custom-fields-tab) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab-) - * [K8STLSSecr Store Type](#k8stlssecr-store-type) - + [kfutil Create K8STLSSecr Store Type](#kfutil-create-k8stlssecr-store-type) - + [UI Configuration](#ui-configuration-1) - - [UI Basic Tab](#ui-basic-tab-2) - - [UI Advanced Tab](#ui-advanced-tab-2) - - [UI Custom Fields Tab](#ui-custom-fields-tab-1) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--1) - * [K8SPKCS12 Store Type](#k8spkcs12-store-type) - + [kfutil Create K8SPKCS12 Store Type](#kfutil-create-k8spkcs12-store-type) - + [UI Configuration](#ui-configuration-2) - - [UI Basic Tab](#ui-basic-tab-3) - - [UI Advanced Tab](#ui-advanced-tab-3) - - [UI Custom Fields Tab](#ui-custom-fields-tab-2) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--2) - * [K8SJKS Store Type](#k8sjks-store-type) - + [Storepath Patterns](#storepath-patterns) - + [Alias Patterns](#alias-patterns) - + [kfutil Create K8SJKS Store Type](#kfutil-create-k8sjks-store-type) - + [UI Configuration](#ui-configuration-3) - - [UI Basic Tab](#ui-basic-tab-4) - - [UI Advanced Tab](#ui-advanced-tab-4) - - [UI Custom Fields Tab](#ui-custom-fields-tab-3) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--3) - * [K8SCluster Store Type](#k8scluster-store-type) - + [Storepath Patterns](#storepath-patterns-1) - + [Alias Patterns](#alias-patterns-1) - + [kfutil Create K8SCluster Store Type](#kfutil-create-k8scluster-store-type) - + [UI Configuration](#ui-configuration-4) - - [UI Basic Tab](#ui-basic-tab-5) - - [UI Advanced Tab](#ui-advanced-tab-5) - - [UI Custom Fields Tab](#ui-custom-fields-tab-4) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--4) - * [K8SNS Store Type](#k8sns-store-type) - + [Storepath Patterns](#storepath-patterns-2) - + [Alias Patterns](#alias-patterns-2) - + [kfutil Create K8SNS Store Type](#kfutil-create-k8sns-store-type) - + [UI Configuration](#ui-configuration-5) - - [UI Basic Tab](#ui-basic-tab-6) - - [UI Advanced Tab](#ui-advanced-tab-6) - - [UI Custom Fields Tab](#ui-custom-fields-tab-5) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--5) - * [K8SCert Store Type](#k8scert-store-type) - + [UI Configuration](#ui-configuration-6) - - [UI Basic Tab](#ui-basic-tab-7) - - [UI Advanced Tab](#ui-advanced-tab-7) - - [UI Custom Fields Tab](#ui-custom-fields-tab-6) - - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--6) -- [Creating Certificate Stores and Scheduling Discovery Jobs](#creating-certificate-stores-and-scheduling-discovery-jobs) -- [Certificate Discovery](#certificate-discovery) - * [K8SNS Discovery](#k8sns-discovery) - * [K8SPKCS12 and K8SJKS Discovery](#k8spkcs12-and-k8sjks-discovery) -- [Certificate Inventory](#certificate-inventory) -- [Certificate Management](#certificate-management) - * [K8STLSSecr & K8SSecret](#k8stlssecr---k8ssecret) - + [Opaque & tls secret w/o ca.crt](#opaque---tls-secret-w-o-cacrt) - + [Opaque & tls secret w/ ca.crt](#opaque---tls-secret-w--cacrt) - + [Opaque & tls secret w/o private key](#opaque---tls-secret-w-o-private-key) - * [K8SJKS & K8SPKCS12](#k8sjks---k8spkcs12) -- [Development](#development) -- [License](#license) - - -## Keyfactor Version Supported - -The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.1 - -| Keyfactor Version | Universal Orchestrator Framework Version | Supported | -|-------------------|------------------------------------------|--------------| -| 10.2.1 | 10.1, 10.2 | ✓ | -| 10.1.1 | 10.1, 10.2 | ✓ | -| 10.0.0 | 10.1, 10.2 | ✓ | -| 9.10.1 | Not supported on KF 9.X.X | x | -| 9.5.0 | Not supported on KF 9.X.X | x | - -## 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. -See the store type specific sections below for more details on specific cababilities based on Kubernetes resource type. - -## 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 | -|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ServerPassword | This is a raw JSON file that contains service account credentials to interact with the Kubernetes APIs. See the service account setup guide for permission details. | -| ServerUsername | This is a static value that must be set to `kubeconfig`. | - - -It is not necessary to implement all of the secrets available to be managed by a PAM provider. -For each value that you want managed by a PAM provider, simply enter the key value inside your -specific PAM provider that will hold this value into the corresponding field when setting up -the certificate store, discovery job, or API call. - -Setting up a PAM provider for use involves adding an additional section to the manifest.json -file for this extension as well as setting up the PAM provider you will be using. Each of -these steps is specific to the PAM provider you will use and are documented in the specific -GitHub repo for that provider. For a list of Keyfactor supported PAM providers, please -reference the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam). - ---- - - -## Overview -The Kubernetes Orchestrator Extension is an integration that can remotely manage certificate -resources in a Kubernetes cluster. The certificate store types that can be managed in the -current version are: -- K8SCert - Kubernetes certificates of type `certificates.k8s.io/v1` -- K8SSecret - Kubernetes secrets of type `Opaque` -- K8STLSSecret - Kubernetes secrets of type `kubernetes.io/tls` -- K8SCluster - This allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. -This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores across all k8s namespaces. -- K8SNS - This allows for a single store to manage a k8s namespace's secrets or type `Opaque` and `kubernetes.io/tls`. -This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores for a single k8s namespace. -- K8SJKS - Kubernetes secrets of type `Opaque` that contain one or more Java Keystore(s). These cannot be managed at the -cluster or namespace level as they should all require unique credentials. -- K8SPKCS12 - Kubernetes secrets of type `Opaque` that contain one or more PKCS12(s). These cannot be managed at the -cluster or namespace level as they should all require unique credentials. - -This orchestrator extension makes use of the Kubernetes API by using a service account -to communicate remotely with certificate stores. The service account must have the correct permissions -in order to perform the desired operations. For more information on the required permissions, see the -[service account setup guide](#service-account-setup). +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SCert kfutil details -### K8SCert -The K8SCert store type is used to manage Kubernetes certificates of type `certificates.k8s.io/v1`. -To provision these certs use the [k8s-csr-signer](https://github.com/Keyfactor/k8s-csr-signer) -documentation for more information. + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SCert + kfutil store-types create K8SCert + ``` -### K8SSecret -The K8SSecret store type is used to manage Kubernetes secrets of type `Opaque`. These secrets can have any -arbitrary fields, but except for the `tls.crt` and `tls.key` fields, these are reserved for the `kubernetes.io/tls` -secret type. -**NOTE**: The orchestrator will only manage the fields named `certificates` and `private_keys` in the -secret. Any other fields will be ignored. + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SCert store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SCert details + + Create a store type called `K8SCert` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SCert | Display name for the store type (may be customized) | + | Short Name | K8SCert | Short display name for the store type | + | Capability | K8SCert | Store type name orchestrator will register with. Check the box to allow entry of value | + | Supports Add | 🔲 Unchecked | Indicates that the Store Type supports Management Add | + | Supports Remove | 🔲 Unchecked | 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 | 🔲 Unchecked | 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 | 🔲 Unchecked | 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: + + ![K8SCert Basic Tab](docsource/images/K8SCert-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 | Forbidden | 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: + + ![K8SCert Advanced Tab](docsource/images/K8SCert-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeNamespace | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | String | default | 🔲 Unchecked | + | KubeSecretName | KubeSecretName | The name of the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretType | KubeSecretType | This defaults to and must be `csr` | String | cert | ✅ Checked | + + The Custom Fields tab should look like this: + + ![K8SCert Custom Fields Tab](docsource/images/K8SCert-custom-fields-store-type-dialog.png) + +
+
+ +### K8SCluster + +
Click to expand details + + +The `K8SCluster` store type allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | 🔲 Unchecked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SCluster kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SCluster + kfutil store-types create K8SCluster + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SCluster store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SCluster details + + Create a store type called `K8SCluster` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SCluster | Display name for the store type (may be customized) | + | Short Name | K8SCluster | Short display name for the store type | + | Capability | K8SCluster | 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 | 🔲 Unchecked | 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 | 🔲 Unchecked | 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: + + ![K8SCluster Basic Tab](docsource/images/K8SCluster-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: + + ![K8SCluster Advanced Tab](docsource/images/K8SCluster-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | SeparateChain | Separate Certificate Chain | Whether to store the certificate chain separately from the certificate. | Bool | false | 🔲 Unchecked | + | IncludeCertChain | Include Certificate Chain | Whether to include the certificate chain in the certificate. | Bool | true | 🔲 Unchecked | -### K8STLSSecret -The K8STLSSecret store type is used to manage Kubernetes secrets of type `kubernetes.io/tls`. These secrets -must have the `tls.crt` and `tls.key` fields and may only contain a single key and single certificate. + The Custom Fields tab should look like this: + + ![K8SCluster Custom Fields Tab](docsource/images/K8SCluster-custom-fields-store-type-dialog.png) + +
+
### K8SJKS -The K8SJKS store type is used to manage Kubernetes secrets of type `Opaque`. These secrets + +
Click to expand details + + +The `K8SJKS` store type is used to manage Kubernetes secrets of type `Opaque`. These secrets must have a field that ends in `.jks`. The orchestrator will inventory and manage using a *custom alias* of the following pattern: `/`. For example, if the secret has a field named `mykeystore.jks` and the keystore contains a certificate with an alias of `mycert`, the orchestrator will manage the certificate using the -alias `mykeystore.jks/mycert`. +alias `mykeystore.jks/mycert`. *NOTE* *This store type cannot be managed at the `cluster` or `namespace` level as they +should all require unique credentials.* + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SJKS kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SJKS + kfutil store-types create K8SJKS + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SJKS store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SJKS details + + Create a store type called `K8SJKS` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SJKS | Display name for the store type (may be customized) | + | Short Name | K8SJKS | Short display name for the store type | + | Capability | K8SJKS | 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: + + ![K8SJKS Basic Tab](docsource/images/K8SJKS-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: + + ![K8SJKS Advanced Tab](docsource/images/K8SJKS-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeNamespace | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | String | default | 🔲 Unchecked | + | KubeSecretName | KubeSecretName | The name of the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretType | KubeSecretType | This defaults to and must be `jks` | String | jks | ✅ Checked | + | CertificateDataFieldName | CertificateDataFieldName | | String | .jks | ✅ Checked | + | PasswordFieldName | PasswordFieldName | | String | password | 🔲 Unchecked | + | PasswordIsK8SSecret | Password Is K8S Secret | | Bool | false | 🔲 Unchecked | + | StorePasswordPath | StorePasswordPath | | String | | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![K8SJKS Custom Fields Tab](docsource/images/K8SJKS-custom-fields-store-type-dialog.png) + +
+
+ +### K8SNS + +
Click to expand details + + +The `K8SNS` store type is used to manage Kubernetes secrets of type `kubernetes.io/tls` and/or type `Opaque` in a single +Keyfactor Command certificate store using an alias pattern of + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SNS kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SNS + kfutil store-types create K8SNS + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SNS store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SNS details + + Create a store type called `K8SNS` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SNS | Display name for the store type (may be customized) | + | Short Name | K8SNS | Short display name for the store type | + | Capability | K8SNS | 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 | 🔲 Unchecked | 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: + + ![K8SNS Basic Tab](docsource/images/K8SNS-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: + + ![K8SNS Advanced Tab](docsource/images/K8SNS-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeNamespace | Kube Namespace | | String | default | 🔲 Unchecked | + | SeparateChain | Separate Certificate Chain | Whether to store the certificate chain separately from the certificate. | Bool | false | 🔲 Unchecked | + | IncludeCertChain | Include Certificate Chain | Whether to include the certificate chain in the certificate. | Bool | true | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![K8SNS Custom Fields Tab](docsource/images/K8SNS-custom-fields-store-type-dialog.png) + +
+
### K8SPKCS12 -The K8SPKCS12 store type is used to manage Kubernetes secrets of type `Opaque`. These secrets -must have a field that ends in `.p12`, `.pkcs12`, `.pfx`. The orchestrator will inventory and manage using a -*custom alias* of the following pattern: `/`. For example, if the secret has a -field named `mykeystore.p12` and the keystore contains a certificate with an alias of `mycert`, the orchestrator will -manage the certificate using the alias `mykeystore.p12/mycert`. - -## Versioning - -The version number of a the Kubernetes Orchestrator Extension can be verified by right clicking on the -`Kyefactor.Orchestrators.K8S.dll` file in the `///Extensions/Kubernetes` installation folder, -selecting Properties, and then clicking on the Details tab. - -## Security Considerations -For the Kubernetes Orchestrator Extension to be able to communicate with a Kubernetes cluster, it must -be able to authenticate with the cluster. This is done by providing the extension with a service account -token that has the appropriate permissions to perform the desired operations. The service account token -can be provided to the extension in one of two ways: -- As a raw JSON file that contains the service account credentials -- As a base64 encoded string that contains the service account credentials -### Service Account Setup -To set up a service account user on your Kubernetes cluster to be used by the Kubernetes Orchestrator Extension, use the following example as a guide: -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: keyfactor - namespace: keyfactor ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: keyfactor -rules: -- apiGroups: ["certificates.k8s.io"] - resources: ["certificatesigningrequests"] - verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] -- apiGroups: [""] - resources: ["secrets"] - verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] -- apiGroups: [""] - resources: ["namespaces"] - verbs: ["get", "list", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: keyfactor -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: keyfactor -subjects: -- kind: ServiceAccount - name: keyfactor - namespace: keyfactor -``` - -## Kubernetes Orchestrator Extension Installation -1. Create the certificate store types you wish to manage. Please refer to the individual sections - devoted to each supported store type under "Certificate Store Types" later in this README. -2. Stop the Keyfactor Universal Orchestrator Service for the orchestrator you plan to install this - extension to run on. -3. In the Keyfactor Orchestrator installation folder (by convention usually - C:\Program Files\Keyfactor\Keyfactor Orchestrator), find the "Extensions" folder. Underneath that, - create a new folder named "Kubernetes". You may choose to use a different name if you wish. -4. Download the latest version of the Kubernetes orchestrator extension from - [GitHub](https://github.com/Keyfactor/kubernetes-orchestrator). Click on the "Latest" release - link on the right hand side of the main page and download the first zip file. -5. Copy the contents of the download installation zip file to the folder created in Step 3. -6. (Optional) If you decide to create one or more certificate store types with short names different - than the suggested values (please see the individual certificate store type sections in "Certificate - Store Types" later in this README for more information regarding certificate store types), edit the - manifest.json file in the folder you created in step 3, and modify each "ShortName" in each - "Certstores.{ShortName}.{Operation}" line with the ShortName you used to create the respective - certificate store type. If you created it with the suggested values, this step can be skipped. -7. Modify the config.json file (See the "Configuration File Setup" section later in this README) -8. Start the Keyfactor Universal Orchestrator Service. -9. Create the certificate store types you wish to manage. Please refer to the individual sections - devoted to each supported store type under [Certificate Store Types](#certificate-store-types) later in this README. -10. (Optional) Run certificate discovery jobs to populate the certificate stores with existing - certificates. See the [Certificate Store Discovery](#certificate-store-discovery) section later in this README for more - information. +
Click to expand details + + +The `K8SPKCS12` store type is used to manage Kubernetes secrets of type `Opaque`. These secrets +must have a field that ends in `.pkcs12`. The orchestrator will inventory and manage using a *custom alias* of the following +pattern: `/`. For example, if the secret has a field named `mykeystore.pkcs12` and +the keystore contains a certificate with an alias of `mycert`, the orchestrator will manage the certificate using the +alias `mykeystore.pkcs12/mycert`. *NOTE* *This store type cannot be managed at the `cluster` or `namespace` level as they +should all require unique credentials.* + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SPKCS12 kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SPKCS12 + kfutil store-types create K8SPKCS12 + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SPKCS12 store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SPKCS12 details + + Create a store type called `K8SPKCS12` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SPKCS12 | Display name for the store type (may be customized) | + | Short Name | K8SPKCS12 | Short display name for the store type | + | Capability | K8SPKCS12 | 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: + + ![K8SPKCS12 Basic Tab](docsource/images/K8SPKCS12-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: + + ![K8SPKCS12 Advanced Tab](docsource/images/K8SPKCS12-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeSecretType | Kube Secret Type | This defaults to and must be `pkcs12` | String | pkcs12 | ✅ Checked | + | CertificateDataFieldName | CertificateDataFieldName | | String | .p12 | ✅ Checked | + | PasswordFieldName | Password Field Name | | String | password | 🔲 Unchecked | + | PasswordIsK8SSecret | Password Is K8S Secret | | Bool | false | 🔲 Unchecked | + | KubeNamespace | Kube Namespace | | String | default | 🔲 Unchecked | + | KubeSecretName | Kube Secret Name | | String | | 🔲 Unchecked | + | StorePasswordPath | StorePasswordPath | | String | | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![K8SPKCS12 Custom Fields Tab](docsource/images/K8SPKCS12-custom-fields-store-type-dialog.png) + +
+
+ +### K8SSecret + +
Click to expand details + + +The `K8SSecret` store type is used to manage Kubernetes secrets of type `Opaque`. + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8SSecret kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8SSecret + kfutil store-types create K8SSecret + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8SSecret store type manually in +the Keyfactor Command Portal +
Click to expand manual K8SSecret details + + Create a store type called `K8SSecret` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8SSecret | Display name for the store type (may be customized) | + | Short Name | K8SSecret | Short display name for the store type | + | Capability | K8SSecret | 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 | 🔲 Unchecked | 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: + + ![K8SSecret Basic Tab](docsource/images/K8SSecret-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: + + ![K8SSecret Advanced Tab](docsource/images/K8SSecret-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeNamespace | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretName | KubeSecretName | The name of the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretType | KubeSecretType | This defaults to and must be `secret` | String | secret | ✅ Checked | + | SeparateChain | Separate Certificate Chain | Whether to store the certificate chain separately from the certificate. | Bool | false | 🔲 Unchecked | + | IncludeCertChain | Include Certificate Chain | Whether to include the certificate chain in the certificate. | Bool | true | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![K8SSecret Custom Fields Tab](docsource/images/K8SSecret-custom-fields-store-type-dialog.png) + +
+
+ +### K8STLSSecr + +
Click to expand details + + +The `K8STLSSecret` store type is used to manage Kubernetes secrets of type `kubernetes.io/tls` + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | ✅ Checked | +| Remove | ✅ Checked | +| Discovery | ✅ Checked | +| Reenrollment | 🔲 Unchecked | +| Create | ✅ Checked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand K8STLSSecr kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # K8STLSSecr + kfutil store-types create K8STLSSecr + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the K8STLSSecr store type manually in +the Keyfactor Command Portal +
Click to expand manual K8STLSSecr details + + Create a store type called `K8STLSSecr` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | K8STLSSecr | Display name for the store type (may be customized) | + | Short Name | K8STLSSecr | Short display name for the store type | + | Capability | K8STLSSecr | 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 | 🔲 Unchecked | 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: + + ![K8STLSSecr Basic Tab](docsource/images/K8STLSSecr-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: + + ![K8STLSSecr Advanced Tab](docsource/images/K8STLSSecr-advanced-store-type-dialog.png) + + > For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX. + + ##### 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 | This should be no value or `kubeconfig` | Secret | None | 🔲 Unchecked | + | ServerPassword | Server Password | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | Secret | None | ✅ Checked | + | KubeNamespace | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretName | KubeSecretName | The name of the K8S secret object. | String | | 🔲 Unchecked | + | KubeSecretType | KubeSecretType | This defaults to and must be `tls_secret` | String | tls_secret | ✅ Checked | + | SeparateChain | Separate Certificate Chain | Whether to store the certificate chain separately from the certificate. | Bool | false | 🔲 Unchecked | + | IncludeCertChain | Include Certificate Chain | Whether to include the certificate chain in the certificate. | Bool | true | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![K8STLSSecr Custom Fields Tab](docsource/images/K8STLSSecr-custom-fields-store-type-dialog.png) + +
+
+ + +## Installation + +1. **Download the latest Kubernetes Universal Orchestrator extension from GitHub.** + + Navigate to the [Kubernetes Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/k8s-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` | `k8s-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` | `Disable` | `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 Kubernetes Universal Orchestrator extension inside the extensions directory.** + + Create a new directory called `k8s-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 `k8s-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 Kubernetes 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 supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm?Highlight=extensions). + + + +## Defining Certificate Stores + +The Kubernetes Universal Orchestrator extension implements 7 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. + +
K8SCert (K8SCert) + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8SCert" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SCert` certificates. Specifically, one with the `K8SCert` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | KubeSecretName | The name of the K8S secret object. | + | KubeSecretType | This defaults to and must be `csr` | + +
-## Certificate Store Types -When setting up the certificate store types you wish the Kubernetes Orchestrator Extension to -manage, there are some common settings that will be the same for all supported types. -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`. Alternatively, -there are cURL scripts for all of the currently implemented certificate store types -in the Certificate Store Type cURL Scripts folder in this repo if you wish to automate -the creation of the desired store types. - -### Configuration Information -Below is a table of the common values that should be used for all certificate store types. - -#### Note about StorePath -A Keyfactor Command certificate store `StorePath` for the K8S orchestrator extension can follow the following formats: - -| Pattern | Description | -|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| `secretName` | The name of the secret to use. This assumes `KubeNamespace` is defined or `default` and will be the `secret` or `cert` name on k8s. | -| `namespace/secretName` | If `KubeNamespace` or `KubeSecretName` are not set, then the path will be split by `/` and the values will be parsed according to the pattern. | -| `clusterName/namespace/secretName` | Same as above, clusterName is purely informational | -| `clusterName/namespace/secretType/secretName` | Considered a `full` path, this is what discovery will return as `StorePath` | - -#### Common Values -##### UI Basic Tab -| Field Name | Required | Description | Value | -|-------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------| -| Name | ✓ | The display name you wish to use for the new Certificate Store Type. | Depends on store type. | -| ShortName | ✓ | The short name you wish to use for the new Certificate Store Type. | Depends on store type. | -| Custom Capability | ✓ | Whether or not the certificate store type supports custom capabilities. | Checked [x] | -| Supported Job Types | ✓ | The job types supported by the certificate store type. | Depends on store type. | -| Needs Server | ✓ | Must be set to true or checked. NOTE: If using this `ServerUsername` must be equal to `kubeconfig` and `ServerPassword` will be the kubeconfig file in JSON format | Checked [x] | -| 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. | Unchecked [ ] | -| Uses PowerShell | | Whether or not the certificate store type uses PowerShell. | Unchecked [ ] | -| Requires Store Password | | Whether or not the certificate store type requires a password. | Unchecked [ ] | -| Supports Entry Password | | Whether or not the certificate store type supports entry passwords. | Unchecked [ ] | - -##### UI Advanced Tab -| Field Name | Required | Description | Value | -|-----------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------|------------------------| -| Store Path Type | | The type of path the certificate store type uses. | Freeform | -| Supports Custom Alias | | Whether or not the certificate store type supports custom aliases. | Depends on store type. | -| Private Key Handling | | Whether or not the certificate store type supports private key handling. | Depends on store type. | -| PFX Password Style | | The password style used by the certificate store type. | Default | - -##### Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|------------------|---------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | This field overrides implied `Store Path` value. The Kubernetes namespace the store will reside. This will override the value parsed from `storepath`. | -| KubeSecretName | Kube Secret Name | String | | | This field overrides implied `Store Path` value. The Kubernetes secret or certificate resource name. | -| KubeSecretType | Kube Secret Type | String | ✓ | | Must be one of the following `secret`, `secret_tls` or `cert`. See [kube-secret-types](#kube-secret-types). | -| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. Set this to `false` if you do not want certificate chains deployed. | -| SeparateChain | SeparateChain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | - -##### Kube Secret Types -- `secret` - A generic secret of type `Opaque`. Must contain a key of one of the following values: [ `cert`, `certficate`, `certs`,`certificates` ] to be inventoried. -- `tls_secret` - A secret of type `kubernetes.io/tls`. Must contain the following keys: [ `tls.crt`, `tls.key` ] to be inventoried. -- `cert` - A certificate `certificates.k8s.io/v1` resource. Must contain the following keys: [ `csr`, `cert` ] to be inventoried. - -##### Entry Parameters Tab: -- See specific certificate store type instructions below - -### K8SSecret Store Type - -#### kfutil Create K8SSecret Store Type - -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. -kfuti -```bash -kfutil login -kfutil store-types create --name K8SSecret -``` - -#### UI Configuration - -##### UI Basic Tab -| Field Name | Required | Value | -|-------------------------|----------|-------------------------------------------| -| Name | ✓ | `K8SSecret` | -| ShortName | ✓ | `K8SSecret` | -| Custom Capability | ✓ | Checked [x] + `K8SSecret` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | - -**NOTE:** If using PAM, `server_username` must be equal to `kubeconfig` and `server_password` will be the kubeconfig file in JSON format. - -![k8ssecret_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_basic.png) - -##### UI Advanced Tab -| Field Name | Value | -|-----------------------|-----------| -| Store Path Type | Freeform | -| Supports Custom Alias | Forbidden | -| Private Key Handling | Optional | -| PFX Password Style | Default | - -![k8ssecret_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_advanced.png) - -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|------------------|---------------------------|--------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | `default` | The K8S namespace the `Opaque` secret lives. This will override any value inferred in the `Store Path` | -| KubeSecretName | Kube Secret Name | String | ✓ | | The name of the K8S `Opaque` secret. This will override any value inferred in the `Store Path` | -| KubeSecretType | Kube Secret Type | String | ✓ | `secret` | | -| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | -| SeparateChain | SeparateChain | Bool | | `false` | Will default to `false` if not set. `true` will deploy leaf cert to `tls.crt` and the rest of the cert chain to `ca.crt`. If set to `false` the full chain is deployed to `tls.crt` | - -![k8ssecret_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8STLSSecr Store Type - -#### kfutil Create K8STLSSecr Store Type - -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. - -```bash -kfutil login -kfutil store-types create --name K8STLSSecr -``` - -#### UI Configuration - -##### UI Basic Tab -| Field Name | Required | Value | -|-------------------------|----------|-------------------------------------------| -| Name | ✓ | `K8STLSSecr` | -| ShortName | ✓ | `K8STLSSecr` | -| Custom Capability | ✓ | Checked [x] + `K8STLSSecr` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | - -![k8sstlssecr_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_basic.png) - -##### UI Advanced Tab -| Field Name | Required | Value | -|-----------------------|----------|-----------| -| Store Path Type | | Freeform | -| Supports Custom Alias | | Forbidden | -| Private Key Handling | | Optional | -| PFX Password Style | | Default | - -![k8sstlssecr_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_advanced.png) - -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|------------------|----------------------------|--------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | The K8S namespace the `tls` secret lives. This will override any value inferred in the `Store Path` | -| KubeSecretName | Kube Secret Name | String | | | The name of the K8S `tls` secret. This will override any value inferred in the `Store Path` | -| KubeSecretType | Kube Secret Type | String | ✓ | `tls_secret` | | -| IncludeCertChain | Include Certificate Chain | Bool | | `true` | If set to `false` only leaf cert will be deployed. | -| SeparateChain | SeparateChain | Bool | | `true` | `true` will deploy leaf cert to `tls.crt` and the rest of the cert chain to `ca.crt`. If set to `false` the full chain is deployed to `tls.crt` | - - -![k8sstlssecr_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8SPKCS12 Store Type - -#### kfutil Create K8SPKCS12 Store Type - -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. - -```bash -kfutil login -kfutil store-types create --name K8SPKCS12 -``` - -#### UI Configuration - -##### UI Basic Tab -| Field Name | Required | Value | -|---------------------------|----------|-------------------------------------------| -| Name | ✓ | `K8SPKCS12` | -| ShortName | ✓ | `K8SPKCS12` | -| Custom Capability | ✓ | Checked [x] + `K8SPKCS12` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password** | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | - -**NOTE:** `Requires Store Password` is required if pkcs12 password is not being sourced from a separate secret in the -K8S cluster. - -![k8spkcs12_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_basic.png) - -##### UI Advanced Tab -| Field Name | Value | -|-----------------------|----------| -| Store Path Type | Freeform | -| Supports Custom Alias | Required | -| Private Key Handling | Optional | -| PFX Password Style | Default | -![k8spkcs12_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_advanced.png) +#### Using kfutil CLI -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|--------------------------|-----------------------------|--------|----------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | K8S namespace the PKCS12 secret lives. This will override any value inferred in the `Store Path` | -| KubeSecretName | Kube Secret Name | String | | | The K8S secret name that contains PKCS12 data. This will override any value inferred in the `Store Path` | -| KubeSecretType | Kube Secret Type | String | ✓ | `pkcs12` | This must be set to `pkcs12`. | -| CertificateDataFieldName | Certificate Data Field Name | String | ✓ | `.p12` | The K8S secret field name to source the PKCS12 data from. You can provide an extension `.p12` or `.pfx` for a secret with a key `example.p12` | -| PasswordFieldName | Password Field Name | String | | `password` | If sourcing the PKCS12 password from a K8S secret this is the field it will look for the password in. | -| PasswordIsK8SSecret | Password Is K8S Secret | Bool | | `false` | If you want to use the PKCS12 secret or a separate secret specific in `KubeSecretPasswordPath` set this to `true` | -| StorePasswordPath | Kube Secret Password Path | String | | | Source PKCS12 password from a separate K8S secret. Pattern: `namespace_name/secret_name` | - - -![k8spkcs12_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8SJKS Store Type - -#### Storepath Patterns -- `namespace_name/secret_name` -- `namespace_name/secrets/secret_name` -- `cluster_name/namespace_name/secrets/secret_name` - -#### Alias Patterns -- `k8s_secret_field_name/keystore_alias` - -#### kfutil Create K8SJKS Store Type - -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. - -```bash -kfutil login -kfutil store-types create --name K8SJKS -``` - -#### UI Configuration - -##### UI Basic Tab -| Field Name | Required | Value | -|---------------------------|----------|-------------------------------------------| -| Name | ✓ | `K8SJKS` | -| ShortName | ✓ | `K8SJKS` | -| Custom Capability | ✓ | Checked [x] + `K8SJKS` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password** | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | +
Click to expand details -**NOTE:** `Requires Store Password` is required if pkcs12 password is not being sourced from a separate secret in the -K8S cluster. +1. **Generate a CSV template for the K8SCert certificate store** -![k8sjks_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_basic.png) + ```shell + kfutil stores import generate-template --store-type-name K8SCert --outpath K8SCert.csv + ``` +2. **Populate the generated CSV file** -##### UI Advanced Tab -| Field Name | Value | -|-----------------------|----------| -| Store Path Type | Freeform | -| Supports Custom Alias | Required | -| Private Key Handling | Optional | -| PFX Password Style | Default | + Open the CSV file, and reference the table below to populate parameters for each **Attribute**. -![k8sjks_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_advanced.png) - -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|--------------------------|-----------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | K8S namespace the JKS secret lives. This will override any value inferred in the `Store Path`. | -| KubeSecretName | Kube Secret Name | String | | | The K8S secret name that contains JKS data. This will override any value inferred in the `Store Path`. | -| KubeSecretType | Kube Secret Type | String | ✓ | `jks` | | -| CertificateDataFieldName | Certificate Data Field Name | String | ✓ | `.jks` | The K8S secret field name to source the JKS data from | -| PasswordFieldName | Password Field Name | String | ✓ | `password` | If sourcing the JKS password from a K8S secret this is the field it will look for the password in. | -| PasswordIsK8SSecret | Password Is K8S Secret | Bool | | `false` | If you want to use the JKS secret or a separate secret specific in `` set this to `true` | -| StorePasswordPath | Kube Secret Password Path | String | | | Source JKS password from a separate K8S secret. Pattern: `namespace_name/secret_name` | - - -![k8sjks_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8SCluster Store Type + | Attribute | Description | + | --------- | ----------- | + | Category | Select "K8SCert" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SCert` certificates. Specifically, one with the `K8SCert` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | Properties.KubeSecretName | The name of the K8S secret object. | + | Properties.KubeSecretType | This defaults to and must be `csr` | -#### Storepath Patterns -- `cluster_name` +3. **Import the CSV file to create the certificate stores** -#### Alias Patterns -- `namespace_name/secrets/secret_type/secret_name` - -#### kfutil Create K8SCluster Store Type - -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + ```shell + kfutil stores import csv --store-type-name K8SCert --file K8SCert.csv + ``` -```bash -kfutil login -kfutil store-types create --name K8SCluster -``` +
-#### UI Configuration -##### UI Basic Tab -| Field Name | Required | Value | -|-------------------------|----------|---------------------------------| -| Name | ✓ | `K8SCluster` | -| ShortName | ✓ | `K8SCluster` | -| Custom Capability | ✓ | Checked [x] + `K8SCluster` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | +#### PAM Provider Eligible Fields +
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator -![k8scluster_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_basic.png) +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_. -##### UI Advanced Tab -| Field Name | Value | -|-----------------------|----------| -| Store Path Type | Freeform | -| Supports Custom Alias | Required | -| Private Key Handling | Optional | -| PFX Password Style | Default | + | Attribute | Description | + | --------- | ----------- | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | -![k8scluster_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_advanced.png) +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. +
-##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|------------------|---------------------------|--------|----------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | -| SeparateChain | Separate Chain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | - -![k8scluster_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8SNS Store Type - -**NOTE**: This store type will only inventory K8S secrets that contain the keys `tls.crt` and `tls.key`. - -#### Storepath Patterns -- `namespace_name` -- `cluster_name/namespace_name` - -#### Alias Patterns -- `secrets/secret_type/secret_name` -#### kfutil Create K8SNS Store Type +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. -```bash -kfutil login -kfutil store-types create --name K8SNS -``` +
-#### UI Configuration +
K8SCluster (K8SCluster) -##### UI Basic Tab -| Field Name | Required | Value | -|-------------------------|----------|--------------------------------| -| Name | ✓ | `K8SNS` | -| ShortName | ✓ | `K8SNS` | -| Custom Capability | ✓ | Checked [x] + `K8SNS` | -| Supported Job Types | ✓ | Inventory, Add, Remove, Create | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | +In order for certificates of type `Opaque` and/or `kubernetes.io/tls` to be inventoried in `K8SCluster` store types, they must +have specific keys in the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` -![k8sns_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_basic.png) +### Storepath Patterns +- `` -##### UI Advanced Tab -| Field Name | Value | -|-----------------------|----------| -| Store Path Type | Freeform | -| Supports Custom Alias | Required | -| Private Key Handling | Optional | -| PFX Password Style | Default | +### Alias Patterns +- `/secrets//` -![k8sns_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_advanced.png) -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|------------------|---------------------------|--------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | K8S namespace to manage. This will override any value inferred in the `Store Path`. | -| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | -| SeparateChain | Separate Chain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | +### Store Creation -![k8sns_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_custom_fields.png) - -##### UI Entry Parameters Tab: -Empty - -### K8SCert Store Type +#### Manually with the Command UI -The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. - -```bash -kfutil login -kfutil store-types create --name K8SCert -``` - -#### UI Configuration +
Click to expand details -##### UI Basic Tab -| Field Name | Required | Value | -|-------------------------|----------|--------------------------| -| Name | ✓ | `K8SCert` | -| ShortName | ✓ | `K8SCert` | -| Custom Capability | ✓ | Checked [x] + `K8SCert` | -| Supported Job Types | ✓ | Inventory, Discovery | -| Needs Server | ✓ | Checked [x] | -| Blueprint Allowed | | Unchecked [ ] | -| Uses PowerShell | | Unchecked [ ] | -| Requires Store Password | | Unchecked [ ] | -| Supports Entry Password | | Unchecked [ ] | +1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.** -![k8scert_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_basic.png) + Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_. -##### UI Advanced Tab -| Field Name | Required | Value | -|-----------------------|----------|------------| -| Store Path Type | | Freeform | -| Supports Custom Alias | | Forbidden | -| Private Key Handling | | Forbidden | -| PFX Password Style | | Default | +2. **Add a Certificate Store.** -![k8scert_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_advanced.png) + Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form. -##### UI Custom Fields Tab -| Name | Display Name | Type | Required | Default Value | Description | -|--------------------|---------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------| -| KubeNamespace | Kube Namespace | String | | | The K8S namespace the `cert` resource lives. This will override any value inferred in the `Store Path` | -| KubeSecretName | Kube Secret Name | String | | | The K8S `cert` name. This will override any value inferred in the `Store Path`. | -| KubeSecretType | Kube Secret Type | String | ✓ | `cert` | | + | Attribute | Description | + | --------- |---------------------------------------------------------| + | Category | Select "K8SCluster" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SCluster` certificates. Specifically, one with the `K8SCluster` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | SeparateChain | Whether to store the certificate chain separately from the certificate. | + | IncludeCertChain | Whether to include the certificate chain in the certificate. | -![k8scert_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_custom_fields.png) -##### UI Entry Parameters Tab: -Empty - -## Creating Certificate Stores and Scheduling Discovery Jobs - -Please refer to the Keyfactor Command Reference Guide for information on creating -certificate stores and scheduling Discovery jobs in Keyfactor Command. - -## Certificate Discovery -**NOTE:** To use disovery jobs, you must have the story type created in Keyfactor Command and the `needs_server` checkbox MUST be checked. -Otherwise you will not be able to provide credentials to the discovery job. +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8SCluster certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8SCluster --outpath K8SCluster.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 "K8SCluster" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SCluster` certificates. Specifically, one with the `K8SCluster` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.SeparateChain | Whether to store the certificate chain separately from the certificate. | + | Properties.IncludeCertChain | Whether to include the certificate chain in the certificate. | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8SCluster --file K8SCluster.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + +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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +
K8SJKS (K8SJKS) + +In order for certificates of type `Opaque` to be inventoried as `K8SJKS` store types, they must have specific keys in +the Kubernetes secret. +- Valid Keys: `*.jks` + +### Storepath Patterns +- `/` +- `/secrets/` +- `//secrets/` + +### Alias Patterns +- `/` + +Example: `test.jks/load_balancer` where `test.jks` is the field name on the `Opaque` secret and `load_balancer` is +the certificate alias in the `jks` data store. + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8SJKS" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Store Password | Password to use when reading/writing to store | + | Orchestrator | Select an approved orchestrator capable of managing `K8SJKS` certificates. Specifically, one with the `K8SJKS` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | KubeSecretName | The name of the K8S secret object. | + | KubeSecretType | This defaults to and must be `jks` | + | CertificateDataFieldName | | + | PasswordFieldName | | + | PasswordIsK8SSecret | | + | StorePasswordPath | | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8SJKS certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8SJKS --outpath K8SJKS.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 "K8SJKS" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Store Password | Password to use when reading/writing to store | + | Orchestrator | Select an approved orchestrator capable of managing `K8SJKS` certificates. Specifically, one with the `K8SJKS` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | Properties.KubeSecretName | The name of the K8S secret object. | + | Properties.KubeSecretType | This defaults to and must be `jks` | + | Properties.CertificateDataFieldName | | + | Properties.PasswordFieldName | | + | Properties.PasswordIsK8SSecret | | + | Properties.StorePasswordPath | | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8SJKS --file K8SJKS.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | StorePassword | Password to use when reading/writing to 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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +
K8SNS (K8SNS) + +In order for certificates of type `Opaque` and/or `kubernetes.io/tls` to be inventoried in `K8SNS` store types, they must +have specific keys in the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` + +### Storepath Patterns +- `` +- `/` + +### Alias Patterns +- `secrets//` + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8SNS" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SNS` certificates. Specifically, one with the `K8SNS` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeNamespace | | + | SeparateChain | Whether to store the certificate chain separately from the certificate. | + | IncludeCertChain | Whether to include the certificate chain in the certificate. | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8SNS certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8SNS --outpath K8SNS.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 "K8SNS" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SNS` certificates. Specifically, one with the `K8SNS` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeNamespace | | + | Properties.SeparateChain | Whether to store the certificate chain separately from the certificate. | + | Properties.IncludeCertChain | Whether to include the certificate chain in the certificate. | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8SNS --file K8SNS.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + +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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +
K8SPKCS12 (K8SPKCS12) + +In order for certificates of type `Opaque` to be inventoried as `K8SPKCS12` store types, they must have specific keys in +the Kubernetes secret. +- Valid Keys: `*.pfx`, `*.pkcs12`, `*.p12` + +### Storepath Patterns +- `/` +- `/secrets/` +- `//secrets/` + +### Alias Patterns +- `/` + +Example: `test.pkcs12/load_balancer` where `test.pkcs12` is the field name on the `Opaque` secret and `load_balancer` is +the certificate alias in the `pkcs12` data store. + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8SPKCS12" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Store Password | Password to use when reading/writing to store | + | Orchestrator | Select an approved orchestrator capable of managing `K8SPKCS12` certificates. Specifically, one with the `K8SPKCS12` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeSecretType | This defaults to and must be `pkcs12` | + | CertificateDataFieldName | | + | PasswordFieldName | | + | PasswordIsK8SSecret | | + | KubeNamespace | | + | KubeSecretName | | + | StorePasswordPath | | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8SPKCS12 certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8SPKCS12 --outpath K8SPKCS12.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 "K8SPKCS12" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Store Password | Password to use when reading/writing to store | + | Orchestrator | Select an approved orchestrator capable of managing `K8SPKCS12` certificates. Specifically, one with the `K8SPKCS12` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeSecretType | This defaults to and must be `pkcs12` | + | Properties.CertificateDataFieldName | | + | Properties.PasswordFieldName | | + | Properties.PasswordIsK8SSecret | | + | Properties.KubeNamespace | | + | Properties.KubeSecretName | | + | Properties.StorePasswordPath | | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8SPKCS12 --file K8SPKCS12.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | StorePassword | Password to use when reading/writing to 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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +
K8SSecret (K8SSecret) + +In order for certificates of type `Opaque` to be inventoried as `K8SSecret` store types, they must have specific keys in +the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8SSecret" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SSecret` certificates. Specifically, one with the `K8SSecret` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | KubeSecretName | The name of the K8S secret object. | + | KubeSecretType | This defaults to and must be `secret` | + | SeparateChain | Whether to store the certificate chain separately from the certificate. | + | IncludeCertChain | Whether to include the certificate chain in the certificate. | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8SSecret certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8SSecret --outpath K8SSecret.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 "K8SSecret" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8SSecret` certificates. Specifically, one with the `K8SSecret` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | Properties.KubeSecretName | The name of the K8S secret object. | + | Properties.KubeSecretType | This defaults to and must be `secret` | + | Properties.SeparateChain | Whether to store the certificate chain separately from the certificate. | + | Properties.IncludeCertChain | Whether to include the certificate chain in the certificate. | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8SSecret --file K8SSecret.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + +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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +
K8STLSSecr (K8STLSSecr) + +In order for certificates of type `kubernetes.io/tls` to be inventoried, they must have specific keys in +the Kubernetes secret. +- Required keys: `tls.crt` and `tls.key` +- Optional keys: `ca.crt` + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +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 "K8STLSSecr" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8STLSSecr` certificates. Specifically, one with the `K8STLSSecr` capability. | + | ServerUsername | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | KubeSecretName | The name of the K8S secret object. | + | KubeSecretType | This defaults to and must be `tls_secret` | + | SeparateChain | Whether to store the certificate chain separately from the certificate. | + | IncludeCertChain | Whether to include the certificate chain in the certificate. | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the K8STLSSecr certificate store** + + ```shell + kfutil stores import generate-template --store-type-name K8STLSSecr --outpath K8STLSSecr.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 "K8STLSSecr" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `K8STLSSecr` certificates. Specifically, one with the `K8STLSSecr` capability. | + | Properties.ServerUsername | This should be no value or `kubeconfig` | + | Properties.ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + | Properties.KubeNamespace | The K8S namespace to use to manage the K8S secret object. | + | Properties.KubeSecretName | The name of the K8S secret object. | + | Properties.KubeSecretType | This defaults to and must be `tls_secret` | + | Properties.SeparateChain | Whether to store the certificate chain separately from the certificate. | + | Properties.IncludeCertChain | Whether to include the certificate chain in the certificate. | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name K8STLSSecr --file K8STLSSecr.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
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 | This should be no value or `kubeconfig` | + | ServerPassword | The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json | + +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. + +
+ + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + +
+ +## Discovering Certificate Stores with the Discovery Job +**NOTE:** To use discovery jobs, you must have the story type created in Keyfactor Command and the `needs_server` +checkbox *MUST* be checked, if you do not select `needs_server` you will not be able to provide credentials to the +discovery job and it will fail. The Kubernetes Orchestrator Extension supports certificate discovery jobs. This allows you to populate the certificate stores with existing certificates. To run a discovery job, follow these steps: 1. Click on the "Locations > Certificate Stores" menu item. 2. Click the "Discover" tab. 3. Click the "Schedule" button. 4. Configure the job based on storetype. **Note** the "Server Username" field must be set to `kubeconfig` and the "Server Password" field is the `kubeconfig` formatted JSON file containing the service account credentials. See the "Service Account Setup" section earlier in this README for more information on setting up a service account. - ![discover_schedule_start.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_schedule_start.png) - ![discover_schedule_config.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_schedule_config.png) - ![discover_server_username.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_server_username.png) - ![discover_server_password.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_server_password.png) + ![discover_schedule_start.png](./docs/screenshots/discovery/discover_schedule_start.png) + ![discover_schedule_config.png](./docs/screenshots/discovery/discover_schedule_config.png) + ![discover_server_username.png](./docs/screenshots/discovery/discover_server_username.png) + ![discover_server_password.png](./docs/screenshots/discovery/discover_server_password.png) 5. Click the "Save" button and wait for the Orchestrator to run the job. This may take some time depending on the number of certificates in the store and the Orchestrator's check-in schedule. -### K8SNS Discovery + + +
K8SJKS + + +### K8SJKS Discovery Job + +For discovery of `K8SJKS` stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all +namespaces. *This cannot be left blank.* +- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or JKS data. Will use +the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.jks`,`jks`. +
+ + +
K8SNS + + +### K8SNS Discovery Job + For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: -- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all namespaces. *This cannot be left blank.* - -### K8SPKCS12 and K8SJKS Discovery -For discovery of K8SPKCS12 and K8SJKS stores toy can use the following params to filter the certificates that will be discovered: -- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all namespaces. *This cannot be left blank.* -- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or JKS data. Will use the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.jks`,`jks`. - -## Certificate Inventory -In order for certificates to be inventoried by the Keyfactor k8s-orchestrator, they must have specific keys and values -in the Kubernetes Secret. The following table shows the required keys and values for each type of certificate store. - -| Store Type | Valid Secret Keys | -|------------|--------------------------------| -| K8STLSSecr | `tls.crt`,`tls.key`, `ca.crt` | -| K8SSecret | `tls.crt`,`tls.crts`, `ca.crt` | -| K8SCert | `cert`, `csr` | -| K8SPKCS12 | `*.pfx`,`*.pkcs12`, `*.p12` | -| K8SJKS | `*.jks` | -| K8SNS | `tls.crt`,`tls.crts`, `ca.crt` | -| K8SCluster | `tls.crt`,`tls.crts`, `ca.crt` | - -## Certificate Management -Management add/remove/create operations will attempt to write back to the Kubernetes Secret. -The following table shows the keys that the orchestrator will write back to the Kubernetes Secret for -each type of certificate store. - -| Store Type | Managed Secret Keys | -|------------|-----------------------------------------------------------| -| K8STLSSecr | `tls.crt`,`tls.key`, `ca.crt` | -| K8SSecret | `tls.crt`,`tls.key`, `ca.crt` | -| K8SPKCS12 | Specified in custom field `KubeSecretKey` or use defaults | -| K8SJKS | Specified in custom field `KubeSecretKey` or use defaults | -| K8SCluster | `tls.crt`,`tls.key` | -| K8SNS | `tls.crt`,`tls.key` | - -### K8STLSSecr & K8SSecret -These store types are virtually the same, they only differ in what K8S secret type they create. Both store types allow -for **ONLY** a single certificate to be stored in the secret. This means any `add` job will **overwrite** the existing -`tls.crt`, `tls.key`, and `ca.crt` values in the secret. If a secret does not exist, the orchestrator will create one -with the fields `tls.crt`, `tls.key`. Additionally, if `SeparateChain` on the store definition is set to -`true`, then the field `ca.crt` will be populated with the certificate chain data. - -**NOTE:** If a secret already exists and does not contain the field `ca.crt`, the orchestrator will **NOT** add the field -`ca.crt` to the secret, and instead will deploy a full certificate chain to the `tls.crt` field. - -#### Opaque & tls secret w/o ca.crt -Here's what an `Opaque` secret looks like in the UI when it does not contain the `ca.crt` field **NOTE** the chain is -included in the `tls.crt` field: -![opaque_no_cacrt_field.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_no_cacrt_field.png) - -#### Opaque & tls secret w/ ca.crt -Here's what an `Opaque` secret looks like in the UI when it does contain the `ca.crt` field: -![opaque_cacrt.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_cacrt.png) - -#### Opaque & tls secret w/o private key -It is possible to deploy a certificate without the private key from Command, and this is how it will look in the UI -**NOTE** the chain will only be included if Command has inventoried it: -![opaque_no_private_key.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_no_private_key.png) - -### K8SJKS & K8SPKCS12 - -The K8SJKS store type is a Java Key Store (JKS) that is stored in a Kubernetes Secret. The secret can contain multiple -JKS files. The orchestrator will attempt to manage the JKS files found in the secret that match the `allowed_keys` or -`CertificateDataFieldName` custom field values. - -Alias pattern: `/`. - -Example of secret containing 2 JKS stores: -![k8sjks_multi.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_multi.png) - -Here's what this looks like in the UI: -![k8sjks_inventory_ui.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_inventory_ui.png) - -## Development - -[See the development guide](Development.md) +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all +namespaces. *This cannot be left blank.* +
+ + +
K8SPKCS12 + + +### K8SPKCS12 Discovery Job + +For discovery of `K8SPKCS12` stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* +- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or PKCS12 data. Will use + the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.pkcs12`,`pkcs12`. +
+ + +
K8SSecret -## License -[Apache](https://apache.org/licenses/LICENSE-2.0) +### K8SSecret Discovery Job + +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* +
+ + +
K8STLSSecr + + +### K8STLSSecr Discovery Job + +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* +
+ + + + + +## License -When creating cert store type manually, that store property names and entry parameter names are case sensitive +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/TestConsole/Program.cs b/TestConsole/Program.cs index 90f794f..a3bfed6 100644 --- a/TestConsole/Program.cs +++ b/TestConsole/Program.cs @@ -17,7 +17,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using IdentityModel.Client; using Keyfactor.Extensions.Orchestrator.K8S.Jobs; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -25,678 +24,694 @@ using Moq; using Newtonsoft.Json; -namespace TestConsole +namespace TestConsole; + +public class OrchTestCase { - public class OrchTestCase - { - public string TestName { get; set; } + public string TestName { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public bool Fail { get; set; } + public bool Fail { get; set; } - public string ExpectedValue { get; set; } + public string ExpectedValue { get; set; } - public JobConfig JobConfig { get; set; } - } - - public class CertificateStoreDetails - { - public string ClientMachine { get; set; } + public JobConfig JobConfig { get; set; } +} - public string StorePath { get; set; } +public class CertificateStoreDetails +{ + public string ClientMachine { get; set; } - public string StorePassword { get; set; } + public string StorePath { get; set; } - public string Properties { get; set; } + public string StorePassword { get; set; } - public int Type { get; set; } - } + public string Properties { get; set; } - public class JobCertificate - { - public object Thumbprint { get; set; } + public int Type { get; set; } +} - public string Contents { get; set; } +public class JobCertificate +{ + public object Thumbprint { get; set; } - public string Alias { get; set; } + public string Contents { get; set; } - public string PrivateKeyPassword { get; set; } - } + public string Alias { get; set; } - public class JobConfig - { + public string PrivateKeyPassword { get; set; } +} - public List LastInventory { get; set; } +public class JobConfig +{ + public List LastInventory { get; set; } - public CertificateStoreDetails CertificateStoreDetails { get; set; } + public CertificateStoreDetails CertificateStoreDetails { get; set; } - public bool JobCancelled { get; set; } + public bool JobCancelled { get; set; } - public object ServerError { get; set; } + public object ServerError { get; set; } - public int JobHistoryId { get; set; } + public int JobHistoryId { get; set; } - public int RequestStatus { get; set; } + public int RequestStatus { get; set; } - public string ServerUsername { get; set; } + public string ServerUsername { get; set; } - public string ServerPassword { get; set; } + public string ServerPassword { get; set; } - public bool UseSSL { get; set; } + public bool UseSSL { get; set; } - public object JobProperties { get; set; } + public object JobProperties { get; set; } - public string JobTypeId { get; set; } + public string JobTypeId { get; set; } - public string JobId { get; set; } + public string JobId { get; set; } - public string Capability { get; set; } + public string Capability { get; set; } - public int OperationType { get; set; } + public int OperationType { get; set; } - public bool Overwrite { get; set; } + public bool Overwrite { get; set; } - public JobCertificate JobCertificate { get; set; } - } + public JobCertificate JobCertificate { get; set; } +} - public class JobProperties - { - [JsonProperty("Trusted Root")] public bool TrustedRoot { get; set; } - } +public class JobProperties +{ + [JsonProperty("Trusted Root")] public bool TrustedRoot { get; set; } +} - public class OrchestratorTestConfig - { - public List inventory { get; set; } +public class OrchestratorTestConfig +{ + public List inventory { get; set; } - public List add { get; set; } + public List add { get; set; } - public List remove { get; set; } + public List remove { get; set; } - public List discovery { get; set; } - } + public List discovery { get; set; } +} - class Program - { - private const string EnvironmentVariablePrefix = "TEST_"; - private const string KubeConfigEnvVar = "TEST_KUBECONFIG"; - private const string KubeNamespaceEnvVar = "TEST_KUBE_NAMESPACE"; +internal class Program +{ + private const string EnvironmentVariablePrefix = "TEST_"; + private const string KubeConfigEnvVar = "TEST_KUBECONFIG"; + private const string KubeNamespaceEnvVar = "TEST_KUBE_NAMESPACE"; - public static int tableWidth = 200; + public static int tableWidth = 200; - private static readonly TestEnvironmentalVariable[] _envVariables; + private static readonly TestEnvironmentalVariable[] _envVariables; - static Program() + static Program() + { + _envVariables = new[] { - _envVariables = new[] + new TestEnvironmentalVariable { - new TestEnvironmentalVariable - { - Name = "TEST_KUBECONFIG", - Description = "Kubeconfig file contents", - Default = "kubeconfig", - Type = "string", - Secret = true - }, - new TestEnvironmentalVariable - { - Name = "TEST_KUBE_NAMESPACE", - Description = "Kubernetes namespace", - Default = "default", - Type = "string" - }, - new TestEnvironmentalVariable - { - Name = "TEST_CERT_MGMT_TYPE", - Description = "Certificate management type", - Default = "inv", - Choices = new[] { "inv", "add", "remove" }, - Type = "string" - }, - new TestEnvironmentalVariable - { - Name = "TEST_MANUAL", - Description = "Manual test", - Default = "false", - Type = "bool" - }, - new TestEnvironmentalVariable - { - Name = "TEST_ORCH_OPERATION", - Description = "Orchestrator operation", - Default = "inv", - Type = "string", - Choices = new[] { "inv", "mgmt" } - } - }; - } + Name = "TEST_KUBECONFIG", + Description = "Kubeconfig file contents", + Default = "kubeconfig", + Type = "string", + Secret = true + }, + new TestEnvironmentalVariable + { + Name = "TEST_KUBE_NAMESPACE", + Description = "Kubernetes namespace", + Default = "default", + Type = "string" + }, + new TestEnvironmentalVariable + { + Name = "TEST_CERT_MGMT_TYPE", + Description = "Certificate management type", + Default = "inv", + Choices = new[] { "inv", "add", "remove" }, + Type = "string" + }, + new TestEnvironmentalVariable + { + Name = "TEST_MANUAL", + Description = "Manual test", + Default = "false", + Type = "bool" + }, + new TestEnvironmentalVariable + { + Name = "TEST_ORCH_OPERATION", + Description = "Orchestrator operation", + Default = "inv", + Type = "string", + Choices = new[] { "inv", "mgmt" } + } + }; + } - public static string ShowEnvConfig(string format = "json") + public static string ShowEnvConfig(string format = "json") + { + var envConfig = new Dictionary(); + var showSecrets = Environment.GetEnvironmentVariable("TEST_SHOW_SECRETS") == "true"; + foreach (var testVar in _envVariables) { - var envConfig = new Dictionary(); - var showSecrets = Environment.GetEnvironmentVariable("TEST_SHOW_SECRETS") == "true"; - foreach (var testVar in _envVariables) + if (testVar.Secret) { - if (testVar.Secret) + if (showSecrets) { - if (showSecrets) - { - envConfig.Add(testVar.Name, Environment.GetEnvironmentVariable(testVar.Name)); - continue; - } - envConfig.Add(testVar.Name, "********"); + envConfig.Add(testVar.Name, Environment.GetEnvironmentVariable(testVar.Name)); continue; } - envConfig.Add(testVar.Name, Environment.GetEnvironmentVariable(testVar.Name)); + + envConfig.Add(testVar.Name, "********"); + continue; } - return format == "json" ? JsonConvert.SerializeObject(envConfig, Formatting.Indented) : envConfig.ToString(); + + envConfig.Add(testVar.Name, Environment.GetEnvironmentVariable(testVar.Name)); } + return format == "json" ? JsonConvert.SerializeObject(envConfig, Formatting.Indented) : envConfig.ToString(); + } - public static OrchTestCase[] GetTestConfig(string testFileName, string jobType = "inventory") - { - // Read test config from file as JSON and deserialize to TestConfiguration - var testConfig = JsonConvert.DeserializeObject(File.ReadAllText(testFileName)); - //convert testList to array of objects - switch (jobType) - { - case "inventory": - case "inv": - case "i": - return testConfig.inventory.ToArray(); - case "add": - case "a": - return testConfig.add.ToArray(); - case "remove": - case "rem": - case "r": - return testConfig.remove.ToArray(); - case "discovery": - case "discover": - case "disc": - case "d": - return testConfig.discovery.ToArray(); + public static OrchTestCase[] GetTestConfig(string testFileName, string jobType = "inventory") + { + // Read test config from file as JSON and deserialize to TestConfiguration + var testConfig = JsonConvert.DeserializeObject(File.ReadAllText(testFileName)); - } - throw new Exception("Invalid job type"); - } - private async static Task Main(string[] args) + //convert testList to array of objects + switch (jobType) { - var runTypeStr = Environment.GetEnvironmentVariable("TEST_MANUAL"); - var isManualTest = !string.IsNullOrEmpty(runTypeStr) && bool.Parse(runTypeStr); - var hasFailure = false; + case "inventory": + case "inv": + case "i": + return testConfig.inventory.ToArray(); + case "add": + case "a": + return testConfig.add.ToArray(); + case "remove": + case "rem": + case "r": + return testConfig.remove.ToArray(); + case "discovery": + case "discover": + case "disc": + case "d": + return testConfig.discovery.ToArray(); + } + + throw new Exception("Invalid job type"); + } + + private static async Task Main(string[] args) + { + var runTypeStr = Environment.GetEnvironmentVariable("TEST_MANUAL"); + var isManualTest = !string.IsNullOrEmpty(runTypeStr) && bool.Parse(runTypeStr); + var hasFailure = false; - var testOutputDict = new Dictionary(); + var testOutputDict = new Dictionary(); - Console.WriteLine("====KubeTestConsole===="); - Console.WriteLine("Environment Variables:"); - Console.WriteLine(ShowEnvConfig()); - Console.WriteLine("====End Environmental Variables===="); + Console.WriteLine("====KubeTestConsole===="); + Console.WriteLine("Environment Variables:"); + Console.WriteLine(ShowEnvConfig()); + Console.WriteLine("====End Environmental Variables===="); - var pamUserNameField = Environment.GetEnvironmentVariable("TEST_PAM_USERNAME_FIELD") ?? "ServerUsername"; - var pamPasswordField = Environment.GetEnvironmentVariable("TEST_PAM_PASSWORD_FIELD") ?? "ServerPassword"; + var pamUserNameField = Environment.GetEnvironmentVariable("TEST_PAM_USERNAME_FIELD") ?? "ServerUsername"; + var pamPasswordField = Environment.GetEnvironmentVariable("TEST_PAM_PASSWORD_FIELD") ?? "ServerPassword"; - if (args.Length == 0) + if (args.Length == 0) + { + // check TEST_OPERATION env var and use that if it else prompt user + var testOperation = Environment.GetEnvironmentVariable("TEST_ORCH_OPERATION"); + var input = testOperation; + if (string.IsNullOrEmpty(testOperation) || isManualTest) { - // check TEST_OPERATION env var and use that if it else prompt user - var testOperation = Environment.GetEnvironmentVariable("TEST_ORCH_OPERATION"); - var input = testOperation; - if (string.IsNullOrEmpty(testOperation) || isManualTest) - { - Console.WriteLine("Enter Operation: (I)nventory, or (M)anagement"); - input = Console.ReadLine(); - } + Console.WriteLine("Enter Operation: (I)nventory, or (M)anagement"); + input = Console.ReadLine(); + } - var testConfigPath = Environment.GetEnvironmentVariable("TEST_CONFIG_PATH") ?? "tests.json"; + var testConfigPath = Environment.GetEnvironmentVariable("TEST_CONFIG_PATH") ?? "tests.json"; - var pamMockUsername = Environment.GetEnvironmentVariable("TEST_PAM_MOCK_USERNAME") ?? string.Empty; - var pamMockPassword = Environment.GetEnvironmentVariable("TEST_PAM_MOCK_PASSWORD") ?? string.Empty; + var pamMockUsername = Environment.GetEnvironmentVariable("TEST_PAM_MOCK_USERNAME") ?? string.Empty; + var pamMockPassword = Environment.GetEnvironmentVariable("TEST_PAM_MOCK_PASSWORD") ?? string.Empty; - Console.WriteLine("TEST_PAM_USERNAME_FIELD: " + pamUserNameField); - Console.WriteLine("TEST_PAM_MOCK_USERNAME: " + pamMockUsername); + Console.WriteLine("TEST_PAM_USERNAME_FIELD: " + pamUserNameField); + Console.WriteLine("TEST_PAM_MOCK_USERNAME: " + pamMockUsername); - Console.WriteLine("TEST_PAM_PASSWORD_FIELD: " + pamPasswordField); - Console.WriteLine("TEST_PAM_MOCK_PASSWORD: " + pamMockPassword); + Console.WriteLine("TEST_PAM_PASSWORD_FIELD: " + pamPasswordField); + Console.WriteLine("TEST_PAM_MOCK_PASSWORD: " + pamMockPassword); - var secretResolver = new Mock(); - // Get from env var TEST_KUBECONFIG - // setup resolver for "Server Username" to return "kubeconfig" - secretResolver.Setup(m => - m.Resolve(It.Is(s => s == pamUserNameField))).Returns(() => pamMockUsername); - // setup resolver for "Server Password" to return the value of the env var TEST_KUBECONFIG - secretResolver.Setup(m => - m.Resolve(It.Is(s => s == pamPasswordField))).Returns(() => pamMockPassword); + var secretResolver = new Mock(); + // Get from env var TEST_KUBECONFIG + // setup resolver for "Server Username" to return "kubeconfig" + secretResolver.Setup(m => + m.Resolve(It.Is(s => s == pamUserNameField))).Returns(() => pamMockUsername); + // setup resolver for "Server Password" to return the value of the env var TEST_KUBECONFIG + secretResolver.Setup(m => + m.Resolve(It.Is(s => s == pamPasswordField))).Returns(() => pamMockPassword); - var tests = new OrchTestCase[] { }; + var tests = new OrchTestCase[] { }; - input = input.ToLower(); - switch (input) - { - case "inventory": - case "inv": - case "i": - // Get test configurations from testConfigPath + input = input.ToLower(); + switch (input) + { + case "inventory": + case "inv": + case "i": + // Get test configurations from testConfigPath - tests = GetTestConfig(testConfigPath, input); - var inv = new Inventory(secretResolver.Object); + tests = GetTestConfig(testConfigPath, input); + var inv = new Inventory(secretResolver.Object); - Console.WriteLine("Running Inventory Job Test Cases"); - foreach (var testCase in tests) + Console.WriteLine("Running Inventory Job Test Cases"); + foreach (var testCase in tests) + { + testOutputDict.Add(testCase.TestName, "Running"); + try { - testOutputDict.Add(testCase.TestName, "Running"); - try - { - //convert testCase to InventoryJobConfig - Console.WriteLine($"=============={testCase.TestName}=================="); - Console.WriteLine($"Description: {testCase.Description}"); - Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); - Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); + //convert testCase to InventoryJobConfig + Console.WriteLine($"=============={testCase.TestName}=================="); + Console.WriteLine($"Description: {testCase.Description}"); + Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); + Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); - var invJobConfig = GetInventoryJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig)); - SubmitInventoryUpdate sui = GetItems; + var invJobConfig = + GetInventoryJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig)); + SubmitInventoryUpdate sui = GetItems; - var jobResult = inv.ProcessJob(invJobConfig, sui); + var jobResult = inv.ProcessJob(invJobConfig, sui); - if (jobResult.Result == OrchestratorJobStatusJobResult.Success || - (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) - { - testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Green; - } - else - { - testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Red; - hasFailure = true; - } - Console.WriteLine( - $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{invJobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{invJobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); - Console.ResetColor(); + if (jobResult.Result == OrchestratorJobStatusJobResult.Success || + (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) + { + testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; + Console.ForegroundColor = ConsoleColor.Green; } - catch (Exception e) + else { - testOutputDict[testCase.TestName] = $"Failure - {e.Message}"; + testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e); - Console.WriteLine($"Failed to run inventory test case: {testCase.TestName}"); - Console.ResetColor(); + hasFailure = true; } - + Console.WriteLine( + $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{invJobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{invJobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); + Console.ResetColor(); } - Console.WriteLine("Finished Running Inventory Job Test Cases"); - break; - case "management": - case "man": - case "m": - // Get from env var TEST_CERT_MGMT_TYPE or prompt for it if not set - var testMgmtType = Environment.GetEnvironmentVariable("TEST_CERT_MGMT_TYPE"); - - if (string.IsNullOrEmpty(testMgmtType) || isManualTest) + catch (Exception e) { - Console.WriteLine("Select Management Type Add or Remove"); - testMgmtType = Console.ReadLine(); + testOutputDict[testCase.TestName] = $"Failure - {e.Message}"; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e); + Console.WriteLine($"Failed to run inventory test case: {testCase.TestName}"); + Console.ResetColor(); } + } + + Console.WriteLine("Finished Running Inventory Job Test Cases"); + break; + case "management": + case "man": + case "m": + // Get from env var TEST_CERT_MGMT_TYPE or prompt for it if not set + var testMgmtType = Environment.GetEnvironmentVariable("TEST_CERT_MGMT_TYPE"); - tests = GetTestConfig(testConfigPath, testMgmtType); + if (string.IsNullOrEmpty(testMgmtType) || isManualTest) + { + Console.WriteLine("Select Management Type Add or Remove"); + testMgmtType = Console.ReadLine(); + } - Console.WriteLine("Running Management Job Test Cases"); - foreach (var testCase in tests) + tests = GetTestConfig(testConfigPath, testMgmtType); + + Console.WriteLine("Running Management Job Test Cases"); + foreach (var testCase in tests) + { + testOutputDict.Add(testCase.TestName, "Running"); + try { - testOutputDict.Add(testCase.TestName, "Running"); - try - { - //convert testCase to InventoryJobConfig - Console.WriteLine($"=============={testCase.TestName}=================="); - Console.WriteLine($"Description: {testCase.Description}"); - Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); - Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); - // var jobConfig = GetManagementJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig), testCase.JobConfig.JobCertificate.Alias); + //convert testCase to InventoryJobConfig + Console.WriteLine($"=============={testCase.TestName}=================="); + Console.WriteLine($"Description: {testCase.Description}"); + Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); + Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); + // var jobConfig = GetManagementJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig), testCase.JobConfig.JobCertificate.Alias); - //====================================================================================================== + //====================================================================================================== - var jobResult = new JobResult(); - switch (testMgmtType) + var jobResult = new JobResult(); + switch (testMgmtType) + { + case "Add": + case "add": + case "a": { - case "Add": - case "add": - case "a": + // Get from env var TEST_PKEY_PASSWORD or prompt for it if not set + var testPrivateKeyPwd = Environment.GetEnvironmentVariable("TEST_PKEY_PASSWORD") ?? + testCase.JobConfig.JobCertificate.PrivateKeyPassword; + var privateKeyPwd = testPrivateKeyPwd; + if (string.IsNullOrEmpty(testPrivateKeyPwd) && + isManualTest) //Only prompt on explicit set of TEST_USE_PKEY_PASS and that password has not been provided + { + Console.WriteLine( + "Enter private key password or leave blank if no private key"); + privateKeyPwd = Console.ReadLine(); + } + else { - // Get from env var TEST_PKEY_PASSWORD or prompt for it if not set - var testPrivateKeyPwd = Environment.GetEnvironmentVariable("TEST_PKEY_PASSWORD") ?? - testCase.JobConfig.JobCertificate.PrivateKeyPassword; - var privateKeyPwd = testPrivateKeyPwd; - if (string.IsNullOrEmpty(testPrivateKeyPwd) && - isManualTest) //Only prompt on explicit set of TEST_USE_PKEY_PASS and that password has not been provided - { - Console.WriteLine("Enter private key password or leave blank if no private key"); - privateKeyPwd = Console.ReadLine(); - } - else - { - Console.WriteLine("Using Private Key Password from env var 'TEST_PKEY_PASSWORD'"); - Console.WriteLine("Password: " + testPrivateKeyPwd); - } - - var isOverwriteStr = Environment.GetEnvironmentVariable("TEST_JOB_OVERWRITE") ?? "true"; - var isOverwrite = !string.IsNullOrEmpty(isOverwriteStr) && bool.Parse(isOverwriteStr); - if (string.IsNullOrEmpty(isOverwriteStr) && isManualTest) - { - Console.WriteLine("Overwrite? Enter true or false"); - isOverwriteStr = Console.ReadLine(); - isOverwrite = bool.Parse(isOverwriteStr); - } - - var certAlias = Environment.GetEnvironmentVariable("TEST_CERT_ALIAS") ?? testCase.JobConfig.JobCertificate.Alias; - if (string.IsNullOrEmpty(certAlias) && isManualTest) - { - Console.WriteLine("Enter cert alias. This is usually the cert thumbprint."); - certAlias = Console.ReadLine(); - } - - var isTrustedRootStr = Environment.GetEnvironmentVariable("TEST_IS_TRUSTED_ROOT") ?? "false"; - var isTrustedRoot = !string.IsNullOrEmpty(isTrustedRootStr) && bool.Parse(isTrustedRootStr); - if (string.IsNullOrEmpty(isTrustedRootStr) && isManualTest) - { - Console.WriteLine("Trusted Root? Enter true or false"); - isTrustedRootStr = Console.ReadLine(); - isTrustedRoot = bool.Parse(isTrustedRootStr); - } - - var mgmt = new Management(secretResolver.Object); - - var jobConfig = GetJobManagementConfiguration( - JsonConvert.SerializeObject(testCase.JobConfig), - certAlias, - privateKeyPwd, - isOverwrite, - isTrustedRoot - ); - - jobResult = mgmt.ProcessJob(jobConfig); - if (testCase.Fail && jobResult.Result == OrchestratorJobStatusJobResult.Success) - { - testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage} This test case was expected to fail but succeeded."; - Console.ForegroundColor = ConsoleColor.Red; - hasFailure = true; - } - else if (!testCase.Fail && jobResult.Result == OrchestratorJobStatusJobResult.Failure) - { - testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage} This test case was expected to succeed but failed."; - Console.ForegroundColor = ConsoleColor.Red; - hasFailure = true; - } - else - { - testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Green; - } Console.WriteLine( - $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{jobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{jobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); + "Using Private Key Password from env var 'TEST_PKEY_PASSWORD'"); + Console.WriteLine("Password: " + testPrivateKeyPwd); + } + + var isOverwriteStr = Environment.GetEnvironmentVariable("TEST_JOB_OVERWRITE") ?? + "true"; + var isOverwrite = !string.IsNullOrEmpty(isOverwriteStr) && + bool.Parse(isOverwriteStr); + if (string.IsNullOrEmpty(isOverwriteStr) && isManualTest) + { + Console.WriteLine("Overwrite? Enter true or false"); + isOverwriteStr = Console.ReadLine(); + isOverwrite = bool.Parse(isOverwriteStr); + } + + var certAlias = Environment.GetEnvironmentVariable("TEST_CERT_ALIAS") ?? + testCase.JobConfig.JobCertificate.Alias; + if (string.IsNullOrEmpty(certAlias) && isManualTest) + { + Console.WriteLine("Enter cert alias. This is usually the cert thumbprint."); + certAlias = Console.ReadLine(); + } + + var isTrustedRootStr = Environment.GetEnvironmentVariable("TEST_IS_TRUSTED_ROOT") ?? + "false"; + var isTrustedRoot = !string.IsNullOrEmpty(isTrustedRootStr) && + bool.Parse(isTrustedRootStr); + if (string.IsNullOrEmpty(isTrustedRootStr) && isManualTest) + { + Console.WriteLine("Trusted Root? Enter true or false"); + isTrustedRootStr = Console.ReadLine(); + isTrustedRoot = bool.Parse(isTrustedRootStr); + } + + var mgmt = new Management(secretResolver.Object); + + var jobConfig = GetJobManagementConfiguration( + JsonConvert.SerializeObject(testCase.JobConfig), + certAlias, + privateKeyPwd, + isOverwrite, + isTrustedRoot + ); + + jobResult = mgmt.ProcessJob(jobConfig); + if (testCase.Fail && jobResult.Result == OrchestratorJobStatusJobResult.Success) + { + testOutputDict[testCase.TestName] = + $"Failure - {jobResult.FailureMessage} This test case was expected to fail but succeeded."; + Console.ForegroundColor = ConsoleColor.Red; + hasFailure = true; + } + else if (!testCase.Fail && + jobResult.Result == OrchestratorJobStatusJobResult.Failure) + { + testOutputDict[testCase.TestName] = + $"Failure - {jobResult.FailureMessage} This test case was expected to succeed but failed."; + Console.ForegroundColor = ConsoleColor.Red; + hasFailure = true; + } + else + { + testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; + Console.ForegroundColor = ConsoleColor.Green; + } + + Console.WriteLine( + $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{jobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{jobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); + + Console.ResetColor(); + break; + } + case "Remove": + case "remove": + case "rem": + case "r": + { + // Get alias from env TEST_CERT_REMOVE_ALIAS or prompt for it if not set + var alias = Environment.GetEnvironmentVariable("TEST_CERT_ALIAS") ?? + testCase.JobConfig.JobCertificate.Thumbprint?.ToString() ?? + testCase.JobConfig.JobCertificate.Alias; + if (string.IsNullOrEmpty(alias) && isManualTest) + { + Console.WriteLine("Alias Enter Alias Name"); + alias = Console.ReadLine(); + } + + var mgmt = new Management(secretResolver.Object); + + var jobConfig = + GetJobManagementConfiguration(JsonConvert.SerializeObject(testCase.JobConfig), + alias); - Console.ResetColor(); - break; + jobResult = mgmt.ProcessJob(jobConfig); + if (jobResult.Result == OrchestratorJobStatusJobResult.Success || + (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) + { + testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; + Console.ForegroundColor = ConsoleColor.Green; } - case "Remove": - case "remove": - case "rem": - case "r": + else { - // Get alias from env TEST_CERT_REMOVE_ALIAS or prompt for it if not set - var alias = Environment.GetEnvironmentVariable("TEST_CERT_ALIAS") ?? - testCase.JobConfig.JobCertificate.Thumbprint?.ToString() ?? testCase.JobConfig.JobCertificate.Alias; - if (string.IsNullOrEmpty(alias) && isManualTest) - { - Console.WriteLine("Alias Enter Alias Name"); - alias = Console.ReadLine(); - } - - var mgmt = new Management(secretResolver.Object); - - var jobConfig = GetJobManagementConfiguration(JsonConvert.SerializeObject(testCase.JobConfig), alias); - - jobResult = mgmt.ProcessJob(jobConfig); - if (jobResult.Result == OrchestratorJobStatusJobResult.Success || - (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) - { - testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Green; - } - else - { - testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Red; - hasFailure = true; - } - Console.ResetColor(); - break; + testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; + Console.ForegroundColor = ConsoleColor.Red; + hasFailure = true; } - default: - testOutputDict[testCase.TestName] = $"Invalid Management Type {testMgmtType}. Valid types are 'Add' or 'Remove'."; - // Console.WriteLine($"Invalid Management Type {testMgmtType}. Valid types are 'Add' or 'Remove'."); - break; + + Console.ResetColor(); + break; } + default: + testOutputDict[testCase.TestName] = + $"Invalid Management Type {testMgmtType}. Valid types are 'Add' or 'Remove'."; + // Console.WriteLine($"Invalid Management Type {testMgmtType}. Valid types are 'Add' or 'Remove'."); + break; } - catch (Exception e) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e); - Console.WriteLine($"Failed to run inventory test case: {testCase.JobConfig.JobId}({testCase.JobConfig.CertificateStoreDetails.StorePath})"); - Console.ResetColor(); - } } - Console.WriteLine("Finished Running Management Job Test Cases"); - break; - case "discovery": - case "discover": - case "disc": - case "d": - tests = GetTestConfig(testConfigPath, input); - var discovery = new Discovery(secretResolver.Object); - - Console.WriteLine("Running Discovery Job Test Cases"); - foreach (var testCase in tests) + catch (Exception e) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e); + Console.WriteLine( + $"Failed to run inventory test case: {testCase.JobConfig.JobId}({testCase.JobConfig.CertificateStoreDetails.StorePath})"); + Console.ResetColor(); + } + } + + Console.WriteLine("Finished Running Management Job Test Cases"); + break; + case "discovery": + case "discover": + case "disc": + case "d": + tests = GetTestConfig(testConfigPath, input); + var discovery = new Discovery(secretResolver.Object); + + Console.WriteLine("Running Discovery Job Test Cases"); + foreach (var testCase in tests) + { + testOutputDict.Add(testCase.TestName, "Running"); + try { - testOutputDict.Add(testCase.TestName, "Running"); - try + //convert testCase to DiscoveryJobConfig + Console.WriteLine($"=============={testCase.TestName}=================="); + Console.WriteLine($"Description: {testCase.Description}"); + Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); + Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); + + + var discoveryJobConfiguration = + GetDiscoveryJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig)); + // create array of strings for discovery paths + var discPaths = new List(); + // foreach (var path in invJobConfig.DiscoveryPaths) + // { + // dicoveryPaths.Add(path.Path); + // } + discPaths.Add("tls"); + SubmitDiscoveryUpdate dui = DiscoverItems; + var jobResult = discovery.ProcessJob(discoveryJobConfiguration, dui); + + if (jobResult.Result == OrchestratorJobStatusJobResult.Success || + (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) { - //convert testCase to DiscoveryJobConfig - Console.WriteLine($"=============={testCase.TestName}=================="); - Console.WriteLine($"Description: {testCase.Description}"); - Console.WriteLine($"Expected Fail: {testCase.Fail.ToString()}"); - Console.WriteLine($"Expected Result: {testCase.ExpectedValue}"); - - - var discoveryJobConfiguration = GetDiscoveryJobConfiguration(JsonConvert.SerializeObject(testCase.JobConfig)); - // create array of strings for discovery paths - var discPaths = new List(); - // foreach (var path in invJobConfig.DiscoveryPaths) - // { - // dicoveryPaths.Add(path.Path); - // } - discPaths.Add("tls"); - SubmitDiscoveryUpdate dui = DiscoverItems; - var jobResult = discovery.ProcessJob(discoveryJobConfiguration, dui); - - if (jobResult.Result == OrchestratorJobStatusJobResult.Success || - (jobResult.Result == OrchestratorJobStatusJobResult.Failure && testCase.Fail)) - { - testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Green; - } - else - { - testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; - Console.ForegroundColor = ConsoleColor.Red; - hasFailure = true; - } - // Console.WriteLine( - // $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{invJobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{invJobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); - Console.ResetColor(); + testOutputDict[testCase.TestName] = $"Success {jobResult.FailureMessage}"; + Console.ForegroundColor = ConsoleColor.Green; } - catch (Exception e) + else { - testOutputDict[testCase.TestName] = $"Failure - {e.Message}"; + testOutputDict[testCase.TestName] = $"Failure - {jobResult.FailureMessage}"; Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e); - Console.WriteLine($"Failed to run inventory test case: {testCase.TestName}"); - Console.ResetColor(); + hasFailure = true; } - + // Console.WriteLine( + // $"Job Hist ID:{jobResult.JobHistoryId}\nStorePath:{invJobConfig.CertificateStoreDetails.StorePath}\nStore Properties:\n{invJobConfig.CertificateStoreDetails.Properties}\nMessage: {jobResult.FailureMessage}\nResult: {jobResult.Result}"); + Console.ResetColor(); + } + catch (Exception e) + { + testOutputDict[testCase.TestName] = $"Failure - {e.Message}"; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e); + Console.WriteLine($"Failed to run inventory test case: {testCase.TestName}"); + Console.ResetColor(); } - Console.WriteLine("Finished Running Inventory Job Test Cases"); - break; - } - if (input == "SerializeTest") - { - - var xml = - " cannot be deleted because of references from: certificate-profile -> Keyfactor -> CA -> Boingy"; - // using System.Xml.Serialization; - // var serializer = new XmlSerializer(typeof(ErrorSuccessResponse)); - // using var reader = new StringReader(xml); - // var test = (ErrorSuccessResponse)serializer.Deserialize(reader); - // Console.Write(test); - } - else - { - // output test results as a table to the console - - //write output to csv file - var csv = new StringBuilder(); - csv.AppendLine("Test Name,Result"); - PrintLine(); - PrintRow("Test Name", "Result"); - PrintLine(); - foreach (var res in testOutputDict) - { - PrintRow(res.Key, res.Value); - csv.AppendLine($"{res.Key},{res.Value}"); - } - PrintLine(); - var resultFilePath = Environment.GetEnvironmentVariable("TEST_OUTPUT_FILE_PATH") ?? "testResults.csv"; - try - { - File.WriteAllText(resultFilePath, csv.ToString()); - } - catch (Exception e) - { - var currentColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Unable to write test results to file {resultFilePath}. Please check the file path and try again."); - Console.WriteLine(e.Message); - Console.ForegroundColor = currentColor; } + Console.WriteLine("Finished Running Inventory Job Test Cases"); + break; + } + + if (input == "SerializeTest") + { + var xml = + " cannot be deleted because of references from: certificate-profile -> Keyfactor -> CA -> Boingy"; + // using System.Xml.Serialization; + // var serializer = new XmlSerializer(typeof(ErrorSuccessResponse)); + // using var reader = new StringReader(xml); + // var test = (ErrorSuccessResponse)serializer.Deserialize(reader); + // Console.Write(test); + } + else + { + // output test results as a table to the console + + //write output to csv file + var csv = new StringBuilder(); + csv.AppendLine("Test Name,Result"); + PrintLine(); + PrintRow("Test Name", "Result"); + PrintLine(); + foreach (var res in testOutputDict) + { + PrintRow(res.Key, res.Value); + csv.AppendLine($"{res.Key},{res.Value}"); } - if (hasFailure) + + PrintLine(); + var resultFilePath = Environment.GetEnvironmentVariable("TEST_OUTPUT_FILE_PATH") ?? "testResults.csv"; + try { - // Send a failure exit code - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Some tests failed please check the output above."); - Environment.Exit(1); + File.WriteAllText(resultFilePath, csv.ToString()); } - else + catch (Exception e) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("All tests passed."); + var currentColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine( + $"Unable to write test results to file {resultFilePath}. Please check the file path and try again."); + Console.WriteLine(e.Message); + Console.ForegroundColor = currentColor; } } + + if (hasFailure) + { + // Send a failure exit code + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Some tests failed please check the output above."); + Environment.Exit(1); + } + else + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("All tests passed."); + } } + } - private static void PrintLine() - { - Console.WriteLine(new string('-', tableWidth)); - } + private static void PrintLine() + { + Console.WriteLine(new string('-', tableWidth)); + } - private static void PrintRow(params string[] columns) - { - var width = (tableWidth - columns.Length) / columns.Length; - var row = "|"; + private static void PrintRow(params string[] columns) + { + var width = (tableWidth - columns.Length) / columns.Length; + var row = "|"; - foreach (var column in columns) - { - row += AlignLeft(column, width) + "|"; - } + foreach (var column in columns) row += AlignLeft(column, width) + "|"; - Console.WriteLine(row); - } + Console.WriteLine(row); + } - private static string AlignCentre(string text, int width) - { - text = text.Length > width ? text.Substring(0, width - 3) + "..." : text; + private static string AlignCentre(string text, int width) + { + text = text.Length > width ? text.Substring(0, width - 3) + "..." : text; - if (string.IsNullOrEmpty(text)) - { - return new string(' ', width); - } - return text.PadRight(width - (width - text.Length) / 2).PadLeft(width); - } + if (string.IsNullOrEmpty(text)) return new string(' ', width); + return text.PadRight(width - (width - text.Length) / 2).PadLeft(width); + } - private static string AlignLeft(string text, int width) - { - text = text.Length > width ? text.Substring(0, width - 3) + "..." : text; + private static string AlignLeft(string text, int width) + { + text = text.Length > width ? text.Substring(0, width - 3) + "..." : text; - return text.PadRight(width); - } + return text.PadRight(width); + } - public static bool GetItems(IEnumerable items) - { - return true; - } + public static bool GetItems(IEnumerable items) + { + return true; + } - public static bool DiscoverItems(IEnumerable items) - { - return true; - } + public static bool DiscoverItems(IEnumerable items) + { + return true; + } - public static ManagementJobConfiguration GetJobManagementConfiguration(string jobConfigString, string alias, string privateKeyPwd = "", bool overWrite = true, - bool trustedRoot = false) - { - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } + public static ManagementJobConfiguration GetJobManagementConfiguration(string jobConfigString, string alias, + string privateKeyPwd = "", bool overWrite = true, + bool trustedRoot = false) + { + var result = JsonConvert.DeserializeObject(jobConfigString); + return result; + } - public static InventoryJobConfiguration GetInventoryJobConfiguration(string jobConfigString) - { - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } + public static InventoryJobConfiguration GetInventoryJobConfiguration(string jobConfigString) + { + var result = JsonConvert.DeserializeObject(jobConfigString); + return result; + } - public static DiscoveryJobConfiguration GetDiscoveryJobConfiguration(string jobConfigString) - { - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } + public static DiscoveryJobConfiguration GetDiscoveryJobConfiguration(string jobConfigString) + { + var result = JsonConvert.DeserializeObject(jobConfigString); + return result; + } - public static ManagementJobConfiguration GetManagementJobConfiguration(string jobConfigString, string alias = null) - { - if (alias != null) - { - jobConfigString = jobConfigString.Replace("{{alias}}", alias); - } - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } + public static ManagementJobConfiguration GetManagementJobConfiguration(string jobConfigString, string alias = null) + { + if (alias != null) jobConfigString = jobConfigString.Replace("{{alias}}", alias); + var result = JsonConvert.DeserializeObject(jobConfigString); + return result; + } - public struct TestEnvironmentalVariable - { - public string Name { get; set; } + public struct TestEnvironmentalVariable + { + public string Name { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public string Default { get; set; } + public string Default { get; set; } - public string Type { get; set; } + public string Type { get; set; } - public string[] Choices { get; set; } + public string[] Choices { get; set; } - public bool Secret { get; set; } - } + public bool Secret { get; set; } } -} +} \ No newline at end of file diff --git a/TestConsole/TestConsole.csproj b/TestConsole/TestConsole.csproj index c594706..52616d3 100644 --- a/TestConsole/TestConsole.csproj +++ b/TestConsole/TestConsole.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/docs_old/README.md b/docs_old/README.md new file mode 100644 index 0000000..dfff94a --- /dev/null +++ b/docs_old/README.md @@ -0,0 +1,928 @@ + +# Kubernetes Orchestrator Extension + +The Kubernetes Orchestrator allows for the remote management of certificate stores defined in a Kubernetes cluster. The following types of Kubernetes resources are supported: kubernetes secrets of `kubernetes.io/tls` or `Opaque` and kubernetes certificates `certificates.k8s.io/v1` + +#### 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 Kubernetes Orchestrator Extension + +Kubernetes Orchestrator Extension 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 + +The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.x +## 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 | Must be set to `kubeconfig` if used. If you do not set it to `kubeconfig` the `ServerPassword` will be ignored. | +| ServerPassword | Must be set if `ServerUsername` is provided. The service account credentials for the Universal Orchestrator to use. Must be in `kubeconfig` format. For more information review [Kubernetes service account](https://github.com/Keyfactor/kubernetes-orchestrator/blob/main/scripts/kubernetes/README.md) docs and scripts. | + + +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. +

+
+ + + + +--- + + +## Table of Contents +- [Keyfactor Version Supported](#keyfactor-version-supported) +- [Platform Specific Notes](#platform-specific-notes) +- [PAM Integration](#pam-integration) +- [Overview](#overview) + * [K8SCert](#k8scert) + * [K8SSecret](#k8ssecret) + * [K8STLSSecret](#k8stlssecret) + * [K8SJKS](#k8sjks) +- [Versioning](#versioning) +- [Security Considerations](#security-considerations) + * [Service Account Setup](#service-account-setup) +- [Kubernetes Orchestrator Extension Installation](#kubernetes-orchestrator-extension-installation) +- [Certificate Store Types](#certificate-store-types) + * [Configuration Information](#configuration-information) + + [Note about StorePath](#note-about-storepath) + + [Common Values](#common-values) + - [UI Basic Tab](#ui-basic-tab) + - [UI Advanced Tab](#ui-advanced-tab) + - [Custom Fields Tab](#custom-fields-tab) + - [Kube Secret Types](#kube-secret-types) + - [Entry Parameters Tab:](#entry-parameters-tab-) + * [K8SSecret Store Type](#k8ssecret-store-type) + + [kfutil Create K8SSecret Store Type](#kfutil-create-k8ssecret-store-type) + + [UI Configuration](#ui-configuration) + - [UI Basic Tab](#ui-basic-tab-1) + - [UI Advanced Tab](#ui-advanced-tab-1) + - [UI Custom Fields Tab](#ui-custom-fields-tab) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab-) + * [K8STLSSecr Store Type](#k8stlssecr-store-type) + + [kfutil Create K8STLSSecr Store Type](#kfutil-create-k8stlssecr-store-type) + + [UI Configuration](#ui-configuration-1) + - [UI Basic Tab](#ui-basic-tab-2) + - [UI Advanced Tab](#ui-advanced-tab-2) + - [UI Custom Fields Tab](#ui-custom-fields-tab-1) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--1) + * [K8SPKCS12 Store Type](#k8spkcs12-store-type) + + [kfutil Create K8SPKCS12 Store Type](#kfutil-create-k8spkcs12-store-type) + + [UI Configuration](#ui-configuration-2) + - [UI Basic Tab](#ui-basic-tab-3) + - [UI Advanced Tab](#ui-advanced-tab-3) + - [UI Custom Fields Tab](#ui-custom-fields-tab-2) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--2) + * [K8SJKS Store Type](#k8sjks-store-type) + + [Storepath Patterns](#storepath-patterns) + + [Alias Patterns](#alias-patterns) + + [kfutil Create K8SJKS Store Type](#kfutil-create-k8sjks-store-type) + + [UI Configuration](#ui-configuration-3) + - [UI Basic Tab](#ui-basic-tab-4) + - [UI Advanced Tab](#ui-advanced-tab-4) + - [UI Custom Fields Tab](#ui-custom-fields-tab-3) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--3) + * [K8SCluster Store Type](#k8scluster-store-type) + + [Storepath Patterns](#storepath-patterns-1) + + [Alias Patterns](#alias-patterns-1) + + [kfutil Create K8SCluster Store Type](#kfutil-create-k8scluster-store-type) + + [UI Configuration](#ui-configuration-4) + - [UI Basic Tab](#ui-basic-tab-5) + - [UI Advanced Tab](#ui-advanced-tab-5) + - [UI Custom Fields Tab](#ui-custom-fields-tab-4) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--4) + * [K8SNS Store Type](#k8sns-store-type) + + [Storepath Patterns](#storepath-patterns-2) + + [Alias Patterns](#alias-patterns-2) + + [kfutil Create K8SNS Store Type](#kfutil-create-k8sns-store-type) + + [UI Configuration](#ui-configuration-5) + - [UI Basic Tab](#ui-basic-tab-6) + - [UI Advanced Tab](#ui-advanced-tab-6) + - [UI Custom Fields Tab](#ui-custom-fields-tab-5) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--5) + * [K8SCert Store Type](#k8scert-store-type) + + [UI Configuration](#ui-configuration-6) + - [UI Basic Tab](#ui-basic-tab-7) + - [UI Advanced Tab](#ui-advanced-tab-7) + - [UI Custom Fields Tab](#ui-custom-fields-tab-6) + - [UI Entry Parameters Tab:](#ui-entry-parameters-tab--6) +- [Creating Certificate Stores and Scheduling Discovery Jobs](#creating-certificate-stores-and-scheduling-discovery-jobs) +- [Certificate Discovery](#certificate-discovery) + * [K8SNS Discovery](#k8sns-discovery) + * [K8SPKCS12 and K8SJKS Discovery](#k8spkcs12-and-k8sjks-discovery) +- [Certificate Inventory](#certificate-inventory) +- [Certificate Management](#certificate-management) + * [K8STLSSecr & K8SSecret](#k8stlssecr---k8ssecret) + + [Opaque & tls secret w/o ca.crt](#opaque---tls-secret-w-o-cacrt) + + [Opaque & tls secret w/ ca.crt](#opaque---tls-secret-w--cacrt) + + [Opaque & tls secret w/o private key](#opaque---tls-secret-w-o-private-key) + * [K8SJKS & K8SPKCS12](#k8sjks---k8spkcs12) +- [Development](#development) +- [License](#license) + + +## Keyfactor Version Supported + +The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.1 + +| Keyfactor Version | Universal Orchestrator Framework Version | Supported | +|-------------------|------------------------------------------|--------------| +| 10.2.1 | 10.1, 10.2 | ✓ | +| 10.1.1 | 10.1, 10.2 | ✓ | +| 10.0.0 | 10.1, 10.2 | ✓ | +| 9.10.1 | Not supported on KF 9.X.X | x | +| 9.5.0 | Not supported on KF 9.X.X | x | + +## 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. +See the store type specific sections below for more details on specific cababilities based on Kubernetes resource type. + +## 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 | +|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ServerPassword | This is a raw JSON file that contains service account credentials to interact with the Kubernetes APIs. See the service account setup guide for permission details. | +| ServerUsername | This is a static value that must be set to `kubeconfig`. | + + +It is not necessary to implement all of the secrets available to be managed by a PAM provider. +For each value that you want managed by a PAM provider, simply enter the key value inside your +specific PAM provider that will hold this value into the corresponding field when setting up +the certificate store, discovery job, or API call. + +Setting up a PAM provider for use involves adding an additional section to the manifest.json +file for this extension as well as setting up the PAM provider you will be using. Each of +these steps is specific to the PAM provider you will use and are documented in the specific +GitHub repo for that provider. For a list of Keyfactor supported PAM providers, please +reference the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam). + +--- + + +## Overview +The Kubernetes Orchestrator Extension is an integration that can remotely manage certificate +resources in a Kubernetes cluster. The certificate store types that can be managed in the +current version are: +- K8SCert - Kubernetes certificates of type `certificates.k8s.io/v1` +- K8SSecret - Kubernetes secrets of type `Opaque` +- K8STLSSecret - Kubernetes secrets of type `kubernetes.io/tls` +- K8SCluster - This allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. +This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores across all k8s namespaces. +- K8SNS - This allows for a single store to manage a k8s namespace's secrets or type `Opaque` and `kubernetes.io/tls`. +This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores for a single k8s namespace. +- K8SJKS - Kubernetes secrets of type `Opaque` that contain one or more Java Keystore(s). These cannot be managed at the +cluster or namespace level as they should all require unique credentials. +- K8SPKCS12 - Kubernetes secrets of type `Opaque` that contain one or more PKCS12(s). These cannot be managed at the +cluster or namespace level as they should all require unique credentials. + +This orchestrator extension makes use of the Kubernetes API by using a service account +to communicate remotely with certificate stores. The service account must have the correct permissions +in order to perform the desired operations. For more information on the required permissions, see the +[service account setup guide](#service-account-setup). + +### K8SCert +The K8SCert store type is used to manage Kubernetes certificates of type `certificates.k8s.io/v1`. +To provision these certs use the [k8s-csr-signer](https://github.com/Keyfactor/k8s-csr-signer) +documentation for more information. + +### K8SSecret +The K8SSecret store type is used to manage Kubernetes secrets of type `Opaque`. These secrets can have any +arbitrary fields, but except for the `tls.crt` and `tls.key` fields, these are reserved for the `kubernetes.io/tls` +secret type. +**NOTE**: The orchestrator will only manage the fields named `certificates` and `private_keys` in the +secret. Any other fields will be ignored. + +### K8STLSSecret +The K8STLSSecret store type is used to manage Kubernetes secrets of type `kubernetes.io/tls`. These secrets +must have the `tls.crt` and `tls.key` fields and may only contain a single key and single certificate. + +### K8SJKS +The K8SJKS store type is used to manage Kubernetes secrets of type `Opaque`. These secrets +must have a field that ends in `.jks`. The orchestrator will inventory and manage using a *custom alias* of the following +pattern: `/`. For example, if the secret has a field named `mykeystore.jks` and +the keystore contains a certificate with an alias of `mycert`, the orchestrator will manage the certificate using the +alias `mykeystore.jks/mycert`. + +### K8SPKCS12 +The K8SPKCS12 store type is used to manage Kubernetes secrets of type `Opaque`. These secrets +must have a field that ends in `.p12`, `.pkcs12`, `.pfx`. The orchestrator will inventory and manage using a +*custom alias* of the following pattern: `/`. For example, if the secret has a +field named `mykeystore.p12` and the keystore contains a certificate with an alias of `mycert`, the orchestrator will +manage the certificate using the alias `mykeystore.p12/mycert`. + +## Versioning + +The version number of a the Kubernetes Orchestrator Extension can be verified by right clicking on the +`Kyefactor.Orchestrators.K8S.dll` file in the `///Extensions/Kubernetes` installation folder, +selecting Properties, and then clicking on the Details tab. + +## Security Considerations +For the Kubernetes Orchestrator Extension to be able to communicate with a Kubernetes cluster, it must +be able to authenticate with the cluster. This is done by providing the extension with a service account +token that has the appropriate permissions to perform the desired operations. The service account token +can be provided to the extension in one of two ways: +- As a raw JSON file that contains the service account credentials +- As a base64 encoded string that contains the service account credentials + +### Service Account Setup +To set up a service account user on your Kubernetes cluster to be used by the Kubernetes Orchestrator Extension, use the following example as a guide: +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: keyfactor + namespace: keyfactor +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keyfactor +rules: +- apiGroups: ["certificates.k8s.io"] + resources: ["certificatesigningrequests"] + verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: keyfactor +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: keyfactor +subjects: +- kind: ServiceAccount + name: keyfactor + namespace: keyfactor +``` + +## Kubernetes Orchestrator Extension Installation +1. Create the certificate store types you wish to manage. Please refer to the individual sections + devoted to each supported store type under "Certificate Store Types" later in this README. +2. Stop the Keyfactor Universal Orchestrator Service for the orchestrator you plan to install this + extension to run on. +3. In the Keyfactor Orchestrator installation folder (by convention usually + C:\Program Files\Keyfactor\Keyfactor Orchestrator), find the "Extensions" folder. Underneath that, + create a new folder named "Kubernetes". You may choose to use a different name if you wish. +4. Download the latest version of the Kubernetes orchestrator extension from + [GitHub](https://github.com/Keyfactor/kubernetes-orchestrator). Click on the "Latest" release + link on the right hand side of the main page and download the first zip file. +5. Copy the contents of the download installation zip file to the folder created in Step 3. +6. (Optional) If you decide to create one or more certificate store types with short names different + than the suggested values (please see the individual certificate store type sections in "Certificate + Store Types" later in this README for more information regarding certificate store types), edit the + manifest.json file in the folder you created in step 3, and modify each "ShortName" in each + "Certstores.{ShortName}.{Operation}" line with the ShortName you used to create the respective + certificate store type. If you created it with the suggested values, this step can be skipped. +7. Modify the config.json file (See the "Configuration File Setup" section later in this README) +8. Start the Keyfactor Universal Orchestrator Service. +9. Create the certificate store types you wish to manage. Please refer to the individual sections + devoted to each supported store type under [Certificate Store Types](#certificate-store-types) later in this README. +10. (Optional) Run certificate discovery jobs to populate the certificate stores with existing + certificates. See the [Certificate Store Discovery](#certificate-store-discovery) section later in this README for more + information. + +## Certificate Store Types + +When setting up the certificate store types you wish the Kubernetes Orchestrator Extension to +manage, there are some common settings that will be the same for all supported types. +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`. Alternatively, +there are cURL scripts for all of the currently implemented certificate store types +in the Certificate Store Type cURL Scripts folder in this repo if you wish to automate +the creation of the desired store types. + +### Configuration Information +Below is a table of the common values that should be used for all certificate store types. + +#### Note about StorePath +A Keyfactor Command certificate store `StorePath` for the K8S orchestrator extension can follow the following formats: + +| Pattern | Description | +|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `secretName` | The name of the secret to use. This assumes `KubeNamespace` is defined or `default` and will be the `secret` or `cert` name on k8s. | +| `namespace/secretName` | If `KubeNamespace` or `KubeSecretName` are not set, then the path will be split by `/` and the values will be parsed according to the pattern. | +| `clusterName/namespace/secretName` | Same as above, clusterName is purely informational | +| `clusterName/namespace/secretType/secretName` | Considered a `full` path, this is what discovery will return as `StorePath` | + +#### Common Values +##### UI Basic Tab +| Field Name | Required | Description | Value | +|-------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------| +| Name | ✓ | The display name you wish to use for the new Certificate Store Type. | Depends on store type. | +| ShortName | ✓ | The short name you wish to use for the new Certificate Store Type. | Depends on store type. | +| Custom Capability | ✓ | Whether or not the certificate store type supports custom capabilities. | Checked [x] | +| Supported Job Types | ✓ | The job types supported by the certificate store type. | Depends on store type. | +| Needs Server | ✓ | Must be set to true or checked. NOTE: If using this `ServerUsername` must be equal to `kubeconfig` and `ServerPassword` will be the kubeconfig file in JSON format | Checked [x] | +| 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. | Unchecked [ ] | +| Uses PowerShell | | Whether or not the certificate store type uses PowerShell. | Unchecked [ ] | +| Requires Store Password | | Whether or not the certificate store type requires a password. | Unchecked [ ] | +| Supports Entry Password | | Whether or not the certificate store type supports entry passwords. | Unchecked [ ] | + +##### UI Advanced Tab +| Field Name | Required | Description | Value | +|-----------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------|------------------------| +| Store Path Type | | The type of path the certificate store type uses. | Freeform | +| Supports Custom Alias | | Whether or not the certificate store type supports custom aliases. | Depends on store type. | +| Private Key Handling | | Whether or not the certificate store type supports private key handling. | Depends on store type. | +| PFX Password Style | | The password style used by the certificate store type. | Default | + +##### Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|------------------|---------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | This field overrides implied `Store Path` value. The Kubernetes namespace the store will reside. This will override the value parsed from `storepath`. | +| KubeSecretName | Kube Secret Name | String | | | This field overrides implied `Store Path` value. The Kubernetes secret or certificate resource name. | +| KubeSecretType | Kube Secret Type | String | ✓ | | Must be one of the following `secret`, `secret_tls` or `cert`. See [kube-secret-types](#kube-secret-types). | +| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. Set this to `false` if you do not want certificate chains deployed. | +| SeparateChain | SeparateChain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | + +##### Kube Secret Types +- `secret` - A generic secret of type `Opaque`. Must contain a key of one of the following values: [ `cert`, `certficate`, `certs`,`certificates` ] to be inventoried. +- `tls_secret` - A secret of type `kubernetes.io/tls`. Must contain the following keys: [ `tls.crt`, `tls.key` ] to be inventoried. +- `cert` - A certificate `certificates.k8s.io/v1` resource. Must contain the following keys: [ `csr`, `cert` ] to be inventoried. + +##### Entry Parameters Tab: +- See specific certificate store type instructions below + +### K8SSecret Store Type + +#### kfutil Create K8SSecret Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. +kfuti +```bash +kfutil login +kfutil store-types create --name K8SSecret +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|-------------------------|----------|-------------------------------------------| +| Name | ✓ | `K8SSecret` | +| ShortName | ✓ | `K8SSecret` | +| Custom Capability | ✓ | Checked [x] + `K8SSecret` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +**NOTE:** If using PAM, `server_username` must be equal to `kubeconfig` and `server_password` will be the kubeconfig file in JSON format. + +![k8ssecret_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_basic.png) + +##### UI Advanced Tab +| Field Name | Value | +|-----------------------|-----------| +| Store Path Type | Freeform | +| Supports Custom Alias | Forbidden | +| Private Key Handling | Optional | +| PFX Password Style | Default | + +![k8ssecret_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|------------------|---------------------------|--------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | `default` | The K8S namespace the `Opaque` secret lives. This will override any value inferred in the `Store Path` | +| KubeSecretName | Kube Secret Name | String | ✓ | | The name of the K8S `Opaque` secret. This will override any value inferred in the `Store Path` | +| KubeSecretType | Kube Secret Type | String | ✓ | `secret` | | +| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | +| SeparateChain | SeparateChain | Bool | | `false` | Will default to `false` if not set. `true` will deploy leaf cert to `tls.crt` and the rest of the cert chain to `ca.crt`. If set to `false` the full chain is deployed to `tls.crt` | + +![k8ssecret_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8ssecret_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8STLSSecr Store Type + +#### kfutil Create K8STLSSecr Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8STLSSecr +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|-------------------------|----------|-------------------------------------------| +| Name | ✓ | `K8STLSSecr` | +| ShortName | ✓ | `K8STLSSecr` | +| Custom Capability | ✓ | Checked [x] + `K8STLSSecr` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +![k8sstlssecr_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_basic.png) + +##### UI Advanced Tab +| Field Name | Required | Value | +|-----------------------|----------|-----------| +| Store Path Type | | Freeform | +| Supports Custom Alias | | Forbidden | +| Private Key Handling | | Optional | +| PFX Password Style | | Default | + +![k8sstlssecr_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|------------------|----------------------------|--------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | The K8S namespace the `tls` secret lives. This will override any value inferred in the `Store Path` | +| KubeSecretName | Kube Secret Name | String | | | The name of the K8S `tls` secret. This will override any value inferred in the `Store Path` | +| KubeSecretType | Kube Secret Type | String | ✓ | `tls_secret` | | +| IncludeCertChain | Include Certificate Chain | Bool | | `true` | If set to `false` only leaf cert will be deployed. | +| SeparateChain | SeparateChain | Bool | | `true` | `true` will deploy leaf cert to `tls.crt` and the rest of the cert chain to `ca.crt`. If set to `false` the full chain is deployed to `tls.crt` | + + +![k8sstlssecr_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sstlssecr_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8SPKCS12 Store Type + +#### kfutil Create K8SPKCS12 Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8SPKCS12 +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|---------------------------|----------|-------------------------------------------| +| Name | ✓ | `K8SPKCS12` | +| ShortName | ✓ | `K8SPKCS12` | +| Custom Capability | ✓ | Checked [x] + `K8SPKCS12` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password** | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +**NOTE:** `Requires Store Password` is required if pkcs12 password is not being sourced from a separate secret in the +K8S cluster. + +![k8spkcs12_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_basic.png) + +##### UI Advanced Tab +| Field Name | Value | +|-----------------------|----------| +| Store Path Type | Freeform | +| Supports Custom Alias | Required | +| Private Key Handling | Optional | +| PFX Password Style | Default | + +![k8spkcs12_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|--------------------------|-----------------------------|--------|----------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | K8S namespace the PKCS12 secret lives. This will override any value inferred in the `Store Path` | +| KubeSecretName | Kube Secret Name | String | | | The K8S secret name that contains PKCS12 data. This will override any value inferred in the `Store Path` | +| KubeSecretType | Kube Secret Type | String | ✓ | `pkcs12` | This must be set to `pkcs12`. | +| CertificateDataFieldName | Certificate Data Field Name | String | ✓ | `.p12` | The K8S secret field name to source the PKCS12 data from. You can provide an extension `.p12` or `.pfx` for a secret with a key `example.p12` | +| PasswordFieldName | Password Field Name | String | | `password` | If sourcing the PKCS12 password from a K8S secret this is the field it will look for the password in. | +| PasswordIsK8SSecret | Password Is K8S Secret | Bool | | `false` | If you want to use the PKCS12 secret or a separate secret specific in `KubeSecretPasswordPath` set this to `true` | +| StorePasswordPath | Kube Secret Password Path | String | | | Source PKCS12 password from a separate K8S secret. Pattern: `namespace_name/secret_name` | + + +![k8spkcs12_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8spkcs12_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8SJKS Store Type + +#### Storepath Patterns +- `namespace_name/secret_name` +- `namespace_name/secrets/secret_name` +- `cluster_name/namespace_name/secrets/secret_name` + +#### Alias Patterns +- `k8s_secret_field_name/keystore_alias` + +#### kfutil Create K8SJKS Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8SJKS +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|---------------------------|----------|-------------------------------------------| +| Name | ✓ | `K8SJKS` | +| ShortName | ✓ | `K8SJKS` | +| Custom Capability | ✓ | Checked [x] + `K8SJKS` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create, Discovery | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password** | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +**NOTE:** `Requires Store Password` is required if pkcs12 password is not being sourced from a separate secret in the +K8S cluster. + +![k8sjks_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_basic.png) + +##### UI Advanced Tab +| Field Name | Value | +|-----------------------|----------| +| Store Path Type | Freeform | +| Supports Custom Alias | Required | +| Private Key Handling | Optional | +| PFX Password Style | Default | + +![k8sjks_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|--------------------------|-----------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | K8S namespace the JKS secret lives. This will override any value inferred in the `Store Path`. | +| KubeSecretName | Kube Secret Name | String | | | The K8S secret name that contains JKS data. This will override any value inferred in the `Store Path`. | +| KubeSecretType | Kube Secret Type | String | ✓ | `jks` | | +| CertificateDataFieldName | Certificate Data Field Name | String | ✓ | `.jks` | The K8S secret field name to source the JKS data from | +| PasswordFieldName | Password Field Name | String | ✓ | `password` | If sourcing the JKS password from a K8S secret this is the field it will look for the password in. | +| PasswordIsK8SSecret | Password Is K8S Secret | Bool | | `false` | If you want to use the JKS secret or a separate secret specific in `` set this to `true` | +| StorePasswordPath | Kube Secret Password Path | String | | | Source JKS password from a separate K8S secret. Pattern: `namespace_name/secret_name` | + + +![k8sjks_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8SCluster Store Type + +#### Storepath Patterns +- `cluster_name` + +#### Alias Patterns +- `namespace_name/secrets/secret_type/secret_name` + +#### kfutil Create K8SCluster Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8SCluster +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|-------------------------|----------|---------------------------------| +| Name | ✓ | `K8SCluster` | +| ShortName | ✓ | `K8SCluster` | +| Custom Capability | ✓ | Checked [x] + `K8SCluster` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +![k8scluster_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_basic.png) + +##### UI Advanced Tab +| Field Name | Value | +|-----------------------|----------| +| Store Path Type | Freeform | +| Supports Custom Alias | Required | +| Private Key Handling | Optional | +| PFX Password Style | Default | + +![k8scluster_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_advanced.png) + + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|------------------|---------------------------|--------|----------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | +| SeparateChain | Separate Chain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | + +![k8scluster_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8scluster_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8SNS Store Type + +**NOTE**: This store type will only inventory K8S secrets that contain the keys `tls.crt` and `tls.key`. + +#### Storepath Patterns +- `namespace_name` +- `cluster_name/namespace_name` + +#### Alias Patterns +- `secrets/secret_type/secret_name` + +#### kfutil Create K8SNS Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8SNS +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|-------------------------|----------|--------------------------------| +| Name | ✓ | `K8SNS` | +| ShortName | ✓ | `K8SNS` | +| Custom Capability | ✓ | Checked [x] + `K8SNS` | +| Supported Job Types | ✓ | Inventory, Add, Remove, Create | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +![k8sns_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_basic.png) + +##### UI Advanced Tab +| Field Name | Value | +|-----------------------|----------| +| Store Path Type | Freeform | +| Supports Custom Alias | Required | +| Private Key Handling | Optional | +| PFX Password Style | Default | + +![k8sns_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|------------------|---------------------------|--------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | K8S namespace to manage. This will override any value inferred in the `Store Path`. | +| IncludeCertChain | Include Certificate Chain | Bool | | `true` | Will default to `true` if not set. If set to `false` only leaf cert will be deployed. | +| SeparateChain | Separate Chain | Bool | | `false` | Will default to `false` if not set. Set this to `true` if you want to deploy certificate chain to the `ca.crt` field for `Opaque` and `tls` secrets. | + +![k8sns_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8sns_custom_fields.png) + +##### UI Entry Parameters Tab: +Empty + +### K8SCert Store Type + +The following commands can be used with [kfutil](https://github.com/Keyfactor/kfutil). Please refer to the kfutil documentation for more information on how to use the tool to interact w/ Keyfactor Command. + +```bash +kfutil login +kfutil store-types create --name K8SCert +``` + +#### UI Configuration + +##### UI Basic Tab +| Field Name | Required | Value | +|-------------------------|----------|--------------------------| +| Name | ✓ | `K8SCert` | +| ShortName | ✓ | `K8SCert` | +| Custom Capability | ✓ | Checked [x] + `K8SCert` | +| Supported Job Types | ✓ | Inventory, Discovery | +| Needs Server | ✓ | Checked [x] | +| Blueprint Allowed | | Unchecked [ ] | +| Uses PowerShell | | Unchecked [ ] | +| Requires Store Password | | Unchecked [ ] | +| Supports Entry Password | | Unchecked [ ] | + +![k8scert_basic.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_basic.png) + +##### UI Advanced Tab +| Field Name | Required | Value | +|-----------------------|----------|------------| +| Store Path Type | | Freeform | +| Supports Custom Alias | | Forbidden | +| Private Key Handling | | Forbidden | +| PFX Password Style | | Default | + +![k8scert_advanced.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_advanced.png) + +##### UI Custom Fields Tab +| Name | Display Name | Type | Required | Default Value | Description | +|--------------------|---------------------------|--------|----------|---------------|--------------------------------------------------------------------------------------------------------| +| KubeNamespace | Kube Namespace | String | | | The K8S namespace the `cert` resource lives. This will override any value inferred in the `Store Path` | +| KubeSecretName | Kube Secret Name | String | | | The K8S `cert` name. This will override any value inferred in the `Store Path`. | +| KubeSecretType | Kube Secret Type | String | ✓ | `cert` | | + +![k8scert_custom_fields.png](docs%2Fscreenshots%2Fstore_types%2Fk8scert_custom_fields.png) +##### UI Entry Parameters Tab: +Empty + +## Creating Certificate Stores and Scheduling Discovery Jobs + +Please refer to the Keyfactor Command Reference Guide for information on creating +certificate stores and scheduling Discovery jobs in Keyfactor Command. + +## Certificate Discovery +**NOTE:** To use disovery jobs, you must have the story type created in Keyfactor Command and the `needs_server` checkbox MUST be checked. +Otherwise you will not be able to provide credentials to the discovery job. + +The Kubernetes Orchestrator Extension supports certificate discovery jobs. This allows you to populate the certificate stores with existing certificates. To run a discovery job, follow these steps: +1. Click on the "Locations > Certificate Stores" menu item. +2. Click the "Discover" tab. +3. Click the "Schedule" button. +4. Configure the job based on storetype. **Note** the "Server Username" field must be set to `kubeconfig` and the "Server Password" field is the `kubeconfig` formatted JSON file containing the service account credentials. See the "Service Account Setup" section earlier in this README for more information on setting up a service account. + ![discover_schedule_start.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_schedule_start.png) + ![discover_schedule_config.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_schedule_config.png) + ![discover_server_username.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_server_username.png) + ![discover_server_password.png](docs%2Fscreenshots%2Fdiscovery%2Fdiscover_server_password.png) +5. Click the "Save" button and wait for the Orchestrator to run the job. This may take some time depending on the number of certificates in the store and the Orchestrator's check-in schedule. + +### K8SNS Discovery +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all namespaces. *This cannot be left blank.* + +### K8SPKCS12 and K8SJKS Discovery +For discovery of K8SPKCS12 and K8SJKS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all namespaces. *This cannot be left blank.* +- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or JKS data. Will use the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.jks`,`jks`. + +## Certificate Inventory +In order for certificates to be inventoried by the Keyfactor k8s-orchestrator, they must have specific keys and values +in the Kubernetes Secret. The following table shows the required keys and values for each type of certificate store. + +| Store Type | Valid Secret Keys | +|------------|--------------------------------| +| K8STLSSecr | `tls.crt`,`tls.key`, `ca.crt` | +| K8SSecret | `tls.crt`,`tls.crts`, `ca.crt` | +| K8SCert | `cert`, `csr` | +| K8SPKCS12 | `*.pfx`,`*.pkcs12`, `*.p12` | +| K8SJKS | `*.jks` | +| K8SNS | `tls.crt`,`tls.crts`, `ca.crt` | +| K8SCluster | `tls.crt`,`tls.crts`, `ca.crt` | + +## Certificate Management +Management add/remove/create operations will attempt to write back to the Kubernetes Secret. +The following table shows the keys that the orchestrator will write back to the Kubernetes Secret for +each type of certificate store. + +| Store Type | Managed Secret Keys | +|------------|-----------------------------------------------------------| +| K8STLSSecr | `tls.crt`,`tls.key`, `ca.crt` | +| K8SSecret | `tls.crt`,`tls.key`, `ca.crt` | +| K8SPKCS12 | Specified in custom field `KubeSecretKey` or use defaults | +| K8SJKS | Specified in custom field `KubeSecretKey` or use defaults | +| K8SCluster | `tls.crt`,`tls.key` | +| K8SNS | `tls.crt`,`tls.key` | + +### K8STLSSecr & K8SSecret +These store types are virtually the same, they only differ in what K8S secret type they create. Both store types allow +for **ONLY** a single certificate to be stored in the secret. This means any `add` job will **overwrite** the existing +`tls.crt`, `tls.key`, and `ca.crt` values in the secret. If a secret does not exist, the orchestrator will create one +with the fields `tls.crt`, `tls.key`. Additionally, if `SeparateChain` on the store definition is set to +`true`, then the field `ca.crt` will be populated with the certificate chain data. + +**NOTE:** If a secret already exists and does not contain the field `ca.crt`, the orchestrator will **NOT** add the field +`ca.crt` to the secret, and instead will deploy a full certificate chain to the `tls.crt` field. + +#### Opaque & tls secret w/o ca.crt +Here's what an `Opaque` secret looks like in the UI when it does not contain the `ca.crt` field **NOTE** the chain is +included in the `tls.crt` field: +![opaque_no_cacrt_field.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_no_cacrt_field.png) + +#### Opaque & tls secret w/ ca.crt +Here's what an `Opaque` secret looks like in the UI when it does contain the `ca.crt` field: +![opaque_cacrt.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_cacrt.png) + +#### Opaque & tls secret w/o private key +It is possible to deploy a certificate without the private key from Command, and this is how it will look in the UI +**NOTE** the chain will only be included if Command has inventoried it: +![opaque_no_private_key.png](docs%2Fscreenshots%2Fmanagement%2Fopaque_no_private_key.png) + +### K8SJKS & K8SPKCS12 + +The K8SJKS store type is a Java Key Store (JKS) that is stored in a Kubernetes Secret. The secret can contain multiple +JKS files. The orchestrator will attempt to manage the JKS files found in the secret that match the `allowed_keys` or +`CertificateDataFieldName` custom field values. + +Alias pattern: `/`. + +Example of secret containing 2 JKS stores: +![k8sjks_multi.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_multi.png) + +Here's what this looks like in the UI: +![k8sjks_inventory_ui.png](docs%2Fscreenshots%2Fstore_types%2Fk8sjks_inventory_ui.png) + +## Development + +[See the development guide](Development.md) + +## License +[Apache](https://apache.org/licenses/LICENSE-2.0) + + +When creating cert store type manually, that store property names and entry parameter names are case sensitive + + diff --git a/readme_source.md b/docs_old/readme_source.md similarity index 100% rename from readme_source.md rename to docs_old/readme_source.md diff --git a/docsource/content.md b/docsource/content.md new file mode 100644 index 0000000..100074c --- /dev/null +++ b/docsource/content.md @@ -0,0 +1,54 @@ +## Overview + +The Kubernetes Orchestrator allows for the remote management of certificate stores defined in a Kubernetes cluster. +The following types of Kubernetes resources are supported: kubernetes secrets of `kubernetes.io/tls` or `Opaque` and +kubernetes certificates `certificates.k8s.io/v1` + +The certificate store types that can be managed in the current version are: +- `K8SCert` - Kubernetes certificates of type `certificates.k8s.io/v1` +- `K8SSecret` - Kubernetes secrets of type `Opaque` +- `K8STLSSecret` - Kubernetes secrets of type `kubernetes.io/tls` +- `K8SCluster` - This allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. + This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores across all k8s namespaces. +- `K8SNS` - This allows for a single store to manage a k8s namespace's secrets or type `Opaque` and `kubernetes.io/tls`. + This can be thought of as a container of `K8SSecret` and `K8STLSSecret` stores for a single k8s namespace. +- `K8SJKS` - Kubernetes secrets of type `Opaque` that contain one or more Java Keystore(s). These cannot be managed at the + cluster or namespace level as they should all require unique credentials. +- `K8SPKCS12` - Kubernetes secrets of type `Opaque` that contain one or more PKCS12(s). These cannot be managed at the + cluster or namespace level as they should all require unique credentials. + +This orchestrator extension makes use of the Kubernetes API by using a service account +to communicate remotely with certificate stores. The service account must have the correct permissions +in order to perform the desired operations. For more information on the required permissions, see the +[service account setup guide](#service-account-setup). + +## Requirements + +### Kubernetes API Access +This orchestrator extension makes use of the Kubernetes API by using a service account +to communicate remotely with certificate stores. The service account must exist and have the appropriate permissions. +The service account token can be provided to the extension in one of two ways: +- As a raw JSON file that contains the service account credentials +- As a base64 encoded string that contains the service account credentials + +#### Service Account Setup +To set up a service account user on your Kubernetes cluster to be used by the Kubernetes Orchestrator Extension. For full +information on the required permissions, see the [service account setup guide](./scripts/kubernetes/README.md). + +## Discovery + +**NOTE:** To use discovery jobs, you must have the story type created in Keyfactor Command and the `needs_server` +checkbox *MUST* be checked, if you do not select `needs_server` you will not be able to provide credentials to the +discovery job and it will fail. + +The Kubernetes Orchestrator Extension supports certificate discovery jobs. This allows you to populate the certificate stores with existing certificates. To run a discovery job, follow these steps: +1. Click on the "Locations > Certificate Stores" menu item. +2. Click the "Discover" tab. +3. Click the "Schedule" button. +4. Configure the job based on storetype. **Note** the "Server Username" field must be set to `kubeconfig` and the "Server Password" field is the `kubeconfig` formatted JSON file containing the service account credentials. See the "Service Account Setup" section earlier in this README for more information on setting up a service account. + ![discover_schedule_start.png](./docs/screenshots/discovery/discover_schedule_start.png) + ![discover_schedule_config.png](./docs/screenshots/discovery/discover_schedule_config.png) + ![discover_server_username.png](./docs/screenshots/discovery/discover_server_username.png) + ![discover_server_password.png](./docs/screenshots/discovery/discover_server_password.png) +5. Click the "Save" button and wait for the Orchestrator to run the job. This may take some time depending on the number of certificates in the store and the Orchestrator's check-in schedule. + diff --git a/docsource/images/K8SCert-advanced-store-type-dialog.png b/docsource/images/K8SCert-advanced-store-type-dialog.png new file mode 100644 index 0000000..9fc6c81 Binary files /dev/null and b/docsource/images/K8SCert-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SCert-basic-store-type-dialog.png b/docsource/images/K8SCert-basic-store-type-dialog.png new file mode 100644 index 0000000..9fc4ba9 Binary files /dev/null and b/docsource/images/K8SCert-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SCert-custom-fields-store-type-dialog.png b/docsource/images/K8SCert-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..3760261 Binary files /dev/null and b/docsource/images/K8SCert-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8SCluster-advanced-store-type-dialog.png b/docsource/images/K8SCluster-advanced-store-type-dialog.png new file mode 100644 index 0000000..c0419a9 Binary files /dev/null and b/docsource/images/K8SCluster-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SCluster-basic-store-type-dialog.png b/docsource/images/K8SCluster-basic-store-type-dialog.png new file mode 100644 index 0000000..4dc0320 Binary files /dev/null and b/docsource/images/K8SCluster-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SCluster-custom-fields-store-type-dialog.png b/docsource/images/K8SCluster-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..3d2d82b Binary files /dev/null and b/docsource/images/K8SCluster-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8SJKS-advanced-store-type-dialog.png b/docsource/images/K8SJKS-advanced-store-type-dialog.png new file mode 100644 index 0000000..c0419a9 Binary files /dev/null and b/docsource/images/K8SJKS-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SJKS-basic-store-type-dialog.png b/docsource/images/K8SJKS-basic-store-type-dialog.png new file mode 100644 index 0000000..ef24d04 Binary files /dev/null and b/docsource/images/K8SJKS-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SJKS-custom-fields-store-type-dialog.png b/docsource/images/K8SJKS-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..459adb1 Binary files /dev/null and b/docsource/images/K8SJKS-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8SNS-advanced-store-type-dialog.png b/docsource/images/K8SNS-advanced-store-type-dialog.png new file mode 100644 index 0000000..c0419a9 Binary files /dev/null and b/docsource/images/K8SNS-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SNS-basic-store-type-dialog.png b/docsource/images/K8SNS-basic-store-type-dialog.png new file mode 100644 index 0000000..adb8197 Binary files /dev/null and b/docsource/images/K8SNS-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SNS-custom-fields-store-type-dialog.png b/docsource/images/K8SNS-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..6016cf0 Binary files /dev/null and b/docsource/images/K8SNS-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8SPKCS12-advanced-store-type-dialog.png b/docsource/images/K8SPKCS12-advanced-store-type-dialog.png new file mode 100644 index 0000000..c0419a9 Binary files /dev/null and b/docsource/images/K8SPKCS12-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SPKCS12-basic-store-type-dialog.png b/docsource/images/K8SPKCS12-basic-store-type-dialog.png new file mode 100644 index 0000000..11844bf Binary files /dev/null and b/docsource/images/K8SPKCS12-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SPKCS12-custom-fields-store-type-dialog.png b/docsource/images/K8SPKCS12-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..b24ac0d Binary files /dev/null and b/docsource/images/K8SPKCS12-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8SSecret-advanced-store-type-dialog.png b/docsource/images/K8SSecret-advanced-store-type-dialog.png new file mode 100644 index 0000000..8b43572 Binary files /dev/null and b/docsource/images/K8SSecret-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8SSecret-basic-store-type-dialog.png b/docsource/images/K8SSecret-basic-store-type-dialog.png new file mode 100644 index 0000000..9cb2966 Binary files /dev/null and b/docsource/images/K8SSecret-basic-store-type-dialog.png differ diff --git a/docsource/images/K8SSecret-custom-fields-store-type-dialog.png b/docsource/images/K8SSecret-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..d2a7005 Binary files /dev/null and b/docsource/images/K8SSecret-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/K8STLSSecr-advanced-store-type-dialog.png b/docsource/images/K8STLSSecr-advanced-store-type-dialog.png new file mode 100644 index 0000000..8b43572 Binary files /dev/null and b/docsource/images/K8STLSSecr-advanced-store-type-dialog.png differ diff --git a/docsource/images/K8STLSSecr-basic-store-type-dialog.png b/docsource/images/K8STLSSecr-basic-store-type-dialog.png new file mode 100644 index 0000000..6d80f4b Binary files /dev/null and b/docsource/images/K8STLSSecr-basic-store-type-dialog.png differ diff --git a/docsource/images/K8STLSSecr-custom-fields-store-type-dialog.png b/docsource/images/K8STLSSecr-custom-fields-store-type-dialog.png new file mode 100644 index 0000000..9ea4619 Binary files /dev/null and b/docsource/images/K8STLSSecr-custom-fields-store-type-dialog.png differ diff --git a/docsource/k8scert.md b/docsource/k8scert.md new file mode 100644 index 0000000..abd1f27 --- /dev/null +++ b/docsource/k8scert.md @@ -0,0 +1,7 @@ +## Overview + +The `K8SCert` store type is used to manage Kubernetes certificates of type `certificates.k8s.io/v1`. + +**NOTE**: only `inventory` and `discovery` of these resources is supported with this extension. To provision these certs use the +[k8s-csr-signer](https://github.com/Keyfactor/k8s-csr-signer). + diff --git a/docsource/k8scluster.md b/docsource/k8scluster.md new file mode 100644 index 0000000..aeb6e82 --- /dev/null +++ b/docsource/k8scluster.md @@ -0,0 +1,18 @@ +## Overview + +The `K8SCluster` store type allows for a single store to manage a k8s cluster's secrets or type `Opaque` and `kubernetes.io/tls`. + +## Certificate Store Configuration + +In order for certificates of type `Opaque` and/or `kubernetes.io/tls` to be inventoried in `K8SCluster` store types, they must +have specific keys in the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` + +### Storepath Patterns +- `` + +### Alias Patterns +- `/secrets//` + + diff --git a/docsource/k8sjks.md b/docsource/k8sjks.md new file mode 100644 index 0000000..ae678ad --- /dev/null +++ b/docsource/k8sjks.md @@ -0,0 +1,33 @@ +## Overview + +The `K8SJKS` store type is used to manage Kubernetes secrets of type `Opaque`. These secrets +must have a field that ends in `.jks`. The orchestrator will inventory and manage using a *custom alias* of the following +pattern: `/`. For example, if the secret has a field named `mykeystore.jks` and +the keystore contains a certificate with an alias of `mycert`, the orchestrator will manage the certificate using the +alias `mykeystore.jks/mycert`. *NOTE* *This store type cannot be managed at the `cluster` or `namespace` level as they +should all require unique credentials.* + +## Discovery Job Configuration + +For discovery of `K8SJKS` stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all +namespaces. *This cannot be left blank.* +- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or JKS data. Will use +the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.jks`,`jks`. + +## Certificate Store Configuration + +In order for certificates of type `Opaque` to be inventoried as `K8SJKS` store types, they must have specific keys in +the Kubernetes secret. +- Valid Keys: `*.jks` + +### Storepath Patterns +- `/` +- `/secrets/` +- `//secrets/` + +### Alias Patterns +- `/` + +Example: `test.jks/load_balancer` where `test.jks` is the field name on the `Opaque` secret and `load_balancer` is +the certificate alias in the `jks` data store. diff --git a/docsource/k8sns.md b/docsource/k8sns.md new file mode 100644 index 0000000..5121b1a --- /dev/null +++ b/docsource/k8sns.md @@ -0,0 +1,26 @@ +## Overview + +The `K8SNS` store type is used to manage Kubernetes secrets of type `kubernetes.io/tls` and/or type `Opaque` in a single +Keyfactor Command certificate store using an alias pattern of + +## Discovery Job Configuration + +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all +namespaces. *This cannot be left blank.* + +## Certificate Store Configuration + +In order for certificates of type `Opaque` and/or `kubernetes.io/tls` to be inventoried in `K8SNS` store types, they must +have specific keys in the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` + +### Storepath Patterns +- `` +- `/` + +### Alias Patterns +- `secrets//` + + diff --git a/docsource/k8spkcs12.md b/docsource/k8spkcs12.md new file mode 100644 index 0000000..080eb49 --- /dev/null +++ b/docsource/k8spkcs12.md @@ -0,0 +1,34 @@ +## Overview + +The `K8SPKCS12` store type is used to manage Kubernetes secrets of type `Opaque`. These secrets +must have a field that ends in `.pkcs12`. The orchestrator will inventory and manage using a *custom alias* of the following +pattern: `/`. For example, if the secret has a field named `mykeystore.pkcs12` and +the keystore contains a certificate with an alias of `mycert`, the orchestrator will manage the certificate using the +alias `mykeystore.pkcs12/mycert`. *NOTE* *This store type cannot be managed at the `cluster` or `namespace` level as they +should all require unique credentials.* + +## Discovery Job Configuration + +For discovery of `K8SPKCS12` stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* +- `File name patterns to match` - comma separated list of K8S secret keys to search for PKCS12 or PKCS12 data. Will use + the following keys by default: `tls.pfx`,`tls.pkcs12`,`pfx`,`pkcs12`,`tls.pkcs12`,`pkcs12`. + +## Certificate Store Configuration + +In order for certificates of type `Opaque` to be inventoried as `K8SPKCS12` store types, they must have specific keys in +the Kubernetes secret. +- Valid Keys: `*.pfx`, `*.pkcs12`, `*.p12` + +### Storepath Patterns +- `/` +- `/secrets/` +- `//secrets/` + +### Alias Patterns +- `/` + +Example: `test.pkcs12/load_balancer` where `test.pkcs12` is the field name on the `Opaque` secret and `load_balancer` is +the certificate alias in the `pkcs12` data store. + diff --git a/docsource/k8ssecret.md b/docsource/k8ssecret.md new file mode 100644 index 0000000..6bb91ee --- /dev/null +++ b/docsource/k8ssecret.md @@ -0,0 +1,18 @@ +## Overview + +The `K8SSecret` store type is used to manage Kubernetes secrets of type `Opaque`. + +## Discovery Job Configuration + +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* + +## Certificate Store Configuration + +In order for certificates of type `Opaque` to be inventoried as `K8SSecret` store types, they must have specific keys in +the Kubernetes secret. +- Required keys: `tls.crt` or `ca.crt` +- Additional keys: `tls.key` + + diff --git a/docsource/k8stlssecr.md b/docsource/k8stlssecr.md new file mode 100644 index 0000000..2791731 --- /dev/null +++ b/docsource/k8stlssecr.md @@ -0,0 +1,17 @@ +## Overview + +The `K8STLSSecret` store type is used to manage Kubernetes secrets of type `kubernetes.io/tls` + +## Discovery Job Configuration + +For discovery of K8SNS stores toy can use the following params to filter the certificates that will be discovered: +- `Directories to search` - comma separated list of namespaces to search for certificates OR `all` to search all + namespaces. *This cannot be left blank.* + +## Certificate Store Configuration + +In order for certificates of type `kubernetes.io/tls` to be inventoried, they must have specific keys in +the Kubernetes secret. +- Required keys: `tls.crt` and `tls.key` +- Optional keys: `ca.crt` + diff --git a/integration-manifest.json b/integration-manifest.json index 0b75735..fe94f21 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -19,12 +19,13 @@ "keyfactor-universal-orchestrator" ], "update_catalog": true, - "support_level": "kf-supported", + "support_level": "community", "release_dir": "kubernetes-orchestrator-extension\\bin\\Release", + "release_project": "kubernetes-orchestrator-extension\\Keyfactor.Orchestrators.K8S.csproj", "about": { "orchestrator": { - "keyfactor_platform_version": "10.x", - "UOFramework": "10.x", + "keyfactor_platform_version": "24.4", + "UOFramework": "12.4", "pam_support": true, "win": { "supportsCreateStore": true, @@ -58,9 +59,28 @@ "Remove": false }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "default", @@ -69,21 +89,23 @@ { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `csr`", "Type": "String", "DependsOn": "", "DefaultValue": "cert", "Required": true } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, "StoreRequired": false, @@ -111,9 +133,28 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "SeparateChain", "DisplayName": "Separate Certificate Chain", + "Description": "Whether to store the certificate chain separately from the certificate.", "Type": "Bool", "DefaultValue": "false", "Required": false @@ -121,12 +162,13 @@ { "Name": "IncludeCertChain", "DisplayName": "Include Certificate Chain", + "Description": "Whether to include the certificate chain in the certificate.", "Type": "Bool", "DefaultValue": "true", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, "StoreRequired": false, @@ -154,9 +196,28 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "default", @@ -165,14 +226,16 @@ { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `jks`", "Type": "String", "DependsOn": "", "DefaultValue": "jks", @@ -207,14 +270,14 @@ "DisplayName": "StorePasswordPath", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, - "StoreRequired": false, + "StoreRequired": true, "Style": "Default" }, "StorePathType": "", @@ -239,6 +302,24 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeNamespace", "DisplayName": "Kube Namespace", @@ -250,6 +331,7 @@ { "Name": "SeparateChain", "DisplayName": "Separate Certificate Chain", + "Description": "Whether to store the certificate chain separately from the certificate.", "Type": "Bool", "DefaultValue": "false", "Required": false @@ -257,12 +339,13 @@ { "Name": "IncludeCertChain", "DisplayName": "Include Certificate Chain", + "Description": "Whether to include the certificate chain in the certificate.", "Type": "Bool", "DefaultValue": "true", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, "StoreRequired": false, @@ -290,9 +373,28 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeSecretType", "DisplayName": "Kube Secret Type", + "Description": "This defaults to and must be `pkcs12`", "Type": "String", "DependsOn": "", "DefaultValue": "pkcs12", @@ -335,7 +437,7 @@ "DisplayName": "Kube Secret Name", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { @@ -343,14 +445,14 @@ "DisplayName": "StorePasswordPath", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, - "StoreRequired": false, + "StoreRequired": true, "Style": "Default" }, "StorePathType": "", @@ -375,25 +477,46 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `secret`", "Type": "String", "DependsOn": "", "DefaultValue": "secret", @@ -402,6 +525,7 @@ { "Name": "SeparateChain", "DisplayName": "Separate Certificate Chain", + "Description": "Whether to store the certificate chain separately from the certificate.", "Type": "Bool", "DefaultValue": "false", "Required": false @@ -409,12 +533,13 @@ { "Name": "IncludeCertChain", "DisplayName": "Include Certificate Chain", + "Description": "Whether to include the certificate chain in the certificate.", "Type": "Bool", "DefaultValue": "true", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, "StoreRequired": false, @@ -442,25 +567,46 @@ "Remove": true }, "Properties": [ + { + "Name": "ServerUsername", + "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "Name": "ServerPassword", + "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", - "DefaultValue": null, + "DefaultValue": "", "Required": false }, { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `tls_secret`", "Type": "String", "DependsOn": "", "DefaultValue": "tls_secret", @@ -469,6 +615,7 @@ { "Name": "SeparateChain", "DisplayName": "Separate Certificate Chain", + "Description": "Whether to store the certificate chain separately from the certificate.", "Type": "Bool", "DefaultValue": "false", "Required": false @@ -476,12 +623,13 @@ { "Name": "IncludeCertChain", "DisplayName": "Include Certificate Chain", + "Description": "Whether to include the certificate chain in the certificate.", "Type": "Bool", "DefaultValue": "true", "Required": false } ], - "EntryParameters": null, + "EntryParameters": [], "PasswordOptions": { "EntrySupported": false, "StoreRequired": false, diff --git a/kubernetes-orchestrator-extension/Clients/KubeClient.cs b/kubernetes-orchestrator-extension/Clients/KubeClient.cs index 5ccd293..612a245 100644 --- a/kubernetes-orchestrator-extension/Clients/KubeClient.cs +++ b/kubernetes-orchestrator-extension/Clients/KubeClient.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Net; @@ -269,7 +268,7 @@ private IKubernetes GetKubeClient(string kubeconfig) } else if (string.IsNullOrEmpty( - credentialFileName)) // If no config defined in store parameters, use default config. This should never happen though. + credentialFileName)) // If no config defined in store parameters, use default config. This should never happen though. { _logger.LogWarning( "No config defined in store parameters, using default config. This should never happen!"); @@ -569,37 +568,117 @@ public V1Secret UpdatePKCS12SecretStore(K8SJobCertificate jobCertificate, string if (certdataFieldNames != null && !certdataFieldNames.Contains(searchFieldName)) continue; certdataFieldName = fieldName; - _logger.LogTrace($"Adding cert '{fieldName}' to existingPkcs12"); + _logger.LogTrace("Adding cert '{FieldName}' to existingPkcs12", fieldName); if (jobCertificate.PasswordIsK8SSecret) { + _logger.LogDebug("Job certificate password is a K8S secret"); if (!string.IsNullOrEmpty(jobCertificate.StorePasswordPath)) { + _logger.LogDebug("Job certificate store password path is {StorePasswordPath}", + jobCertificate.StorePasswordPath); + + _logger.LogDebug("Splitting store password path into namespace and secret name"); var passwordPath = jobCertificate.StorePasswordPath.Split("/"); - var passwordNamespace = passwordPath[0]; - var passwordSecretName = passwordPath[1]; - // Get password from k8s secre + + string passwordNamespace; + string passwordSecretName; + + if (passwordPath.Length == 1) + { + _logger.LogDebug("Password path length is 1, using KubeNamespace"); + passwordNamespace = namespaceName; + _logger.LogTrace("Password namespace: {Namespace}", passwordNamespace); + passwordSecretName = passwordPath[0]; + } + else + { + _logger.LogDebug( + "Password path length is not 1, using passwordPath[0] and passwordPath[^1]"); + passwordNamespace = passwordPath[0]; + _logger.LogTrace("Password namespace: {Namespace}", passwordNamespace); + passwordSecretName = passwordPath[^1]; + } + + _logger.LogDebug("Password namespace: {PasswordNamespace}", passwordNamespace); + _logger.LogDebug("Password secret name: {PasswordSecretName}", passwordSecretName); + var k8sPasswordObj = ReadBuddyPass(passwordSecretName, passwordNamespace); - storePasswordBytes = k8sPasswordObj.Data[passwordFieldName]; + _logger.LogDebug( + "Successfully read password secret {PasswordSecretName} in namespace {PasswordNamespace}", + passwordSecretName, passwordNamespace); + + if (k8sPasswordObj?.Data == null) + { + _logger.LogError("Unable to read K8S buddy secret {SecretName} in namespace {Namespace}", + passwordSecretName, passwordNamespace); + throw new InvalidK8SSecretException( + $"Unable to read K8S buddy secret {passwordSecretName} in namespace {passwordNamespace}"); + } + + _logger.LogTrace("Secret response fields: {Keys}", k8sPasswordObj.Data.Keys); + + if (!k8sPasswordObj.Data.TryGetValue(passwordFieldName, out storePasswordBytes) || + storePasswordBytes == null) + { + _logger.LogError("Unable to find password field {FieldName}", passwordFieldName); + throw new InvalidK8SSecretException( + $"Unable to find password field '{passwordFieldName}' in secret '{passwordSecretName}' in namespace '{passwordNamespace}'" + ); + } + + // storePasswordBytes = k8sPasswordObj.Data[passwordFieldName]; + if (storePasswordBytes == null || storePasswordBytes.Length == 0) + { + _logger.LogError( + "Password field {FieldName} in secret {SecretName} in namespace {Namespace} is empty", + passwordFieldName, passwordSecretName, passwordNamespace); + throw new InvalidK8SSecretException( + $"Password field '{passwordFieldName}' in secret '{passwordSecretName}' in namespace '{passwordNamespace}' is empty" + ); + } + var storePasswdString = Encoding.UTF8.GetString(storePasswordBytes); + _logger.LogTrace("Importing existing PKCS12 data with store password: {StorePassword}", + storePasswdString); //TODO: INSECURE COMMENT OUT existingPkcs12.Import(existingPkcs12DataObj.Data[fieldName], storePasswdString, X509KeyStorageFlags.Exportable); } else { + _logger.LogDebug("Job certificate store password path is empty, using existing secret data"); storePasswordBytes = existingPkcs12DataObj.Data[passwordFieldName]; + if (storePasswordBytes == null || storePasswordBytes.Length == 0) + { + _logger.LogError( + "Password field {FieldName} in secret {SecretName} in namespace {Namespace} is empty", + passwordFieldName, secretName, namespaceName); + throw new InvalidK8SSecretException( + $"Password field '{passwordFieldName}' in secret '{secretName}' in namespace '{namespaceName}' is empty" + ); + } + + _logger.LogTrace("Importing existing PKCS12 data with store password: {StorePassword}", + Encoding.UTF8.GetString(storePasswordBytes)); //TODO: INSECURE COMMENT OUT existingPkcs12.Import(existingPkcs12DataObj.Data[fieldName], Encoding.UTF8.GetString(storePasswordBytes), X509KeyStorageFlags.Exportable); } } else if (!string.IsNullOrEmpty(jobCertificate.StorePassword)) { + _logger.LogDebug( + "Job certificate store password is not empty, using job certificate store password"); storePasswordBytes = Encoding.UTF8.GetBytes(jobCertificate.StorePassword); + // _logger.LogTrace("Importing existing PKCS12 data with store password: {StorePassword}", + // Encoding.UTF8.GetString(storePasswordBytes)); //TODO: INSECURE COMMENT OUT existingPkcs12.Import(existingPkcs12DataObj.Data[fieldName], Encoding.UTF8.GetString(storePasswordBytes), X509KeyStorageFlags.Exportable); } else { + _logger.LogDebug("Job certificate store password is empty, using provided store password"); storePasswordBytes = Encoding.UTF8.GetBytes(storePasswd); + _logger.LogTrace("Importing existing PKCS12 data with store password: {StorePassword}", + Encoding.UTF8.GetString(storePasswordBytes)); //TODO: INSECURE COMMENT OUT existingPkcs12.Import(existingPkcs12DataObj.Data[fieldName], Encoding.UTF8.GetString(storePasswordBytes), X509KeyStorageFlags.Exportable); } @@ -646,7 +725,10 @@ public V1Secret UpdatePKCS12SecretStore(K8SJobCertificate jobCertificate, string // Certificate not found // add the new certificate to the existingPkcs12 var storePasswordString = Encoding.UTF8.GetString(storePasswordBytes); - _logger.LogTrace("Certificate not found, adding the new certificate to the existingPkcs12"); + _logger.LogDebug("Certificate not found, adding the new certificate to the existingPkcs12"); + // _logger.LogTrace( + // "Importing jobCertificate.CertBytes into existingPkcs12 with store password: {StorePassword}", + // storePasswd); //TODO: INSECURE COMMENT OUT existingPkcs12.Import(jobCertificate.Pkcs12, storePasswd, X509KeyStorageFlags.Exportable); } } @@ -657,15 +739,21 @@ public V1Secret UpdatePKCS12SecretStore(K8SJobCertificate jobCertificate, string } else { + _logger.LogDebug("No existing PKCS12 data found, creating new PKCS12 collection"); + // _logger.LogTrace( + // "Importing jobCertificate.CertBytes into newPkcs12Collection with store password: {StorePassword}", + // storePasswd); //TODO: INSECURE COMMENT OUT newPkcs12Collection.Import(jobCertificate.CertBytes, storePasswd, X509KeyStorageFlags.Exportable); k8sCollection = newPkcs12Collection; } } - _logger.LogTrace("Creating V1Secret object"); - - var p12bytes = k8sCollection.Export(X509ContentType.Pkcs12, Encoding.UTF8.GetString(storePasswordBytes)); + // _logger.LogDebug("Exporting PKCS12 data to byte array using store password: {StorePassword}", + // Encoding.UTF8.GetString(storePasswordBytes)); //TODO: INSECURE COMMENT OUT + var p12Bytes = k8sCollection.Export(X509ContentType.Pkcs12, Encoding.UTF8.GetString(storePasswordBytes)); + _logger.LogDebug("Creating V1Secret object for PKCS12 data with name {SecretName} in namespace {NamespaceName}", + secretName, namespaceName); var secret = new V1Secret { ApiVersion = "v1", @@ -678,20 +766,20 @@ public V1Secret UpdatePKCS12SecretStore(K8SJobCertificate jobCertificate, string Type = "Opaque", Data = new Dictionary { - { certdataFieldName, p12bytes } + { certdataFieldName, p12Bytes } } }; if (existingPkcs12DataObj?.Data != null) { secret.Data = existingPkcs12DataObj.Data; - secret.Data[certdataFieldName] = p12bytes; + secret.Data[certdataFieldName] = p12Bytes; } // Convert p12bytes to pkcs12store var pkcs12StoreBuilder = new Pkcs12StoreBuilder(); var pkcs12Store = pkcs12StoreBuilder.Build(); - pkcs12Store.Load(new MemoryStream(p12bytes), storePasswd.ToCharArray()); + pkcs12Store.Load(new MemoryStream(p12Bytes), storePasswd.ToCharArray()); switch (string.IsNullOrEmpty(storePasswd)) @@ -1123,14 +1211,19 @@ private V1Secret CreateOrUpdatePKCS12Secret(string secretName, string namespaceN public V1Secret ReadBuddyPass(string secretName, string passwordSecretPath) { + _logger.MethodEntry(); // Lookup password secret path on cluster to see if it exists _logger.LogDebug("Attempting to lookup password secret path on cluster..."); var splitPasswordPath = passwordSecretPath.Split("/"); - // Assume secret pattern is namespace/secretName - var passwordSecretName = splitPasswordPath[splitPasswordPath.Length - 1]; + _logger.LogDebug("Split password secret path: {SplitPasswordPath}", splitPasswordPath.ToString()); + var passwordSecretName = splitPasswordPath[^1]; var passwordSecretNamespace = splitPasswordPath[0]; - _logger.LogDebug($"Attempting to lookup secret {passwordSecretName} in namespace {passwordSecretNamespace}"); + _logger.LogDebug("Attempting to lookup secret {PasswordSecretName} in namespace {PasswordSecretNamespace}", + passwordSecretName, passwordSecretNamespace); var passwordSecretResponse = Client.CoreV1.ReadNamespacedSecret(secretName, passwordSecretNamespace); + _logger.LogDebug("Successfully found secret {PasswordSecretName} in namespace {PasswordSecretNamespace}", + passwordSecretName, passwordSecretNamespace); + _logger.MethodExit(); return passwordSecretResponse; } @@ -1835,7 +1928,6 @@ private IEnumerable FetchNamespaces(string clusterName) private IEnumerable FilterNamespaces(IEnumerable namespaces, string[] nsList) { foreach (var nsObj in namespaces) - { if (nsList.Contains("all") || nsList.Contains(nsObj.Metadata.Name)) { _logger.LogDebug("Processing namespace: {Namespace}", nsObj.Metadata.Name); @@ -1845,7 +1937,6 @@ private IEnumerable FilterNamespaces(IEnumerable names { _logger.LogDebug("Skipping namespace '{Namespace}' as it does not match filter", nsObj.Metadata.Name); } - } } private void AddNamespaceLocation(List locations, string clusterName, string namespaceName) @@ -1864,9 +1955,7 @@ private void DiscoverSecretsInNamespace( Client.CoreV1.ListNamespacedSecret(namespaceName).Items); foreach (var secret in secrets) - { ProcessSecretIfSupported(secret, secType, allowedKeys, clusterName, namespaceName, locations); - } } private void ProcessSecretIfSupported( @@ -1894,7 +1983,6 @@ private T RetryPolicy(Func action) const double maxDelaySeconds = 30.0; for (var attempt = 1; attempt <= maxRetries; attempt++) - { try { return action(); @@ -1914,7 +2002,6 @@ private T RetryPolicy(Func action) attempt, maxRetries, ex.Message, delay.TotalSeconds); Thread.Sleep(delay); } - } throw new InvalidOperationException("Unexpected error in retry logic."); // This will never be reached } @@ -2008,28 +2095,6 @@ private void ParseOpaqueSecret(V1Secret secretData, string[] allowedKeys) } } - public struct JksSecret - { - public string SecretPath; - public string SecretFieldName; - public V1Secret Secret; - public string Password; - public string PasswordPath; - public List AllowedKeys; - public Dictionary Inventory; - } - - public struct Pkcs12Secret - { - public string SecretPath; - public string SecretFieldName; - public V1Secret Secret; - public string Password; - public string PasswordPath; - public List AllowedKeys; - public Dictionary Inventory; - } - public JksSecret GetJksSecret(string secretName, string namespaceName, string password = null, string passwordPath = null, List allowedKeys = null) { @@ -2344,6 +2409,28 @@ public V1Secret CreateOrUpdatePkcs12Secret(Pkcs12Secret k8SData, string kubeSecr return Client.CoreV1.ReplaceNamespacedSecret(s1, kubeSecretName, kubeNamespace); } + public struct JksSecret + { + public string SecretPath; + public string SecretFieldName; + public V1Secret Secret; + public string Password; + public string PasswordPath; + public List AllowedKeys; + public Dictionary Inventory; + } + + public struct Pkcs12Secret + { + public string SecretPath; + public string SecretFieldName; + public V1Secret Secret; + public string Password; + public string PasswordPath; + public List AllowedKeys; + public Dictionary Inventory; + } + public struct CsrObject { public string Csr; diff --git a/kubernetes-orchestrator-extension/Jobs/Discovery.cs b/kubernetes-orchestrator-extension/Jobs/Discovery.cs index 8d94260..3dded07 100644 --- a/kubernetes-orchestrator-extension/Jobs/Discovery.cs +++ b/kubernetes-orchestrator-extension/Jobs/Discovery.cs @@ -9,9 +9,9 @@ using System.Collections.Generic; using System.Linq; using Keyfactor.Extensions.Orchestrator.K8S.Clients; +using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; -using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; @@ -166,7 +166,8 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd //make secretAllowedKeys unique secretAllowedKeys = secretAllowedKeys.Distinct().ToArray(); - Logger.LogInformation("Discovering k8s secrets with allowed keys: `{AllowedKeys}` and type: `pkcs12`", + Logger.LogInformation( + "Discovering k8s secrets with allowed keys: `{AllowedKeys}` and type: `pkcs12`", string.Join(",", secretAllowedKeys)); Logger.LogDebug("Calling KubeClient.DiscoverSecrets()"); locations = KubeClient.DiscoverSecrets(secretAllowedKeys, "pkcs12", @@ -211,7 +212,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd //Status: 2=Success, 3=Warning, 4=Error Logger.LogError("Discovery job has failed due to an unknown error"); Logger.LogError("{Message}", ex.Message); - Logger.LogTrace("{Message}",ex.ToString()); + Logger.LogTrace("{Message}", ex.ToString()); // iterate through the inner exceptions var inner = ex.InnerException; while (inner != null) @@ -220,6 +221,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd Logger.LogTrace("{Message}", inner.ToString()); inner = inner.InnerException; } + Logger.LogInformation("End DISCOVERY for K8S Orchestrator Extension for job '{JobID}' with failure", config.JobId); return FailJob(ex.Message, config.JobHistoryId); @@ -238,7 +240,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId, - FailureMessage = "Discovered the following locations: " + string.Join(",\n", locations), + FailureMessage = "Discovered the following locations: " + string.Join(",\n", locations) }; } catch (Exception ex) @@ -254,6 +256,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd Logger.LogTrace("{Message}", inner.ToString()); inner = inner.InnerException; } + Logger.LogInformation("End DISCOVERY for K8S Orchestrator Extension for job '{JobID}' with failure", config.JobId); return FailJob(ex.Message, config.JobHistoryId); diff --git a/kubernetes-orchestrator-extension/Jobs/Inventory.cs b/kubernetes-orchestrator-extension/Jobs/Inventory.cs index 896ce05..ac849e0 100644 --- a/kubernetes-orchestrator-extension/Jobs/Inventory.cs +++ b/kubernetes-orchestrator-extension/Jobs/Inventory.cs @@ -59,7 +59,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd Logger.LogDebug("Host: {Host}", KubeClient.GetHost()); Logger.LogTrace("Inventory entering switch based on KubeSecretType: " + KubeSecretType + "..."); - + var hasPrivateKey = false; Logger.LogTrace("Inventory entering switch based on KubeSecretType: " + KubeSecretType + "..."); @@ -89,7 +89,8 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd KubeNamespace, KubeSecretName); return PushInventory(new List(), config.JobHistoryId, submitInventory, false, "WARNING: Store not found in Kubernetes cluster. Assuming empty inventory."); - } catch (Exception ex) + } + catch (Exception ex) { Logger.LogError("Inventory failed with exception: " + ex.Message); Logger.LogTrace(ex.Message); @@ -323,7 +324,7 @@ private Dictionary> HandleJKSSecret(JobConfiguration config KubeNamespace + " and key " + keyName); var keyPassword = getK8SStorePassword(k8sData.Secret); var passwordHash = GetSHA256Hash(keyPassword); - Logger.LogTrace("Password hash for '{Secret}/{Key}': {Hash}", KubeSecretName, keyName, passwordHash); + // Logger.LogTrace("Password hash for '{Secret}/{Key}': {Hash}", KubeSecretName, keyName, passwordHash); //TODO: Insecure comment out! var keyAlias = keyName; Logger.LogTrace("Key alias: {Alias}", keyAlias); Logger.LogDebug("Attempting to deserialize JKS store '{Secret}/{Key}'", KubeSecretName, keyName); diff --git a/kubernetes-orchestrator-extension/Jobs/JobBase.cs b/kubernetes-orchestrator-extension/Jobs/JobBase.cs index e3c9d62..8a15101 100644 --- a/kubernetes-orchestrator-extension/Jobs/JobBase.cs +++ b/kubernetes-orchestrator-extension/Jobs/JobBase.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -102,30 +101,22 @@ public class K8SJobCertificate public abstract class JobBase { - protected static readonly string[] SupportedKubeStoreTypes; - - private static readonly string[] RequiredProperties; private const string DefaultPFXSecretFieldName = "pfx"; private const string DefaultJKSSecretFieldName = "jks"; private const string DefaultPFXPasswordSecretFieldName = "password"; + protected const string CertChainSeparator = ","; + protected static readonly string[] SupportedKubeStoreTypes; + + private static readonly string[] RequiredProperties; + protected static readonly string[] TLSAllowedKeys; protected static readonly string[] OpaqueAllowedKeys; protected static readonly string[] CertAllowedKeys; protected static readonly string[] Pkcs12AllowedKeys; protected static readonly string[] JksAllowedKeys; - - protected internal bool SeparateChain { get; set; } = - false; //Don't arbitrarily change this to true without specifying BREAKING CHANGE in the release notes. - - protected internal bool IncludeCertChain { get; set; } = - true; //Don't arbitrarily change this to false without specifying BREAKING CHANGE in the release notes. - - protected internal string OperationType { get; set; } - protected internal bool SkipTlsValidation { get; set; } = false; - - protected const string CertChainSeparator = ","; + protected IPAMSecretResolver _resolver; protected KubeCertificateManagerClient KubeClient; @@ -143,12 +134,20 @@ static JobBase() JksAllowedKeys = new[] { "jks" }; } + + protected internal bool SeparateChain { get; set; } = + false; //Don't arbitrarily change this to true without specifying BREAKING CHANGE in the release notes. + + protected internal bool IncludeCertChain { get; set; } = + true; //Don't arbitrarily change this to false without specifying BREAKING CHANGE in the release notes. + + protected internal string OperationType { get; set; } + protected internal bool SkipTlsValidation { get; set; } + public K8SJobCertificate K8SCertificate { get; set; } protected internal string Capability { get; set; } - protected IPAMSecretResolver _resolver; - public string StorePath { get; set; } protected internal string KubeNamespace { get; set; } @@ -189,6 +188,10 @@ static JobBase() public string KubeCluster { get; set; } + public bool PasswordIsK8SSecret { get; set; } + + public object KubeSecretPassword { get; set; } + protected void InitializeStore(InventoryJobConfiguration config) { Logger ??= LogHandler.GetClassLogger(GetType()); @@ -202,13 +205,17 @@ protected void InitializeStore(InventoryJobConfiguration config) // Logger.LogTrace("Properties: {Properties}", props); // Commented out to avoid logging sensitive information ServerUsername = config.ServerUsername; + Logger.LogTrace("ServerUsername: {ServerUsername}", ServerUsername); + ServerPassword = config.ServerPassword; + if (!string.IsNullOrEmpty(ServerPassword)) Logger.LogTrace("ServerPassword: {ServerPassword}", ""); + StorePassword = config.CertificateStoreDetails?.StorePassword; + if (!string.IsNullOrEmpty(StorePassword)) Logger.LogTrace("StorePassword: {StorePassword}", ""); + StorePath = config.CertificateStoreDetails?.StorePath; - // StorePath = GetStorePath(); - Logger.LogTrace("ServerUsername: {ServerUsername}", ServerUsername); - // Logger.LogTrace($"ServerPassword: {ServerPassword}"); // Commented out to avoid logging sensitive information Logger.LogTrace("StorePath: {StorePath}", StorePath); + Logger.LogDebug("Calling InitializeProperties()"); InitializeProperties(props); Logger.LogDebug("Returned from InitializeStore()"); @@ -362,6 +369,14 @@ protected K8SJobCertificate InitJobCertificate(dynamic config) { pKeyPassword = ""; Logger.LogDebug("Certificate {CertThumbprint} does have a password", jobCertObject.CertThumbprint); + + if (config.JobCertificate == null || + string.IsNullOrEmpty(config.JobCertificate.Contents)) + { + Logger.LogError("Job certificate contents are null or empty, cannot initialize job certificate"); + return jobCertObject; + } + Logger.LogTrace("Calling Convert.FromBase64String()"); byte[] certBytes = Convert.FromBase64String(config.JobCertificate.Contents); Logger.LogTrace("Returned from Convert.FromBase64String()"); @@ -494,7 +509,7 @@ protected string ResolveStorePath(string spath) { Logger.LogInformation( "`StorePath`: `{StorePath}` is 1 part, assuming that it is the k8s secret name and setting 'KubeSecretName' to `{StorePath}`", - sPathParts[0],sPathParts[0]); + sPathParts[0], sPathParts[0]); KubeSecretName = sPathParts[0]; } else @@ -662,10 +677,14 @@ protected string ResolveStorePath(string spath) private void InitializeProperties(dynamic storeProperties) { - Logger.LogTrace("Entered InitializeProperties()"); + Logger.MethodEntry(); if (storeProperties == null) + { + Logger.MethodExit(); throw new ConfigurationException( $"Invalid configuration. Please provide {RequiredProperties}. Or review the documentation at https://github.com/Keyfactor/kubernetes-orchestrator#custom-fields-tab"); + } + // check if key is present and set values if not try @@ -731,7 +750,7 @@ private void InitializeProperties(dynamic storeProperties) //check if storeProperties contains ServerUsername key Logger.LogInformation("Attempting to resolve 'ServerUsername' from store properties or PAM provider"); var pamServerUsername = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "ServerUsername", ServerUsername); + PAMUtilities.ResolvePAMField(_resolver, Logger, "ServerUsername", ServerUsername); if (!string.IsNullOrEmpty(pamServerUsername)) { Logger.LogInformation( @@ -744,7 +763,7 @@ private void InitializeProperties(dynamic storeProperties) Logger.LogInformation( "ServerUsername not resolved from PAM provider, attempting to resolve 'Server Username' from store properties"); pamServerUsername = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "Server Username", ServerUsername); + PAMUtilities.ResolvePAMField(_resolver, Logger, "Server Username", ServerUsername); if (!string.IsNullOrEmpty(pamServerUsername)) { Logger.LogInformation( @@ -765,7 +784,7 @@ private void InitializeProperties(dynamic storeProperties) { Logger.LogInformation("Attempting to resolve 'ServerPassword' from store properties or PAM provider"); var pamServerPassword = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "ServerPassword", ServerPassword); + PAMUtilities.ResolvePAMField(_resolver, Logger, "ServerPassword", ServerPassword); if (!string.IsNullOrEmpty(pamServerPassword)) { Logger.LogInformation( @@ -778,7 +797,7 @@ private void InitializeProperties(dynamic storeProperties) Logger.LogInformation( "ServerPassword not resolved from PAM provider, attempting to resolve 'Server Password' from store properties"); pamServerPassword = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "Server Password", ServerPassword); + PAMUtilities.ResolvePAMField(_resolver, Logger, "Server Password", ServerPassword); if (!string.IsNullOrEmpty(pamServerPassword)) { Logger.LogInformation( @@ -803,7 +822,7 @@ private void InitializeProperties(dynamic storeProperties) { Logger.LogInformation("Attempting to resolve 'StorePassword' from store properties or PAM provider"); var pamStorePassword = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "StorePassword", StorePassword); + PAMUtilities.ResolvePAMField(_resolver, Logger, "StorePassword", StorePassword); if (!string.IsNullOrEmpty(pamStorePassword)) { Logger.LogInformation( @@ -815,7 +834,7 @@ private void InitializeProperties(dynamic storeProperties) Logger.LogInformation( "StorePassword not resolved from PAM provider, attempting to resolve 'Store Password' from store properties"); pamStorePassword = - (string)PAMUtilities.ResolvePAMField(_resolver, Logger, "Store Password", StorePassword); + PAMUtilities.ResolvePAMField(_resolver, Logger, "Store Password", StorePassword); if (!string.IsNullOrEmpty(pamStorePassword)) { Logger.LogInformation( @@ -826,9 +845,13 @@ private void InitializeProperties(dynamic storeProperties) } catch (Exception e) { - Logger.LogError( - "Unable to resolve 'StorePassword' from store properties or PAM provider, defaulting to empty string"); - StorePassword = ""; + if (string.IsNullOrEmpty(StorePassword)) + { + Logger.LogError( + "Unable to resolve 'StorePassword' from store properties or PAM provider, defaulting to empty string"); + StorePassword = ""; + } + Logger.LogError("{Message}", e.Message); Logger.LogTrace("{Message}", e.ToString()); Logger.LogTrace("{Trace}", e.StackTrace); @@ -879,24 +902,43 @@ private void InitializeProperties(dynamic storeProperties) case "jks": Logger.LogInformation( "Kubernetes certificate store type is 'jks'. Setting default values for 'PasswordFieldName' and 'CertificateDataFieldName'"); + Logger.LogDebug("Parsing 'PasswordFieldName' from store properties"); PasswordFieldName = storeProperties.ContainsKey("PasswordFieldName") ? storeProperties["PasswordFieldName"] : DefaultPFXPasswordSecretFieldName; + Logger.LogTrace("PasswordFieldName: {PasswordFieldName}", PasswordFieldName); + + Logger.LogDebug("Parsing 'PasswordIsSeparateSecret' from store properties"); PasswordIsSeparateSecret = storeProperties.ContainsKey("PasswordIsSeparateSecret") ? bool.Parse(storeProperties["PasswordIsSeparateSecret"]) : false; + Logger.LogTrace("PasswordIsSeparateSecret: {PasswordIsSeparateSecret}", PasswordIsSeparateSecret); + + Logger.LogDebug("Parsing 'StorePasswordPath' from store properties"); StorePasswordPath = storeProperties.ContainsKey("StorePasswordPath") ? storeProperties["StorePasswordPath"] : ""; - PasswordIsK8SSecret = storeProperties.ContainsKey("PasswordIsK8SSecret") - ? storeProperties["PasswordIsK8SSecret"] + Logger.LogTrace("StorePasswordPath: {StorePasswordPath}", StorePasswordPath); // TODO: Remove this it's insecure + + Logger.LogDebug("Parsing 'PasswordIsK8SSecret' from store properties"); + PasswordIsK8SSecret = storeProperties.ContainsKey("PasswordIsK8SSecret") && + !string.IsNullOrEmpty(storeProperties["PasswordIsK8SSecret"]?.ToString()) + ? bool.Parse(storeProperties["PasswordIsK8SSecret"].ToString()) : false; + Logger.LogTrace("PasswordIsK8SSecret: {PasswordIsK8SSecret}", PasswordIsK8SSecret); + + Logger.LogDebug("Parsing 'KubeSecretPassword' from store properties"); KubeSecretPassword = storeProperties.ContainsKey("KubeSecretPassword") ? storeProperties["KubeSecretPassword"] : ""; + Logger.LogTrace("KubeSecretPassword: {KubeSecretPassword}", KubeSecretPassword); + + Logger.LogDebug("Parsing 'CertificateDataFieldName' from store properties"); CertificateDataFieldName = storeProperties.ContainsKey("CertificateDataFieldName") ? storeProperties["CertificateDataFieldName"] : DefaultJKSSecretFieldName; + Logger.LogTrace("CertificateDataFieldName: {CertificateDataFieldName}", CertificateDataFieldName); + break; } @@ -939,12 +981,9 @@ private void InitializeProperties(dynamic storeProperties) Logger.LogWarning("KubeSecretName is empty, setting 'KubeSecretName' to StorePath"); KubeSecretName = StorePath; Logger.LogTrace("KubeSecretName: {KubeSecretName}", KubeSecretName); + Logger.MethodExit(); } - public bool PasswordIsK8SSecret { get; set; } = false; - - public object KubeSecretPassword { get; set; } - public string GetStorePath() { Logger.LogTrace("Entered GetStorePath()"); @@ -1192,7 +1231,7 @@ protected string ParseJobPrivateKey(ManagementJobConfiguration config) protected string getK8SStorePassword(V1Secret certData) { - Logger.LogDebug("Entered getK8SStorePassword()"); + Logger.MethodEntry(); Logger.LogDebug("Attempting to get store password from K8S secret"); var storePasswordBytes = Array.Empty(); @@ -1200,21 +1239,22 @@ protected string getK8SStorePassword(V1Secret certData) if (!string.IsNullOrEmpty(StorePassword)) { Logger.LogDebug("Using provided 'StorePassword'"); - // var passwordHash = GetSHA256Hash(StorePassword); - // Logger.LogTrace("Password hash: " + passwordHash); storePasswordBytes = Encoding.UTF8.GetBytes(StorePassword); } else if (!string.IsNullOrEmpty(StorePasswordPath)) { // Split password path into namespace and secret name Logger.LogDebug( - "Store password is null or empty and StorePasswordPath is set, attempting to read password from K8S buddy secret"); + "StorePassword is null or empty and StorePasswordPath is set, attempting to read password from K8S buddy secret at {StorePasswordPath}", + StorePasswordPath); Logger.LogTrace("Password path: {Path}", StorePasswordPath); Logger.LogTrace("Splitting password path by /"); var passwordPath = StorePasswordPath.Split("/"); Logger.LogDebug("Password path length: {Len}", passwordPath.Length.ToString()); - var passwordNamespace = ""; - var passwordSecretName = ""; + + string passwordNamespace; + string passwordSecretName; + if (passwordPath.Length == 1) { Logger.LogDebug("Password path length is 1, using KubeNamespace"); @@ -1237,24 +1277,33 @@ protected string getK8SStorePassword(V1Secret certData) Logger.LogDebug("Attempting to read K8S buddy secret"); var k8sPasswordObj = KubeClient.ReadBuddyPass(passwordSecretName, passwordNamespace); - storePasswordBytes = k8sPasswordObj.Data[PasswordFieldName]; - // var passwordHash = GetSHA256Hash(Encoding.UTF8.GetString(storePasswordBytes)); - // Logger.LogTrace("Password hash: {Pwd}", passwordHash); - if (storePasswordBytes == null) + if (k8sPasswordObj?.Data == null) { - Logger.LogError("Password not found in K8S buddy secret"); + Logger.LogError("Unable to read K8S buddy secret {SecretName} in namespace {Namespace}", + passwordSecretName, passwordNamespace); throw new InvalidK8SSecretException( - "Password not found in K8S buddy secret"); // todo: should this be thrown? + $"Unable to read K8S buddy secret {passwordSecretName} in namespace {passwordNamespace}"); } - Logger.LogDebug("K8S buddy secret read successfully"); + Logger.LogTrace("Secret response fields: {Keys}", k8sPasswordObj.Data.Keys); + + if (!k8sPasswordObj.Data.TryGetValue(PasswordFieldName, out storePasswordBytes) || + storePasswordBytes == null) + { + Logger.LogError("Unable to find password field {FieldName}", PasswordFieldName); + throw new InvalidK8SSecretException( + $"Unable to find password field '{PasswordFieldName}' in secret '{passwordSecretName}' in namespace '{passwordNamespace}'" + ); + } + + Logger.LogDebug( + "Successfully read password from K8S buddy secret '{SecretName}' in namespace '{Namespace}'", + passwordSecretName, passwordNamespace); } else if (certData != null && certData.Data.TryGetValue(PasswordFieldName, out var value1)) { Logger.LogDebug("Attempting to read password from PasswordFieldName"); storePasswordBytes = value1; - // var passwordHash = GetSHA256Hash(Encoding.UTF8.GetString(storePasswordBytes)); - // Logger.LogTrace("Password hash: {Pwd}", passwordHash); if (storePasswordBytes == null) { Logger.LogError("Password not found in K8S secret"); @@ -1265,8 +1314,7 @@ protected string getK8SStorePassword(V1Secret certData) } else { - Logger.LogDebug("No password found"); - var passwdEx = ""; + string passwdEx; if (!string.IsNullOrEmpty(StorePasswordPath)) passwdEx = "Store secret '" + StorePasswordPath + "'did not contain key '" + CertificateDataFieldName + "' or '" + PasswordFieldName + "'" + @@ -1280,10 +1328,12 @@ protected string getK8SStorePassword(V1Secret certData) //convert password to string var storePassword = Encoding.UTF8.GetString(storePasswordBytes); - // Logger.LogTrace("Store password: {Pwd}", storePassword); - // var passwordHash2 = GetSHA256Hash(storePassword); - // Logger.LogTrace("Password hash: {Pwd}", passwordHash2); - Logger.LogDebug("Returning store password"); + Logger.LogTrace("K8S Store Password show new lines: {StorePassword}", storePassword.Replace("\n","\\n")); + // remove any trailing new line characters from the string + storePassword = storePassword.TrimEnd('\r','\n'); + Logger.LogTrace("Store password bytes converted to string: {StorePassword}", + storePassword); //TODO: INSECURE COMMENT OUT + Logger.MethodExit(); return storePassword; } @@ -1304,7 +1354,7 @@ protected Pkcs12Store LoadPkcs12Store(byte[] pkcs12Data, string password) protected string GetCertificatePem(Pkcs12Store store, string password, string alias = "") { Logger.LogDebug("Entered GetCertificatePem()"); - if (string.IsNullOrEmpty(alias)) alias = store.Aliases.Cast().FirstOrDefault(store.IsKeyEntry); + if (string.IsNullOrEmpty(alias)) alias = store.Aliases.FirstOrDefault(store.IsKeyEntry); Logger.LogDebug("Attempting to get certificate with alias {Alias}", alias); var cert = store.GetCertificate(alias).Certificate; @@ -1352,7 +1402,7 @@ protected List getCertChain(Pkcs12Store store, string password, string a if (string.IsNullOrEmpty(alias)) { Logger.LogDebug("Alias is empty, attempting to get key entry alias"); - alias = store.Aliases.Cast().FirstOrDefault(store.IsKeyEntry); + alias = store.Aliases.FirstOrDefault(store.IsKeyEntry); } var chain = new List(); diff --git a/kubernetes-orchestrator-extension/Jobs/Management.cs b/kubernetes-orchestrator-extension/Jobs/Management.cs index e1ca522..2c79ac6 100644 --- a/kubernetes-orchestrator-extension/Jobs/Management.cs +++ b/kubernetes-orchestrator-extension/Jobs/Management.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using k8s.Autorest; using k8s.Models; using Keyfactor.Extensions.Orchestrator.K8S.Clients; @@ -17,7 +16,6 @@ using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; -using Keyfactor.PKI.PEM; using Microsoft.Extensions.Logging; namespace Keyfactor.Extensions.Orchestrator.K8S.Jobs; @@ -52,6 +50,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) //NLog Logging to c:\CMS\Logs\CMS_Agent_Log.txt Logger = LogHandler.GetClassLogger(GetType()); + Logger.MethodEntry(); K8SJobCertificate jobCertObj; try { @@ -85,10 +84,12 @@ public JobResult ProcessJob(ManagementJobConfiguration config) case CertStoreOperationType.Add: case CertStoreOperationType.Create: //OperationType == Add - Add a certificate to the certificate store passed in the config object - Logger.LogInformation($"Processing Management-{config.OperationType.GetType()} job for certificate '{config.JobCertificate.Alias}'..."); + Logger.LogInformation( + $"Processing Management-{config.OperationType.GetType()} job for certificate '{config.JobCertificate.Alias}'..."); return HandleCreateOrUpdate(KubeSecretType, config, jobCertObj, Overwrite); case CertStoreOperationType.Remove: - Logger.LogInformation($"Processing Management-{config.OperationType.GetType()} job for certificate '{config.JobCertificate.Alias}'..."); + Logger.LogInformation( + $"Processing Management-{config.OperationType.GetType()} job for certificate '{config.JobCertificate.Alias}'..."); return HandleRemove(KubeSecretType, config); case CertStoreOperationType.Unknown: case CertStoreOperationType.Inventory: @@ -99,10 +100,13 @@ public JobResult ProcessJob(ManagementJobConfiguration config) case CertStoreOperationType.FetchLogs: Logger.LogInformation("End MANAGEMENT for K8S Orchestrator Extension for job " + config.JobId + $" - OperationType '{config.OperationType.GetType()}' not supported by Kubernetes certificate store job. Failed!"); - return FailJob($"OperationType '{config.OperationType.GetType()}' not supported by Kubernetes certificate store job.", config.JobHistoryId); + return FailJob( + $"OperationType '{config.OperationType.GetType()}' not supported by Kubernetes certificate store job.", + config.JobHistoryId); default: //Invalid OperationType. Return error. Should never happen though - var impError = $"Invalid OperationType '{config.OperationType.GetType()}' passed to Kubernetes certificate store job. This should never happen."; + var impError = + $"Invalid OperationType '{config.OperationType.GetType()}' passed to Kubernetes certificate store job. This should never happen."; Logger.LogError(impError); Logger.LogInformation("End MANAGEMENT for K8S Orchestrator Extension for job " + config.JobId + $" - OperationType '{config.OperationType.GetType()}' not supported by Kubernetes certificate store job. Failed!"); @@ -115,7 +119,8 @@ public JobResult ProcessJob(ManagementJobConfiguration config) Logger.LogError(ex.Message); Logger.LogTrace(ex.StackTrace); //Status: 2=Success, 3=Warning, 4=Error - Logger.LogInformation("End MANAGEMENT for K8S Orchestrator Extension for job " + config.JobId + " with failure."); + Logger.LogInformation("End MANAGEMENT for K8S Orchestrator Extension for job " + config.JobId + + " with failure."); return FailJob(ex.Message, config.JobHistoryId); } } @@ -123,7 +128,8 @@ public JobResult ProcessJob(ManagementJobConfiguration config) private V1Secret creatEmptySecret(string secretType) { - Logger.LogWarning("Certificate object and certificate alias are both null or empty. Assuming this is a 'create_store' action and populating an empty store."); + Logger.LogWarning( + "Certificate object and certificate alias are both null or empty. Assuming this is a 'create_store' action and populating an empty store."); var emptyStrArray = Array.Empty(); var createResponse = KubeClient.CreateOrUpdateCertificateStoreSecret( "", @@ -141,14 +147,15 @@ private V1Secret creatEmptySecret(string secretType) return createResponse; } - private V1Secret HandleOpaqueSecret(string certAlias, K8SJobCertificate certObj, string keyPasswordStr = "", bool overwrite = false, bool append = false) + private V1Secret HandleOpaqueSecret(string certAlias, K8SJobCertificate certObj, string keyPasswordStr = "", + bool overwrite = false, bool append = false) { Logger.LogTrace("Entered HandleOpaqueSecret()"); Logger.LogTrace("certAlias: " + certAlias); // Logger.LogTrace("keyPasswordStr: " + keyPasswordStr); Logger.LogTrace("overwrite: " + overwrite); Logger.LogTrace("append: " + append); - + Logger.LogDebug("Calling CreateOrUpdateCertificateStoreSecret() to create or update secret in Kubernetes..."); var createResponse = KubeClient.CreateOrUpdateCertificateStoreSecret( certObj.PrivateKeyPem, @@ -161,23 +168,18 @@ private V1Secret HandleOpaqueSecret(string certAlias, K8SJobCertificate certObj, overwrite ); if (createResponse == null) - { Logger.LogError("createResponse is null"); - } else - { Logger.LogTrace(createResponse.ToString()); - } Logger.LogInformation( $"Successfully created or updated secret '{KubeSecretName}' in Kubernetes namespace '{KubeNamespace}' on cluster '{KubeClient.GetHost()}' with certificate '{certAlias}'"); return createResponse; - } - + private V1Secret HandleJksSecret(ManagementJobConfiguration config, bool remove = false) { - Logger.LogDebug("Entering HandleJKSSecret()..."); + Logger.MethodEntry(); // get the jks store from the secret Logger.LogDebug("Attempting to serialize JKS store"); var jksStore = new JksCertificateStoreSerializer(config.JobProperties?.ToString()); @@ -188,58 +190,68 @@ private V1Secret HandleJksSecret(ManagementJobConfiguration config, bool remove Logger.LogTrace("OperationType is: {OperationType}", config.OperationType.GetType()); try { - Logger.LogDebug("Attempting to get JKS store from Kubernetes secret {Name} in namespace {Namespace}", KubeSecretName, KubeNamespace); + Logger.LogDebug("Attempting to get JKS store from Kubernetes secret {Name} in namespace {Namespace}", + KubeSecretName, KubeNamespace); k8sData = KubeClient.GetJksSecret(KubeSecretName, KubeNamespace); } catch (StoreNotFoundException) { if (config.OperationType == CertStoreOperationType.Remove) { - Logger.LogWarning("Secret '{Name}' not found in Kubernetes namespace '{Ns}' so nothing to remove...", KubeSecretName, KubeNamespace); + Logger.LogWarning( + "Secret '{Name}' not found in Kubernetes namespace '{Ns}' so nothing to remove...", + KubeSecretName, KubeNamespace); return null; } - Logger.LogWarning("Secret '{Name}' not found in Kubernetes namespace '{Ns}' so creating new secret...", KubeSecretName, KubeNamespace); + + Logger.LogWarning("Secret '{Name}' not found in Kubernetes namespace '{Ns}' so creating new secret...", + KubeSecretName, KubeNamespace); } } + // get newCert bytes from config.JobCertificate.Contents Logger.LogDebug("Attempting to get newCert bytes from config.JobCertificate.Contents"); - var newCertBytes = Convert.FromBase64String(config.JobCertificate.Contents); + var newCertBytes = config.JobCertificate?.Contents == null + ? [] + : Convert.FromBase64String(config.JobCertificate.Contents); - var alias = config.JobCertificate.Alias; - Logger.LogTrace("alias: " + alias); + var alias = string.IsNullOrEmpty(config.JobCertificate?.Alias) ? "default" : config.JobCertificate.Alias; + Logger.LogTrace("alias: {Alias}", alias); var existingDataFieldName = "jks"; // if alias contains a '/' then the pattern is 'k8s-secret-field-name/alias' - if (alias.Contains('/')) + if (!string.IsNullOrEmpty(alias) && alias.Contains('/')) { Logger.LogDebug("alias contains a '/' so splitting on '/'..."); var aliasParts = alias.Split("/"); existingDataFieldName = aliasParts[0]; alias = aliasParts[1]; } + Logger.LogTrace("existingDataFieldName: {Name}", existingDataFieldName); Logger.LogTrace("alias: {Alias}", alias); byte[] existingData = null; if (k8sData.Secret?.Data != null) { - Logger.LogDebug("k8sData.Secret.Data is not null so attempting to get existingData from secret data field {Name}...", existingDataFieldName); + Logger.LogDebug( + "k8sData.Secret.Data is not null so attempting to get existingData from secret data field {Name}...", + existingDataFieldName); existingData = k8sData.Secret.Data.TryGetValue(existingDataFieldName, out var value) ? value : null; } if (!string.IsNullOrEmpty(config.CertificateStoreDetails.StorePassword)) { - Logger.LogDebug("StorePassword is not null or empty so setting StorePassword to config.CertificateStoreDetails.StorePassword"); + Logger.LogDebug( + "StorePassword is not null or empty so setting StorePassword to config.CertificateStoreDetails.StorePassword"); StorePassword = config.CertificateStoreDetails.StorePassword; - var hashedStorePassword = GetSHA256Hash(StorePassword); - Logger.LogTrace("hashedStorePassword: {Hash}", hashedStorePassword); } + Logger.LogDebug("Getting store password"); var sPass = getK8SStorePassword(k8sData.Secret); - var hashedSPass = GetSHA256Hash(sPass); - Logger.LogTrace("hashedStorePassword: {Hash}", hashedSPass); Logger.LogDebug("Calling CreateOrUpdateJks()..."); try { - var newJksStore = jksStore.CreateOrUpdateJks(newCertBytes, config.JobCertificate.PrivateKeyPassword, alias, existingData, sPass, remove); + var newJksStore = jksStore.CreateOrUpdateJks(newCertBytes, config.JobCertificate?.PrivateKeyPassword, alias, + existingData, sPass, remove); if (k8sData.Inventory == null || k8sData.Inventory.Count == 0) { Logger.LogDebug("k8sData.JksInventory is null or empty so creating new Dictionary..."); @@ -251,6 +263,7 @@ private V1Secret HandleJksSecret(ManagementJobConfiguration config, bool remove Logger.LogDebug("k8sData.JksInventory is not null or empty so updating existing Dictionary..."); k8sData.Inventory[existingDataFieldName] = newJksStore; } + // update the secret Logger.LogDebug("Calling CreateOrUpdateJksSecret()..."); var updateResponse = KubeClient.CreateOrUpdateJksSecret(k8sData, KubeSecretName, KubeNamespace); @@ -261,7 +274,6 @@ private V1Secret HandleJksSecret(ManagementJobConfiguration config, bool remove { return HandlePkcs12Secret(config, remove); } - } private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remove = false) @@ -272,7 +284,6 @@ private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remo //getPkcs12BytesFromKubeSecret var k8sData = new KubeCertificateManagerClient.Pkcs12Secret(); if (config.OperationType is CertStoreOperationType.Add or CertStoreOperationType.Remove) - { try { k8sData = KubeClient.GetPkcs12Secret(KubeSecretName, KubeNamespace); @@ -284,9 +295,9 @@ private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remo Logger.LogWarning("Secret {Name} not found in Kubernetes, nothing to remove...", KubeSecretName); return null; } + Logger.LogWarning("Secret {Name} not found in Kubernetes, creating new secret...", KubeSecretName); } - } // get newCert bytes from config.JobCertificate.Contents var newCertBytes = Convert.FromBase64String(config.JobCertificate.Contents); @@ -307,18 +318,15 @@ private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remo Logger.LogDebug("alias: " + alias); byte[] existingData = null; if (k8sData.Secret?.Data != null) - { existingData = k8sData.Secret.Data.TryGetValue(existingDataFieldName, out var value) ? value : null; - } if (!string.IsNullOrEmpty(config.CertificateStoreDetails.StorePassword)) - { StorePassword = config.CertificateStoreDetails.StorePassword; - } Logger.LogDebug("Getting store password"); var sPass = getK8SStorePassword(k8sData.Secret); Logger.LogDebug("Calling CreateOrUpdatePkcs12()..."); - var newPkcs12Store = pkcs12Store.CreateOrUpdatePkcs12(newCertBytes, config.JobCertificate.PrivateKeyPassword, alias, existingData, sPass, remove); + var newPkcs12Store = pkcs12Store.CreateOrUpdatePkcs12(newCertBytes, config.JobCertificate.PrivateKeyPassword, + alias, existingData, sPass, remove); if (k8sData.Inventory == null || k8sData.Inventory.Count == 0) { Logger.LogDebug("k8sData.Pkcs12Inventory is null or empty so creating new Dictionary..."); @@ -330,6 +338,7 @@ private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remo Logger.LogDebug("k8sData.Pkcs12Inventory is not null or empty so updating existing Dictionary..."); k8sData.Inventory[existingDataFieldName] = newPkcs12Store; } + // update the secret Logger.LogDebug("Calling CreateOrUpdatePkcs12Secret()..."); var updateResponse = KubeClient.CreateOrUpdatePkcs12Secret(k8sData, KubeSecretName, KubeNamespace); @@ -389,7 +398,8 @@ private V1Secret HandlePkcs12Secret(ManagementJobConfiguration config, bool remo // return createResponse; // } - private V1Secret HandleTlsSecret(string certAlias, K8SJobCertificate certObj, string certPassword, bool overwrite = false, bool append = true) + private V1Secret HandleTlsSecret(string certAlias, K8SJobCertificate certObj, string certPassword, + bool overwrite = false, bool append = true) { Logger.LogTrace("Entered HandleTlsSecret()"); Logger.LogTrace("certAlias: " + certAlias); @@ -409,14 +419,12 @@ private V1Secret HandleTlsSecret(string certAlias, K8SJobCertificate certObj, st catch (Exception ex) { if (!string.IsNullOrEmpty(certAlias)) - { Logger.LogWarning("This is fine"); - } else - { - Logger.LogError(ex, "Unknown error processing HandleTlsSecret(). Will try to continue as if everything is fine...for now."); - } + Logger.LogError(ex, + "Unknown error processing HandleTlsSecret(). Will try to continue as if everything is fine...for now."); } + var pemString = certObj.CertPem; Logger.LogTrace("pemString: " + pemString); @@ -434,16 +442,14 @@ private V1Secret HandleTlsSecret(string certAlias, K8SJobCertificate certObj, st string[] keyPems = { "" }; - Logger.LogInformation($"Secret type is 'tls_secret', so extracting private key from certificate '{certAlias}'..."); + Logger.LogInformation( + $"Secret type is 'tls_secret', so extracting private key from certificate '{certAlias}'..."); Logger.LogTrace("Calling GetKeyBytes() to extract private key from certificate..."); var keyBytes = certObj.PrivateKeyBytes; var keyPem = certObj.PrivateKeyPem; - if (!string.IsNullOrEmpty(keyPem)) - { - keyPems = new[] { keyPem }; - } + if (!string.IsNullOrEmpty(keyPem)) keyPems = new[] { keyPem }; Logger.LogDebug("Calling CreateOrUpdateCertificateStoreSecret() to create or update secret in Kubernetes..."); var createResponse = KubeClient.CreateOrUpdateCertificateStoreSecret( @@ -457,27 +463,24 @@ private V1Secret HandleTlsSecret(string certAlias, K8SJobCertificate certObj, st overwrite ); if (createResponse == null) - { Logger.LogError("createResponse is null"); - } else - { Logger.LogTrace(createResponse.ToString()); - } Logger.LogInformation( $"Successfully created or updated secret '{KubeSecretName}' in Kubernetes namespace '{KubeNamespace}' on cluster '{KubeClient.GetHost()}' with certificate '{certAlias}'"); return createResponse; } - private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfiguration config, K8SJobCertificate jobCertObj, bool overwrite = false) + private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfiguration config, + K8SJobCertificate jobCertObj, bool overwrite = false) { var certPassword = jobCertObj.Password; Logger.LogDebug("Entered HandleCreateOrUpdate()"); var jobCert = config.JobCertificate; var certAlias = config.JobCertificate.Alias; - if (string.IsNullOrEmpty(certAlias)) + if (string.IsNullOrEmpty(certAlias) && !string.IsNullOrEmpty(jobCertObj.CertThumbprint)) { certAlias = jobCertObj.CertThumbprint; } @@ -513,15 +516,17 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura case "tls": case "tlssecret": case "tls_secrets": - Logger.LogInformation("Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + certAlias + "..."); + Logger.LogInformation("Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + + certAlias + "..."); _ = HandleTlsSecret(certAlias, jobCertObj, certPassword, overwrite); Logger.LogInformation("Successfully called HandleTlsSecret() for certificate " + certAlias + "."); break; case "opaque": case "secret": case "secrets": - Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + certAlias + "..."); - _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite, false); + Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + + certAlias + "..."); + _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite); Logger.LogInformation("Successfully called HandleOpaqueSecret() for certificate " + certAlias + "."); break; case "certificate": @@ -536,7 +541,8 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura return FailJob(csrErrorMsg, config.JobHistoryId); case "pfx": case "pkcs12": - Logger.LogInformation("Secret type is 'pkcs12', calling HandlePKCS12Secret() for certificate " + certAlias + "..."); + Logger.LogInformation("Secret type is 'pkcs12', calling HandlePKCS12Secret() for certificate " + + certAlias + "..."); _ = HandlePkcs12Secret(config); Logger.LogInformation("Successfully called HandlePKCS12Secret() for certificate " + certAlias + "."); break; @@ -550,26 +556,34 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura var splitAlias = jobCertObj.Alias.Split("/"); if (splitAlias.Length < 2) { - var invalidAliasErrMsg = "Invalid alias format for K8SNS store type. Alias pattern: `/` where `secret_type` is one of 'opaque' or 'tls' and `secret_name` is the name of the secret."; + var invalidAliasErrMsg = + "Invalid alias format for K8SNS store type. Alias pattern: `/` where `secret_type` is one of 'opaque' or 'tls' and `secret_name` is the name of the secret."; Logger.LogError(invalidAliasErrMsg); Logger.LogInformation("End MANAGEMENT job " + config.JobId + " " + invalidAliasErrMsg + " Failed!"); return FailJob(invalidAliasErrMsg, config.JobHistoryId); } + KubeSecretType = splitAlias[^2]; KubeSecretName = splitAlias[^1]; - Logger.LogDebug("Handling managment add job for K8SNS secret type '" + KubeSecretType + "(" + jobCertObj.Alias + ")'..."); + Logger.LogDebug("Handling managment add job for K8SNS secret type '" + KubeSecretType + "(" + + jobCertObj.Alias + ")'..."); switch (KubeSecretType) { case "tls": - Logger.LogInformation("Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + certAlias + "..."); + Logger.LogInformation( + "Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + certAlias + + "..."); _ = HandleTlsSecret(certAlias, jobCertObj, certPassword, overwrite); - Logger.LogInformation("Successfully called HandleTlsSecret() for certificate " + certAlias + "."); + Logger.LogInformation( + "Successfully called HandleTlsSecret() for certificate " + certAlias + "."); break; case "opaque": - Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + certAlias + "..."); - _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite, false); - Logger.LogInformation("Successfully called HandleOpaqueSecret() for certificate " + certAlias + "."); + Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + + certAlias + "..."); + _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite); + Logger.LogInformation("Successfully called HandleOpaqueSecret() for certificate " + certAlias + + "."); break; default: { @@ -579,13 +593,14 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura return FailJob(nsErrMsg, config.JobHistoryId); } } + break; case "cluster": jobCertObj.Alias = config.JobCertificate.Alias; // Split alias by / and get second to last element KubeSecretType //pattern: namespace/secrets/secret_type/secert_name var clusterSplitAlias = jobCertObj.Alias.Split("/"); - + // Check splitAlias length if (clusterSplitAlias.Length < 3) { @@ -594,23 +609,29 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura Logger.LogInformation("End MANAGEMENT job " + config.JobId + " " + invalidAliasErrMsg + " Failed!"); return FailJob(invalidAliasErrMsg, config.JobHistoryId); } - + KubeSecretType = clusterSplitAlias[^2]; KubeSecretName = clusterSplitAlias[^1]; KubeNamespace = clusterSplitAlias[0]; - Logger.LogDebug("Handling managment add job for K8SNS secret type '" + KubeSecretType + "(" + jobCertObj.Alias + ")'..."); + Logger.LogDebug("Handling managment add job for K8SNS secret type '" + KubeSecretType + "(" + + jobCertObj.Alias + ")'..."); switch (KubeSecretType) { case "tls": - Logger.LogInformation("Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + certAlias + "..."); + Logger.LogInformation( + "Secret type is 'tls_secret', calling HandleTlsSecret() for certificate " + certAlias + + "..."); _ = HandleTlsSecret(certAlias, jobCertObj, certPassword, overwrite); - Logger.LogInformation("Successfully called HandleTlsSecret() for certificate " + certAlias + "."); + Logger.LogInformation( + "Successfully called HandleTlsSecret() for certificate " + certAlias + "."); break; case "opaque": - Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + certAlias + "..."); - _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite, false); - Logger.LogInformation("Successfully called HandleOpaqueSecret() for certificate " + certAlias + "."); + Logger.LogInformation("Secret type is 'secret', calling HandleOpaqueSecret() for certificate " + + certAlias + "..."); + _ = HandleOpaqueSecret(certAlias, jobCertObj, certPassword, overwrite); + Logger.LogInformation("Successfully called HandleOpaqueSecret() for certificate " + certAlias + + "."); break; default: { @@ -620,6 +641,7 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura return FailJob(nsErrMsg, config.JobHistoryId); } } + break; default: var errMsg = $"Unsupported secret type {secretType}."; @@ -627,6 +649,7 @@ private JobResult HandleCreateOrUpdate(string secretType, ManagementJobConfigura Logger.LogInformation("End MANAGEMENT job " + config.JobId + " " + errMsg + " Failed!"); return FailJob(errMsg, config.JobHistoryId); } + Logger.LogInformation("End MANAGEMENT job " + config.JobId + " Success!"); return SuccessJob(config.JobHistoryId); } @@ -666,10 +689,7 @@ private JobResult HandleRemove(string secretType, ManagementJobConfiguration con // Split alias by / and get second to last element KubeSecretType KubeSecretType = splitAlias[^2]; KubeSecretName = splitAlias[^1]; - if (string.IsNullOrEmpty(KubeNamespace)) - { - KubeNamespace = StorePath; - } + if (string.IsNullOrEmpty(KubeNamespace)) KubeNamespace = StorePath; } else if (Capability.Contains("K8SCluster")) { @@ -678,6 +698,7 @@ private JobResult HandleRemove(string secretType, ManagementJobConfiguration con KubeNamespace = splitAlias[0]; } } + Logger.LogInformation( $"Removing certificate '{certAlias}' from Kubernetes client '{kubeHost}' cert store {KubeSecretName} in namespace {KubeNamespace}..."); Logger.LogTrace("Calling DeleteCertificateStoreSecret() to remove certificate from Kubernetes..."); @@ -689,7 +710,8 @@ private JobResult HandleRemove(string secretType, ManagementJobConfiguration con KubeSecretType, jobCert.Alias ); - Logger.LogTrace($"REMOVE '{kubeHost}/{KubeNamespace}/{KubeSecretType}/{KubeSecretName}' response from Kubernetes:\n\t{response}"); + Logger.LogTrace( + $"REMOVE '{kubeHost}/{KubeNamespace}/{KubeSecretType}/{KubeSecretName}' response from Kubernetes:\n\t{response}"); } catch (HttpOperationException rErr) { @@ -706,7 +728,8 @@ private JobResult HandleRemove(string secretType, ManagementJobConfiguration con } catch (Exception e) { - Logger.LogError(e, $"Error removing certificate '{certAlias}' from Kubernetes client '{kubeHost}' cert store {KubeSecretName} in namespace {KubeNamespace}."); + Logger.LogError(e, + $"Error removing certificate '{certAlias}' from Kubernetes client '{kubeHost}' cert store {KubeSecretName} in namespace {KubeNamespace}."); Logger.LogInformation("End MANAGEMENT job " + config.JobId + " Failed!"); return FailJob(e.Message, config.JobHistoryId); } @@ -714,4 +737,4 @@ private JobResult HandleRemove(string secretType, ManagementJobConfiguration con Logger.LogInformation("End MANAGEMENT job " + config.JobId + " Success!"); return SuccessJob(config.JobHistoryId); } -} +} \ No newline at end of file diff --git a/kubernetes-orchestrator-extension/Jobs/PAMUtilities.cs b/kubernetes-orchestrator-extension/Jobs/PAMUtilities.cs index 2749e54..873d0c4 100644 --- a/kubernetes-orchestrator-extension/Jobs/PAMUtilities.cs +++ b/kubernetes-orchestrator-extension/Jobs/PAMUtilities.cs @@ -7,11 +7,10 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; -using Unity.Injection; namespace Keyfactor.Extensions.Orchestrator.K8S.Jobs; -class PAMUtilities +internal class PAMUtilities { internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) { @@ -21,19 +20,16 @@ internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logg logger.LogWarning("PAM field is empty, skipping PAM resolution"); return key; } - + // test if field is JSON string if (key.StartsWith("{") && key.EndsWith("}")) { - var resolved = resolver.Resolve(key); - if (string.IsNullOrEmpty(resolved)) - { - logger.LogWarning("Failed to resolve PAM field {Name}", name); - } + var resolved = resolver.Resolve(key); + if (string.IsNullOrEmpty(resolved)) logger.LogWarning("Failed to resolve PAM field {Name}", name); return resolved; } - + logger.LogDebug("Field '{Name}' is not a JSON string, skipping PAM resolution", name); return key; } -} +} \ No newline at end of file diff --git a/kubernetes-orchestrator-extension/Keyfactor.Orchestrators.K8S.csproj b/kubernetes-orchestrator-extension/Keyfactor.Orchestrators.K8S.csproj index ab2f54c..1442605 100644 --- a/kubernetes-orchestrator-extension/Keyfactor.Orchestrators.K8S.csproj +++ b/kubernetes-orchestrator-extension/Keyfactor.Orchestrators.K8S.csproj @@ -1,30 +1,37 @@  - - false - net6.0 - Keyfactor.Extensions.Orchestrator.K8S - latest - true - true - Keyfactor.Orchestrators.K8S - + + true + net6.0;net8.0 + Keyfactor.Extensions.Orchestrator.K8S + latest + true + true + Keyfactor.Orchestrators.K8S - - - Always - - + true + portable + false + - - - - - - - - - - + + + Always + + + + + + + + + + + + + + + + diff --git a/kubernetes-orchestrator-extension/Models/SerializedStoreInfo.cs b/kubernetes-orchestrator-extension/Models/SerializedStoreInfo.cs index c736d2c..e5af7d7 100644 --- a/kubernetes-orchestrator-extension/Models/SerializedStoreInfo.cs +++ b/kubernetes-orchestrator-extension/Models/SerializedStoreInfo.cs @@ -7,12 +7,11 @@ using System.Security.Cryptography.X509Certificates; -namespace Keyfactor.Extensions.Orchestrator.K8S.Models +namespace Keyfactor.Extensions.Orchestrator.K8S.Models; + +internal class SerializedStoreInfo : X509Certificate2 { - class SerializedStoreInfo : X509Certificate2 - { - public string FilePath { get; set; } + public string FilePath { get; set; } - public byte[] Contents { get; set; } - } -} + public byte[] Contents { get; set; } +} \ No newline at end of file diff --git a/kubernetes-orchestrator-extension/StoreTypes/ICertificateStoreSerializer.cs b/kubernetes-orchestrator-extension/StoreTypes/ICertificateStoreSerializer.cs index be5393f..5f859be 100644 --- a/kubernetes-orchestrator-extension/StoreTypes/ICertificateStoreSerializer.cs +++ b/kubernetes-orchestrator-extension/StoreTypes/ICertificateStoreSerializer.cs @@ -9,14 +9,14 @@ using Keyfactor.Extensions.Orchestrator.K8S.Models; using Org.BouncyCastle.Pkcs; -namespace Keyfactor.Extensions.Orchestrator.K8S.StoreTypes +namespace Keyfactor.Extensions.Orchestrator.K8S.StoreTypes; + +internal interface ICertificateStoreSerializer { - interface ICertificateStoreSerializer - { - Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword); + Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword); - List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, string storeFileName, string storePassword); + List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, + string storeFileName, string storePassword); - string GetPrivateKeyPath(); - } -} + string GetPrivateKeyPath(); +} \ No newline at end of file diff --git a/kubernetes-orchestrator-extension/StoreTypes/K8SJKS/Store.cs b/kubernetes-orchestrator-extension/StoreTypes/K8SJKS/Store.cs index c57fb80..837a176 100644 --- a/kubernetes-orchestrator-extension/StoreTypes/K8SJKS/Store.cs +++ b/kubernetes-orchestrator-extension/StoreTypes/K8SJKS/Store.cs @@ -15,374 +15,426 @@ using Keyfactor.Extensions.Orchestrator.K8S.Models; using Keyfactor.Logging; using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; -namespace Keyfactor.Extensions.Orchestrator.K8S.StoreTypes.K8SJKS +namespace Keyfactor.Extensions.Orchestrator.K8S.StoreTypes.K8SJKS; + +internal class JksCertificateStoreSerializer : ICertificateStoreSerializer { - class JksCertificateStoreSerializer : ICertificateStoreSerializer + private readonly ILogger _logger; + + public JksCertificateStoreSerializer(string storeProperties) + { + _logger = LogHandler.GetClassLogger(GetType()); + } + + public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword) { - private readonly ILogger _logger; - public JksCertificateStoreSerializer(string storeProperties) + _logger.MethodEntry(); + var storeBuilder = new Pkcs12StoreBuilder(); + var pkcs12Store = storeBuilder.Build(); + var pkcs12StoreNew = storeBuilder.Build(); + + _logger.LogTrace("storePath: {Path}", storePath); + + if (string.IsNullOrEmpty(storePassword)) { - _logger = LogHandler.GetClassLogger(GetType()); + _logger.LogError("JKS store password is null or empty for store at path '{Path}'", storePath); + throw new ArgumentException("JKS store password is null or empty"); } + + // _logger.LogTrace("storePassword: {Pass}", storePassword.Replace("\n","\\n")); //TODO: INSECURE - Remove this line, it is for debugging purposes only + // var hashedStorePassword = GetSha256Hash(storePassword); + // _logger.LogTrace("hashedStorePassword: {Pass}", hashedStorePassword ?? "null"); + + var jksStore = new JksStore(); - public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword) + _logger.LogDebug("Loading JKS store"); + try { - _logger.LogDebug("Entering DeserializeRemoteCertificateStore"); - var storeBuilder = new Pkcs12StoreBuilder(); - var pkcs12Store = storeBuilder.Build(); - var pkcs12StoreNew = storeBuilder.Build(); - - _logger.LogTrace("storePath: {Path}", storePath); - // _logger.LogTrace("storePassword: {Pass}", storePassword ?? "null"); - var hashedStorePassword = GetSha256Hash(storePassword); - _logger.LogTrace("hashedStorePassword: {Pass}", hashedStorePassword ?? "null"); + // _logger.LogTrace("Attempting to load JKS store w/ password"); + // _logger.LogTrace("Attempting to load JKS store w/ password '{Pass}'", + // storePassword.Replace("\n","\\n")); //TODO: INSECURE - Remove this line, it is for debugging purposes only - var jksStore = new JksStore(); + using (var ms = new MemoryStream(storeContents)) + { + jksStore.Load(ms, string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray()); + } + + _logger.LogDebug("JKS store loaded"); + } + catch (Exception ex) + { + _logger.LogError("Error loading JKS store: {Ex}", ex.Message); + if (ex.Message.Contains("password incorrect or store tampered with")) + { + _logger.LogError("Unable to load JKS store using password '{Pass}'", + storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + throw; + } - _logger.LogDebug("Loading JKS store"); + // Attempt to read JKS store as Pkcs12Store try { - _logger.LogTrace("Attempting to load JKS store w/ password '{Pass}'", hashedStorePassword ?? "null"); + _logger.LogTrace("Attempting to load JKS store as Pkcs12Store w/ password '{Pass}'", + storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only using (var ms = new MemoryStream(storeContents)) { - jksStore.Load(ms, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); + pkcs12Store.Load(ms, string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray()); } - _logger.LogDebug("JKS store loaded"); - } catch (Exception ex) + + _logger.LogDebug("JKS store loaded as Pkcs12Store"); + // return pkcs12Store; + throw new JkSisPkcs12Exception("JKS store is actually a Pkcs12Store"); + } + catch (Exception ex2) { - _logger.LogError("Error loading JKS store: {Ex}", ex.Message); - - // Attempt to read JKS store as Pkcs12Store - try - { - _logger.LogTrace("Attempting to load JKS store as Pkcs12Store w/ password '{Pass}'", hashedStorePassword ?? "null"); - using (var ms = new MemoryStream(storeContents)) - { - pkcs12Store.Load(ms, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); - } - _logger.LogDebug("JKS store loaded as Pkcs12Store"); - // return pkcs12Store; - throw new JkSisPkcs12Exception("JKS store is actually a Pkcs12Store"); - } - catch (Exception ex2) - { - _logger.LogError("Error loading JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); - throw; - } + _logger.LogError("Error loading JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); + throw; } - - _logger.LogDebug("Converting JKS store to Pkcs12Store ny iterating over aliases"); - foreach (var alias in jksStore.Aliases) + } + + _logger.LogDebug("Converting JKS store to Pkcs12Store ny iterating over aliases"); + foreach (var alias in jksStore.Aliases) + { + _logger.LogDebug("Processing alias '{Alias}'", alias); + + _logger.LogDebug("Getting key for alias '{Alias}'", alias); + var keyParam = jksStore.GetKey(alias, + string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray()); + + _logger.LogDebug("Creating AsymmetricKeyEntry for alias '{Alias}'", alias); + var keyEntry = new AsymmetricKeyEntry(keyParam); + + if (jksStore.IsKeyEntry(alias)) { - _logger.LogDebug("Processing alias '{Alias}'", alias); - - _logger.LogDebug("Getting key for alias '{Alias}'", alias); - var keyParam = jksStore.GetKey(alias, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); - - _logger.LogDebug("Creating AsymmetricKeyEntry for alias '{Alias}'", alias); - var keyEntry = new AsymmetricKeyEntry(keyParam); - - if (jksStore.IsKeyEntry(alias)) - { - _logger.LogDebug("Alias '{Alias}' is a key entry", alias); - _logger.LogDebug("Getting certificate chain for alias '{Alias}'", alias); - var certificateChain = jksStore.GetCertificateChain(alias); + _logger.LogDebug("Alias '{Alias}' is a key entry", alias); + _logger.LogDebug("Getting certificate chain for alias '{Alias}'", alias); + var certificateChain = jksStore.GetCertificateChain(alias); - _logger.LogDebug("Adding key entry and certificate chain to Pkcs12Store"); - pkcs12Store.SetKeyEntry(alias, keyEntry, certificateChain.Select(certificate => new X509CertificateEntry(certificate)).ToArray()); - } - else - { - _logger.LogDebug("Alias '{Alias}' is a certificate entry", alias); - _logger.LogDebug("Setting certificate for alias '{Alias}'", alias); - pkcs12Store.SetCertificateEntry(alias, new X509CertificateEntry(jksStore.GetCertificate(alias))); - } + _logger.LogDebug("Adding key entry and certificate chain to Pkcs12Store"); + pkcs12Store.SetKeyEntry(alias, keyEntry, + certificateChain.Select(certificate => new X509CertificateEntry(certificate)).ToArray()); + } + else + { + _logger.LogDebug("Alias '{Alias}' is a certificate entry", alias); + _logger.LogDebug("Setting certificate for alias '{Alias}'", alias); + pkcs12Store.SetCertificateEntry(alias, new X509CertificateEntry(jksStore.GetCertificate(alias))); } + } + + // Second Pkcs12Store necessary because of an obscure BC bug where creating a Pkcs12Store without .Load (code above using "Set" methods only) does not set all + // internal hashtables necessary to avoid an error later when processing store. + var ms2 = new MemoryStream(); + _logger.LogDebug("Saving Pkcs12Store to MemoryStream"); + _logger.LogTrace("Saving Pkcs12Store to MemoryStream w/ password '{Pass}'", + storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + pkcs12Store.Save(ms2, string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray(), + new SecureRandom()); + ms2.Position = 0; + + _logger.LogDebug("Loading Pkcs12Store from MemoryStream"); + // _logger.LogTrace("Loading Pkcs12Store from MemoryStream w/ password '{Pass}'", + // storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + pkcs12StoreNew.Load(ms2, string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray()); + + _logger.LogDebug("Returning Pkcs12Store"); + return pkcs12StoreNew; + } - // Second Pkcs12Store necessary because of an obscure BC bug where creating a Pkcs12Store without .Load (code above using "Set" methods only) does not set all - // internal hashtables necessary to avoid an error later when processing store. - var ms2 = new MemoryStream(); - _logger.LogDebug("Saving Pkcs12Store to MemoryStream"); - pkcs12Store.Save(ms2, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray(), new SecureRandom()); - ms2.Position = 0; + public List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, + string storeFileName, string storePassword) + { + _logger.MethodEntry(); - _logger.LogDebug("Loading Pkcs12Store from MemoryStream"); - pkcs12StoreNew.Load(ms2, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); + var jksStore = new JksStore(); - _logger.LogDebug("Returning Pkcs12Store"); - return pkcs12StoreNew; + foreach (var alias in certificateStore.Aliases) + { + var keyEntry = certificateStore.GetKey(alias); + var certificateChain = certificateStore.GetCertificateChain(alias); + var certificates = new List(); + if (certificateStore.IsKeyEntry(alias)) + { + certificates.AddRange(certificateChain.Select(certificateEntry => certificateEntry.Certificate)); + _logger.LogDebug("Alias '{Alias}' is a key entry, setting key entry in JKS store using store password '{Pass}'", + alias, storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + jksStore.SetKeyEntry(alias, keyEntry.Key, + string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray(), certificates.ToArray()); + } + else + { + jksStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias).Certificate); + } } - // public JksStore ConvertPkcs12toJks(byte[] jksBytes, string jksPassword, byte[] pkcs12Bytes, string pkcs12Password) - // { - // // Attempt to load JKS store as JksStore - // var jksStore = new JksStore(); - // try - // { - // _logger.LogTrace("Attempting to load JKS store w/ password '{Pass}'", jksPassword ?? "null"); - // using (var ms = new MemoryStream(jksBytes)) - // { - // jksStore.Load(ms, string.IsNullOrEmpty(jksPassword) ? Array.Empty() : jksPassword.ToCharArray()); - // } - // _logger.LogDebug("JKS store loaded"); - // } - // catch (Exception ex) - // { - // _logger.LogError("Error loading JKS store: {Ex}", ex.Message); - // // Attempt to read JKS store as Pkcs12Store - // try - // { - // _logger.LogTrace("Attempting to load JKS store as Pkcs12Store w/ password '{Pass}'", jksPassword ?? "null"); - // var pkcs12Store = new Pkcs12StoreBuilder().Build(); - // using (var ms = new MemoryStream(jksBytes)) - // { - // pkcs12Store.Load(ms, string.IsNullOrEmpty(jksPassword) ? Array.Empty() : jksPassword.ToCharArray()); - // } - // _logger.LogDebug("JKS store loaded as Pkcs12Store"); - // // return pkcs12Store; - // throw new JkSisPkcs12Exception("JKS store is actually a Pkcs12Store"); - // } - // catch (Exception ex2) - // { - // _logger.LogError("Error loading JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); - // throw; - // } - // } - // - // } - - public byte[] CreateOrUpdateJks(byte[] newPkcs12Bytes, string newCertPassword, string alias, byte[] existingStore = null, string existingStorePassword = null, - bool remove = false) + using var outStream = new MemoryStream(); + _logger.LogDebug("Saving JKS store to MemoryStream w/ password '{Pass}'", + storePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + jksStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? [] : storePassword.ToCharArray()); + + var storeInfo = new List + { new() { FilePath = storePath + storeFileName, Contents = outStream.ToArray() } }; + + _logger.MethodExit(); + return storeInfo; + } + + public string GetPrivateKeyPath() + { + return null; + } + + // public JksStore ConvertPkcs12toJks(byte[] jksBytes, string jksPassword, byte[] pkcs12Bytes, string pkcs12Password) + // { + // // Attempt to load JKS store as JksStore + // var jksStore = new JksStore(); + // try + // { + // _logger.LogTrace("Attempting to load JKS store w/ password '{Pass}'", jksPassword ?? "null"); + // using (var ms = new MemoryStream(jksBytes)) + // { + // jksStore.Load(ms, string.IsNullOrEmpty(jksPassword) ? Array.Empty() : jksPassword.ToCharArray()); + // } + // _logger.LogDebug("JKS store loaded"); + // } + // catch (Exception ex) + // { + // _logger.LogError("Error loading JKS store: {Ex}", ex.Message); + // // Attempt to read JKS store as Pkcs12Store + // try + // { + // _logger.LogTrace("Attempting to load JKS store as Pkcs12Store w/ password '{Pass}'", jksPassword ?? "null"); + // var pkcs12Store = new Pkcs12StoreBuilder().Build(); + // using (var ms = new MemoryStream(jksBytes)) + // { + // pkcs12Store.Load(ms, string.IsNullOrEmpty(jksPassword) ? Array.Empty() : jksPassword.ToCharArray()); + // } + // _logger.LogDebug("JKS store loaded as Pkcs12Store"); + // // return pkcs12Store; + // throw new JkSisPkcs12Exception("JKS store is actually a Pkcs12Store"); + // } + // catch (Exception ex2) + // { + // _logger.LogError("Error loading JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); + // throw; + // } + // } + // + // } + + public byte[] CreateOrUpdateJks(byte[] newPkcs12Bytes, string newCertPassword, string alias, + byte[] existingStore = null, string existingStorePassword = null, + bool remove = false) + { + _logger.MethodEntry(); + // If existingStore is null, create a new store + var existingJksStore = new JksStore(); + var newJksStore = new JksStore(); + var createdNewStore = false; + + var hashedNewCertPassword = GetSha256Hash(newCertPassword); + var hashedExistingStorePassword = GetSha256Hash(existingStorePassword); + + _logger.LogTrace("alias: {Alias}", alias); + _logger.LogTrace("newCertPassword: {Pass}", + newCertPassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + _logger.LogTrace("existingStorePassword: {Pass}", existingStorePassword ?? "null"); + + // If existingStore is not null, load it into jksStore + if (existingStore != null) { - _logger.LogDebug("Entering CreateOrUpdateJks"); - // If existingStore is null, create a new store - var existingJksStore = new JksStore(); - var newJksStore = new JksStore(); - var createdNewStore = false; - - var hashedNewCertPassword = GetSha256Hash(newCertPassword); - var hashedExistingStorePassword = GetSha256Hash(existingStorePassword); - - _logger.LogTrace("newCertPassword: {Pass}", hashedNewCertPassword ?? "null"); - _logger.LogTrace("alias: {Alias}", alias); - _logger.LogTrace("existingStorePassword: {Pass}", hashedExistingStorePassword ?? "null"); + _logger.LogDebug("Loading existing JKS store"); + using var ms = new MemoryStream(existingStore); - // If existingStore is not null, load it into jksStore - if (existingStore != null) + try { - _logger.LogDebug("Loading existing JKS store"); - using var ms = new MemoryStream(existingStore); + existingJksStore.Load(ms, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); + } + catch (Exception ex) + { + _logger.LogError("Error loading existing JKS store: {Ex}", ex.Message); - try + if (ex.Message.Contains("password incorrect or store tampered with")) { - existingJksStore.Load(ms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); - } catch (Exception ex) + _logger.LogError("Unable to load existing JKS store using password '{Pass}'", + existingStorePassword ?? + "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + throw; + } + + try { - _logger.LogError("Error loading existing JKS store: {Ex}", ex.Message); - // Check if existingStore is actually a Pkcs12Store - try - { - _logger.LogDebug("Attempting to load existing JKS store as Pkcs12Store"); - var pkcs12Store = new Pkcs12StoreBuilder().Build(); - using (var ms2 = new MemoryStream(existingStore)) - { - pkcs12Store.Load(ms2, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); - } - _logger.LogDebug("Existing JKS store loaded as Pkcs12Store"); - // return pkcs12Store; - throw new JkSisPkcs12Exception("Existing JKS store is actually a Pkcs12Store"); - } - catch (Exception ex2) + _logger.LogDebug("Attempting to load existing JKS store as Pkcs12Store"); + var pkcs12Store = new Pkcs12StoreBuilder().Build(); + using (var ms2 = new MemoryStream(existingStore)) { - _logger.LogError("Error loading existing JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); - throw; + pkcs12Store.Load(ms2, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); } + + _logger.LogDebug("Existing JKS store loaded as Pkcs12Store"); + // return pkcs12Store; + throw new JkSisPkcs12Exception("Existing JKS store is actually a Pkcs12Store"); } - - if (existingJksStore.ContainsAlias(alias)) + catch (Exception ex2) { - // If alias exists, delete it from existingJksStore - _logger.LogDebug("Alias '{Alias}' exists in existing JKS store, deleting it", alias); - existingJksStore.DeleteEntry(alias); - if (remove) - { - // If remove is true, save existingJksStore and return - _logger.LogDebug("This is a removal operation, saving existing JKS store"); - using var mms = new MemoryStream(); - existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); - _logger.LogDebug("Returning existing JKS store"); - return mms.ToArray(); - } + _logger.LogError("Error loading existing JKS store as Jks or Pkcs12Store: {Ex}", ex2.Message); + throw; } - else if (remove) + } + + if (existingJksStore.ContainsAlias(alias)) + { + // If alias exists, delete it from existingJksStore + _logger.LogDebug("Alias '{Alias}' exists in existing JKS store, deleting it", alias); + existingJksStore.DeleteEntry(alias); + if (remove) { - // If alias does not exist and remove is true, return existingStore - _logger.LogDebug("Alias '{Alias}' does not exist in existing JKS store and this is a removal operation, returning existing JKS store as-is", alias); + // If remove is true, save existingJksStore and return + _logger.LogDebug("This is a removal operation, saving existing JKS store"); using var mms = new MemoryStream(); - existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); + existingJksStore.Save(mms, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); + _logger.LogDebug("Returning existing JKS store"); return mms.ToArray(); } } - else + else if (remove) { - _logger.LogDebug("Existing JKS store is null, creating new JKS store"); - createdNewStore = true; + // If alias does not exist and remove is true, return existingStore + _logger.LogDebug( + "Alias '{Alias}' does not exist in existing JKS store and this is a removal operation, returning existing JKS store as-is", + alias); + using var mms = new MemoryStream(); + existingJksStore.Save(mms, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); + return mms.ToArray(); } + } + else + { + _logger.LogDebug("Existing JKS store is null, creating new JKS store"); + createdNewStore = true; + } - // Create new Pkcs12Store from newPkcs12Bytes - var storeBuilder = new Pkcs12StoreBuilder(); - var newCert = storeBuilder.Build(); + // Create new Pkcs12Store from newPkcs12Bytes + var storeBuilder = new Pkcs12StoreBuilder(); + var newCert = storeBuilder.Build(); - try - { - _logger.LogDebug("Loading new Pkcs12Store from newPkcs12Bytes"); - _logger.LogTrace("hashedNewCertPassword: {Pass}", hashedNewCertPassword ?? "null"); - using var pkcs12Ms = new MemoryStream(newPkcs12Bytes); - newCert.Load(pkcs12Ms, string.IsNullOrEmpty(newCertPassword) ? Array.Empty() : newCertPassword.ToCharArray()); - } - catch (Exception) - { - _logger.LogDebug("Loading new Pkcs12Store from newPkcs12Bytes failed, trying to load as X509Certificate"); - var certificateParser = new X509CertificateParser(); - var certificate = certificateParser.ReadCertificate(newPkcs12Bytes); - - _logger.LogDebug("Creating new Pkcs12Store from certificate"); - // create new Pkcs12Store from certificate - storeBuilder = new Pkcs12StoreBuilder(); - newCert = storeBuilder.Build(); - _logger.LogDebug("Setting certificate entry in new Pkcs12Store as alias '{Alias}'", alias); - newCert.SetCertificateEntry(alias, new X509CertificateEntry(certificate)); - } + try + { + _logger.LogDebug("Loading new Pkcs12Store from newPkcs12Bytes"); + _logger.LogTrace("newCertPassword: {Pass}", + newCertPassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + using var pkcs12Ms = new MemoryStream(newPkcs12Bytes); + if (pkcs12Ms.Length != 0) newCert.Load(pkcs12Ms, (newCertPassword ?? string.Empty).ToCharArray()); + } + catch (Exception) + { + _logger.LogDebug("Loading new Pkcs12Store from newPkcs12Bytes failed, trying to load as X509Certificate"); + var certificateParser = new X509CertificateParser(); + var certificate = certificateParser.ReadCertificate(newPkcs12Bytes); + + _logger.LogDebug("Creating new Pkcs12Store from certificate"); + // create new Pkcs12Store from certificate + storeBuilder = new Pkcs12StoreBuilder(); + newCert = storeBuilder.Build(); + _logger.LogDebug("Setting certificate entry in new Pkcs12Store as alias '{Alias}'", alias); + newCert.SetCertificateEntry(alias, new X509CertificateEntry(certificate)); + } - // Iterate through newCert aliases. - _logger.LogDebug("Iterating through new Pkcs12Store aliases"); - foreach (var al in newCert.Aliases) + // Iterate through newCert aliases. + _logger.LogDebug("Iterating through new Pkcs12Store aliases"); + foreach (var al in newCert.Aliases) + { + _logger.LogTrace("Alias: {Alias}", al); + if (newCert.IsKeyEntry(al)) { - _logger.LogTrace("Alias: {Alias}", al); - if (newCert.IsKeyEntry(al)) - { - _logger.LogDebug("Alias '{Alias}' is a key entry, getting key entry and certificate chain", al); - var keyEntry = newCert.GetKey(al); - _logger.LogDebug("Getting certificate chain for alias '{Alias}'", al); - var certificateChain = newCert.GetCertificateChain(al); + _logger.LogDebug("Alias '{Alias}' is a key entry, getting key entry and certificate chain", al); + var keyEntry = newCert.GetKey(al); + _logger.LogDebug("Getting certificate chain for alias '{Alias}'", al); + var certificateChain = newCert.GetCertificateChain(al); - _logger.LogDebug("Creating certificate list from certificate chain"); - var certificates = certificateChain.Select(certificateEntry => certificateEntry.Certificate).ToList(); + _logger.LogDebug("Creating certificate list from certificate chain"); + var certificates = certificateChain.Select(certificateEntry => certificateEntry.Certificate).ToList(); - if (createdNewStore) - { - // If createdNewStore is true, create a new store - _logger.LogDebug("Created new JKS store, setting key entry for alias '{Alias}'", al); - newJksStore.SetKeyEntry(alias, - keyEntry.Key, - string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), - certificates.ToArray()); - } - else - { - // If createdNewStore is false, add to existingJksStore - // check if alias exists in existingJksStore - if (existingJksStore.ContainsAlias(alias)) - { - // If alias exists, delete it from existingJksStore - _logger.LogDebug("Alias '{Alias}' exists in existing JKS store, deleting it", alias); - existingJksStore.DeleteEntry(alias); - } - - _logger.LogDebug("Setting key entry for alias '{Alias}'", alias); - existingJksStore.SetKeyEntry(alias, - keyEntry.Key, - string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), - certificates.ToArray()); - } + if (createdNewStore) + { + // If createdNewStore is true, create a new store + _logger.LogDebug("Created new JKS store, setting key entry for alias '{Alias}'", al); + newJksStore.SetKeyEntry(alias, + keyEntry.Key, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray(), + certificates.ToArray()); } else { - if (createdNewStore) + // If createdNewStore is false, add to existingJksStore + // check if alias exists in existingJksStore + if (existingJksStore.ContainsAlias(alias)) { - _logger.LogDebug("Created new JKS store, setting certificate entry for alias '{Alias}'", alias); - _logger.LogDebug("Setting certificate entry for new JKS store, alias '{Alias}'", alias); - newJksStore.SetCertificateEntry(alias, newCert.GetCertificate(alias).Certificate); - } - else - { - _logger.LogDebug("Setting certificate entry for existing JKS store, alias '{Alias}'", alias); - existingJksStore.SetCertificateEntry(alias, newCert.GetCertificate(alias).Certificate); + // If alias exists, delete it from existingJksStore + _logger.LogDebug("Alias '{Alias}' exists in existing JKS store, deleting it", alias); + existingJksStore.DeleteEntry(alias); } + _logger.LogDebug("Setting key entry for alias '{Alias}'", alias); + existingJksStore.SetKeyEntry(alias, + keyEntry.Key, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray(), + certificates.ToArray()); } } - - using var outStream = new MemoryStream(); - if (createdNewStore) - { - _logger.LogDebug("Created new JKS store, saving it to outStream"); - newJksStore.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); - } else { - _logger.LogDebug("Saving existing JKS store to outStream"); - existingJksStore.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); - } - // Return existingJksStore as byte[] - _logger.LogDebug("Returning JKS store as byte[]"); - return outStream.ToArray(); - } - public List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, string storeFileName, string storePassword) - { - _logger.MethodEntry(LogLevel.Debug); - - var jksStore = new JksStore(); - - foreach (var alias in certificateStore.Aliases) - { - var keyEntry = certificateStore.GetKey(alias); - var certificateChain = certificateStore.GetCertificateChain(alias); - var certificates = new List(); - if (certificateStore.IsKeyEntry(alias)) + if (createdNewStore) { - - certificates.AddRange(certificateChain.Select(certificateEntry => certificateEntry.Certificate)); - - jksStore.SetKeyEntry(alias, keyEntry.Key, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray(), certificates.ToArray()); + _logger.LogDebug("Created new JKS store, setting certificate entry for alias '{Alias}'", alias); + _logger.LogDebug("Setting certificate entry for new JKS store, alias '{Alias}'", alias); + newJksStore.SetCertificateEntry(alias, newCert.GetCertificate(alias).Certificate); } else { - jksStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias).Certificate); + _logger.LogDebug("Setting certificate entry for existing JKS store, alias '{Alias}'", alias); + existingJksStore.SetCertificateEntry(alias, newCert.GetCertificate(alias).Certificate); } } - - using var outStream = new MemoryStream(); - jksStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); - - var storeInfo = new List(); - storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = outStream.ToArray() }); - - _logger.MethodExit(LogLevel.Debug); - return storeInfo; - } - public string GetPrivateKeyPath() + using var outStream = new MemoryStream(); + if (createdNewStore) { - return null; + _logger.LogDebug("Created new JKS store, saving it to outStream"); + _logger.LogTrace("Saving new JKS store to outStream w/ password '{Pass}'", + existingStorePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + newJksStore.Save(outStream, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); } - - private static string GetSha256Hash(string input) + else { - if (string.IsNullOrEmpty(input)) - { - return null; - } - var passwordHashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(input)); - var passwordHash = BitConverter.ToString(passwordHashBytes).Replace("-", "").ToLower(); - return passwordHash; + _logger.LogDebug("Saving existing JKS store to outStream"); + _logger.LogTrace("Saving existing JKS store to outStream w/ password '{Pass}'", + existingStorePassword ?? "null"); //TODO: INSECURE - Remove this line, it is for debugging purposes only + existingJksStore.Save(outStream, + string.IsNullOrEmpty(existingStorePassword) ? [] : existingStorePassword.ToCharArray()); } + + // Return existingJksStore as byte[] + _logger.MethodExit(); + return outStream.ToArray(); + } + + private static string GetSha256Hash(string input) + { + if (string.IsNullOrEmpty(input)) return null; + var passwordHashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(input)); + var passwordHash = BitConverter.ToString(passwordHashBytes).Replace("-", "").ToLower(); + return passwordHash; } -} +} \ No newline at end of file diff --git a/kubernetes-orchestrator-extension/StoreTypes/K8SPKCS12/Store.cs b/kubernetes-orchestrator-extension/StoreTypes/K8SPKCS12/Store.cs index 96c4746..7ee3991 100644 --- a/kubernetes-orchestrator-extension/StoreTypes/K8SPKCS12/Store.cs +++ b/kubernetes-orchestrator-extension/StoreTypes/K8SPKCS12/Store.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Keyfactor.Extensions.Orchestrator.K8S.Models; using Keyfactor.Logging; using Microsoft.Extensions.Logging; @@ -18,9 +17,10 @@ namespace Keyfactor.Extensions.Orchestrator.K8S.StoreTypes.K8SPKCS12; -class Pkcs12CertificateStoreSerializer : ICertificateStoreSerializer +internal class Pkcs12CertificateStoreSerializer : ICertificateStoreSerializer { private readonly ILogger _logger; + public Pkcs12CertificateStoreSerializer(string storeProperties) { _logger = LogHandler.GetClassLogger(GetType()); @@ -34,17 +34,74 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, strin var store = storeBuilder.Build(); using var ms = new MemoryStream(storeContents); + _logger.LogDebug("Loading Pkcs12Store from MemoryStream from {Path}", storePath); store.Load(ms, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray()); + _logger.LogDebug("Pkcs12Store loaded from {Path}", storePath); return store; } - public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword, string alias, byte[] existingStore = null, string existingStorePassword = null, + public List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, + string storeFileName, string storePassword) + { + _logger.MethodEntry(LogLevel.Debug); + + var storeBuilder = new Pkcs12StoreBuilder(); + var pkcs12Store = storeBuilder.Build(); + + foreach (var alias in certificateStore.Aliases) + { + _logger.LogDebug("Processing alias '{Alias}'", alias); + var keyEntry = certificateStore.GetKey(alias); + + if (certificateStore.IsKeyEntry(alias)) + { + _logger.LogDebug("Alias '{Alias}' is a key entry", alias); + pkcs12Store.SetKeyEntry(alias, keyEntry, certificateStore.GetCertificateChain(alias)); + } + else + { + _logger.LogDebug("Alias '{Alias}' is a certificate entry", alias); + var certEntry = certificateStore.GetCertificate(alias); + _logger.LogTrace("Certificate entry '{Entry}'", certEntry.Certificate.SubjectDN.ToString()); + _logger.LogDebug("Attempting to SetCertificateEntry for '{Alias}'", alias); + pkcs12Store.SetCertificateEntry(alias, certEntry); + } + } + + using var outStream = new MemoryStream(); + _logger.LogDebug("Saving Pkcs12Store to MemoryStream"); + pkcs12Store.Save(outStream, + string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray(), + new SecureRandom()); + + var storeInfo = new List(); + + _logger.LogDebug("Adding store to list of serialized stores"); + var filePath = Path.Combine(storePath, storeFileName); + _logger.LogDebug("Filepath '{Path}", filePath); + storeInfo.Add(new SerializedStoreInfo + { + FilePath = filePath, + Contents = outStream.ToArray() + }); + + _logger.MethodExit(LogLevel.Debug); + return storeInfo; + } + + public string GetPrivateKeyPath() + { + return null; + } + + public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword, string alias, + byte[] existingStore = null, string existingStorePassword = null, bool remove = false) { _logger.MethodEntry(LogLevel.Debug); - _logger.LogDebug("Creating or updating PKCS12 store"); + _logger.LogDebug("Creating or updating PKCS12 store for alias'{Alias}'", alias); // If existingStore is null, create a new store var storeBuilder = new Pkcs12StoreBuilder(); var existingPkcs12Store = storeBuilder.Build(); @@ -54,32 +111,56 @@ public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword // If existingStore is not null, load it into pkcs12Store if (existingStore != null) { + _logger.LogDebug("Attempting to load existing Pkcs12Store"); using var ms = new MemoryStream(existingStore); - existingPkcs12Store.Load(ms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray()); + existingPkcs12Store.Load(ms, + string.IsNullOrEmpty(existingStorePassword) + ? Array.Empty() + : existingStorePassword.ToCharArray()); + _logger.LogDebug("Existing Pkcs12Store loaded"); + + _logger.LogDebug("Checking if alias '{Alias}' exists in existingPkcs12Store", alias); if (existingPkcs12Store.ContainsAlias(alias)) { // If alias exists, delete it from existingPkcs12Store + _logger.LogDebug("Alias '{Alias}' exists in existingPkcs12Store", alias); + _logger.LogDebug("Deleting alias '{Alias}' from existingPkcs12Store", alias); existingPkcs12Store.DeleteEntry(alias); if (remove) { // If remove is true, save existingPkcs12Store and return + _logger.LogDebug("Alias '{Alias}' was removed from existing store", alias); using var mms = new MemoryStream(); - existingPkcs12Store.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), new SecureRandom()); + _logger.LogDebug("Saving removal operation"); + existingPkcs12Store.Save(mms, + string.IsNullOrEmpty(existingStorePassword) + ? Array.Empty() + : existingStorePassword.ToCharArray(), new SecureRandom()); + + _logger.LogDebug("Converting existingPkcs12Store to byte[] and returning"); + _logger.MethodExit(LogLevel.Debug); return mms.ToArray(); } } else if (remove) { // If alias does not exist and remove is true, return existingStore + _logger.LogDebug("Alias '{Alias}' does not exist in existingPkcs12Store, nothing to remove", alias); using var existingPkcs12StoreMs = new MemoryStream(); existingPkcs12Store.Save(existingPkcs12StoreMs, - string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), + string.IsNullOrEmpty(existingStorePassword) + ? Array.Empty() + : existingStorePassword.ToCharArray(), new SecureRandom()); + + _logger.LogDebug("Converting existingPkcs12Store to byte[] and returning"); + _logger.MethodExit(LogLevel.Debug); return existingPkcs12StoreMs.ToArray(); } } else { + _logger.LogDebug("Attempting to create new Pkcs12Store"); createdNewStore = true; } @@ -87,31 +168,47 @@ public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword try { + _logger.LogDebug("Attempting to load pkcs12 bytes"); using var newPkcs12Ms = new MemoryStream(newPkcs12Bytes); - newCert.Load(newPkcs12Ms, string.IsNullOrEmpty(newCertPassword) ? Array.Empty() : newCertPassword.ToCharArray()); + newCert.Load(newPkcs12Ms, + string.IsNullOrEmpty(newCertPassword) ? Array.Empty() : newCertPassword.ToCharArray()); + _logger.LogDebug("pkcs12 bytes loaded"); } catch (Exception) { + _logger.LogError("Unknown error loading pkcs12 bytes, attempting to parse certificate"); var certificateParser = new X509CertificateParser(); var certificate = certificateParser.ReadCertificate(newPkcs12Bytes); + _logger.LogDebug("Certificate parse successful, attempting to create new Pkcs12Store from certificate"); // create new Pkcs12Store from certificate storeBuilder = new Pkcs12StoreBuilder(); newCert = storeBuilder.Build(); + + _logger.LogDebug("Attempting to set PKCS12 certificate entry using alias '{Alias}'", alias); newCert.SetCertificateEntry(alias, new X509CertificateEntry(certificate)); + _logger.LogDebug("PKCS12 certificate entry set using alias '{Alias}'", alias); } // Iterate through newCert aliases. WARNING: This assumes there is only one alias in the newCert + _logger.LogTrace("Iterating through PKCS12 certificate aliases"); foreach (var al in newCert.Aliases) { + _logger.LogTrace("Handling alias {Alias}", al); if (newCert.IsKeyEntry(al)) { + _logger.LogDebug("Attempting to parse key for alias {Alias}", al); var keyEntry = newCert.GetKey(al); + _logger.LogDebug("Key parsed for alias {Alias}", al); + + _logger.LogDebug("Attempting to parse certificate chain for alias {Alias}", al); var certificateChain = newCert.GetCertificateChain(al); + _logger.LogDebug("Certificate chain parsed for alias {Alias}", al); if (createdNewStore) { // If createdNewStore is true, create a new store + _logger.LogDebug("Attempting to set key entry for alias '{Alias}'", alias); pkcs12StoreNew.SetKeyEntry( alias, keyEntry, @@ -124,10 +221,12 @@ public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword // check if alias exists in existingPkcs12Store if (existingPkcs12Store.ContainsAlias(alias)) { + _logger.LogDebug("Removing existing entry for alias '{Alias}'", alias); // If alias exists, delete it from existingPkcs12Store existingPkcs12Store.DeleteEntry(alias); } + _logger.LogDebug("Attempting to set key entry for alias '{Alias}'", alias); existingPkcs12Store.SetKeyEntry( alias, keyEntry, @@ -140,62 +239,38 @@ public byte[] CreateOrUpdatePkcs12(byte[] newPkcs12Bytes, string newCertPassword { if (createdNewStore) { + _logger.LogDebug("Attempting to set certificate entry for alias '{Alias}'", alias); pkcs12StoreNew.SetCertificateEntry(alias, newCert.GetCertificate(alias)); } else { + _logger.LogDebug("Attempting to set certificate entry for alias '{Alias}'", alias); existingPkcs12Store.SetCertificateEntry(alias, newCert.GetCertificate(alias)); } - } } using var outStream = new MemoryStream(); if (createdNewStore) { - pkcs12StoreNew.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), new SecureRandom()); + _logger.LogDebug("Attempting to save new Pkcs12Store"); + pkcs12StoreNew.Save(outStream, + string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), + new SecureRandom()); + _logger.LogDebug("New Pkcs12Store saved"); } else { - existingPkcs12Store.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), new SecureRandom()); + _logger.LogDebug("Attempting to save existing Pkcs12Store"); + existingPkcs12Store.Save(outStream, + string.IsNullOrEmpty(existingStorePassword) ? Array.Empty() : existingStorePassword.ToCharArray(), + new SecureRandom()); + _logger.LogDebug("Existing Pkcs12Store saved"); } // Return existingPkcs12Store as byte[] - return outStream.ToArray(); - } - - public List SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, string storeFileName, string storePassword) - { - _logger.MethodEntry(LogLevel.Debug); - - var storeBuilder = new Pkcs12StoreBuilder(); - var pkcs12Store = storeBuilder.Build(); - - foreach (var alias in certificateStore.Aliases) - { - var keyEntry = certificateStore.GetKey(alias); - if (certificateStore.IsKeyEntry(alias)) - { - pkcs12Store.SetKeyEntry(alias, keyEntry, certificateStore.GetCertificateChain(alias)); - } - else - { - pkcs12Store.SetCertificateEntry(alias, certificateStore.GetCertificate(alias)); - } - } - - using var outStream = new MemoryStream(); - pkcs12Store.Save(outStream, string.IsNullOrEmpty(storePassword) ? Array.Empty() : storePassword.ToCharArray(), new SecureRandom()); - - var storeInfo = new List(); - storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = outStream.ToArray() }); + _logger.LogDebug("Converting existingPkcs12Store to byte[] and returning"); _logger.MethodExit(LogLevel.Debug); - return storeInfo; - - } - - public string GetPrivateKeyPath() - { - return null; + return outStream.ToArray(); } -} +} \ No newline at end of file diff --git a/scripts/kubernetes/README.md b/scripts/kubernetes/README.md index b295ba6..ad6675a 100644 --- a/scripts/kubernetes/README.md +++ b/scripts/kubernetes/README.md @@ -1,7 +1,7 @@ # Keyfactor Kubernetes Orchestrator Service Account Definition This document describes the Kubernetes Service Account definition for the Keyfactor Kubernetes Orchestrator Extension to fully function. -Please note that this is only an example and you may need to modify the script and service account definition to suit your environment. +Please note that this is only an example, you may need to modify the script and service account definition to suit your environment. ## Table of Contents - [Keyfactor Kubernetes Orchestrator Service Account Definition](#keyfactor-kubernetes-orchestrator-service-account-definition) diff --git a/store_types.json b/store_types.json index 8d8e595..df66bc4 100644 --- a/store_types.json +++ b/store_types.json @@ -15,6 +15,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -23,6 +24,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -31,6 +33,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true", @@ -68,6 +71,7 @@ { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "default", @@ -76,6 +80,7 @@ { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -84,6 +89,7 @@ { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `jks`", "Type": "String", "DependsOn": "", "DefaultValue": "jks", @@ -124,6 +130,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -132,6 +139,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -140,6 +148,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true", @@ -177,6 +186,7 @@ { "Name": "KubeNamespace", "DisplayName": "Kube Namespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "default", @@ -185,6 +195,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -193,6 +204,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -201,6 +213,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true", @@ -238,6 +251,7 @@ { "Name": "KubeSecretKey", "DisplayName": "Kube Secret Key", + "Description": "The field name to use when looking for PFX/PKCS12 data in the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "pfx", @@ -246,6 +260,7 @@ { "Name": "PasswordFieldName", "DisplayName": "Password Field Name", + "Description": "The field name to use when looking for the password to the PFX/PKCS12 data in the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "password", @@ -254,6 +269,7 @@ { "Name": "PasswordIsK8SSecret", "DisplayName": "Password Is K8S Secret", + "Description": "Indicates whether the password to the PFX/PKCS12 data is stored in a separate K8S secret object.", "Type": "Bool", "DependsOn": "", "DefaultValue": "false", @@ -262,6 +278,7 @@ { "Name": "KubeNamespace", "DisplayName": "Kube Namespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": "default", @@ -270,6 +287,7 @@ { "Name": "KubeSecretName", "DisplayName": "Kube Secret Name", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -278,6 +296,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -286,6 +305,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -294,6 +314,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true", @@ -302,6 +323,7 @@ { "Name": "KubeSecretType", "DisplayName": "Kube Secret Type", + "Description": "This defaults to and must be `pkcs12`", "Type": "String", "DependsOn": "", "DefaultValue": "pkcs12", @@ -310,6 +332,7 @@ { "Name": "StorePasswordPath", "DisplayName": "StorePasswordPath", + "Description": "The path to the K8S secret object to use as the password to the PFX/PKCS12 data. Example: `/`", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -347,6 +370,7 @@ { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -355,6 +379,7 @@ { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -363,6 +388,7 @@ { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `secret`", "Type": "String", "DependsOn": "", "DefaultValue": "secret", @@ -371,6 +397,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -379,6 +406,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -387,6 +415,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true", @@ -424,6 +453,7 @@ { "Name": "KubeNamespace", "DisplayName": "KubeNamespace", + "Description": "The K8S namespace to use to manage the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -432,6 +462,7 @@ { "Name": "KubeSecretName", "DisplayName": "KubeSecretName", + "Description": "The name of the K8S secret object.", "Type": "String", "DependsOn": "", "DefaultValue": null, @@ -440,6 +471,7 @@ { "Name": "KubeSecretType", "DisplayName": "KubeSecretType", + "Description": "This defaults to and must be `tls_secret`", "Type": "String", "DependsOn": "", "DefaultValue": "tls_secret", @@ -448,6 +480,7 @@ { "Name": "ServerUsername", "DisplayName": "Server Username", + "Description": "This should be no value or `kubeconfig`", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -456,6 +489,7 @@ { "Name": "ServerPassword", "DisplayName": "Server Password", + "Description": "The credentials to use to connect to the K8S cluster API. This needs to be in `kubeconfig` format. Example: https://github.com/Keyfactor/k8s-orchestrator/tree/main/scripts/kubernetes#example-service-account-json", "Type": "Secret", "DependsOn": "", "DefaultValue": null, @@ -464,6 +498,7 @@ { "Name": "ServerUseSsl", "DisplayName": "Use SSL", + "Description": "Use SSL to connect to the K8S cluster API.", "Type": "Bool", "DependsOn": "", "DefaultValue": "true",