Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f7b578a
feat(config): support env var overrides for AuthType, AccessId, Acces…
spbsoluble Jul 16, 2026
841842c
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 16, 2026
a977de5
docs: document Akeyless API endpoints called by the extension
spbsoluble Jul 16, 2026
6e69bab
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 16, 2026
e7e547d
docs: surface env var config in Requirements so it bubbles into README
spbsoluble Jul 16, 2026
b891cf3
docs: regenerate with fixed doctool (dedup Extension Mechanics, inclu…
spbsoluble Jul 16, 2026
d4d7f0e
Merge remote-tracking branch 'origin/release-1.1' into feature/env-va…
spbsoluble Jul 22, 2026
deddf75
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 22, 2026
44ded50
fix(tests): prevent env var override from masking bad-credentials test
spbsoluble Jul 22, 2026
f6db764
Merge remote-tracking branch 'origin/feature/env-var-credential-overr…
spbsoluble Jul 22, 2026
bc1bc0f
fix(docs): revert CI doc regeneration regression
spbsoluble Jul 22, 2026
f411c05
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 22, 2026
c373bf5
fix: whitespace-only env var overrides and add override audit log
spbsoluble Jul 22, 2026
a598f13
Merge remote-tracking branch 'origin/feature/env-var-credential-overr…
spbsoluble Jul 22, 2026
06c8db1
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 22, 2026
5c5089c
fix(docs): revert another CI doc regeneration regression
spbsoluble Jul 22, 2026
bf1f5b8
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 22, 2026
4fa6b0f
fix: validate AKEYLESS_AUTH_TYPE override, trim env-var overrides, lo…
spbsoluble Aug 5, 2026
354cfad
fix: reject embedded line breaks in env overrides, log AccessId on au…
spbsoluble Aug 5, 2026
737af79
fix: reject control characters and Unicode line separators, not just …
spbsoluble Aug 5, 2026
1eb02ca
fix: reject Unicode format/bidi chars in overrides; stop logging raw …
spbsoluble Aug 5, 2026
9ad4156
fix: switch to a printable-ASCII allowlist, applied to both override …
spbsoluble Aug 5, 2026
43ee79c
fix: validate configured Url/AuthType/AccessId/AccessKey before Build…
spbsoluble Aug 5, 2026
8eabfec
fix: log before EnsurePrintableAscii rejects a non-ASCII value
spbsoluble Aug 5, 2026
20e63b4
refactor: extract ResolveAndValidate helper in InitClient
spbsoluble Aug 6, 2026
11dd843
refactor: drop vacuous single-case switch in InitClient
spbsoluble Aug 6, 2026
e755d96
fix: trim configured Url/AuthType/AccessId/AccessKey before validating
spbsoluble Aug 6, 2026
9925975
fix: null-safe AuthType/Url trim; warn on ambient credential env-var …
spbsoluble Aug 6, 2026
4c3e2b8
fix: whitespace-only configured Url falls back to default, not empty …
spbsoluble Aug 6, 2026
568aa1f
refactor: drop dead default-URL fallback in InitClient
spbsoluble Aug 6, 2026
97e8f74
refactor: fold embedded-line-break AccessId test into the control-cha…
spbsoluble Aug 6, 2026
a0f0980
docs: condense CHANGELOG entries for env var override feature
spbsoluble Aug 6, 2026
f0f6003
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v1.1.0

## Features

- **Environment variable overrides for connection parameters** — `AuthType`, `AccessId`, and `AccessKey` can now be overridden at runtime via the `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, and `AKEYLESS_ACCESS_KEY` environment variables, matching the existing `AKEYLESS_API_URL` override. This lets deployments control Akeyless connection details at the infrastructure level instead of only via `manifest.json` or the Command portal.
- Environment variable overrides are trimmed of leading/trailing whitespace before use.
- Hardened validation and logging around connection parameters and authentication failures.

# v1.0.0

Initial release of the Akeyless PAM Provider for Keyfactor Command and Universal Orchestrator.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ To install Akeyless PAM Provider, it is recommended you install [kfutil](https:/

#### Requirements
- Akeyless credentials w/ permission to access the secret(s) being used. See the [Akeyless documentation](https://docs.akeyless.io/reference/auth) for more information on how to configure the different types of auth.
- (Optional) `AKEYLESS_API_URL`, `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, and `AKEYLESS_ACCESS_KEY` environment variables can be set on the provider's host process to override the corresponding `manifest.json`/Command portal parameters at runtime. See [Configuration](docs/akeyless.md#configuration) for details and precedence.

#### Create PAM type in Keyfactor Command

Expand Down
198 changes: 162 additions & 36 deletions akeyless-pam/AkeylessPam.cs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions docs/akeyless.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ these authentication methods, see the [Akeyless documentation](https://docs.akey
## Requirements

- Akeyless credentials w/ permission to access the secret(s) being used. See the [Akeyless documentation](https://docs.akeyless.io/reference/auth) for more information on how to configure the different types of auth.
- (Optional) `AKEYLESS_API_URL`, `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, and `AKEYLESS_ACCESS_KEY` environment variables can be set on the provider's host process to override the corresponding `manifest.json`/Command portal parameters at runtime. See [Configuration](docs/akeyless.md#configuration) for details and precedence.

## Mechanics

Expand All @@ -17,6 +18,17 @@ docs [here](https://docs.akeyless.io/docs/access-and-authentication-methods).

Once API access is configured the credential *MUST* be granted access to view secret(s) you'll be using.

### Akeyless API Endpoints Used

The provider calls exactly two Akeyless REST API endpoints, both against the configured base URL (default `https://api.akeyless.io`, see the `Url` initialization parameter / `AKEYLESS_API_URL` environment variable above):

| Endpoint | Method | Called from | Purpose |
|---|---|---|---|
| [`/auth`](https://docs.akeyless.io/reference/auth) | `POST` | `AkeylessApiClient.Authenticate` (invoked once per `GetPassword` call, before secret retrieval) | Exchanges the configured `AccessId`/`AccessKey` for a short-lived auth token. |
| [`/get-secret-value`](https://docs.akeyless.io/reference/getsecretvalue) | `POST` | `AkeylessApiClient.GetSecretValuesAsync` (invoked once per `GetPassword` call, after authentication) | Retrieves the value of the secret named by the `SecretName` instance parameter, using the token from `/auth`. |

No other Akeyless API endpoints are called by this provider — it only ever authenticates and reads a single static secret value per credential lookup. It never creates, updates, deletes, or lists items in Akeyless.

### Granting an Auth Method Access to a Secret

In Akeyless, access is controlled through **Access Roles**. A role ties one or more auth methods to a set of permitted item paths. The steps below show how to grant an API Key auth method read access to a secret using the Akeyless console.
Expand Down Expand Up @@ -116,6 +128,25 @@ Below you will find a list of supported [auth methods](#supported-authentication
these authentication methods, see the [Akeyless documentation](https://docs.akeyless.io/reference/auth)

- Akeyless credentials w/ permission to access the secret(s) being used. See the [Akeyless documentation](https://docs.akeyless.io/reference/auth) for more information on how to configure the different types of auth.
- (Optional) `AKEYLESS_API_URL`, `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, and `AKEYLESS_ACCESS_KEY` environment variables can be set on the provider's host process to override the corresponding `manifest.json`/Command portal parameters at runtime. See [Configuration](docs/akeyless.md#configuration) for details and precedence.

## Configuration

Connection and authentication parameters can be set in two ways:

1. **`manifest.json`/Command portal parameters** — set via the `manifest.json` `InitializationInfo` block (Universal Orchestrator installs) or the corresponding fields in the Command portal PAM provider configuration (Command host installs). This is the standard way to configure the provider.
2. **Environment variables** — if set on the host process running the PAM provider (the Keyfactor Command server for local installs, or the Universal Orchestrator host for remote installs), these override whatever value is configured via `manifest.json` or the Command portal. This is useful when connection details need to be controlled at the infrastructure/deployment level rather than baked into provider configuration — for example, pointing different environments (dev/stage/prod) at different Akeyless instances or credentials without changing `manifest.json` or Command PAM provider settings.

| Environment Variable | Overrides | Falls Back To |
|---|---|---|
| `AKEYLESS_API_URL` | `Url` | configured `Url` initialization parameter, then default (`https://api.akeyless.io`) |
| `AKEYLESS_AUTH_TYPE` | `AuthType` | configured `AuthType` initialization parameter |
| `AKEYLESS_ACCESS_ID` | `AccessId` | configured `AccessId` initialization parameter |
| `AKEYLESS_ACCESS_KEY` | `AccessKey` | configured `AccessKey` initialization parameter |

Precedence for each: environment variable (if set to a non-empty, non-whitespace-only value) > configured initialization parameter > default (`Url` only). An environment variable that is unset, or explicitly set to an empty or whitespace-only string, is treated as "not overriding" and falls through to the configured value. An override value is trimmed of leading/trailing whitespace before use (a trailing newline is a common artifact of file-mounted/`envFrom` secret provisioning). The effective value of `Url`, `AuthType`, `AccessId`, and `AccessKey` — whether it came from an override or from Command's/`manifest.json`'s configuration — must be printable ASCII; any other character (an embedded newline, ANSI escape sequence, Unicode line separator, or a bidirectional-override/zero-width character such as U+202E) is rejected with an error rather than used as-is. When an override is active, the provider logs a **Warning** stating which environment variable is overriding (never the value), so an incident investigation can confirm whether the effective connection parameter matches Command's recorded configuration. An `AKEYLESS_AUTH_TYPE` override that does not match a supported auth type fails the request immediately rather than silently skipping authentication.

**Upgrading an existing installation:** if the provider's host process already has `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, or `AKEYLESS_ACCESS_KEY` set for an unrelated reason (for example, a co-located Akeyless CLI or another Akeyless SDK conventionally uses these same variable names), upgrading to a version of this provider that reads them will silently start authenticating with that ambient identity instead of the one recorded in `manifest.json`/Command — with no configuration change on Command's side. Check the host environment for these variable names before upgrading, and watch for the Warning-level "Environment variable override active" log line afterward.

## Supported Authentication Methods

Expand Down Expand Up @@ -229,6 +260,17 @@ docs [here](https://docs.akeyless.io/docs/access-and-authentication-methods).

Once API access is configured the credential *MUST* be granted access to view secret(s) you'll be using.

### Akeyless API Endpoints Used

The provider calls exactly two Akeyless REST API endpoints, both against the configured base URL (default `https://api.akeyless.io`, see the `Url` initialization parameter / `AKEYLESS_API_URL` environment variable above):

| Endpoint | Method | Called from | Purpose |
|---|---|---|---|
| [`/auth`](https://docs.akeyless.io/reference/auth) | `POST` | `AkeylessApiClient.Authenticate` (invoked once per `GetPassword` call, before secret retrieval) | Exchanges the configured `AccessId`/`AccessKey` for a short-lived auth token. |
| [`/get-secret-value`](https://docs.akeyless.io/reference/getsecretvalue) | `POST` | `AkeylessApiClient.GetSecretValuesAsync` (invoked once per `GetPassword` call, after authentication) | Retrieves the value of the secret named by the `SecretName` instance parameter, using the token from `/auth`. |

No other Akeyless API endpoints are called by this provider — it only ever authenticates and reads a single static secret value per credential lookup. It never creates, updates, deletes, or lists items in Akeyless.

### Granting an Auth Method Access to a Secret

In Akeyless, access is controlled through **Access Roles**. A role ties one or more auth methods to a set of permitted item paths. The steps below show how to grant an API Key auth method read access to a secret using the Akeyless console.
Expand Down
30 changes: 30 additions & 0 deletions docsource/akeyless.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ these authentication methods, see the [Akeyless documentation](https://docs.akey
## Requirements

- Akeyless credentials w/ permission to access the secret(s) being used. See the [Akeyless documentation](https://docs.akeyless.io/reference/auth) for more information on how to configure the different types of auth.
- (Optional) `AKEYLESS_API_URL`, `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, and `AKEYLESS_ACCESS_KEY` environment variables can be set on the provider's host process to override the corresponding `manifest.json`/Command portal parameters at runtime. See [Configuration](docs/akeyless.md#configuration) for details and precedence.

## Configuration

Connection and authentication parameters can be set in two ways:

1. **`manifest.json`/Command portal parameters** — set via the `manifest.json` `InitializationInfo` block (Universal Orchestrator installs) or the corresponding fields in the Command portal PAM provider configuration (Command host installs). This is the standard way to configure the provider.
2. **Environment variables** — if set on the host process running the PAM provider (the Keyfactor Command server for local installs, or the Universal Orchestrator host for remote installs), these override whatever value is configured via `manifest.json` or the Command portal. This is useful when connection details need to be controlled at the infrastructure/deployment level rather than baked into provider configuration — for example, pointing different environments (dev/stage/prod) at different Akeyless instances or credentials without changing `manifest.json` or Command PAM provider settings.

| Environment Variable | Overrides | Falls Back To |
|---|---|---|
| `AKEYLESS_API_URL` | `Url` | configured `Url` initialization parameter, then default (`https://api.akeyless.io`) |
| `AKEYLESS_AUTH_TYPE` | `AuthType` | configured `AuthType` initialization parameter |
| `AKEYLESS_ACCESS_ID` | `AccessId` | configured `AccessId` initialization parameter |
| `AKEYLESS_ACCESS_KEY` | `AccessKey` | configured `AccessKey` initialization parameter |

Precedence for each: environment variable (if set to a non-empty, non-whitespace-only value) > configured initialization parameter > default (`Url` only). An environment variable that is unset, or explicitly set to an empty or whitespace-only string, is treated as "not overriding" and falls through to the configured value. An override value is trimmed of leading/trailing whitespace before use (a trailing newline is a common artifact of file-mounted/`envFrom` secret provisioning). The effective value of `Url`, `AuthType`, `AccessId`, and `AccessKey` — whether it came from an override or from Command's/`manifest.json`'s configuration — must be printable ASCII; any other character (an embedded newline, ANSI escape sequence, Unicode line separator, or a bidirectional-override/zero-width character such as U+202E) is rejected with an error rather than used as-is. When an override is active, the provider logs a **Warning** stating which environment variable is overriding (never the value), so an incident investigation can confirm whether the effective connection parameter matches Command's recorded configuration. An `AKEYLESS_AUTH_TYPE` override that does not match a supported auth type fails the request immediately rather than silently skipping authentication.

**Upgrading an existing installation:** if the provider's host process already has `AKEYLESS_AUTH_TYPE`, `AKEYLESS_ACCESS_ID`, or `AKEYLESS_ACCESS_KEY` set for an unrelated reason (for example, a co-located Akeyless CLI or another Akeyless SDK conventionally uses these same variable names), upgrading to a version of this provider that reads them will silently start authenticating with that ambient identity instead of the one recorded in `manifest.json`/Command — with no configuration change on Command's side. Check the host environment for these variable names before upgrading, and watch for the Warning-level "Environment variable override active" log line afterward.

## Supported Authentication Methods

Expand Down Expand Up @@ -123,6 +142,17 @@ docs [here](https://docs.akeyless.io/docs/access-and-authentication-methods).

Once API access is configured the credential *MUST* be granted access to view secret(s) you'll be using.

### Akeyless API Endpoints Used

The provider calls exactly two Akeyless REST API endpoints, both against the configured base URL (default `https://api.akeyless.io`, see the `Url` initialization parameter / `AKEYLESS_API_URL` environment variable above):

| Endpoint | Method | Called from | Purpose |
|---|---|---|---|
| [`/auth`](https://docs.akeyless.io/reference/auth) | `POST` | `AkeylessApiClient.Authenticate` (invoked once per `GetPassword` call, before secret retrieval) | Exchanges the configured `AccessId`/`AccessKey` for a short-lived auth token. |
| [`/get-secret-value`](https://docs.akeyless.io/reference/getsecretvalue) | `POST` | `AkeylessApiClient.GetSecretValuesAsync` (invoked once per `GetPassword` call, after authentication) | Retrieves the value of the secret named by the `SecretName` instance parameter, using the token from `/auth`. |

No other Akeyless API endpoints are called by this provider — it only ever authenticates and reads a single static secret value per credential lookup. It never creates, updates, deletes, or lists items in Akeyless.

### Granting an Auth Method Access to a Secret

In Akeyless, access is controlled through **Access Roles**. A role ties one or more auth methods to a set of permitted item paths. The steps below show how to grant an API Key auth method read access to a secret using the Akeyless console.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@
<ProjectReference Include="..\..\akeyless-pam\akeyless-pam.csproj"/>
</ItemGroup>

<ItemGroup>
<Compile Include="..\AkeylessPam.Unit.Tests\EnvVarScope.cs" Link="EnvVarScope.cs"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ public void GetPassword_BadCredentials_ThrowsInvalidClientConfigurationException
["SecretName"] = Env("AKEYLESS_SECRET_STATIC_TEXT")
};

// AKEYLESS_ACCESS_ID/AKEYLESS_ACCESS_KEY (required for every other test in this suite to run)
// would otherwise override the bad credentials above via AkeylessPam's env var override support,
// making auth succeed instead of failing. Clear them for the duration of this test only.
using var idScope = new EnvVarScope("AKEYLESS_ACCESS_ID", null);
using var keyScope = new EnvVarScope("AKEYLESS_ACCESS_KEY", null);

var pam = new AkeylessPam();
var ex = Assert.Throws<AggregateException>(() => pam.GetPassword(instance, server));
Assert.IsType<InvalidClientConfigurationException>(ex.InnerException);
Expand Down
15 changes: 15 additions & 0 deletions tests/AkeylessPam.Integration.Tests/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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 Xunit;

// GetPassword_BadCredentials_ThrowsInvalidClientConfigurationException temporarily clears the process-wide
// AKEYLESS_ACCESS_ID/AKEYLESS_ACCESS_KEY environment variables so the bad credentials it passes via server
// config parameters aren't overridden by AkeylessPam's env var override support. Test classes run in
// parallel by default in xUnit, which could race with AkeylessApiClientTests reading those same env vars.
// Disabling parallelization keeps env var state deterministic across the whole assembly.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
3 changes: 2 additions & 1 deletion tests/AkeylessPam.Integration.Tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ End-to-end tests for `AkeylessPam.GetPassword()` against a live Akeyless instanc
| `GetPassword_StaticJson_UsernameField_ReturnsValue` | credentials + `AKEYLESS_SECRET_STATIC_JSON` | Retrieves the `username` field from a `static_json` secret |
| `GetPassword_StaticJson_PasswordField_ReturnsValue` | credentials + `AKEYLESS_SECRET_STATIC_JSON` | Retrieves the `password` field from a `static_json` secret |
| `GetPassword_StaticJson_NoFieldName_ReturnsRawJsonBlob` | credentials + `AKEYLESS_SECRET_STATIC_JSON_RAW` | Retrieves a `static_json` secret without specifying a field, asserts result is a JSON object or array |
| `GetPassword_BadCredentials_ThrowsInvalidClientConfigurationException` | `AKEYLESS_SECRET_STATIC_TEXT` (no credentials needed) | Intentionally uses invalid credentials and asserts `InvalidClientConfigurationException` is thrown |
| `GetPassword_BadCredentials_ThrowsInvalidClientConfigurationException` | `AKEYLESS_SECRET_STATIC_TEXT` | Intentionally uses invalid credentials and asserts `InvalidClientConfigurationException` is thrown. Clears `AKEYLESS_ACCESS_ID`/`AKEYLESS_ACCESS_KEY` for the duration of the test so the env var override feature doesn't replace the bad credentials with real ones |
| `GetPassword_NonexistentSecret_ThrowsException` | credentials | Requests a secret path that does not exist and asserts an exception is thrown |
| `GetPassword_StaticJson_NoFieldName_ReturnsRawJsonBlob_K8sOrchestratorSecret` | credentials | Retrieves `/pam/test/k8s-orchestrator` as `static_json` with no field name and asserts the full JSON blob is returned |
| `GetPassword_StaticJson_WhitespaceFieldName_ReturnsRawJsonBlob_K8sOrchestratorSecret` | credentials | Same as above but passes a whitespace-only `StaticSecretFieldName` (simulating the Keyfactor Command portal behavior); asserts the full JSON blob is returned |
Expand All @@ -62,3 +62,4 @@ Lower-level tests for `AkeylessApiClient` — the adapter that wraps the Akeyles
| `GetSecretValuesAsync_StaticJsonSecret_ReturnsDictWithValue` | Retrieves a `static_json` secret and asserts the response dictionary contains a non-empty value |
| `GetSecretValuesAsync_MultipleSecrets_ReturnsAllRequested` | Requests two secrets in a single API call and asserts both keys are present in the response |
| `GetSecretValuesAsync_InvalidToken_ThrowsApiException` | Calls `GetSecretValuesAsync` with an invalid token and asserts an `ApiException` is thrown |
| `Debug_K8sOrchestratorSecret_PrintsRawValue` | Retrieves the hardcoded `/pam/test/k8s-orchestrator` secret directly via the API client and asserts the response contains that key (the value itself is intentionally not written to output, to avoid secret exposure in CI logs) |
Loading