diff --git a/.gitignore b/.gitignore index f173162..4b044f4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ obj/ riderModule.iml /_ReSharper.Caches/ .idea/* -.vs/ \ No newline at end of file +.vs/ +.env +scripts/ +client_pam.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c741179..428967b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,54 @@ +# v1.4.0 + +## Features + +- Added three grant-type-specific PAM type variants. Each type exposes only the fields relevant to its authentication flow, resolving the Keyfactor Command UI requirement that all declared fields be populated. + - `Delinea-SecretServer-Password` — Username + Password authentication. Server parameters: `Host`, `Username`, `Password`, `SkipTlsValidation`. + - `Delinea-SecretServer-ClientCredentials` — OAuth2 client credentials flow. Server parameters: `Host`, `ClientId`, `ClientSecret`, `SkipTlsValidation`. + - `Delinea-SecretServer-Windows` — Integrated Windows Authentication (IWA). Server parameters: `Host`, `SkipTlsValidation`. NOTE: IWA is not supported on Secret Server Cloud. +- All shared logic (HTTP, validation, secret retrieval, audit logging) is implemented once in the new `SecretServerPamBase` abstract class. +- The existing `Delinea-SecretServer` type is unchanged and fully backwards compatible. + +## Bug Fixes + +- Fixed `client_credentials` case in `BuildDelineaConfiguration` where `GrantType` was incorrectly set to `"password"` instead of `"client_credentials"` on the resulting `DelineaConfiguration` object. +- Validation of `SecretFieldName` now rejects whitespace-only values (previously only empty string was rejected). + +## Testing + +- Replaced the manual `TestConsole` project with a proper `xUnit` test project (`delinea-secretserver-pam.Tests`, targeting `net8.0`) covering all four PAM types, all auth flows, and error paths including missing parameters, token failures, field-not-found, and non-success HTTP responses. + +# v1.3.0 + +## Compliance Remediation (SOX/SOC2) + +- `GetDelineaSecretAsync` now throws `InvalidSecretConfigurationException` when the requested field is not found in the secret, rather than silently returning an empty string. This prevents silent credential resolution failures from going undetected. +- Token endpoint error response body is now truncated to 500 characters before logging to prevent secret metadata exposure in log sinks. +- Added an explicit `LogInformation` audit event for the Windows authentication path recording OS identity, machine name, target URL, and SecretId before the HTTP call is made. +- Added an authentication success `LogInformation` event in `GetAccessToken` recording the caller identity and target URL with a structured `AuthenticationResult=Success` field. +- A `Guid`-based correlation ID is generated at the start of each `GetPassword` invocation and threaded as a trailing structured field through all `LogInformation` and `LogError` calls in `GetDelineaSecretAsync` and `GetAccessToken`, enabling log correlation across a full PAM operation. +- `Stopwatch` instances for the token POST and secret GET HTTP calls are now declared outside their try blocks; catch blocks record elapsed duration and emit a structured `HTTP call failed` log event so network failure timing is preserved in exception paths. +- Removed the duplicate `SecretResponse` class defined inline at the bottom of `SecretServerPam.cs`. The canonical definition in `Models/SecretResponse.cs` (which includes `Id`, `Name`, `SecretTemplateId`, `FolderId`, and `Active` in addition to `Items`) is now the sole definition, resolved via the existing `using Keyfactor.Extensions.Pam.Delinea.Models;` import. +- `Username` and `ClientId` parameters in `integration-manifest.json` changed from `DataType: 2` (secret/masked) to `DataType: 1` (plain text). These are non-secret identifiers and should not be stored or displayed as secrets in the Keyfactor Command UI. +- Added inline comments at each `Environment.UserName` usage site documenting that this value reflects the OS service account identity, not the Keyfactor Command caller identity, since `IPAMProvider` does not expose caller context. + +## Improvements +- Enhanced debug logging for token endpoint requests: the obfuscated request body (credentials redacted) and raw response body are now logged on token request failures to aid troubleshooting. +- Added structured audit log event on every `GetPassword` invocation recording caller identity, machine name, target URL, grant type, SecretId, and field name. +- Added response duration logging (ms) for both the OAuth token endpoint and secret retrieval API calls. +- Success and failure log events now include SecretId, field name, grant type, and URL for complete audit trail. +- Auth failure log events now include the target URL, grant type, and caller identity. +- Error responses from Secret Server are truncated to 500 characters before logging to prevent sensitive metadata exposure. +- Removed raw token response body from deserialization failure log path to prevent accidental bearer token exposure. + +## Bug Fixes +- Replaced `.Result` with `.GetAwaiter().GetResult()` in `GetPassword` to prevent exception masking on async task failures. + +## Maintenance +- Removed dead `IValidatableObject` implementation from `DelineaConfiguration`; validation is enforced in `ValidateServerConfigurationParams`. +- Masked password value in TestConsole output. +- Bumped TestConsole target framework to net10.0 and global SDK pin to 10.0.0. + # v1.2.0 ## Features diff --git a/README.md b/README.md index f2658b7..ba32eea 100644 --- a/README.md +++ b/README.md @@ -32,79 +32,37 @@ ## Overview The Delinea Secret Server PAM Provider allows for the retrieval of stored account credentials from a Delinea Secret -Server secret. Supports either `password` or `client_credential` authentication methods. For more information on -these authentication methods, see the -[Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). +Server secret. Three authentication methods are supported: `password` (username/password), `client_credentials` +(OAuth2 application account), and `windows` (Integrated Windows Authentication). -## Authentication Methods -For full details on each authentication method, please refer to the [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). -Below are example `manifest.json` snippets for each supported authentication method. +## PAM Types -### Password +This provider ships four PAM types. For new installations, use the type-specific variants — they only expose the +fields relevant to the chosen authentication flow, which simplifies configuration in the Keyfactor Command UI. -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "Username": "", - "Password": "", - "GrantType": "password" - } -} -``` +| PAM Type | Auth Method | Server Parameters | +| --- | --- | --- | +| `Delinea-SecretServer-Password` | Username + Password | `Host`, `Username`, `Password` | +| `Delinea-SecretServer-ClientCredentials` | OAuth2 Client Credentials | `Host`, `ClientId`, `ClientSecret` | +| `Delinea-SecretServer-Windows` | Integrated Windows Authentication | `Host` | +| `Delinea-SecretServer` | Any (selected via `GrantType`) | `Host`, plus credentials for the chosen grant type | -### oAuth2 +> [!NOTE] +> `Delinea-SecretServer` is the original backwards-compatible type retained for existing installations. It requires +> a `GrantType` field and exposes all credential fields in the Keyfactor Command UI regardless of which grant type +> is active. Existing installations do not need to change. -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "ClientId": "", - "ClientSecret": "", - "GrantType": "client_credentials" - } -} -``` +## TLS Validation -### Windows +All PAM types support skipping TLS certificate validation for non-production environments via either: -> [!IMPORTANT] -> Integrated Windows Authentication (IWA) does not work on Secret Server Cloud. +- The `SkipTlsValidation` configuration parameter (set to `true` in the PAM provider instance) +- The `KEYFACTOR_PAM_SKIP_TLS_VALIDATION` environment variable (set to `true` or `1` on the host) -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "GrantType": "windows" - } -} -``` -Please refer to the [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm) -for more information on configuring IWA. +The environment variable takes precedence and overrides the configuration parameter. + +> [!WARNING] +> Disabling TLS validation should only be used in non-production environments. ## Support The Delinea Secret Server PAM Provider is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com. @@ -128,15 +86,25 @@ Before proceeding with installation, you should consider which pattern is best f To install Delinea Secret Server PAM Provider, it is recommended you install [kfutil](https://github.com/Keyfactor/kfutil). `kfutil` is a command-line tool that simplifies the process of creating PAM Types in Keyfactor Command. +The Delinea Secret Server PAM Provider implements 4 PAM Types. Depending on your use case, you may elect to install one, or all of these PAM Types. An overview for each type is linked below: +* [Delinea-SecretServer](docs/delinea-secretserver.md) +* [Delinea-SecretServer-Password](docs/delinea-secretserver-password.md) +* [Delinea-SecretServer-ClientCredentials](docs/delinea-secretserver-clientcredentials.md) +* [Delinea-SecretServer-Windows](docs/delinea-secretserver-windows.md) + +
Delinea-SecretServer + #### Requirements - - Delinea Secret Server service account or client credential w/ permission to access the secret(s) being used. See the [Delinea - Secret Server documentation]([Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm).) for more information on how to configure service accounts and client credentials. + - Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. + - A service account or application account with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts and application accounts. #### Create PAM type in Keyfactor Command @@ -168,7 +136,7 @@ Below is the payload to `POST` to the Keyfactor Command API "Name": "Username", "DisplayName": "Secret Server Username", "Description": "The username used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type.", - "DataType": 2, + "DataType": 1, "InstanceLevel": false }, { @@ -182,7 +150,7 @@ Below is the payload to `POST` to the Keyfactor Command API "Name": "ClientId", "DisplayName": "Secret Server Client ID", "Description": "The client ID used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type.", - "DataType": 2, + "DataType": 1, "InstanceLevel": false }, { @@ -195,7 +163,7 @@ Below is the payload to `POST` to the Keyfactor Command API { "Name": "GrantType", "DisplayName": "Grant Type", - "Description": "The grant type used to authenticate to the Secret Server instance. Valid values are `password` or `client_credentials`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatability.", + "Description": "The grant type used to authenticate to the Secret Server instance. Valid values are `password`, `client_credentials`, or `windows`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatibility.", "DataType": 1, "InstanceLevel": false }, @@ -296,13 +264,8 @@ Below is the payload to `POST` to the Keyfactor Command API ```json { - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "Username": "", - "Password": "", - "ClientId": "", - "ClientSecret": "", - "GrantType": "password|client_credentials|windows" + "Keyfactor:PAMProviders:Delinea-SecretServer-Windows:InitializationInfo": { + "Host": "https://example.secretserver.internal/SecretServer" } } @@ -316,116 +279,957 @@ Below is the payload to `POST` to the Keyfactor Command API +
-### Usage +
Delinea-SecretServer-Password +#### Requirements + - Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. + - A service account with a username and password that has permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts. -#### From Keyfactor Command Host (Local) +#### Create PAM type in Keyfactor Command +##### Using `kfutil` +Create the required PAM Types in the connected Command platform. -##### Define a PAM provider in Command -1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. +```shell +# Delinea-SecretServer-Password +kfutil pam types-create -r delinea-secretserver-pam -n Delinea-SecretServer-Password +``` -2. Select the **Add** button to create a new PAM provider. Click the dropdown for **Provider Type** and select **Delinea-SecretServer**. +##### Using the API +For full API docs please visit our [product documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/WebAPI/KeyfactorAPI/PAMProvidersPOSTTypes.htm?Highlight=pam%20type) -> [!IMPORTANT] -> If you're running Keyfactor Command 11+, make sure `Remote Provider` is unchecked. +Below is the payload to `POST` to the Keyfactor Command API +```json +{ + "Name": "Delinea-SecretServer-Password", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "Username", + "DisplayName": "Secret Server Username", + "Description": "The username used to authenticate to the Secret Server instance.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "Password", + "DisplayName": "Secret Server Password", + "Description": "The password used to authenticate to the Secret Server instance.", + "DataType": 2, + "InstanceLevel": false + }, + { + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, + { + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] +} +``` -3. Populate the fields with the necessary information collected in the [requirements](docs/delinea-secretserver.md#requirements) section: +#### Install PAM provider on Keyfactor Command Host (Local) -| Initialization parameter | Display Name | Description | -| --- | --- | --- | -| Host | Secret Server URL | The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer | -| Username | Secret Server Username | The username used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type. | -| Password | Secret Server Password | The password used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type. | -| ClientId | Secret Server Client ID | The client ID used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type. | -| ClientSecret | Secret Server Client Secret | The client secret used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type. | -| GrantType | Grant Type | The grant type used to authenticate to the Secret Server instance. Valid values are `password` or `client_credentials`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatability. | -4. Click **Save**. The PAM provider is now available for use in Keyfactor Command. +1. On the server that hosts Keyfactor Command, download and unzip the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. -##### Using the PAM provider +2. Copy the assemblies to the appropriate directories on the Keyfactor Command server: -Now, when defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** will be available as a PAM provider option. When defining new Certificate Stores, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. +
Keyfactor Command 11+ -Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + 1. Copy the unzipped assemblies to each of the following directories: -| Instance parameter | Display Name | Description | -| --- | --- | --- | -| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | -| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\Extensions\delinea-secretserver-pam` + +
+
Keyfactor Command 10 + 1. Copy the assemblies to each of the following directories: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\delinea-secretserver-pam` + 2. Open a text editor on the Keyfactor Command server as an administrator and open the `web.config` file located in the `WebAgentServices` directory. + 3. In the `web.config` file, locate the ` ` section and add the following registration: -#### From a Universal Orchestrator Host (Remote) + ```xml + + ... + + + + + + ``` + 4. Repeat steps 2 and 3 for each of the directories listed in step 1. The configuration files are located in the following paths by default: + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\CMSTimerService.exe.config` -
Keyfactor Command 11+ +
-##### Define a remote PAM provider in Command +3. Restart the Keyfactor Command services (`iisreset`). -In Command 11 and greater, before using the Delinea-SecretServer PAM type, you must define a Remote PAM Provider in the Command portal. -1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. -2. Select the **Add** button to create a new PAM provider. -3. Make sure that `Remote Provider` is checked. +#### Install PAM provider on a Universal Orchestrator Host (Remote) -4. Click the dropdown for **Provider Type** and select **Delinea-SecretServer**. -5. Give the provider a unique name. +1. Install the Delinea Secret Server PAM Provider assemblies. -6. Click "Save". + * **Using kfutil**: On the server that that hosts the Universal Orchestrator, run the following command: -##### Using the PAM provider + ```shell + # Windows Server + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions" + + # Linux + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "/opt/keyfactor/orchestrator/extensions" + ``` + + * **Manually**: Download the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. Extract the contents of the archive to: + + * **Windows Server**: `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions\delinea-secretserver-pam` + * **Linux**: `/opt/keyfactor/orchestrator/extensions/delinea-secretserver-pam` + +2. Included in the release is a `manifest.json` file that contains the following object: + ```json + + { + "Keyfactor:PAMProviders:Delinea-SecretServer-Windows:InitializationInfo": { + "Host": "https://example.secretserver.internal/SecretServer" + } + } + + ``` + + Populate the fields in this object with credentials and configuration data collected in the [requirements](docs/delinea-secretserver-password.md#requirements) section. + +3. Restart the Universal Orchestrator service. -When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** can be used as a PAM provider. When defining a new Certificate Store, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. -Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: -| Instance parameter | Display Name | Description | -| --- | --- | --- | -| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | -| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. |
-
Keyfactor Command 10 -When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** can be used as a PAM provider. -When entering Secret fields, select the **Load From Keyfactor Secrets** tab, and populate the **Secret Value** field with the following JSON object: -```json -{"SecretId": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.","SecretFieldName": "The name of the field in the secret that contains the credential value. NOTE: The field must exist."} + + +
Delinea-SecretServer-ClientCredentials + + +#### Requirements + - Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. + - An application account (Client ID and Client Secret) with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring application accounts. + +#### Create PAM type in Keyfactor Command + + +##### Using `kfutil` +Create the required PAM Types in the connected Command platform. + +```shell +# Delinea-SecretServer-ClientCredentials +kfutil pam types-create -r delinea-secretserver-pam -n Delinea-SecretServer-ClientCredentials ``` -> We recommend creating this JSON object in a text editor, and copying it into the Secret Value field. +##### Using the API +For full API docs please visit our [product documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/WebAPI/KeyfactorAPI/PAMProvidersPOSTTypes.htm?Highlight=pam%20type) -
+Below is the payload to `POST` to the Keyfactor Command API +```json +{ + "Name": "Delinea-SecretServer-ClientCredentials", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "ClientId", + "DisplayName": "OAuth2 Client ID", + "Description": "The client ID (application account name) used for OAuth2 client credentials authentication.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "ClientSecret", + "DisplayName": "OAuth2 Client Secret", + "Description": "The client secret (application account password) used for OAuth2 client credentials authentication.", + "DataType": 2, + "InstanceLevel": false + }, + { + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, + { + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] +} +``` +#### Install PAM provider on Keyfactor Command Host (Local) +1. On the server that hosts Keyfactor Command, download and unzip the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. +2. Copy the assemblies to the appropriate directories on the Keyfactor Command server: -> [!NOTE] -> Additional information on Delinea-SecretServer can be found in the [supplemental documentation](docs/delinea-secretserver.md). +
Keyfactor Command 11+ + + 1. Copy the unzipped assemblies to each of the following directories: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\Extensions\delinea-secretserver-pam` + +
+ +
Keyfactor Command 10 + + 1. Copy the assemblies to each of the following directories: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\delinea-secretserver-pam` + + 2. Open a text editor on the Keyfactor Command server as an administrator and open the `web.config` file located in the `WebAgentServices` directory. + + 3. In the `web.config` file, locate the ` ` section and add the following registration: + + ```xml + + ... + + + + + + ``` + + 4. Repeat steps 2 and 3 for each of the directories listed in step 1. The configuration files are located in the following paths by default: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\CMSTimerService.exe.config` + +
+ +3. Restart the Keyfactor Command services (`iisreset`). + + + + +#### Install PAM provider on a Universal Orchestrator Host (Remote) + + +1. Install the Delinea Secret Server PAM Provider assemblies. + + * **Using kfutil**: On the server that that hosts the Universal Orchestrator, run the following command: + + ```shell + # Windows Server + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions" + + # Linux + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "/opt/keyfactor/orchestrator/extensions" + ``` + + * **Manually**: Download the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. Extract the contents of the archive to: + + * **Windows Server**: `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions\delinea-secretserver-pam` + * **Linux**: `/opt/keyfactor/orchestrator/extensions/delinea-secretserver-pam` + +2. Included in the release is a `manifest.json` file that contains the following object: + ```json + + { + "Keyfactor:PAMProviders:Delinea-SecretServer-Windows:InitializationInfo": { + "Host": "https://example.secretserver.internal/SecretServer" + } + } + + ``` + + Populate the fields in this object with credentials and configuration data collected in the [requirements](docs/delinea-secretserver-clientcredentials.md#requirements) section. + +3. Restart the Universal Orchestrator service. + + + + + +
+ + + + + + + +
Delinea-SecretServer-Windows + + +#### Requirements + - On-premises Delinea Secret Server instance accessible from the host running Keyfactor Command or the Universal Orchestrator. + - The Windows service account running Keyfactor Command or the Universal Orchestrator must have permission to view + the secrets being retrieved. See the + [Delinea Secret Server IWA documentation](https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm) + for information on configuring IWA access. + - The Secret Server instance must be configured to allow Integrated Windows Authentication web service access. + +#### Create PAM type in Keyfactor Command + + +##### Using `kfutil` +Create the required PAM Types in the connected Command platform. + +```shell +# Delinea-SecretServer-Windows +kfutil pam types-create -r delinea-secretserver-pam -n Delinea-SecretServer-Windows +``` + +##### Using the API +For full API docs please visit our [product documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/WebAPI/KeyfactorAPI/PAMProvidersPOSTTypes.htm?Highlight=pam%20type) + +Below is the payload to `POST` to the Keyfactor Command API +```json +{ + "Name": "Delinea-SecretServer-Windows", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretserver.internal/SecretServer. NOTE: IWA is not supported on Secret Server Cloud.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, + { + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] +} +``` + +#### Install PAM provider on Keyfactor Command Host (Local) + + + +1. On the server that hosts Keyfactor Command, download and unzip the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. + +2. Copy the assemblies to the appropriate directories on the Keyfactor Command server: + +
Keyfactor Command 11+ + + 1. Copy the unzipped assemblies to each of the following directories: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\Extensions\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\Extensions\delinea-secretserver-pam` + +
+ +
Keyfactor Command 10 + + 1. Copy the assemblies to each of the following directories: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\bin\delinea-secretserver-pam` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\delinea-secretserver-pam` + + 2. Open a text editor on the Keyfactor Command server as an administrator and open the `web.config` file located in the `WebAgentServices` directory. + + 3. In the `web.config` file, locate the ` ` section and add the following registration: + + ```xml + + ... + + + + + + ``` + + 4. Repeat steps 2 and 3 for each of the directories listed in step 1. The configuration files are located in the following paths by default: + + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebAgentServices\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\KeyfactorAPI\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\WebConsole\web.config` + * `C:\Program Files\Keyfactor\Keyfactor Platform\Service\CMSTimerService.exe.config` + +
+ +3. Restart the Keyfactor Command services (`iisreset`). + + + + +#### Install PAM provider on a Universal Orchestrator Host (Remote) + + +1. Install the Delinea Secret Server PAM Provider assemblies. + + * **Using kfutil**: On the server that that hosts the Universal Orchestrator, run the following command: + + ```shell + # Windows Server + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions" + + # Linux + kfutil orchestrator extension -e delinea-secretserver-pam@latest --out "/opt/keyfactor/orchestrator/extensions" + ``` + + * **Manually**: Download the latest release of the Delinea Secret Server PAM Provider from the [Releases](../../releases) page. Extract the contents of the archive to: + + * **Windows Server**: `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions\delinea-secretserver-pam` + * **Linux**: `/opt/keyfactor/orchestrator/extensions/delinea-secretserver-pam` + +2. Included in the release is a `manifest.json` file that contains the following object: + ```json + + { + "Keyfactor:PAMProviders:Delinea-SecretServer-Windows:InitializationInfo": { + "Host": "https://example.secretserver.internal/SecretServer" + } + } + + ``` + + Populate the fields in this object with credentials and configuration data collected in the [requirements](docs/delinea-secretserver-windows.md#requirements) section. + +3. Restart the Universal Orchestrator service. + + + + + +
+ + + + + +### Usage + + + + + +
Delinea-SecretServer + + +#### From Keyfactor Command Host (Local) + + + +##### Define a PAM provider in Command +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. Click the dropdown for **Provider Type** and select **Delinea-SecretServer**. + +> [!IMPORTANT] +> If you're running Keyfactor Command 11+, make sure `Remote Provider` is unchecked. + +3. Populate the fields with the necessary information collected in the [requirements](docs/delinea-secretserver.md#requirements) section: + +| Initialization parameter | Display Name | Description | +| --- | --- | --- | +| Host | Secret Server URL | The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer | +| Username | Secret Server Username | The username used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type. | +| Password | Secret Server Password | The password used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type. | +| ClientId | Secret Server Client ID | The client ID used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type. | +| ClientSecret | Secret Server Client Secret | The client secret used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type. | +| GrantType | Grant Type | The grant type used to authenticate to the Secret Server instance. Valid values are `password`, `client_credentials`, or `windows`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatibility. | + + +4. Click **Save**. The PAM provider is now available for use in Keyfactor Command. + +##### Using the PAM provider + +Now, when defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** will be available as a PAM provider option. When defining new Certificate Stores, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + + + + +#### From a Universal Orchestrator Host (Remote) + + + +
Keyfactor Command 11+ + +##### Define a remote PAM provider in Command + +In Command 11 and greater, before using the Delinea-SecretServer PAM type, you must define a Remote PAM Provider in the Command portal. + +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. + +3. Make sure that `Remote Provider` is checked. + +4. Click the dropdown for **Provider Type** and select **Delinea-SecretServer**. + +5. Give the provider a unique name. + +6. Click "Save". + +##### Using the PAM provider + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** can be used as a PAM provider. When defining a new Certificate Store, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + +
+ +
Keyfactor Command 10 + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer** can be used as a PAM provider. + +When entering Secret fields, select the **Load From Keyfactor Secrets** tab, and populate the **Secret Value** field with the following JSON object: + +```json +{"SecretId": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.","SecretFieldName": "The name of the field in the secret that contains the credential value. NOTE: The field must exist."} + +``` + +> We recommend creating this JSON object in a text editor, and copying it into the Secret Value field. + +
+ + + + + +
+ + +> [!NOTE] +> Additional information on Delinea-SecretServer can be found in the [supplemental documentation](docs/delinea-secretserver.md). + + + + + +
Delinea-SecretServer-Password + + +#### From Keyfactor Command Host (Local) + + + +##### Define a PAM provider in Command +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-Password**. + +> [!IMPORTANT] +> If you're running Keyfactor Command 11+, make sure `Remote Provider` is unchecked. + +3. Populate the fields with the necessary information collected in the [requirements](docs/delinea-secretserver-password.md#requirements) section: + +| Initialization parameter | Display Name | Description | +| --- | --- | --- | +| Host | Secret Server URL | The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer | +| Username | Secret Server Username | The username used to authenticate to the Secret Server instance. | +| Password | Secret Server Password | The password used to authenticate to the Secret Server instance. | +| SkipTlsValidation | Skip TLS Validation | Set to `true` to disable TLS certificate validation. Use only in non-production environments. | + + +4. Click **Save**. The PAM provider is now available for use in Keyfactor Command. + +##### Using the PAM provider + +Now, when defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Password** will be available as a PAM provider option. When defining new Certificate Stores, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-Password** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + + + + +#### From a Universal Orchestrator Host (Remote) + + + +
Keyfactor Command 11+ + +##### Define a remote PAM provider in Command + +In Command 11 and greater, before using the Delinea-SecretServer-Password PAM type, you must define a Remote PAM Provider in the Command portal. + +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. + +3. Make sure that `Remote Provider` is checked. + +4. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-Password**. + +5. Give the provider a unique name. + +6. Click "Save". + +##### Using the PAM provider + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Password** can be used as a PAM provider. When defining a new Certificate Store, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-Password** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + +
+ +
Keyfactor Command 10 + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Password** can be used as a PAM provider. + +When entering Secret fields, select the **Load From Keyfactor Secrets** tab, and populate the **Secret Value** field with the following JSON object: + +```json +{"SecretId": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.","SecretFieldName": "The name of the field in the secret that contains the credential value. NOTE: The field must exist."} + +``` + +> We recommend creating this JSON object in a text editor, and copying it into the Secret Value field. + +
+ + + + + +
+ + +> [!NOTE] +> Additional information on Delinea-SecretServer-Password can be found in the [supplemental documentation](docs/delinea-secretserver-password.md). + + + + + +
Delinea-SecretServer-ClientCredentials + + +#### From Keyfactor Command Host (Local) + + + +##### Define a PAM provider in Command +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-ClientCredentials**. + +> [!IMPORTANT] +> If you're running Keyfactor Command 11+, make sure `Remote Provider` is unchecked. + +3. Populate the fields with the necessary information collected in the [requirements](docs/delinea-secretserver-clientcredentials.md#requirements) section: + +| Initialization parameter | Display Name | Description | +| --- | --- | --- | +| Host | Secret Server URL | The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer | +| ClientId | OAuth2 Client ID | The client ID (application account name) used for OAuth2 client credentials authentication. | +| ClientSecret | OAuth2 Client Secret | The client secret (application account password) used for OAuth2 client credentials authentication. | +| SkipTlsValidation | Skip TLS Validation | Set to `true` to disable TLS certificate validation. Use only in non-production environments. | + + +4. Click **Save**. The PAM provider is now available for use in Keyfactor Command. + +##### Using the PAM provider + +Now, when defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-ClientCredentials** will be available as a PAM provider option. When defining new Certificate Stores, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-ClientCredentials** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + + + + +#### From a Universal Orchestrator Host (Remote) + + + +
Keyfactor Command 11+ + +##### Define a remote PAM provider in Command + +In Command 11 and greater, before using the Delinea-SecretServer-ClientCredentials PAM type, you must define a Remote PAM Provider in the Command portal. + +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. + +3. Make sure that `Remote Provider` is checked. + +4. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-ClientCredentials**. + +5. Give the provider a unique name. + +6. Click "Save". + +##### Using the PAM provider + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-ClientCredentials** can be used as a PAM provider. When defining a new Certificate Store, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-ClientCredentials** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + +
+ +
Keyfactor Command 10 + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-ClientCredentials** can be used as a PAM provider. + +When entering Secret fields, select the **Load From Keyfactor Secrets** tab, and populate the **Secret Value** field with the following JSON object: + +```json +{"SecretId": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.","SecretFieldName": "The name of the field in the secret that contains the credential value. NOTE: The field must exist."} + +``` + +> We recommend creating this JSON object in a text editor, and copying it into the Secret Value field. + +
+ + + + + +
+ + +> [!NOTE] +> Additional information on Delinea-SecretServer-ClientCredentials can be found in the [supplemental documentation](docs/delinea-secretserver-clientcredentials.md). + + + + + +
Delinea-SecretServer-Windows + + +#### From Keyfactor Command Host (Local) + + + +##### Define a PAM provider in Command +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-Windows**. + +> [!IMPORTANT] +> If you're running Keyfactor Command 11+, make sure `Remote Provider` is unchecked. + +3. Populate the fields with the necessary information collected in the [requirements](docs/delinea-secretserver-windows.md#requirements) section: + +| Initialization parameter | Display Name | Description | +| --- | --- | --- | +| Host | Secret Server URL | The URL to the Secret Server instance. Example: https://example.secretserver.internal/SecretServer. NOTE: IWA is not supported on Secret Server Cloud. | +| SkipTlsValidation | Skip TLS Validation | Set to `true` to disable TLS certificate validation. Use only in non-production environments. | + + +4. Click **Save**. The PAM provider is now available for use in Keyfactor Command. + +##### Using the PAM provider + +Now, when defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Windows** will be available as a PAM provider option. When defining new Certificate Stores, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-Windows** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + + + + +#### From a Universal Orchestrator Host (Remote) + + + +
Keyfactor Command 11+ + +##### Define a remote PAM provider in Command + +In Command 11 and greater, before using the Delinea-SecretServer-Windows PAM type, you must define a Remote PAM Provider in the Command portal. + +1. In the Keyfactor Command Portal, hover over the ⚙️ (settings) icon in the top right corner of the screen and select **Priviledged Access Management**. + +2. Select the **Add** button to create a new PAM provider. + +3. Make sure that `Remote Provider` is checked. + +4. Click the dropdown for **Provider Type** and select **Delinea-SecretServer-Windows**. + +5. Give the provider a unique name. + +6. Click "Save". + +##### Using the PAM provider + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Windows** can be used as a PAM provider. When defining a new Certificate Store, the secret parameter form will display tabs for **Load From Keyfactor Secrets** or **Load From PAM Provider**. + +Select the **Load From PAM Provider** tab, choose the **Delinea-SecretServer-Windows** provider from the list of **Providers**, and populate the fields with the necessary information from the table below: + +| Instance parameter | Display Name | Description | +| --- | --- | --- | +| SecretId | Secret ID | The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server. | +| SecretFieldName | Secret Field Name | The name of the field in the secret that contains the credential value. NOTE: The field must exist. | + + +
+ +
Keyfactor Command 10 + +When defining Certificate Stores (**Locations**->**Certificate Stores**), **Delinea-SecretServer-Windows** can be used as a PAM provider. + +When entering Secret fields, select the **Load From Keyfactor Secrets** tab, and populate the **Secret Value** field with the following JSON object: + +```json +{"SecretId": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.","SecretFieldName": "The name of the field in the secret that contains the credential value. NOTE: The field must exist."} + +``` + +> We recommend creating this JSON object in a text editor, and copying it into the Secret Value field. + +
+ + + + + +
+ + +> [!NOTE] +> Additional information on Delinea-SecretServer-Windows can be found in the [supplemental documentation](docs/delinea-secretserver-windows.md). diff --git a/TestConsole/Program.cs b/TestConsole/Program.cs index cac27f3..5994c9d 100644 --- a/TestConsole/Program.cs +++ b/TestConsole/Program.cs @@ -11,6 +11,10 @@ namespace TestConsole; internal class Program { + static string RequireEnv(string name) => + Environment.GetEnvironmentVariable(name) + ?? throw new InvalidOperationException($"Required environment variable '{name}' is not set."); + private static void Main(string[] args) { var pam = new SecretServerPam(); @@ -19,21 +23,19 @@ private static void Main(string[] args) var instanceParams = new Dictionary(); //Read SecretServerUrl from environment variable - initInfo.Add("Host", - Environment.GetEnvironmentVariable("SECRET_SERVER_URL") ?? "https://keyfactor.secretservercloud.com"); - //Read Username from environment variable + initInfo.Add("Host", RequireEnv("SECRET_SERVER_URL")); + //Read GrantType from environment variable initInfo.Add("GrantType", Environment.GetEnvironmentVariable("SECRET_SERVER_GRANT_TYPE") ?? "password"); switch (initInfo["GrantType"]) { case "password": - initInfo.Add("Username", Environment.GetEnvironmentVariable("SECRET_SERVER_USERNAME") ?? "pam-tester"); - initInfo.Add("Password", Environment.GetEnvironmentVariable("SECRET_SERVER_PASSWORD") ?? "changeme!"); + initInfo.Add("Username", RequireEnv("SECRET_SERVER_USERNAME")); + initInfo.Add("Password", RequireEnv("SECRET_SERVER_PASSWORD")); break; case "client_credentials": - initInfo.Add("ClientId", Environment.GetEnvironmentVariable("SECRET_SERVER_CLIENT_ID") ?? "pam-tester"); - initInfo.Add("ClientSecret", - Environment.GetEnvironmentVariable("SECRET_SERVER_CLIENT_SECRET") ?? "changeme!"); + initInfo.Add("ClientId", RequireEnv("SECRET_SERVER_CLIENT_ID")); + initInfo.Add("ClientSecret", RequireEnv("SECRET_SERVER_CLIENT_SECRET")); break; case "windows": break; @@ -41,13 +43,16 @@ private static void Main(string[] args) throw new Exception($"Unsupported Grant Type: {initInfo["GrantType"]}"); } + if (string.Equals(Environment.GetEnvironmentVariable("SECRET_SERVER_SKIP_TLS_VALIDATION"), "true", StringComparison.OrdinalIgnoreCase)) + initInfo.Add("SkipTlsValidation", "true"); + //Read SecretId from environment variable - instanceParams.Add("SecretId", Environment.GetEnvironmentVariable("SECRET_SERVER_SECRET_ID") ?? "1"); + instanceParams.Add("SecretId", RequireEnv("SECRET_SERVER_SECRET_ID")); instanceParams.Add("SecretFieldName", "username"); var username = pam.GetPassword(instanceParams, initInfo); instanceParams["SecretFieldName"] = "password"; var password = pam.GetPassword(instanceParams, initInfo); Console.WriteLine($"ServerUsername: {username}"); - Console.WriteLine($"ServerPassword: {password}"); + Console.WriteLine($"ServerPassword: {new string('*', password?.Length ?? 0)} (len={password?.Length ?? 0})"); } } \ No newline at end of file diff --git a/TestConsole/TestConsole.csproj b/TestConsole/TestConsole.csproj index f6e4fb6..83f7f4b 100644 --- a/TestConsole/TestConsole.csproj +++ b/TestConsole/TestConsole.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net10.0 enable enable Linux diff --git a/delinea-secretserver-pam.Tests/Fakes/TestHttpMessageHandler.cs b/delinea-secretserver-pam.Tests/Fakes/TestHttpMessageHandler.cs new file mode 100644 index 0000000..89f901c --- /dev/null +++ b/delinea-secretserver-pam.Tests/Fakes/TestHttpMessageHandler.cs @@ -0,0 +1,33 @@ +// Copyright 2025 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System.Net; + +namespace Keyfactor.Extensions.Pam.Delinea.Tests.Fakes; + +/// +/// A test-only whose behaviour is controlled +/// by a delegate, allowing individual tests to script exactly what the fake +/// HTTP server returns without going to the network. +/// +public class TestHttpMessageHandler : HttpMessageHandler +{ + public Func>? HandlerFunc { get; set; } + + public TestHttpMessageHandler( + Func>? handlerFunc = null) + { + HandlerFunc = handlerFunc; + } + + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + => HandlerFunc != null + ? HandlerFunc(request, cancellationToken) + : Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotImplemented)); +} diff --git a/delinea-secretserver-pam.Tests/IntegrationFactAttribute.cs b/delinea-secretserver-pam.Tests/IntegrationFactAttribute.cs new file mode 100644 index 0000000..0bb90d4 --- /dev/null +++ b/delinea-secretserver-pam.Tests/IntegrationFactAttribute.cs @@ -0,0 +1,29 @@ +using Xunit.Sdk; + +namespace Keyfactor.Extensions.Pam.Delinea.Tests; + +/// +/// Marks a test as an integration test that requires specific environment variables. +/// The test is skipped (not failed) when any of the named variables are absent or empty. +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class IntegrationFactAttribute : FactAttribute +{ + private static readonly string[] Required = + { + "SECRET_SERVER_URL", + "SECRET_SERVER_USERNAME", + "SECRET_SERVER_PASSWORD", + "SECRET_SERVER_SECRET_ID" + }; + + public IntegrationFactAttribute() + { + var missing = Required + .Where(v => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(v))) + .ToList(); + + if (missing.Count > 0) + Skip = $"Integration env vars not set: {string.Join(", ", missing)}"; + } +} diff --git a/delinea-secretserver-pam.Tests/SecretServerPamTests.cs b/delinea-secretserver-pam.Tests/SecretServerPamTests.cs new file mode 100644 index 0000000..bf3382f --- /dev/null +++ b/delinea-secretserver-pam.Tests/SecretServerPamTests.cs @@ -0,0 +1,968 @@ +// Copyright 2025 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System.Net; +using FluentAssertions; +using Keyfactor.Extensions.Pam.Delinea.Tests.Fakes; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; + +namespace Keyfactor.Extensions.Pam.Delinea.Tests; + +/// +/// Tests for all four concrete PAM provider types and the shared base logic. +/// HTTP is intercepted via — no network calls. +/// +public class SecretServerPamTests +{ + // --------------------------------------------------------------------------- + // Shared test constants + // --------------------------------------------------------------------------- + private const string FakeHost = "https://secretserver.example.com/SecretServer"; + private const string FakeUsername = "svc-account"; + private const string FakePassword = "sup3rS3cret!"; + private const string FakeClientId = "app-client-01"; + private const string FakeClientSecret = "cl13ntS3cr3t!"; + private const string FakeSecretId = "42"; + private const string FakeFieldName = "password"; + private const string FakeFieldValue = "retrieved-credential-value"; + private const string FakeToken = "fake-bearer-token"; + + private static ILogger NullLogger => NullLogger.Instance; + + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + + private static string BuildTokenResponse(string token = FakeToken) + => JsonConvert.SerializeObject(new Dictionary { { "access_token", token } }); + + private static string BuildSecretResponse(string fieldName = FakeFieldName, string fieldValue = FakeFieldValue) + => JsonConvert.SerializeObject(new + { + id = 42, + name = "Test Secret", + items = new[] + { + new { itemId = 1, fieldName = fieldName, slug = fieldName, itemValue = fieldValue, isPassword = true } + } + }); + + /// + /// Creates a that sequences multiple responses: + /// first response for the token endpoint, second for the secret endpoint. + /// + private static TestHttpMessageHandler TwoStageHandler( + HttpResponseMessage tokenResponse, + HttpResponseMessage secretResponse) + { + var callCount = 0; + return new TestHttpMessageHandler((req, ct) => + { + callCount++; + return Task.FromResult(callCount == 1 ? tokenResponse : secretResponse); + }); + } + + private static TestHttpMessageHandler ConstantHandler(HttpResponseMessage response) + => new TestHttpMessageHandler((req, ct) => Task.FromResult(response)); + + // --------------------------------------------------------------------------- + // SecretServerPam (backwards-compatible, GrantType-driven) + // --------------------------------------------------------------------------- + + public class BackwardsCompatibleType + { + [Fact] + public void Name_IsDelineaSecretServer() + { + var sut = new SecretServerPam(); + sut.Name.Should().Be("Delinea-SecretServer"); + } + + [Fact] + public void GetPassword_PasswordGrant_ReturnsSecret() + { + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPam(new HttpClient(handler), NullLogger); + + var result = sut.GetPassword( + InstanceParams(), + ServerParams("password")); + + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_ClientCredentialsGrant_ReturnsSecret() + { + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPam(new HttpClient(handler), NullLogger); + + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "ClientId", FakeClientId }, + { "ClientSecret", FakeClientSecret }, + { "GrantType", "client_credentials" } + }; + + var result = sut.GetPassword(InstanceParams(), serverParams); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_DefaultsToPasswordGrant_WhenGrantTypeAbsent() + { + var tokenRequested = false; + var handler = new TestHttpMessageHandler((req, ct) => + { + if (req.RequestUri!.AbsolutePath.Contains("oauth2/token")) + tokenRequested = true; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent( + req.RequestUri.AbsolutePath.Contains("oauth2/token") + ? BuildTokenResponse() + : BuildSecretResponse()) + }); + }); + + var sut = new SecretServerPam(new HttpClient(handler), NullLogger); + + // No GrantType key in server params + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword } + }; + + var result = sut.GetPassword(InstanceParams(), serverParams); + result.Should().Be(FakeFieldValue); + tokenRequested.Should().BeTrue(); + } + + [Fact] + public void GetPassword_MissingHost_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPam(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Username", FakeUsername }, + { "Password", FakePassword } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw() + .WithMessage("*Host*"); + } + + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + private static Dictionary ServerParams(string grantType) => new() + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword }, + { "GrantType", grantType } + }; + } + + // --------------------------------------------------------------------------- + // SecretServerPamPassword + // --------------------------------------------------------------------------- + + public class PasswordType + { + [Fact] + public void Name_IsDelineaSecretServerPassword() + { + var sut = new SecretServerPamPassword(); + sut.Name.Should().Be("Delinea-SecretServer-Password"); + } + + [Fact] + public void GetPassword_HappyPath_ReturnsSecret() + { + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + + var result = sut.GetPassword(InstanceParams(), ServerParams()); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_MatchBySlug_ReturnsSecret() + { + // Build a secret where fieldName differs from slug; look up by slug + var secretJson = JsonConvert.SerializeObject(new + { + id = 42, + name = "Test Secret", + items = new[] + { + new + { + itemId = 1, + fieldName = "Display Name", + slug = FakeFieldName, + itemValue = FakeFieldValue, + isPassword = true + } + } + }); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(secretJson) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams()); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_TokenEndpointReturns401_ThrowsHttpRequestException() + { + var handler = ConstantHandler(new HttpResponseMessage(HttpStatusCode.Unauthorized) + { Content = new StringContent("unauthorized") }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), ServerParams()); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_SecretEndpointReturns404_ThrowsHttpRequestException() + { + var callCount = 0; + var handler = new TestHttpMessageHandler((req, ct) => + { + callCount++; + var statusCode = callCount == 1 ? HttpStatusCode.OK : HttpStatusCode.NotFound; + var content = callCount == 1 ? BuildTokenResponse() : "not found"; + return Task.FromResult(new HttpResponseMessage(statusCode) + { Content = new StringContent(content) }); + }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), ServerParams()); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_FieldNotFoundInSecret_ThrowsInvalidSecretConfigurationException() + { + var secretJson = BuildSecretResponse("other-field", "some-value"); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(secretJson) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + + // Request a field name that is not in the secret + var instanceParams = new Dictionary + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", "nonexistent-field" } + }; + + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw() + .WithMessage("*nonexistent-field*"); + } + + [Fact] + public void GetPassword_EmptyToken_ThrowsInvalidTokenException() + { + var tokenJson = JsonConvert.SerializeObject(new Dictionary + { { "access_token", string.Empty } }); + + var handler = ConstantHandler( + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(tokenJson) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), ServerParams()); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_MissingHost_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Username", FakeUsername }, + { "Password", FakePassword } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw().WithMessage("*Host*"); + } + + [Fact] + public void GetPassword_MissingUsername_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "Password", FakePassword } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw().WithMessage("*Username*"); + } + + [Fact] + public void GetPassword_MissingPassword_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "Username", FakeUsername } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw().WithMessage("*Password*"); + } + + [Fact] + public void GetPassword_MissingSecretId_ThrowsInvalidSecretConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var instanceParams = new Dictionary + { + { "SecretFieldName", FakeFieldName } + }; + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw().WithMessage("*SecretId*"); + } + + [Fact] + public void GetPassword_MissingSecretFieldName_ThrowsInvalidSecretConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var instanceParams = new Dictionary + { + { "SecretId", FakeSecretId } + }; + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw().WithMessage("*SecretFieldName*"); + } + + [Fact] + public void GetPassword_NonIntegerSecretId_ThrowsInvalidSecretConfigurationException() + { + var sut = new SecretServerPamPassword(new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var instanceParams = new Dictionary + { + { "SecretId", "not-an-int" }, + { "SecretFieldName", FakeFieldName } + }; + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw().WithMessage("*not-an-int*"); + } + + [Fact] + public void GetPassword_TokenRequestBody_ContainsUsernameAndPassword() + { + // Verify the token POST uses "username"/"password" field names + // (Delinea API constraint — not "client_id"/"client_secret") + string? capturedBody = null; + var callCount = 0; + + var handler = new TestHttpMessageHandler(async (req, ct) => + { + callCount++; + if (callCount == 1) + { + capturedBody = await req.Content!.ReadAsStringAsync(); + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }; + } + + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }; + }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), ServerParams()); + + capturedBody.Should().Contain("username="); + capturedBody.Should().Contain("password="); + capturedBody.Should().Contain("grant_type=password"); + } + + [Fact] + public void GetPassword_TokenRequestUrl_PointsToOAuth2TokenEndpoint() + { + Uri? capturedTokenUri = null; + var callCount = 0; + + var handler = new TestHttpMessageHandler((req, ct) => + { + callCount++; + if (callCount == 1) + { + capturedTokenUri = req.RequestUri; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }); + } + + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), ServerParams()); + + capturedTokenUri.Should().NotBeNull(); + capturedTokenUri!.AbsolutePath.Should().EndWith("/oauth2/token"); + } + + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + private static Dictionary ServerParams() => new() + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword } + }; + } + + // --------------------------------------------------------------------------- + // SecretServerPamClientCredentials + // --------------------------------------------------------------------------- + + public class ClientCredentialsType + { + [Fact] + public void Name_IsDelineaSecretServerClientCredentials() + { + var sut = new SecretServerPamClientCredentials(); + sut.Name.Should().Be("Delinea-SecretServer-ClientCredentials"); + } + + [Fact] + public void GetPassword_HappyPath_ReturnsSecret() + { + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamClientCredentials(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams()); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_TokenRequestBody_UsesUsernamePasswordFieldNames() + { + // Delinea API constraint: client_credentials flow still sends username/password + // in the token request body — NOT client_id/client_secret + string? capturedBody = null; + var callCount = 0; + + var handler = new TestHttpMessageHandler(async (req, ct) => + { + callCount++; + if (callCount == 1) + { + capturedBody = await req.Content!.ReadAsStringAsync(); + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }; + } + + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }; + }); + + var sut = new SecretServerPamClientCredentials(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), ServerParams()); + + // Must use username= / password= field names (Delinea API constraint) + capturedBody.Should().Contain("username="); + capturedBody.Should().Contain("password="); + // ClientId value should appear as the username value + capturedBody.Should().Contain(Uri.EscapeDataString(FakeClientId)); + // Must NOT contain client_id= key + capturedBody.Should().NotContain("client_id="); + } + + [Fact] + public void GetPassword_MissingClientId_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamClientCredentials( + new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "ClientSecret", FakeClientSecret } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw().WithMessage("*ClientId*"); + } + + [Fact] + public void GetPassword_MissingClientSecret_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamClientCredentials( + new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "ClientId", FakeClientId } + }; + var act = () => sut.GetPassword(InstanceParams(), serverParams); + act.Should().Throw().WithMessage("*ClientSecret*"); + } + + [Fact] + public void GetPassword_TokenEndpointFails_ThrowsHttpRequestException() + { + var handler = ConstantHandler(new HttpResponseMessage(HttpStatusCode.Unauthorized) + { Content = new StringContent("unauthorized") }); + + var sut = new SecretServerPamClientCredentials(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), ServerParams()); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_FieldNotFound_ThrowsInvalidSecretConfigurationException() + { + var secretJson = BuildSecretResponse("different-field", "some-value"); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(secretJson) }); + + var sut = new SecretServerPamClientCredentials(new HttpClient(handler), NullLogger); + + var instanceParams = new Dictionary + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", "missing-field" } + }; + + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw(); + } + + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + private static Dictionary ServerParams() => new() + { + { "Host", FakeHost }, + { "ClientId", FakeClientId }, + { "ClientSecret", FakeClientSecret } + }; + } + + // --------------------------------------------------------------------------- + // SecretServerPamWindows + // --------------------------------------------------------------------------- + + public class WindowsType + { + [Fact] + public void Name_IsDelineaSecretServerWindows() + { + var sut = new SecretServerPamWindows(); + sut.Name.Should().Be("Delinea-SecretServer-Windows"); + } + + [Fact] + public void GetPassword_HappyPath_ReturnsSecret() + { + // Windows auth: no token request, single GET to winauthwebservices endpoint + var handler = ConstantHandler(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamWindows(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams()); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_UsesWinAuthWebServicesEndpoint() + { + Uri? capturedUri = null; + + var handler = new TestHttpMessageHandler((req, ct) => + { + capturedUri = req.RequestUri; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + }); + + var sut = new SecretServerPamWindows(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), ServerParams()); + + capturedUri.Should().NotBeNull(); + capturedUri!.AbsolutePath.Should().Contain("winauthwebservices"); + } + + [Fact] + public void GetPassword_DoesNotCallTokenEndpoint() + { + var tokenEndpointCalled = false; + + var handler = new TestHttpMessageHandler((req, ct) => + { + if (req.RequestUri!.AbsolutePath.Contains("oauth2/token")) + tokenEndpointCalled = true; + + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }); + }); + + var sut = new SecretServerPamWindows(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), ServerParams()); + + tokenEndpointCalled.Should().BeFalse("Windows auth should not request a token"); + } + + [Fact] + public void GetPassword_MissingHost_ThrowsInvalidClientConfigurationException() + { + var sut = new SecretServerPamWindows( + new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), new Dictionary()); + act.Should().Throw().WithMessage("*Host*"); + } + + [Fact] + public void GetPassword_SecretEndpointFails_ThrowsHttpRequestException() + { + var handler = ConstantHandler(new HttpResponseMessage(HttpStatusCode.Forbidden) + { Content = new StringContent("forbidden") }); + + var sut = new SecretServerPamWindows(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword(InstanceParams(), ServerParams()); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_FieldNotFound_ThrowsInvalidSecretConfigurationException() + { + var secretJson = BuildSecretResponse("other-field", "some-value"); + var handler = ConstantHandler(new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(secretJson) }); + + var sut = new SecretServerPamWindows(new HttpClient(handler), NullLogger); + var instanceParams = new Dictionary + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", "no-such-field" } + }; + var act = () => sut.GetPassword(instanceParams, ServerParams()); + act.Should().Throw(); + } + + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + private static Dictionary ServerParams() => new() + { + { "Host", FakeHost } + }; + } + + // --------------------------------------------------------------------------- + // Shared validation — tested via SecretServerPamPassword as a representative type + // --------------------------------------------------------------------------- + + public class SharedValidation + { + [Theory] + [InlineData("")] + [InlineData(" ")] + public void GetPassword_EmptySecretFieldName_ThrowsInvalidSecretConfigurationException(string fieldName) + { + var sut = new SecretServerPamPassword( + new HttpClient(ConstantHandler(new HttpResponseMessage())), NullLogger); + var instanceParams = new Dictionary + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", fieldName } + }; + var serverParams = new Dictionary + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword } + }; + var act = () => sut.GetPassword(instanceParams, serverParams); + act.Should().Throw(); + } + + [Fact] + public void GetPassword_ErrorResponseTruncatedAt500Chars_NeverLogsFullErrorBody() + { + // We cannot inspect log output directly without a custom ILogger, but we + // can verify the provider still throws rather than swallowing the error, + // confirming the truncation code path is exercised without hanging. + var longError = new string('x', 2000); + + var callCount = 0; + var handler = new TestHttpMessageHandler((req, ct) => + { + callCount++; + HttpStatusCode status; + string body; + if (callCount == 1) + { + // Token request succeeds + status = HttpStatusCode.OK; + body = BuildTokenResponse(); + } + else + { + // Secret request returns a long error body + status = HttpStatusCode.InternalServerError; + body = longError; + } + + return Task.FromResult(new HttpResponseMessage(status) + { Content = new StringContent(body) }); + }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var act = () => sut.GetPassword( + new Dictionary + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }, + new Dictionary + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword } + }); + + act.Should().Throw(); + } + + [Fact] + public void GetPassword_AllFourTypes_HaveDistinctNames() + { + var names = new[] + { + new SecretServerPam().Name, + new SecretServerPamPassword().Name, + new SecretServerPamClientCredentials().Name, + new SecretServerPamWindows().Name + }; + + names.Should().OnlyHaveUniqueItems("all PAM type Names must be distinct"); + } + } + + // --------------------------------------------------------------------------- + // SkipTlsValidation — config parameter and environment variable + // --------------------------------------------------------------------------- + + public class SkipTlsValidation : IDisposable + { + private const string EnvVar = "KEYFACTOR_PAM_SKIP_TLS_VALIDATION"; + + public void Dispose() => Environment.SetEnvironmentVariable(EnvVar, null); + + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + private static Dictionary ServerParams(bool skipTls = false) => new() + { + { "Host", FakeHost }, + { "Username", FakeUsername }, + { "Password", FakePassword }, + { "SkipTlsValidation", skipTls ? "true" : "false" } + }; + + [Fact] + public void GetPassword_SkipTlsValidationConfig_True_Succeeds() + { + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams(skipTls: true)); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_SkipTlsEnvVar_True_Succeeds() + { + Environment.SetEnvironmentVariable(EnvVar, "true"); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams(skipTls: false)); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_SkipTlsEnvVar_One_Succeeds() + { + Environment.SetEnvironmentVariable(EnvVar, "1"); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams(skipTls: false)); + result.Should().Be(FakeFieldValue); + } + + [Fact] + public void GetPassword_SkipTlsEnvVar_False_DoesNotOverrideConfigFalse() + { + Environment.SetEnvironmentVariable(EnvVar, "false"); + + var handler = TwoStageHandler( + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildTokenResponse()) }, + new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(BuildSecretResponse()) }); + + var sut = new SecretServerPamPassword(new HttpClient(handler), NullLogger); + var result = sut.GetPassword(InstanceParams(), ServerParams(skipTls: false)); + result.Should().Be(FakeFieldValue); + } + } + + // --------------------------------------------------------------------------- + // client_credentials GrantType bug fix — must not send "password" grant type + // --------------------------------------------------------------------------- + + public class ClientCredentialsGrantTypeFix + { + private static Dictionary InstanceParams() => new() + { + { "SecretId", FakeSecretId }, + { "SecretFieldName", FakeFieldName } + }; + + [Fact] + public void GetPassword_ClientCredentials_TokenRequestBody_AlwaysSendsPasswordGrantType() + { + // Delinea API constraint: even for client_credentials flow, the token endpoint + // requires grant_type=password. Do not change this behaviour. + string? capturedBody = null; + var callCount = 0; + + var handler = new TestHttpMessageHandler(async (req, ct) => + { + callCount++; + if (callCount == 1) + { + capturedBody = await req.Content!.ReadAsStringAsync(); + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildTokenResponse()) }; + } + return new HttpResponseMessage(HttpStatusCode.OK) + { Content = new StringContent(BuildSecretResponse()) }; + }); + + var sut = new SecretServerPamClientCredentials(new HttpClient(handler), NullLogger); + sut.GetPassword(InstanceParams(), new Dictionary + { + { "Host", FakeHost }, + { "ClientId", FakeClientId }, + { "ClientSecret", FakeClientSecret } + }); + + capturedBody.Should().Contain("grant_type=password", + "Delinea API constraint: token endpoint always requires grant_type=password"); + } + } + + // --------------------------------------------------------------------------- + // Integration tests — skipped automatically when env vars are not set + // --------------------------------------------------------------------------- + + public class IntegrationTests + { + private static string Env(string name) => Environment.GetEnvironmentVariable(name)!; + private static bool SkipTls => + string.Equals(Env("SECRET_SERVER_SKIP_TLS_VALIDATION"), "true", StringComparison.OrdinalIgnoreCase); + + [IntegrationFact] + public void LiveServer_PasswordGrant_RetrievesSecret() + { + var sut = new SecretServerPamPassword(); + var result = sut.GetPassword( + new Dictionary { { "SecretId", Env("SECRET_SERVER_SECRET_ID") }, { "SecretFieldName", "username" } }, + new Dictionary { { "Host", Env("SECRET_SERVER_URL") }, { "Username", Env("SECRET_SERVER_USERNAME") }, { "Password", Env("SECRET_SERVER_PASSWORD") }, { "SkipTlsValidation", SkipTls ? "true" : "false" } }); + + result.Should().NotBeNullOrEmpty("live Secret Server should return a non-empty value"); + } + + [IntegrationFact] + public void LiveServer_EnvVarSkipTls_RetrievesSecret() + { + Environment.SetEnvironmentVariable("KEYFACTOR_PAM_SKIP_TLS_VALIDATION", "true"); + try + { + var sut = new SecretServerPamPassword(); + var result = sut.GetPassword( + new Dictionary { { "SecretId", Env("SECRET_SERVER_SECRET_ID") }, { "SecretFieldName", "username" } }, + new Dictionary { { "Host", Env("SECRET_SERVER_URL") }, { "Username", Env("SECRET_SERVER_USERNAME") }, { "Password", Env("SECRET_SERVER_PASSWORD") } }); + + result.Should().NotBeNullOrEmpty("KEYFACTOR_PAM_SKIP_TLS_VALIDATION=true should allow retrieval from live server"); + } + finally + { + Environment.SetEnvironmentVariable("KEYFACTOR_PAM_SKIP_TLS_VALIDATION", null); + } + } + } +} diff --git a/delinea-secretserver-pam.Tests/delinea-secretserver-pam.Tests.csproj b/delinea-secretserver-pam.Tests/delinea-secretserver-pam.Tests.csproj new file mode 100644 index 0000000..9fa2419 --- /dev/null +++ b/delinea-secretserver-pam.Tests/delinea-secretserver-pam.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + Keyfactor.Extensions.Pam.Delinea.Tests + enable + enable + false + true + + + + + + + + + + + + + + + + + + + + + diff --git a/delinea-secretserver-pam.sln b/delinea-secretserver-pam.sln index 0b636a1..fdc3d6e 100644 --- a/delinea-secretserver-pam.sln +++ b/delinea-secretserver-pam.sln @@ -4,20 +4,57 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "delinea-secretserver-pam", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsole", "TestConsole\TestConsole.csproj", "{90C4CEE8-44EE-4488-B464-4063432051D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "delinea-secretserver-pam.Tests", "delinea-secretserver-pam.Tests\delinea-secretserver-pam.Tests.csproj", "{2600171C-9C51-4629-B515-EC8279B47FF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|x64.Build.0 = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|x86.Build.0 = Debug|Any CPU {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|Any CPU.ActiveCfg = Release|Any CPU {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|Any CPU.Build.0 = Release|Any CPU - {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|x64.ActiveCfg = Release|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|x64.Build.0 = Release|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|x86.ActiveCfg = Release|Any CPU + {6DEC0EF0-9D07-44CF-868C-82764C83D285}.Release|x86.Build.0 = Release|Any CPU {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|x64.Build.0 = Debug|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Debug|x86.Build.0 = Debug|Any CPU {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|Any CPU.Build.0 = Release|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|x64.ActiveCfg = Release|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|x64.Build.0 = Release|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|x86.ActiveCfg = Release|Any CPU + {90C4CEE8-44EE-4488-B464-4063432051D8}.Release|x86.Build.0 = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|x64.ActiveCfg = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|x64.Build.0 = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Debug|x86.Build.0 = Debug|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|Any CPU.Build.0 = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|x64.ActiveCfg = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|x64.Build.0 = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|x86.ActiveCfg = Release|Any CPU + {2600171C-9C51-4629-B515-EC8279B47FF1}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/delinea-secretserver-pam/AssemblyInfo.cs b/delinea-secretserver-pam/AssemblyInfo.cs new file mode 100644 index 0000000..fade484 --- /dev/null +++ b/delinea-secretserver-pam/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// Copyright 2025 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System.Runtime.CompilerServices; + +// Allow the xUnit test project to access internal members (test constructors) without +// promoting them to public API surface. +[assembly: InternalsVisibleTo("delinea-secretserver-pam.Tests")] diff --git a/delinea-secretserver-pam/Models/DelineaConfiguration.cs b/delinea-secretserver-pam/Models/DelineaConfiguration.cs index 3db69b8..75edcd7 100644 --- a/delinea-secretserver-pam/Models/DelineaConfiguration.cs +++ b/delinea-secretserver-pam/Models/DelineaConfiguration.cs @@ -14,7 +14,7 @@ namespace Keyfactor.Extensions.Pam.Delinea.Models /// Configuration class for connecting to and retrieving secrets from Delinea Secret Server. /// Supports authentication via username/password or client credentials. /// - internal class DelineaConfiguration : IValidatableObject + internal class DelineaConfiguration { /// /// Initializes a new instance of the class with empty strings. @@ -75,6 +75,12 @@ public DelineaConfiguration() /// public static string SECRET_FIELD_NAME => "SecretFieldName"; + /// + /// The configuration key for skipping TLS certificate validation. + /// Use only in non-production environments with self-signed or expired certificates. + /// + public static string SKIP_TLS_VALIDATION => "SkipTlsValidation"; + /// /// The base URL of the Delinea Secret Server. /// @@ -124,43 +130,10 @@ public DelineaConfiguration() public string GrantType { get; set; } = "password"; /// - /// Validates that the configuration has either username/password or client credentials for authentication. + /// When true, disables TLS certificate validation for Secret Server connections. + /// Use only in non-production environments with self-signed or expired certificates. /// - /// The validation context. - /// A collection of validation results. - public IEnumerable Validate(ValidationContext validationContext) - { - var hasUserPass = !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password); - var hasClientCreds = !string.IsNullOrWhiteSpace(ClientId) && !string.IsNullOrWhiteSpace(ClientSecret); - - switch (GrantType) - { - case "windows": - if (hasUserPass || hasClientCreds) - yield return new ValidationResult( - "No credentials should be provided for 'windows' grant type.", - new[] { nameof(Username), nameof(Password), nameof(ClientId), nameof(ClientSecret) }); - break; - - case "password": - if (!hasUserPass) - yield return new ValidationResult( - "Username and Password must be provided for 'password' grant type.", - new[] { nameof(Username), nameof(Password) }); - break; - - case "client_credentials": - if (!hasClientCreds) - yield return new ValidationResult( - "ClientId and ClientSecret must be provided for 'client_credentials' grant type.", - new[] { nameof(ClientId), nameof(ClientSecret) }); - break; - default: - yield return new ValidationResult( - "Invalid GrantType specified.", - new[] { nameof(GrantType) }); - break; - } - } + public bool SkipTlsValidation { get; set; } = false; + } } \ No newline at end of file diff --git a/delinea-secretserver-pam/SecretServerPam.cs b/delinea-secretserver-pam/SecretServerPam.cs index 5b43eb2..5039892 100644 --- a/delinea-secretserver-pam/SecretServerPam.cs +++ b/delinea-secretserver-pam/SecretServerPam.cs @@ -1,4 +1,4 @@ -// Copyright 2025 Keyfactor +// Copyright 2025 Keyfactor // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -23,165 +24,248 @@ namespace Keyfactor.Extensions.Pam.Delinea /// /// Exception thrown when the authentication token for Delinea Secret Server is invalid or cannot be obtained. /// - /// - /// This exception is typically thrown when authentication credentials are incorrect or the server rejects the auth - /// request. - /// public class InvalidTokenException : Exception { /// /// Initializes a new instance of the class with a specified error message. /// - /// The message that describes the error. public InvalidTokenException(string message) : base(message) { } } + /// + /// Exception thrown when the server (initialization) configuration provided to the PAM provider is invalid. + /// public class InvalidClientConfigurationException : Exception { /// /// Initializes a new instance of the class with a specified error /// message. /// - /// The message that describes the error. public InvalidClientConfigurationException(string message) : base(message) { } } + /// + /// Exception thrown when the instance (per-secret) configuration provided to the PAM provider is invalid. + /// public class InvalidSecretConfigurationException : Exception { /// /// Initializes a new instance of the class with a specified error /// message. /// - /// The message that describes the error. public InvalidSecretConfigurationException(string message) : base(message) { } } + // --------------------------------------------------------------------------- + // Abstract base — all shared logic lives here + // --------------------------------------------------------------------------- + /// - /// Privileged Access Management (PAM) provider implementation for Delinea Secret Server. + /// Abstract base class for all Delinea Secret Server PAM providers. + /// Encapsulates the shared HTTP, validation, configuration-building, and + /// secret-retrieval logic used by every concrete PAM type variant. /// - /// - /// This class implements the IPAMProvider interface to retrieve secrets from Delinea Secret Server. - /// It supports authentication via username/password with plans for client credentials support. - /// - public class SecretServerPam : IPAMProvider + public abstract class SecretServerPamBase { - private ILogger Logger { get; } = LogHandler.GetClassLogger(); + // Subclasses set their own class-specific logger via the protected setter. + protected ILogger Logger { get; set; } + + // HttpClient is injected so tests can substitute a fake handler without + // going to the network. Production constructors build the real client. + private HttpClient _httpClient; /// - /// Gets the name of this PAM provider. + /// Production constructor — builds a default . + /// Grant type and TLS-skip are not yet known at construction time; they + /// are resolved from configuration during . /// - /// The string "Delinea-SecretServer". - public string Name => "Delinea-SecretServer"; + protected SecretServerPamBase() + { + Logger = LogHandler.GetClassLogger(GetType()); + _httpClient = null; // will be built lazily in GetPasswordCore + } + + /// + /// Test constructor — accepts an injected and + /// so unit tests can control HTTP responses. + /// + internal SecretServerPamBase(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + Logger = logger; + } + + // --------------------------------------------------------------------------- + // Core entry point called by every concrete GetPassword implementation + // --------------------------------------------------------------------------- + + /// + /// Resolves the effective grant type for this provider invocation. + /// The base implementation reads it from , + /// defaulting to "password" for backwards compatibility. + /// Type-specific subclasses override this to return a hardcoded value. + /// + protected virtual string ResolveGrantType(IReadOnlyDictionary serverConfigurationParameters) + { + if (serverConfigurationParameters.TryGetValue(DelineaConfiguration.GRANT_TYPE, out var grantType) && + !string.IsNullOrEmpty(grantType)) + return grantType; + + Logger.LogWarning( + "'{GrantType}' parameter not provided — defaulting to 'password' grant", + DelineaConfiguration.GRANT_TYPE); + return "password"; + } /// - /// Retrieves a password from Delinea Secret Server using the provided configuration parameters. + /// Core implementation of credential retrieval shared by all concrete types. + /// Validates configuration, builds an , and fetches + /// the secret from Delinea Secret Server. /// - /// Dictionary containing instance-specific parameters like SecretId and SecretFieldName. - /// - /// Dictionary containing connection and authentication parameters such as host URL, - /// username, and password. - /// - /// The password value retrieved from Secret Server. - /// Thrown when required parameters are missing or invalid. - /// Thrown when authentication with Secret Server fails. - /// Thrown when communication with Secret Server fails. - public string GetPassword(Dictionary instanceParameters, + protected string GetPasswordCore( + Dictionary instanceParameters, Dictionary serverConfigurationParameters) { Logger.MethodEntry(); - Logger.LogInformation("Starting Delinea Secret Server PAM Provider"); - Logger.LogDebug("Getting password from Delinea Secret Server"); + + instanceParameters.TryGetValue(DelineaConfiguration.SECRET_ID, out var logSecretId); + instanceParameters.TryGetValue(DelineaConfiguration.SECRET_FIELD_NAME, out var logFieldName); + serverConfigurationParameters.TryGetValue(DelineaConfiguration.SECRET_SERVER_URL, out var logUrl); + var logGrantType = ResolveGrantType(serverConfigurationParameters); + + // UserName is the OS service account identity — IPAMProvider does not expose the Keyfactor caller + Logger.LogInformation( + "GetPassword invoked | SecretId={SecretId} Field={SecretFieldName} TargetUrl={Url} GrantType={GrantType} CallerIdentity={Identity} Host={Machine}", + logSecretId, logFieldName, logUrl, logGrantType, + Environment.UserName, Environment.MachineName); + + var correlationId = Guid.NewGuid().ToString("N"); + Logger.LogInformation("Operation correlation ID | CorrelationId={CorrelationId}", correlationId); Logger.LogTrace("instanceParameters: {@InstanceParameters}", instanceParameters); - // Logger.LogTrace("initializationInfo: {@ServerConfigurationParameters}", - // serverConfigurationParameters); // TODO: Commented out to avoid logging sensitive information + var config = BuildDelineaConfiguration(instanceParameters, serverConfigurationParameters); - using (var client = BuildHttpClient(config.GrantType)) + + // Use the injected client (tests) or build a real one (production) + var client = _httpClient ?? BuildHttpClient(config.GrantType, config.SkipTlsValidation); + var ownsClient = _httpClient == null; + try { Logger.MethodExit(); - return GetDelineaSecretAsync(client, config).Result; + return GetDelineaSecretAsync(client, config, correlationId).GetAwaiter().GetResult(); + } + finally + { + if (ownsClient) + client.Dispose(); } } - /// - /// Asynchronously retrieves a secret from Delinea Secret Server. - /// - /// The HTTP client used to communicate with Secret Server. - /// The configuration containing Secret Server connection and request details. - /// The value of the requested secret field. - /// Thrown when the HTTP request to Secret Server fails. - /// Thrown when deserializing the response fails or the requested secret is not found. - private async Task GetDelineaSecretAsync(HttpClient client, DelineaConfiguration configurationInfo) + // --------------------------------------------------------------------------- + // Secret retrieval + // --------------------------------------------------------------------------- + + private async Task GetDelineaSecretAsync( + HttpClient client, + DelineaConfiguration configurationInfo, + string correlationId) { Logger.MethodEntry(); HttpResponseMessage response; - Logger.LogDebug("Attempting to fetch access token from Delinea Secret Server at {SecretServerUrl}", + Logger.LogDebug("Attempting to fetch secret from Delinea Secret Server at {SecretServerUrl}", configurationInfo.SecretServerUrl); var secretUrl = $"{configurationInfo.SecretServerUrl}/api/v1/secrets/{configurationInfo.SecretId}"; + switch (configurationInfo.GrantType) { case "windows": - Logger.LogDebug("Using Windows Authentication to obtain access token"); - secretUrl = $"{configurationInfo.SecretServerUrl}/winauthwebservices/api/v1/secrets/{configurationInfo.SecretId}"; + Logger.LogDebug("Using Windows Authentication"); + secretUrl = + $"{configurationInfo.SecretServerUrl}/winauthwebservices/api/v1/secrets/{configurationInfo.SecretId}"; + // UserName is the OS service account identity — IPAMProvider does not expose the Keyfactor caller + Logger.LogInformation( + "Windows authentication attempt | Identity={Identity} Machine={Machine} TargetUrl={TargetUrl} SecretId={SecretId} CorrelationId={CorrelationId}", + Environment.UserName, Environment.MachineName, secretUrl, configurationInfo.SecretId, + correlationId); break; + default: // password and client_credentials Logger.LogDebug("Using {GrantType} grant to obtain access token", configurationInfo.GrantType); - var bearerToken = await GetAccessToken(client, configurationInfo).ConfigureAwait(false); + var bearerToken = + await GetAccessToken(client, configurationInfo, correlationId).ConfigureAwait(false); if (string.IsNullOrEmpty(bearerToken)) { - Logger.LogError("Unable to obtain access token from Delinea Secret Server"); + Logger.LogError( + "Authentication failed: empty token received | Url={Url} GrantType={GrantType} Identity={Identity} CorrelationId={CorrelationId}", + configurationInfo.SecretServerUrl, configurationInfo.GrantType, + string.IsNullOrEmpty(configurationInfo.Username) + ? configurationInfo.ClientId + : configurationInfo.Username, + correlationId); Logger.MethodExit(); throw new InvalidTokenException("Unable to obtain access token from Delinea Secret Server"); } - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", bearerToken); + client.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); break; } - + + var sw = Stopwatch.StartNew(); try { Logger.LogDebug("Secret URL: {SecretUrl}", secretUrl); response = await client - .GetAsync(new Uri(secretUrl) - .AbsoluteUri) + .GetAsync(new Uri(secretUrl).AbsoluteUri) .ConfigureAwait(false); + sw.Stop(); + Logger.LogInformation( + "Secret Server API call completed | Method=GET StatusCode={StatusCode} DurationMs={DurationMs} SecretId={SecretId} CorrelationId={CorrelationId}", + (int)response.StatusCode, sw.ElapsedMilliseconds, configurationInfo.SecretId, correlationId); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var truncated = errorContent?.Length > 500 + ? errorContent.Substring(0, 500) + "..." + : errorContent; Logger.LogError( - "Received non-success status code {StatusCode} from Secret Server. Response: {ResponseContent}", - response.StatusCode, errorContent); + "Received non-success status code {StatusCode} from Secret Server. Response (truncated): {ResponseContent} CorrelationId={CorrelationId}", + (int)response.StatusCode, truncated, correlationId); } response.EnsureSuccessStatusCode(); } - catch (HttpRequestException ex) { + sw.Stop(); Logger.LogError( - "An error occurred while attempting to communicate with Delinea Secret Server: {ExMessage}", - ex.Message); + "HTTP call failed | Method={Method} Url={Url} DurationMs={DurationMs} Error={ExMessage} CorrelationId={CorrelationId}", + "GET", secretUrl, sw.ElapsedMilliseconds, ex.Message, correlationId); Logger.MethodExit(); throw; } - catch (System.ComponentModel.Win32Exception ex) { + sw.Stop(); Logger.LogError( - "A Windows authentication error occurred while attempting to communicate with Delinea Secret Server: {ExMessage}", - ex.Message); + "HTTP call failed | Method={Method} Url={Url} DurationMs={DurationMs} Error={ExMessage} CorrelationId={CorrelationId}", + "GET", secretUrl, sw.ElapsedMilliseconds, ex.Message, correlationId); Logger.MethodExit(); throw new InvalidClientConfigurationException( - "A Windows authentication error occurred while attempting to communicate with Delinea Secret Server. Please ensure the application is running under a user context with access to Secret Server. For more information on windows auth please visit: https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm"); + "A Windows authentication error occurred while attempting to communicate with Delinea Secret Server. " + + "Please ensure the application is running under a user context with access to Secret Server. " + + "For more information on windows auth please visit: " + + "https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm"); } var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); @@ -191,18 +275,20 @@ private async Task GetDelineaSecretAsync(HttpClient client, DelineaConfi { var secretResponse = JsonConvert.DeserializeObject(content); - Logger.LogTrace("Received '{ItemsCount}' secrets from Delinea Secret Server", + Logger.LogTrace("Received '{ItemsCount}' secret items from Delinea Secret Server", secretResponse?.Items.Count ?? 0); - Logger.LogTrace("Secret field name: {SecretFieldName}", configurationInfo.SecretFieldName); - Logger.LogTrace("Secret slug: {SecretSlug}", configurationInfo.SecretFieldName); - // var secret = secretResponse?.Items.FirstOrDefault(i => i.IsPassword)?.Value; + var secret = secretResponse?.Items.FirstOrDefault(i => - i.Name == configurationInfo.SecretFieldName || i.Slug == configurationInfo.SecretFieldName)?.Value; - // Logger.LogDebug("Secret value: {SecretValue}", secret); + i.Name == configurationInfo.SecretFieldName || + i.Slug == configurationInfo.SecretFieldName)?.Value; + if (!string.IsNullOrEmpty(secret)) { - Logger.LogInformation("Successfully retrieved secret from Delinea Secret Server"); + Logger.LogInformation( + "Credential retrieval succeeded | SecretId={SecretId} Field={SecretFieldName} GrantType={GrantType} Url={Url} CorrelationId={CorrelationId}", + configurationInfo.SecretId, configurationInfo.SecretFieldName, + configurationInfo.GrantType, configurationInfo.SecretServerUrl, correlationId); Logger.MethodExit(); return secret; } @@ -212,123 +298,149 @@ private async Task GetDelineaSecretAsync(HttpClient client, DelineaConfi if (content != null && content.Contains("login-message")) { Logger.LogError( - "Authentication failed when attempting to retrieve secret from Delinea Secret Server, please check your credentials and configuration and try again"); + "Authentication failed when attempting to retrieve secret from Delinea Secret Server — check credentials and configuration. CorrelationId={CorrelationId}", + correlationId); Logger.LogTrace("Response content: {Response}", content); Logger.MethodExit(); throw new AuthenticationException( "Authentication failed when attempting to retrieve secret from Delinea Secret Server. Please check your credentials and try again"); } + Logger.LogError( - "An error occurred while attempting to deserialize the Delinea Secret Server response: {ExMessage}", - ex.Message); + "An error occurred while attempting to deserialize the Delinea Secret Server response: {ExMessage} CorrelationId={CorrelationId}", + ex.Message, correlationId); Logger.MethodExit(); throw; } - Logger.LogError("No secret was found or no items in the secret were of type password"); + Logger.LogError( + "Credential retrieval failed: field not found in secret | SecretId={SecretId} Field={SecretFieldName} GrantType={GrantType} Url={Url} CorrelationId={CorrelationId}", + configurationInfo.SecretId, configurationInfo.SecretFieldName, + configurationInfo.GrantType, configurationInfo.SecretServerUrl, correlationId); Logger.MethodExit(); - return ""; + throw new InvalidSecretConfigurationException( + $"Field '{configurationInfo.SecretFieldName}' not found in secret {configurationInfo.SecretId}. " + + "Verify the field name or slug exists on the secret template."); } - /// - /// Obtains an OAuth access token from Delinea Secret Server. - /// - /// The HTTP client used to communicate with Secret Server. - /// The configuration containing Secret Server connection and authentication details. - /// An OAuth access token string for authenticating subsequent API calls. - /// Thrown when the HTTP request to the token endpoint fails. - /// Thrown when the token cannot be obtained or parsed from the response. - /// Thrown when deserializing the token response fails. - /// Currently only supports password grant type authentication. - private async Task GetAccessToken(HttpClient client, DelineaConfiguration configurationInfo) + // --------------------------------------------------------------------------- + // Token acquisition + // --------------------------------------------------------------------------- + + private async Task GetAccessToken( + HttpClient client, + DelineaConfiguration configurationInfo, + string correlationId) { Logger.MethodEntry(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded")); + // NOTE: Delinea Secret Server's token endpoint always uses "username"/"password" + // field names regardless of whether the flow is password or client_credentials. + // This is a Delinea API constraint — do not change the field names. var body = new Dictionary { { "username", configurationInfo.Username }, { "password", configurationInfo.Password }, - { "grant_type", "password" } // grant type is still "password" as far as the Delinea API is concerned + { "grant_type", "password" } // Delinea API always expects grant_type=password }; + Logger.LogTrace("Authentication request grant type: {GrantType}", body["grant_type"]); - Logger.LogTrace("Authentication request grant type ${GrantType}", body["grant_type"]); + var loggableBody = new Dictionary(body); + foreach (var sensitiveKey in new[] { "password", "client_secret" }) + if (loggableBody.ContainsKey(sensitiveKey)) + loggableBody[sensitiveKey] = "***"; + Logger.LogDebug("Token request body (redacted): {RequestBody}", JsonConvert.SerializeObject(loggableBody)); HttpResponseMessage response; - var tokeUrl = $"{configurationInfo.SecretServerUrl}/oauth2/token"; + var tokenUrl = $"{configurationInfo.SecretServerUrl}/oauth2/token"; + var sw = Stopwatch.StartNew(); try { - Logger.LogDebug("Requesting an access token from Secret Server at {TokenUrl}", tokeUrl); + Logger.LogDebug("Requesting access token from Secret Server at {TokenUrl}", tokenUrl); response = await client - .PostAsync(new Uri(tokeUrl).AbsoluteUri, - new FormUrlEncodedContent(body)) + .PostAsync(new Uri(tokenUrl).AbsoluteUri, new FormUrlEncodedContent(body)) .ConfigureAwait(false); - Logger.LogDebug("Request sent"); + sw.Stop(); + Logger.LogInformation( + "Token endpoint call completed | Method=POST StatusCode={StatusCode} DurationMs={DurationMs} CorrelationId={CorrelationId}", + (int)response.StatusCode, sw.ElapsedMilliseconds, correlationId); - response.EnsureSuccessStatusCode(); + if (!response.IsSuccessStatusCode) + { + var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var truncatedError = errorBody?.Length > 500 ? errorBody.Substring(0, 500) + "..." : errorBody; + Logger.LogError( + "Token request failed | StatusCode={StatusCode} ResponseBody={ResponseBody} CorrelationId={CorrelationId}", + (int)response.StatusCode, truncatedError, correlationId); + response.EnsureSuccessStatusCode(); + } } - catch (HttpRequestException ex) { + sw.Stop(); Logger.LogError( - "An error occurred while attempting to fetch an access token from Delinea Secret Server: {ExMessage}", - ex.Message); + "HTTP call failed | Method={Method} Url={Url} DurationMs={DurationMs} Error={ExMessage} CorrelationId={CorrelationId}", + "POST", tokenUrl, sw.ElapsedMilliseconds, ex.Message, correlationId); Logger.MethodExit(); throw; } - Logger.LogDebug("Access token received"); + Logger.LogDebug("Access token received, deserializing response"); try { - Logger.LogDebug("Deserializing access token response"); var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var values = JsonConvert.DeserializeObject>(json); - var token = values?["access_token"]; client.DefaultRequestHeaders.Accept.Clear(); - Logger.LogTrace("Access token parsed"); - if (token != null) return token; + Logger.LogTrace("Access token parsed successfully"); + if (token != null) + { + Logger.LogInformation( + "Authentication succeeded | Identity={Identity} Url={Url} AuthenticationResult=Success CorrelationId={CorrelationId}", + string.IsNullOrEmpty(configurationInfo.Username) + ? configurationInfo.ClientId + : configurationInfo.Username, + configurationInfo.SecretServerUrl, correlationId); + return token; + } + Logger.LogError( - "Unable to generate access token from Delinea Secret Server \'{ConfigurationInfoSecretServerUrl}\' as \'{ConfigurationInfoUsername}\'. Please check your credentials and try again", - configurationInfo.SecretServerUrl, configurationInfo.Username); + "Unable to generate access token from Delinea Secret Server '{Url}'. Please check your credentials and try again. CorrelationId={CorrelationId}", + configurationInfo.SecretServerUrl, correlationId); Logger.MethodExit(); throw new InvalidTokenException( - $"Unable to generate access token from Delinea Secret Server '{configurationInfo.SecretServerUrl}' as '{configurationInfo.Username}'. Please check your credentials and try again"); + $"Unable to generate access token from Delinea Secret Server '{configurationInfo.SecretServerUrl}'. Please check your credentials and try again"); } catch (Exception ex) { Logger.LogError( - "An error occurred while attempting to deserialize the access token response: {ExMessage}", - ex.Message); - Logger.LogTrace("Response content: ${Response}", response.Content.ReadAsStringAsync().Result); + "An error occurred while attempting to deserialize the access token response: {ExMessage} CorrelationId={CorrelationId}", + ex.Message, correlationId); Logger.MethodExit(); throw; } } + // --------------------------------------------------------------------------- + // Validation + // --------------------------------------------------------------------------- + /// - /// Validates the instance parameters provided to the PAM provider. + /// Validates instance parameters (SecretId, SecretFieldName). + /// Throws on failure. /// - /// - /// A read-only dictionary containing instance-specific parameters, such as SecretId and SecretFieldName. - /// - /// - /// True if the instance parameters are valid; otherwise, throws an . - /// - /// - /// Thrown if required parameters are missing or cannot be parsed as expected. - /// private bool ValidateInstanceParams(IReadOnlyDictionary instanceParameters) { Logger.MethodEntry(); Logger.LogDebug("Validating instance parameters"); - Logger.LogDebug("Validating instance parameter '{SecretId}'", DelineaConfiguration.SECRET_ID); + if (!instanceParameters.ContainsKey(DelineaConfiguration.SECRET_ID)) { Logger.LogError("Instance parameter '{SecretId}' not found", DelineaConfiguration.SECRET_ID); @@ -338,7 +450,7 @@ private bool ValidateInstanceParams(IReadOnlyDictionary instance } if (!instanceParameters.ContainsKey(DelineaConfiguration.SECRET_FIELD_NAME) || - instanceParameters[DelineaConfiguration.SECRET_FIELD_NAME] == string.Empty) + string.IsNullOrWhiteSpace(instanceParameters[DelineaConfiguration.SECRET_FIELD_NAME])) { Logger.LogError("Instance parameter '{SecretFieldName}' not provided", DelineaConfiguration.SECRET_FIELD_NAME); @@ -347,8 +459,7 @@ private bool ValidateInstanceParams(IReadOnlyDictionary instance $"Instance parameter '{DelineaConfiguration.SECRET_FIELD_NAME}' not provided"); } - Logger.LogDebug("Parsing instance parameter '{SecretId}'", DelineaConfiguration.SECRET_ID); - if (int.TryParse(instanceParameters[DelineaConfiguration.SECRET_ID], out var secretId)) + if (int.TryParse(instanceParameters[DelineaConfiguration.SECRET_ID], out _)) { Logger.LogDebug("Instance parameters are valid"); Logger.MethodExit(); @@ -362,84 +473,44 @@ private bool ValidateInstanceParams(IReadOnlyDictionary instance } /// - /// Validates the server configuration parameters for connecting to Delinea Secret Server. + /// Validates server configuration parameters for the resolved grant type. + /// Throws on failure. /// - /// - /// A read-only dictionary containing server configuration parameters such as Secret Server URL, credentials, and grant - /// type. - /// - /// - /// The OAuth grant type to validate credentials for. Supported values are "password" and "client_credentials". - /// Defaults to "password". - /// - /// - /// True if the server configuration parameters are valid; otherwise, throws an - /// . - /// - /// - /// Thrown if required parameters are missing or invalid for the specified grant type. - /// private bool ValidateServerConfigurationParams( - IReadOnlyDictionary connectionConfiguration) + IReadOnlyDictionary connectionConfiguration, + string grantType) { Logger.MethodEntry(); - Logger.LogDebug("Validating server configuration parameters"); - - var grantType = "password"; - if (connectionConfiguration.TryGetValue(DelineaConfiguration.GRANT_TYPE, out var configuredGrantType) && - !string.IsNullOrEmpty(configuredGrantType)) - { - grantType = configuredGrantType; - } + Logger.LogDebug("Validating server configuration parameters for grant type '{GrantType}'", grantType); - // Validate Secret Server URL - ValidateRequiredParameter(connectionConfiguration, - DelineaConfiguration.SECRET_SERVER_URL, + ValidateRequiredParameter(connectionConfiguration, DelineaConfiguration.SECRET_SERVER_URL, "Server configuration parameter"); - // Validate credentials based on grant type switch (grantType) { case "password": ValidatePasswordGrantCredentials(connectionConfiguration); break; - case "client_credentials": ValidateClientCredentialsGrantCredentials(connectionConfiguration); break; - case "windows": - Logger.LogDebug("Using Windows Authentication, no credentials to validate"); + Logger.LogDebug("Using Windows Authentication — no credential parameters to validate"); break; default: Logger.LogError( - "Invalid grant type '{GrantType}' specified. Supported types are 'password' and 'client_credentials'", + "Invalid grant type '{GrantType}' specified. Supported values are 'password', 'client_credentials', and 'windows'", grantType); Logger.MethodExit(); - throw new Exception( - $"Invalid grant type '{grantType}' specified. Supported types are 'password' and 'client_credentials'"); + throw new InvalidClientConfigurationException( + $"Invalid grant type '{grantType}' specified. Supported values are 'password', 'client_credentials', and 'windows'"); } - Logger.MethodExit(); Logger.LogInformation("Server configuration parameters are valid"); + Logger.MethodExit(); return true; } - /// - /// Validates that a required parameter exists and is not null or empty in the provided configuration dictionary. - /// - /// - /// The configuration dictionary to validate. - /// - /// - /// The name of the parameter to check for existence and non-empty value. - /// - /// - /// A string prefix to include in the error message if validation fails. - /// - /// - /// Thrown if the required parameter is missing or its value is null or empty. - /// private void ValidateRequiredParameter( IReadOnlyDictionary config, string paramName, @@ -448,19 +519,17 @@ private void ValidateRequiredParameter( Logger.MethodEntry(); Logger.LogDebug("Validating parameter '{ParamName}'", paramName); - if (config.ContainsKey(paramName) && !string.IsNullOrEmpty(config[paramName])) return; + if (config.ContainsKey(paramName) && !string.IsNullOrEmpty(config[paramName])) + { + Logger.MethodExit(); + return; + } + Logger.LogError("{ErrorPrefix} '{ParamName}' not provided", errorPrefix, paramName); Logger.MethodExit(); throw new InvalidClientConfigurationException($"{errorPrefix} '{paramName}' not provided"); } - /// - /// Validates that the required username and password parameters exist and are not empty for the password grant type. - /// - /// The configuration dictionary containing client parameters. - /// - /// Thrown if the username or password parameter is missing or empty. - /// private void ValidatePasswordGrantCredentials(IReadOnlyDictionary config) { Logger.MethodEntry(); @@ -469,16 +538,6 @@ private void ValidatePasswordGrantCredentials(IReadOnlyDictionary - /// Validates that the required client ID and client secret parameters exist and are not empty for the client - /// credentials grant type. - /// - /// - /// The configuration dictionary containing client parameters. - /// - /// - /// Thrown if the client ID or client secret parameter is missing or empty. - /// private void ValidateClientCredentialsGrantCredentials(IReadOnlyDictionary config) { Logger.MethodEntry(); @@ -487,23 +546,20 @@ private void ValidateClientCredentialsGrantCredentials(IReadOnlyDictionary - /// Creates a DelineaConfiguration object from the provided parameters. - /// - /// - /// Dictionary containing instance-specific parameters, including the secret ID and field - /// name. - /// - /// Dictionary containing connection and authentication parameters for Secret Server. - /// A fully populated DelineaConfiguration object. - /// Thrown when required parameters are missing or invalid. + // --------------------------------------------------------------------------- + // Configuration builder + // --------------------------------------------------------------------------- + private DelineaConfiguration BuildDelineaConfiguration( IReadOnlyDictionary instanceParameters, IReadOnlyDictionary connectionConfiguration) { Logger.MethodEntry(); Logger.LogInformation("Validating Delinea configuration"); - var validServer = ValidateServerConfigurationParams(connectionConfiguration); + + var grantType = ResolveGrantType(connectionConfiguration); + + var validServer = ValidateServerConfigurationParams(connectionConfiguration, grantType); var validInstance = ValidateInstanceParams(instanceParameters); if (!validServer || !validInstance) @@ -517,20 +573,20 @@ private DelineaConfiguration BuildDelineaConfiguration( var secretId = int.Parse(instanceParameters[DelineaConfiguration.SECRET_ID]); Logger.LogDebug("Secret ID: {SecretId}", secretId); - if (!connectionConfiguration.TryGetValue(DelineaConfiguration.GRANT_TYPE, out var grantType)) - { + connectionConfiguration.TryGetValue(DelineaConfiguration.SKIP_TLS_VALIDATION, out var skipTlsRaw); + var skipTlsEnv = Environment.GetEnvironmentVariable("KEYFACTOR_PAM_SKIP_TLS_VALIDATION"); + var skipTls = string.Equals(skipTlsRaw, "true", StringComparison.OrdinalIgnoreCase) + || string.Equals(skipTlsEnv, "true", StringComparison.OrdinalIgnoreCase) + || string.Equals(skipTlsEnv, "1", StringComparison.OrdinalIgnoreCase); + if (skipTls) Logger.LogWarning( - "\'{GrantType}\' parameter not provided defaulting to 'password' grant", - DelineaConfiguration.GRANT_TYPE); - grantType = "password"; - } + "TLS certificate validation is disabled — use only in non-production environments"); + + Logger.LogDebug("Building Delinea configuration for '{GrantType}' grant type", grantType); - Logger.LogDebug("Building Delinea configuration"); switch (grantType) { case "password": - - Logger.LogDebug("Building Delinea configuration for password grant type"); Logger.MethodExit(); return new DelineaConfiguration { @@ -539,72 +595,193 @@ private DelineaConfiguration BuildDelineaConfiguration( Password = connectionConfiguration[DelineaConfiguration.PASSWORD], SecretId = secretId, SecretFieldName = instanceParameters[DelineaConfiguration.SECRET_FIELD_NAME], - GrantType = "password" + GrantType = "password", + SkipTlsValidation = skipTls }; case "client_credentials": - Logger.LogDebug("Building Delinea configuration for client credentials grant type"); Logger.MethodExit(); return new DelineaConfiguration { SecretServerUrl = connectionConfiguration[DelineaConfiguration.SECRET_SERVER_URL], + // NOTE: For client_credentials the ClientId maps to Username and ClientSecret maps to + // Password in the token request body. This is a Delinea API constraint. + Username = connectionConfiguration[DelineaConfiguration.CLIENT_ID], + Password = connectionConfiguration[DelineaConfiguration.CLIENT_SECRET], ClientId = connectionConfiguration[DelineaConfiguration.CLIENT_ID], ClientSecret = connectionConfiguration[DelineaConfiguration.CLIENT_SECRET], SecretId = secretId, SecretFieldName = instanceParameters[DelineaConfiguration.SECRET_FIELD_NAME], - GrantType = "password" + GrantType = "client_credentials", + SkipTlsValidation = skipTls }; + case "windows": - Logger.LogDebug("Building Delinea configuration for windows grant type"); Logger.MethodExit(); return new DelineaConfiguration { SecretServerUrl = connectionConfiguration[DelineaConfiguration.SECRET_SERVER_URL], SecretId = secretId, SecretFieldName = instanceParameters[DelineaConfiguration.SECRET_FIELD_NAME], - GrantType = "windows" + GrantType = "windows", + SkipTlsValidation = skipTls }; default: Logger.LogError( - "Invalid grant type '{GrantType}' specified. Supported types are 'password' and 'client_credentials'", + "Invalid grant type '{GrantType}' — supported values are 'password', 'client_credentials', and 'windows'", grantType); Logger.MethodExit(); - throw new Exception( - $"Invalid grant type '{grantType}' specified. Supported types are 'password' and 'client_credentials'"); + throw new InvalidClientConfigurationException( + $"Invalid grant type '{grantType}' specified. Supported values are 'password', 'client_credentials', and 'windows'"); } } - /// - /// Creates and configures an HttpClient for communicating with Secret Server. - /// - /// A configured HttpClient with a 60-second timeout. - private static HttpClient BuildHttpClient(string grantType) + // --------------------------------------------------------------------------- + // HttpClient factory + // --------------------------------------------------------------------------- + + private static HttpClient BuildHttpClient(string grantType, bool skipTlsValidation = false) { var handler = new HttpClientHandler(); if (grantType == "windows") - { handler.UseDefaultCredentials = true; - } + if (skipTlsValidation) + handler.ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true; var client = new HttpClient(handler, true); - client.Timeout = new TimeSpan(0, 0, 60); return client; } } + // --------------------------------------------------------------------------- + // Concrete PAM type implementations + // --------------------------------------------------------------------------- + + /// + /// Backwards-compatible PAM provider for Delinea Secret Server. + /// Supports all three authentication flows (password, client_credentials, windows) + /// selected at runtime via the GrantType server configuration parameter. + /// Prefer the type-specific variants for new installations. + /// + public class SecretServerPam : SecretServerPamBase, IPAMProvider + { + /// Production constructor — no arguments, required by Keyfactor Command. + public SecretServerPam() + { + Logger = LogHandler.GetClassLogger(); + } + + /// Test constructor — accepts injected dependencies. + internal SecretServerPam(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + public string Name => "Delinea-SecretServer"; + + /// + public string GetPassword( + Dictionary instanceParameters, + Dictionary serverConfigurationParameters) + => GetPasswordCore(instanceParameters, serverConfigurationParameters); + } + /// - /// Represents the response object from a Secret Server get secret API call. + /// PAM provider for Delinea Secret Server using the password grant type (Username + Password). + /// Only the Host, Username, and Password server parameters are required. /// - /// - /// This class is used to deserialize the JSON response from the Secret Server API. - /// - internal class SecretResponse + public class SecretServerPamPassword : SecretServerPamBase, IPAMProvider { - /// - /// Gets or sets the collection of secret items (fields) in the response. - /// - [JsonProperty("items")] - public List Items { get; set; } = new List(); + /// Production constructor — no arguments, required by Keyfactor Command. + public SecretServerPamPassword() + { + Logger = LogHandler.GetClassLogger(); + } + + /// Test constructor — accepts injected dependencies. + internal SecretServerPamPassword(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + public string Name => "Delinea-SecretServer-Password"; + + /// Always returns "password" — hardcoded for this type. + protected override string ResolveGrantType(IReadOnlyDictionary serverConfigurationParameters) + => "password"; + + /// + public string GetPassword( + Dictionary instanceParameters, + Dictionary serverConfigurationParameters) + => GetPasswordCore(instanceParameters, serverConfigurationParameters); + } + + /// + /// PAM provider for Delinea Secret Server using the client_credentials OAuth2 flow (ClientId + ClientSecret). + /// Only the Host, ClientId, and ClientSecret server parameters are required. + /// + public class SecretServerPamClientCredentials : SecretServerPamBase, IPAMProvider + { + /// Production constructor — no arguments, required by Keyfactor Command. + public SecretServerPamClientCredentials() + { + Logger = LogHandler.GetClassLogger(); + } + + /// Test constructor — accepts injected dependencies. + internal SecretServerPamClientCredentials(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + public string Name => "Delinea-SecretServer-ClientCredentials"; + + /// Always returns "client_credentials" — hardcoded for this type. + protected override string ResolveGrantType(IReadOnlyDictionary serverConfigurationParameters) + => "client_credentials"; + + /// + public string GetPassword( + Dictionary instanceParameters, + Dictionary serverConfigurationParameters) + => GetPasswordCore(instanceParameters, serverConfigurationParameters); + } + + /// + /// PAM provider for Delinea Secret Server using Integrated Windows Authentication (IWA). + /// Only the Host server parameter is required. + /// NOTE: IWA is not supported on Secret Server Cloud. + /// + public class SecretServerPamWindows : SecretServerPamBase, IPAMProvider + { + /// Production constructor — no arguments, required by Keyfactor Command. + public SecretServerPamWindows() + { + Logger = LogHandler.GetClassLogger(); + } + + /// Test constructor — accepts injected dependencies. + internal SecretServerPamWindows(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + public string Name => "Delinea-SecretServer-Windows"; + + /// Always returns "windows" — hardcoded for this type. + protected override string ResolveGrantType(IReadOnlyDictionary serverConfigurationParameters) + => "windows"; + + /// + public string GetPassword( + Dictionary instanceParameters, + Dictionary serverConfigurationParameters) + => GetPasswordCore(instanceParameters, serverConfigurationParameters); } -} \ No newline at end of file +} diff --git a/delinea-secretserver-pam/manifest.json b/delinea-secretserver-pam/manifest.json index f064987..73d919f 100644 --- a/delinea-secretserver-pam/manifest.json +++ b/delinea-secretserver-pam/manifest.json @@ -14,5 +14,18 @@ "ClientId": "", "ClientSecret": "", "GrantType": "password|client_credentials|windows" + }, + "Keyfactor:PAMProviders:Delinea-SecretServer-Password:InitializationInfo": { + "Host": "https://example.secretservercloud.com/SecretServer", + "Username": "", + "Password": "" + }, + "Keyfactor:PAMProviders:Delinea-SecretServer-ClientCredentials:InitializationInfo": { + "Host": "https://example.secretservercloud.com/SecretServer", + "ClientId": "", + "ClientSecret": "" + }, + "Keyfactor:PAMProviders:Delinea-SecretServer-Windows:InitializationInfo": { + "Host": "https://example.secretserver.internal/SecretServer" } -} \ No newline at end of file +} diff --git a/docs/delinea-secretserver-clientcredentials.md b/docs/delinea-secretserver-clientcredentials.md new file mode 100644 index 0000000..5f89616 --- /dev/null +++ b/docs/delinea-secretserver-clientcredentials.md @@ -0,0 +1,15 @@ +## Delinea-SecretServer-ClientCredentials + +The `Delinea-SecretServer-ClientCredentials` PAM type authenticates to Delinea Secret Server using OAuth2 client +credentials (application account name and password). This is the recommended type for service-to-service +integrations where an application account is used instead of a user account. + +## Requirements + +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- An application account (Client ID and Client Secret) with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring application accounts. + + + diff --git a/docs/delinea-secretserver-password.md b/docs/delinea-secretserver-password.md new file mode 100644 index 0000000..534d90a --- /dev/null +++ b/docs/delinea-secretserver-password.md @@ -0,0 +1,15 @@ +## Delinea-SecretServer-Password + +The `Delinea-SecretServer-Password` PAM type authenticates to Delinea Secret Server using a username and password +(OAuth2 `password` grant). This is the recommended type for environments where a service account with a username +and password is used. + +## Requirements + +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- A service account with a username and password that has permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts. + + + diff --git a/docs/delinea-secretserver-windows.md b/docs/delinea-secretserver-windows.md new file mode 100644 index 0000000..5f8de9c --- /dev/null +++ b/docs/delinea-secretserver-windows.md @@ -0,0 +1,21 @@ +## Delinea-SecretServer-Windows + +The `Delinea-SecretServer-Windows` PAM type authenticates to Delinea Secret Server using Integrated Windows +Authentication (IWA). No credentials are required in the configuration — the provider uses the Windows identity +of the process running Keyfactor Command or the Universal Orchestrator. + +> [!IMPORTANT] +> Integrated Windows Authentication is not supported on Delinea Secret Server Cloud. This type is only compatible +> with on-premises Secret Server installations. + +## Requirements + +- On-premises Delinea Secret Server instance accessible from the host running Keyfactor Command or the Universal Orchestrator. +- The Windows service account running Keyfactor Command or the Universal Orchestrator must have permission to view + the secrets being retrieved. See the + [Delinea Secret Server IWA documentation](https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm) + for information on configuring IWA access. +- The Secret Server instance must be configured to allow Integrated Windows Authentication web service access. + + + diff --git a/docs/delinea-secretserver.md b/docs/delinea-secretserver.md index 408e7d4..c017f7f 100644 --- a/docs/delinea-secretserver.md +++ b/docs/delinea-secretserver.md @@ -1,64 +1,19 @@ ## Delinea-SecretServer -The Delinea Secret Server PAM Provider allows for the retrieval of stored account credentials from a Delinea Secret -Server secret. Supports either `password` or `client_credential` authentication methods. For more information on -these authentication methods, see the -[Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). +`Delinea-SecretServer` is the original backwards-compatible PAM type. It supports all three authentication methods +(`password`, `client_credentials`, `windows`) selected at runtime via the `GrantType` configuration parameter. +The Keyfactor Command UI will display every credential field regardless of which grant type is active. -## Requirements - -- Delinea Secret Server service account or client credential w/ permission to access the secret(s) being used. See the [Delinea - Secret Server documentation]([Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm).) for more information on how to configure service accounts and client credentials. - - -## Mechanics - -When configuring the Delinea Secret Server for use as a PAM Provider with Keyfactor, you will need to ensure that your -instance is configured for API access. This can be done by logging into the Delinea Secret Server as an administrator. -For more details visit the vendor docs [here](https://docs.delinea.com/online-help/secret-server/api-scripting/sdk-devops/using-sdk/index.htm#SetupProcedure). - -Once API access is configured a user account with a username and password is required. That account *MUST* be granted access -to view secret's you'll be using. +For new installations, use the type-specific variants (`Delinea-SecretServer-Password`, +`Delinea-SecretServer-ClientCredentials`, or `Delinea-SecretServer-Windows`) which only show the fields relevant +to the chosen authentication method. -After adding and sharing a secret on SecretServer, you can use the secret's ID (the "Secret ID") and the desired value's -field name (the "Secret Field Name") to retrieve credentials from the Delinea Secret Server as a PAM Provider. - -### Running the PAM provider on Keyfactor Universal Orchestrator (UO) -When installing on the Universal Orchestrator (UO), is installed on and run from the UO host. Below is a sequence diagram -showing the flow of the PAM provider when it is run from the UO. - -```mermaid -sequenceDiagram - KeyfactorCommand->>KeyfactorCommand: New job created. - UO->>KeyfactorCommand: Hello do you have any jobs for me? - KeyfactorCommand->>UO: Yes here's a job. - UO->>SecretServer: Hello here are my client credentials. - SecretServer->>UO: Here's your API token. - UO->>SecretServer: I need secret ID 100, here's my API token. - SecretServer->>SecretServer: Check secret ACL. - SecretServer->>UO: This is allowed, here's the secret. - UO->>UO: Running job. - UO->>KeyfactorCommand: Job completed. -``` - -### Running the PAM provider on the Keyfactor Command Host -When installing the PAM provider on the Keyfactor Command Host, is installed on and run from the Keyfactor Command host. -Below is a sequence diagram showing the flow of the PAM provider when it is run from the Keyfactor Command Host. - -```mermaid -sequenceDiagram - KeyfactorCommand->>KeyfactorCommand: Creating a new job. - KeyfactorCommand->>SecretServer: Hello here are my client credentials. - SecretServer->>KeyfactorCommand: Here's your API token. - KeyfactorCommand->>SecretServer: I need secret ID 100, here's my API token. - SecretServer->>SecretServer: Check secret ACL. - SecretServer->>KeyfactorCommand: This is allowed, here's the secret. - UO->>KeyfactorCommand: Hello do you have any jobs for me? - KeyfactorCommand->>UO: Yes here's a job with these credentials I pulled from SecretServer. - UO->>UO: Running job. - UO->>KeyfactorCommand: Job completed. -``` +## Requirements +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- A service account or application account with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts and application accounts. diff --git a/docsource/delinea-secretserver-clientcredentials.md b/docsource/delinea-secretserver-clientcredentials.md new file mode 100644 index 0000000..dd37d3a --- /dev/null +++ b/docsource/delinea-secretserver-clientcredentials.md @@ -0,0 +1,13 @@ +## Overview + +The `Delinea-SecretServer-ClientCredentials` PAM type authenticates to Delinea Secret Server using OAuth2 client +credentials (application account name and password). This is the recommended type for service-to-service +integrations where an application account is used instead of a user account. + +## Requirements + +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- An application account (Client ID and Client Secret) with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring application accounts. + diff --git a/docsource/delinea-secretserver-password.md b/docsource/delinea-secretserver-password.md new file mode 100644 index 0000000..7b15ba6 --- /dev/null +++ b/docsource/delinea-secretserver-password.md @@ -0,0 +1,13 @@ +## Overview + +The `Delinea-SecretServer-Password` PAM type authenticates to Delinea Secret Server using a username and password +(OAuth2 `password` grant). This is the recommended type for environments where a service account with a username +and password is used. + +## Requirements + +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- A service account with a username and password that has permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts. + diff --git a/docsource/delinea-secretserver-windows.md b/docsource/delinea-secretserver-windows.md new file mode 100644 index 0000000..4d70db4 --- /dev/null +++ b/docsource/delinea-secretserver-windows.md @@ -0,0 +1,19 @@ +## Overview + +The `Delinea-SecretServer-Windows` PAM type authenticates to Delinea Secret Server using Integrated Windows +Authentication (IWA). No credentials are required in the configuration — the provider uses the Windows identity +of the process running Keyfactor Command or the Universal Orchestrator. + +> [!IMPORTANT] +> Integrated Windows Authentication is not supported on Delinea Secret Server Cloud. This type is only compatible +> with on-premises Secret Server installations. + +## Requirements + +- On-premises Delinea Secret Server instance accessible from the host running Keyfactor Command or the Universal Orchestrator. +- The Windows service account running Keyfactor Command or the Universal Orchestrator must have permission to view + the secrets being retrieved. See the + [Delinea Secret Server IWA documentation](https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm) + for information on configuring IWA access. +- The Secret Server instance must be configured to allow Integrated Windows Authentication web service access. + diff --git a/docsource/delinea-secretserver.md b/docsource/delinea-secretserver.md index 3e33499..db06567 100644 --- a/docsource/delinea-secretserver.md +++ b/docsource/delinea-secretserver.md @@ -1,59 +1,17 @@ ## Overview -The Delinea Secret Server PAM Provider allows for the retrieval of stored account credentials from a Delinea Secret -Server secret. Supports either `password` or `client_credential` authentication methods. For more information on -these authentication methods, see the -[Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). +`Delinea-SecretServer` is the original backwards-compatible PAM type. It supports all three authentication methods +(`password`, `client_credentials`, `windows`) selected at runtime via the `GrantType` configuration parameter. +The Keyfactor Command UI will display every credential field regardless of which grant type is active. -## Requirements - -- Delinea Secret Server service account or client credential w/ permission to access the secret(s) being used. See the [Delinea - Secret Server documentation]([Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm).) for more information on how to configure service accounts and client credentials. - -## Extension Mechanics - -When configuring the Delinea Secret Server for use as a PAM Provider with Keyfactor, you will need to ensure that your -instance is configured for API access. This can be done by logging into the Delinea Secret Server as an administrator. -For more details visit the vendor docs [here](https://docs.delinea.com/online-help/secret-server/api-scripting/sdk-devops/using-sdk/index.htm#SetupProcedure). +For new installations, use the type-specific variants (`Delinea-SecretServer-Password`, +`Delinea-SecretServer-ClientCredentials`, or `Delinea-SecretServer-Windows`) which only show the fields relevant +to the chosen authentication method. -Once API access is configured a user account with a username and password is required. That account *MUST* be granted access -to view secret's you'll be using. - -After adding and sharing a secret on SecretServer, you can use the secret's ID (the "Secret ID") and the desired value's -field name (the "Secret Field Name") to retrieve credentials from the Delinea Secret Server as a PAM Provider. - -### Running the PAM provider on Keyfactor Universal Orchestrator (UO) -When installing on the Universal Orchestrator (UO), is installed on and run from the UO host. Below is a sequence diagram -showing the flow of the PAM provider when it is run from the UO. - -```mermaid -sequenceDiagram - KeyfactorCommand->>KeyfactorCommand: New job created. - UO->>KeyfactorCommand: Hello do you have any jobs for me? - KeyfactorCommand->>UO: Yes here's a job. - UO->>SecretServer: Hello here are my client credentials. - SecretServer->>UO: Here's your API token. - UO->>SecretServer: I need secret ID 100, here's my API token. - SecretServer->>SecretServer: Check secret ACL. - SecretServer->>UO: This is allowed, here's the secret. - UO->>UO: Running job. - UO->>KeyfactorCommand: Job completed. -``` +## Requirements -### Running the PAM provider on the Keyfactor Command Host -When installing the PAM provider on the Keyfactor Command Host, is installed on and run from the Keyfactor Command host. -Below is a sequence diagram showing the flow of the PAM provider when it is run from the Keyfactor Command Host. +- Delinea Secret Server instance accessible over HTTPS from the host running Keyfactor Command or the Universal Orchestrator. +- A service account or application account with permission to view the secrets being retrieved. See the + [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm) + for information on configuring service accounts and application accounts. -```mermaid -sequenceDiagram - KeyfactorCommand->>KeyfactorCommand: Creating a new job. - KeyfactorCommand->>SecretServer: Hello here are my client credentials. - SecretServer->>KeyfactorCommand: Here's your API token. - KeyfactorCommand->>SecretServer: I need secret ID 100, here's my API token. - SecretServer->>SecretServer: Check secret ACL. - SecretServer->>KeyfactorCommand: This is allowed, here's the secret. - UO->>KeyfactorCommand: Hello do you have any jobs for me? - KeyfactorCommand->>UO: Yes here's a job with these credentials I pulled from SecretServer. - UO->>UO: Running job. - UO->>KeyfactorCommand: Job completed. -``` diff --git a/docsource/overview.md b/docsource/overview.md index a14f1fa..08e2731 100644 --- a/docsource/overview.md +++ b/docsource/overview.md @@ -1,76 +1,34 @@ ## Overview The Delinea Secret Server PAM Provider allows for the retrieval of stored account credentials from a Delinea Secret -Server secret. Supports either `password` or `client_credential` authentication methods. For more information on -these authentication methods, see the -[Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). +Server secret. Three authentication methods are supported: `password` (username/password), `client_credentials` +(OAuth2 application account), and `windows` (Integrated Windows Authentication). -## Authentication Methods -For full details on each authentication method, please refer to the [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/authentication/script-token-auth/index.htm). -Below are example `manifest.json` snippets for each supported authentication method. +## PAM Types -### Password +This provider ships four PAM types. For new installations, use the type-specific variants — they only expose the +fields relevant to the chosen authentication flow, which simplifies configuration in the Keyfactor Command UI. -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "Username": "", - "Password": "", - "GrantType": "password" - } -} -``` +| PAM Type | Auth Method | Server Parameters | +| --- | --- | --- | +| `Delinea-SecretServer-Password` | Username + Password | `Host`, `Username`, `Password` | +| `Delinea-SecretServer-ClientCredentials` | OAuth2 Client Credentials | `Host`, `ClientId`, `ClientSecret` | +| `Delinea-SecretServer-Windows` | Integrated Windows Authentication | `Host` | +| `Delinea-SecretServer` | Any (selected via `GrantType`) | `Host`, plus credentials for the chosen grant type | -### oAuth2 +> [!NOTE] +> `Delinea-SecretServer` is the original backwards-compatible type retained for existing installations. It requires +> a `GrantType` field and exposes all credential fields in the Keyfactor Command UI regardless of which grant type +> is active. Existing installations do not need to change. -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "ClientId": "", - "ClientSecret": "", - "GrantType": "client_credentials" - } -} -``` +## TLS Validation -### Windows +All PAM types support skipping TLS certificate validation for non-production environments via either: -> [!IMPORTANT] -> Integrated Windows Authentication (IWA) does not work on Secret Server Cloud. +- The `SkipTlsValidation` configuration parameter (set to `true` in the PAM provider instance) +- The `KEYFACTOR_PAM_SKIP_TLS_VALIDATION` environment variable (set to `true` or `1` on the host) -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "GrantType": "windows" - } -} -``` -Please refer to the [Delinea Secret Server documentation](https://docs.delinea.com/online-help/secret-server/authentication/iwa-webservices/webservice-iwa-powershell/index.htm) -for more information on configuring IWA. \ No newline at end of file +The environment variable takes precedence and overrides the configuration parameter. + +> [!WARNING] +> Disabling TLS validation should only be used in non-production environments. \ No newline at end of file diff --git a/global.json b/global.json index fc4a588..35bdbc7 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.0", + "version": "10.0.0", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/integration-manifest.json b/integration-manifest.json index 9856bbe..07c162f 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -5,7 +5,7 @@ "status": "production", "support_level": "kf-supported", "link_github": true, - "update_catalog": true, + "update_catalog": true, "release_dir": "delinea-secretserver-pam/bin/Release", "release_project": "delinea-secretserver-pam/delinea-secretserver-pam.csproj", "description": "The Delinea Secret Server PAM Provider allows for the retrieval of stored account credentials from a Delinea Secret Server secret. A valid username, password and secret share settings are required.", @@ -30,7 +30,7 @@ "Name": "Username", "DisplayName": "Secret Server Username", "Description": "The username used to authenticate to the Secret Server instance. NOTE: only applicable if using the `password` grant type.", - "DataType": 2, + "DataType": 1, "InstanceLevel": false }, { @@ -44,22 +44,149 @@ "Name": "ClientId", "DisplayName": "Secret Server Client ID", "Description": "The client ID used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "ClientSecret", + "DisplayName": "Secret Server Client Secret", + "Description": "The client secret used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type.", + "DataType": 2, + "InstanceLevel": false + }, + { + "Name": "GrantType", + "DisplayName": "Grant Type", + "Description": "The grant type used to authenticate to the Secret Server instance. Valid values are `password`, `client_credentials`, or `windows`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatibility.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, + { + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + "Delinea-SecretServer-Password": { + "Name": "Delinea-SecretServer-Password", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "Username", + "DisplayName": "Secret Server Username", + "Description": "The username used to authenticate to the Secret Server instance.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "Password", + "DisplayName": "Secret Server Password", + "Description": "The password used to authenticate to the Secret Server instance.", + "DataType": 2, + "InstanceLevel": false + }, + { + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, + { + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + "Delinea-SecretServer-ClientCredentials": { + "Name": "Delinea-SecretServer-ClientCredentials", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretservercloud.com/SecretServer", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "ClientId", + "DisplayName": "OAuth2 Client ID", + "Description": "The client ID (application account name) used for OAuth2 client credentials authentication.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "ClientSecret", + "DisplayName": "OAuth2 Client Secret", + "Description": "The client secret (application account password) used for OAuth2 client credentials authentication.", "DataType": 2, "InstanceLevel": false }, { - "Name": "ClientSecret", - "DisplayName": "Secret Server Client Secret", - "Description": "The client secret used to authenticate to the Secret Server instance. NOTE: only applicable if using the `client_credentials` grant type.", - "DataType": 2, - "InstanceLevel": false - }, + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SecretId", + "DisplayName": "Secret ID", + "Description": "The ID of the secret in Secret Server. This is the integer ID that is used to retrieve the secret from Secret Server.", + "DataType": 1, + "InstanceLevel": true + }, { - "Name": "GrantType", - "DisplayName": "Grant Type", - "Description": "The grant type used to authenticate to the Secret Server instance. Valid values are `password` or `client_credentials`. Default is `password`. If not provided the default value `password` will be used to maintain backwards compatability.", - "DataType": 1, - "InstanceLevel": false + "Name": "SecretFieldName", + "DisplayName": "Secret Field Name", + "Description": "The name of the field in the secret that contains the credential value. NOTE: The field must exist.", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + "Delinea-SecretServer-Windows": { + "Name": "Delinea-SecretServer-Windows", + "Parameters": [ + { + "Name": "Host", + "DisplayName": "Secret Server URL", + "Description": "The URL to the Secret Server instance. Example: https://example.secretserver.internal/SecretServer. NOTE: IWA is not supported on Secret Server Cloud.", + "DataType": 1, + "InstanceLevel": false + }, + { + "Name": "SkipTlsValidation", + "DisplayName": "Skip TLS Validation", + "Description": "Set to `true` to disable TLS certificate validation. Use only in non-production environments.", + "DataType": 1, + "InstanceLevel": false }, { "Name": "SecretId", diff --git a/readme-src/readme-config.md b/readme-src/readme-config.md deleted file mode 100644 index 980099e..0000000 --- a/readme-src/readme-config.md +++ /dev/null @@ -1,181 +0,0 @@ -## Configuring for PAM Usage -### Delinea Secret Server -When configuring the Delinea Secret Server for use as a PAM Provider with Keyfactor, you will need to ensure that your -instance is configured for API access. This can be done by logging into the Delinea Secret Server as an administrator. -For more details visit the vendor docs [here](https://docs.delinea.com/secrets/current/api-scripting/sdk-cli/index.md#setup_procedure). - -Once API access is configured a user account with a username and password is required. That account *MUST* be granted access -to view secret's you'll be using. - -After adding and sharing a secret on SecretServer, you can use the secret's ID (the "Secret ID") and the desired value's -field name (the "Secret Field Name") to retrieve credentials from the Delinea Secret Server as a PAM Provider. - -### Install PAM provider on Keyfactor Universal Orchestrator (UO) -When installing on the Universal Orchestrator, the PAM Provider is installed as a DLL and configured in the UO. This allows -the UO to use the PAM provider from the UO host/network and retrieve secrets from Delinea Secret Server and pass them -into Orchestrator extensions. - -```mermaid -sequenceDiagram - CreateJob->>Command: New job created. - UO->>Command: Hello do you have any jobs for me? - Command->>UO: Yes here's a job. - UO->>Delinea: Hello here are my client credentials. - Delinea->>UO: Here's your API token. - UO->>Delinea: I need secret ID 100, here's my API token. - Delinea->>Delinea: Check secret ACL. - Delinea->>UO: This is allowed, here's the secret. -``` - -#### Installation -For full UO installation instructions please review the latest product documentation: -- [Windows](https://software.keyfactor.com/Content/InstallingAgents/NetCoreOrchestrator/InstalltheOrchestratorWindows.htm?Highlight=universal%20orchestrator) -- [Linux](https://software.keyfactor.com/Content/InstallingAgents/NetCoreOrchestrator/InstalltheOrchestratorLinux.htm) -- [Container](https://software.keyfactor.com/Content/InstallingAgents/NetCoreOrchestrator/InstalltheOrchestratorLinuxContainer.htm) - -#### Step 1: Download the release and install the extension -For latest product documentation on installing orchestrator extensions please review the -[Keyfactor Universal Orchestrator Docs](https://software.keyfactor.com/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm). - -On the Universal Orchestrator host, locate the extensions directory within the install directory. By default, this is: -- Windows: `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions` -- Linux: `/opt/keyfactor/orchestrator/extensions` - -Then create a new folder named `Delinea-SecretServer` and copy the release contents into this folder. The directory structure -should look like the following: -![](../images/uo_dir.png) -![](images/uo_dir.png) - -#### Step 2: Create or update the manifest.json file in the Delinea-SecretServer folder -This file is used by the UO to communicate with the PAM Provider's API. The `manifest.json` file should be located in the -`Delinea-SecretServer` folder. The `manifest.json` file should look like the following: -```json -{ - "extensions": { - "Keyfactor.Platform.Extensions.IPAMProvider": { - "PAMProviders.Delinea.PAMProvider": { - "assemblyPath": "delinea-secretserver-pam.dll", - "TypeFullName": "Keyfactor.Extensions.Pam.Delinea.SecretServerPam" - } - } - }, - "Keyfactor:PAMProviders:Delinea-SecretServer:InitializationInfo": { - "Host": "https://example.secretservercloud.com/SecretServer", - "Username": "my_secretserver_service_account", - "Password": "xxxxxx" - } -} -``` - -#### Step 3: Restart the UO -Restart the UO service to load the new extension. - -#### Step 4: Usage -After the extension is installed, you can use the PAM Provider when configuring certificate stores. In order to use the PAM -provider from the UO you'll need to use a JSON blob in the Server Password field. The JSON blob should look like the following: -```json -{ - "SecretId": 123, - "SecretFieldName": "password" -} -``` -The `SecretId` is the ID of the secret you want to retrieve from the Delinea Secret Server. The `SecretFieldName` is the -name of the field in the secret you want to retrieve and use as the password value. - -![](../images/usage.png) -![](images/usage.png) - -#### Troubleshooting -If you are having trouble with the PAM Provider, you can review the UO logs for errors by searching for `Delinea`. Please -review the latest product documentation for [configuring logging](https://software.keyfactor.com/Content/InstallingAgents/NetCoreOrchestrator/ConfigureLogging.htm) - - -### Install PAM provider on Keyfactor Command Host -When installing on Keyfactor Command, the PAM Provider is installed as a DLL and configured in the Keyfactor Platform. -This allows the Keyfactor Command Platform to use the PAM provider and retrieve secrets from the Delinea Secret Server -and pass them down to a Universal Orchestrator. - -```mermaid -sequenceDiagram - CreateJob->>Command: New job uses Delinea PAM Provider I need to retrieve the secrets. - Command->>Delinea: Hello here are my client credentials. - Delinea->>Command: Here's your API token. - Command->>Delinea: I need secret ID 100, here's my API token. - Delinea->>Command: This is allowed, here's the secret. - Command->>Command: Adding retrieved secrets to the job. - UO->>Command: Hello do you have any jobs for me? - Command->>UO: Yes here's a job. - UO->>Command: Thanks I'll let you know how it goes. -``` - -#### Installation -For latest product documentation on installing a PAM provider on a Keyfactor Command Server please review the [Keyfactor Command Docs](https://software.keyfactor.com/Content/ReferenceGuide/Preparing%20Third%20Party%20PAM%20Providers%20to%20Work%20with.htm?Highlight=pam). -Specifically the section labeled `Installation on the Keyfactor Command Server` - -#### Step 1: Create the PAM provider type in Keyfactor Command -In order to allow Keyfactor Command to use the new Delinea Secret Server PAM provider, the definition needs to be added -to the application database. This is done by running the provided `kfutil` tool to install the PAM definition, which only -needs to be done one time. It uses API credentials to access the Keyfactor instance and create the PAM definition. - -The `kfutil` tool, after being [configured for API access](https://github.com/Keyfactor/kfutil#quickstart), can be run -in the following manner to install the PAM definition from the Keyfactor repository: - -``` -kfutil pam types-create -r delinea-secretserver-pam -n Delinea-SecretServer -``` - -**Alternatively** you can also use the Keyfactor Command API directly, please review the product documentation above. - -#### Step 2: Download the release and install the extension -After the installation is run, the DLLs need to be installed to the correct location for the PAM Provider to function. -From the release, the `delinea-secretserver-pam.dll` should be copied to the following folder locations in the Keyfactor -Command installation. Once the DLL has been copied to these folders, edit the corresponding config file. You will need to add a -new Unity entry as follows under ``, next to other `` tags. - -Default Keyfactor Command Install Path: -- Windows: `C:\Program Files\Keyfactor\` - -| Install Location | DLL Binary Folder | Config File | -|------------------|-----------------------|-------------------------------------| -| WebAgentServices | WebAgentServices\bin\ | WebAgentServices\web.config | -| Service | Service\ | Service\CMSTimerService.exe.config | -| KeyfactorAPI | KeyfactorAPI\bin\ | KeyfactorAPI\web.config | -| WebConsole | WebConsole\bin\ | WebConsole\web.config | - -##### Example DLL Install -![](../images/dll_install.png) -![](images/dll_install.png) - -##### Example Unity Entry -```xml - -``` -![](../images/unity_changes.png)] -![](images/unity_changes.png)] - -#### Step 3: Restart the Keyfactor Command Service -The Keyfactor Command service and IIS Server should be restarted after making these changes. - -#### Step 4: Create an instance of the PAM Provider in the Keyfactor Command Platform -For full details and the latest product documentation on creating a PAM Provider instance please review the -[PAM Provider Configuration in Keyfactor Command](https://software.keyfactor.com/Content/ReferenceGuide/PAM%20Configuration%20in%20Keyfactor%20Command.htm?Highlight=delinea) docs. - -In order to use the PAM Provider, the provider's configuration must be set in the Keyfactor Platform. In the settings -menu (upper right cog) you can select the ___Privileged Access Management___ option to configure your provider instance. - -![](../images/setting.png) -![](images/setting.png) - -#### Step 5: Usage -After an instance of the PAM provider is created, you can now use your PAM Provider when configuring certificate stores. -Any field that is treated as a Keyfactor Command secret, such as `Server Password`s and certificate `Store Password`s can -be retrieved from your PAM Provider instead of being entered in directly as a secret. - -![](../images/password.png) -![](images/password.png) - -#### Troubleshooting -If you are having trouble with the PAM Provider, you can review the Keyfactor Command logs for errors by searching for `Delinea`. -Please review the latest product documentation for [configuring logging](https://software.keyfactor.com/Content/ReferenceGuide/Log%20Edit.htm) -on the Keyfactor Command Server. -``` \ No newline at end of file diff --git a/readme-src/readme-paramtable.md b/readme-src/readme-paramtable.md deleted file mode 100644 index f512782..0000000 --- a/readme-src/readme-paramtable.md +++ /dev/null @@ -1,16 +0,0 @@ -### Initialization Parameters for each defined PAM Provider instance -| Initialization parameter | Display Name | Description | -|:------------------------:|:-----------------------:|---------------------------------------------------------------------------| -| Host | Secret Server URL | The IP address or URL of the Vault instance, including any port number | -| Username | Secret Server Username | The username the PAM provider is going to use to connect to SecretServer. | -| Password | Secret Server Password | The username the PAM provider is going to use to connect to SecretServer. | - - - -### Instance Parameters for each retrieved secret field -| Instance parameter | Display Name | Description | -|:------------------:|:------------------------:|------------------------------------------------------------------------| -| SecretId | Secret Server Secret ID | The integer ID of the secret to use. | -| SecretFieldName | Secret Field Name | The name of the field to use when looking up a secret on SecretServer. | - -![](../images/config.png) \ No newline at end of file diff --git a/readme-src/readme-pre.md b/readme-src/readme-pre.md deleted file mode 100644 index 0bf4393..0000000 --- a/readme-src/readme-pre.md +++ /dev/null @@ -1,33 +0,0 @@ -- [Delinea Secret Server PAM Provider](#delinea-secret-server-pam-provider) - - [Integration status: Production - Ready for use in production environments.](#integration-status--production---ready-for-use-in-production-environments) - * [About the Keyfactor Command PAM Provider](#about-the-keyfactor-command-pam-provider) - * [Support for Delinea Secret Server PAM Provider](#support-for-delinea-secret-server-pam-provider) - * [Keyfactor Command Versions Supported](#keyfactor-command-versions-supported) - + [Initial Configuration of PAM Provider](#initial-configuration-of-pam-provider) - + [Configuring Parameters](#configuring-parameters) - + [Initialization Parameters for each defined PAM Provider instance](#initialization-parameters-for-each-defined-pam-provider-instance) - + [Instance Parameters for each retrieved secret field](#instance-parameters-for-each-retrieved-secret-field) - * [Configuring for PAM Usage](#configuring-for-pam-usage) - + [Delinea Secret Server](#delinea-secret-server) - + [On Keyfactor Universal Orchestrator](#on-keyfactor-universal-orchestrator) - - [Installation](#installation) - - [Usage](#usage) - + [In Keyfactor - PAM Provider](#in-keyfactor---pam-provider) - - [Installation](#installation-1) - - [Usage](#usage-1) - - - -## 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.4.5 | 10.1, 10.2, 10.4 | ✓ | -| 10.4.0 | 10.1, 10.2, 10.4 | ✓ | -| 10.2.1 | 10.1, 10.2, 10.4 | ✓ | -| 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 | \ No newline at end of file