Skip to content

feat(auth): Support using env variables to supplement and/or override config values. - #11

Open
spbsoluble wants to merge 32 commits into
release-1.1from
feature/env-var-credential-overrides
Open

feat(auth): Support using env variables to supplement and/or override config values.#11
spbsoluble wants to merge 32 commits into
release-1.1from
feature/env-var-credential-overrides

Conversation

@spbsoluble

Copy link
Copy Markdown
Collaborator

Summary

  • Extends the existing AKEYLESS_API_URL environment-variable override pattern in InitClient (akeyless-pam/AkeylessPam.cs) to also cover AuthType, AccessId, and AccessKey:
    • AKEYLESS_AUTH_TYPE overrides configurationInfo.AuthType
    • AKEYLESS_ACCESS_ID overrides configurationInfo.AccessId
    • AKEYLESS_ACCESS_KEY overrides configurationInfo.AccessKey
  • Adds a shared ResolveEnvOverride helper used by all four env vars (including AKEYLESS_API_URL) so precedence is consistent: env var (non-empty) > configured parameter > default (URL only).
  • Logging in InitClient now references the resolved (post-override) AccessId in the debug/info/error messages that already logged it — no new fields are logged, and AccessKey is still never logged.

Empty-string handling (design decision)

The original AKEYLESS_API_URL line used a plain ?? against Environment.GetEnvironmentVariable(...), which only falls back when the env var is unset — an env var explicitly set to "" would have "won" and blanked out a valid configured value. Rather than leave that edge case in place for the URL and add a different, inconsistent rule for the other three, ResolveEnvOverride treats an env var set to an empty string the same as an unset one for all four variables (AKEYLESS_API_URL included). This is a minor behavior tweak to the existing AKEYLESS_API_URL handling, but avoids a footgun where an empty/misconfigured environment variable could silently break a working manifest.json/Command portal configuration. No test previously depended on the old "empty string wins" behavior for the URL.

  • Only InitClient is changed — request validation (ValidateServerConfigurationParams / ValidateAuthTypeAccessKey) is unchanged and out of scope, so AccessId/AccessKey must still be present in the server configuration parameters for the config to build successfully; the env vars override the value used once configuration is valid, matching the literal scope of the issue.

Docs

  • docsource/akeyless.md: expanded the ## Configuration section to document all four env vars in a precedence table (was AKEYLESS_API_URL-only).
  • Regenerated README.md and docs/akeyless.md via doctooldotnet generate-docs.
  • CHANGELOG.md: added a v1.1.0 entry describing the new overrides.

Test plan

  • dotnet build akeyless-pam/akeyless-pam.csproj -c Release — succeeds, no warnings
  • dotnet test tests/AkeylessPam.Unit.Tests/ — 40/40 passing (12 new tests covering override/fallback/empty-string behavior for all four env vars)
  • dotnet test --collect:"XPlat Code Coverage" — ~86.5% line coverage
  • Added [assembly: CollectionBehavior(DisableTestParallelization = true)] since the new tests mutate process-wide env vars and would otherwise race with other test classes running in parallel
  • Integration tests not run (require live Akeyless credentials), per task instructions

Closes #10

spbsoluble and others added 6 commits July 16, 2026 15:09
…sKey

Extends the existing AKEYLESS_API_URL override pattern in InitClient so
AKEYLESS_AUTH_TYPE, AKEYLESS_ACCESS_ID, and AKEYLESS_ACCESS_KEY can also
override their corresponding configured connection parameters at runtime,
letting deployments control Akeyless connection details at the
infrastructure level instead of only via manifest.json/Command portal.

Closes #10
@spbsoluble
spbsoluble changed the base branch from main to release-1.1 July 21, 2026 14:46
spbsoluble and others added 11 commits July 22, 2026 10:29
…r-credential-overrides

# Conflicts:
#	README.md
#	docs/akeyless.md
GetPassword_BadCredentials_ThrowsInvalidClientConfigurationException asserted
that bad server-config credentials fail auth, but AKEYLESS_ACCESS_ID/
AKEYLESS_ACCESS_KEY (required for every other integration test to run) now
override those credentials via this PR's own env var override feature,
making auth succeed instead of failing. Clear both env vars for the
duration of this test only, and disable assembly-level parallelization so
that doesn't race with AkeylessApiClientTests reading the same vars.

Also documents the previously-missing Debug_K8sOrchestratorSecret_PrintsRawValue
test in the integration tests README.
…ides' into feature/env-var-credential-overrides
The CI's auto-generate-docs workflow ran a stale/buggy doctool version
against this branch (docsource/akeyless.md unchanged), which re-stripped
Extension Mechanics from README.md and duplicated content into
docs/akeyless.md — the exact bug already fixed on this branch via the
"regenerate with fixed doctool" commit. Restoring that fixed content.
ResolveEnvOverride only checked IsNullOrEmpty, so a whitespace-only env
var (e.g. AKEYLESS_ACCESS_ID=" ") was treated as an active override
rather than falling back, contradicting its own doc comment and the
codebase's existing whitespace-only-is-empty convention elsewhere. Switch
to IsNullOrWhiteSpace, and add whitespace-only unit tests for all four
env vars alongside the existing empty-string cases.

Also log which specific env var is overriding (never the value) when an
override is active, so an incident investigation can tell whether the
effective connection parameter used at runtime actually matches
Command's recorded configuration — previously nothing distinguished a
configured value from an env-var-overridden one in production logs.
…ides' into feature/env-var-credential-overrides

# Conflicts:
#	README.md
Same recurring issue: the CI auto-generate-docs workflow (Keyfactor
Bootstrap Workflow, pinned keyfactor/actions@v5) ran a stale/buggy
doctool version against this branch again (docsource/akeyless.md
unchanged), stripping the Extension Mechanics section from README.md.
Restoring the correct content. This has now happened on every push to
this branch — see PR discussion for a permanent fix recommendation.
@spbsoluble spbsoluble changed the title Support env var overrides for AuthType, AccessId, and AccessKey feat(auth): Support using env variables to supplement and/or override config values. Jul 22, 2026
…g effective URL

- InitClient now re-validates the resolved AuthType against the supported-auth-type
  allowlist before use. Previously an AKEYLESS_AUTH_TYPE override bypassed the
  validation BuildAkeylessConfiguration already performed on the configured value,
  so a typo'd or unsupported override silently skipped authentication (no throw,
  AuthToken left empty) and let execution continue into an unauthenticated secret
  fetch. Unrecognized overrides now fail fast with InvalidClientConfigurationException.
- ResolveEnvOverride now trims the resolved value, since env vars sourced from
  file-mounted secrets/configmaps commonly carry an incidental trailing newline,
  which previously failed the exact-match auth-type switch and could send
  whitespace-polluted credentials to Akeyless.
- The "Connecting to Akeyless at" debug log now reports the effective (post-override)
  URL used to construct the API client instead of the stale pre-override configured
  value, so log-based incident review can actually confirm the true destination host.
…th failure

- ResolveEnvOverride now rejects a value that still contains an embedded line
  break after trimming (InvalidClientConfigurationException). Trim() only
  strips leading/trailing whitespace, so a value sourced from a multi-line
  mounted secret file could otherwise flow unchanged into the structured log
  messages that echo AccessId/AuthType, forging extra log lines.
- InitClient's ApiException catch block now logs the AccessId that the failed
  authentication attempt used. It previously omitted AccessId (unlike the
  success and empty-token paths), so a failed auth attempt left no record of
  which identity was used -- a real gap once AKEYLESS_ACCESS_ID can make the
  runtime identity diverge from Command's recorded configuration.
…\n/\r

ResolveEnvOverride's embedded-line-break guard only checked for '\n'/'\r',
missing other control characters (ESC/ANSI escape sequences, NUL, form feed,
vertical tab) and the Unicode line/paragraph separators (U+2028/U+2029) that
survive Trim() and .NET's char.IsWhiteSpace() the same way an embedded '\n'
would, and could equally forge or tamper with rendered log output on sinks
that treat them as line breaks or escape codes. Switched the check from a
'\n'/'\r' blocklist to rejecting on Unicode category (char.IsControl, plus
LineSeparator/ParagraphSeparator), which covers all of the above in one check.
…ApiException

- ResolveEnvOverride's character filter covered Control and Line/ParagraphSeparator
  categories but missed Format-category characters (Cf) such as U+202E RIGHT-TO-LEFT
  OVERRIDE and zero-width characters, which could make a logged AccessId/AuthType
  render differently than its actual content. Broadened the rejection to all
  "other" Unicode categories (Control, Format, Surrogate, PrivateUse,
  OtherNotAssigned) plus LineSeparator/ParagraphSeparator.
- InitClient's ApiException catch no longer passes the exception object to
  Logger.LogError. The adjacent comment already said ex.Message is excluded
  because Akeyless's error response may echo back credentials, but passing `ex`
  as the exception parameter still exposes it, since most ILogger providers
  render an attached exception's Message/ToString() independent of the message
  template -- the comment's intent wasn't actually enforced by the code.
…and configured values

Rounds 3-5 kept finding one more Unicode category (ANSI escapes, U+2028/U+2029,
bidi-override/zero-width Format characters, now variation selectors) that
survived the growing character blocklist -- an inherently unbounded
whack-a-mole problem. Replaced ResolveEnvOverride's blocklist with a single
printable-ASCII allowlist (EnsurePrintableAscii), which rejects everything
outside 0x20-0x7E in one check instead of naming specific bad characters.

This also closes a second, more consequential gap: the blocklist only ever
guarded the env-var override path. A malicious/malformed AccessId or Url
supplied through Command's normal server configuration (no override involved)
reached the exact same log statements completely unvalidated. The allowlist
is now applied to the final resolved value in InitClient regardless of which
source it came from, so both paths get the same guarantee.
…AkeylessConfiguration logs them

EnsurePrintableAscii was only ever called from InitClient, which runs on a
later call path (GetAkeylessSecretAsync -> InitClient) than
BuildAkeylessConfiguration's own "Using Akeyless URL ..." and "Access key auth
configured with AccessId ..." debug logs. A malicious/malformed value supplied
via Command's server configuration (no env var override needed) was echoed by
those two log statements before InitClient's validation ever ran -- the
allowlist protected the override path and the InitClient-side re-resolution
of the configured value, but not the earlier BuildAkeylessConfiguration log
statements that fire first. Added the same EnsurePrintableAscii calls to
BuildAkeylessConfiguration, right after each value is read and before it's
logged.
EnsurePrintableAscii was the one validation-throw site in the file with no
corresponding Logger.LogError call -- every other validation failure
(ValidateRequiredParameter, ValidateAuthTypeAccessKey, the unsupported-auth-type
checks, model validation, the ApiException catch) logs before throwing. Made
the method an instance method so it can log the parameter name (never the
value) before throwing, giving an audit trail for rejected log-injection/
spoofing attempts instead of silent fail-closed with no record.
Collapses the repeated resolve-override-then-EnsurePrintableAscii pattern
for Url/AuthType/AccessId/AccessKey into a single helper, so the sequencing
only needs to be verified in one place instead of four.
The SupportedAuthMethods guard clause immediately above already proves
authType is "access_key" by this point, making the switch a no-op wrapper.
BuildAkeylessConfiguration validated configured connection values with
EnsurePrintableAscii but never trimmed them first, unlike env-var overrides
(which ResolveEnvOverride already trims). A previously-working configured
value with an incidental trailing newline/tab — a common artifact of
hand-edited manifest.json or portal paste — silently worked before this
PR's hardening (Uri/HttpClient normalize it) but now hard-fails on every
GetPassword call. Trim configured values the same way overrides are, so
the hardening doesn't regress previously-valid configuration.

Also dedupes the EnvVarScope test helper (previously copy-pasted across
both test projects) into a single linked file, and reuses
AkeylessConstants.DefaultAkeylessApiUrl instead of a duplicated literal
default in InitClient.
…override

BuildAkeylessConfiguration's new .Trim() calls threw an unhandled
NullReferenceException when the connectionConfiguration dictionary had
an explicit null value (key present, value null) for AuthType or Url,
bypassing the previously-handled InvalidClientConfigurationException path
and its logging. Both now default/fall back the same way an absent key
already did, before trimming.

Also bumps ResolveEnvOverride's "Environment variable override active"
log from Information to Warning: a host that already has
AKEYLESS_AUTH_TYPE/AKEYLESS_ACCESS_ID/AKEYLESS_ACCESS_KEY set for an
unrelated reason (e.g. a co-located Akeyless CLI using the same variable
names) will now silently authenticate with a different identity than
Command's recorded configuration after upgrading, with zero config
change on Command's side. This is the PR's intended behavior extended
from the pre-existing AKEYLESS_API_URL override, not a bug, but the
upgrade-collision risk deserves louder visibility than Information level,
plus a documented callout in docsource/akeyless.md.
…basePath

BuildAkeylessConfiguration checked string.IsNullOrEmpty before substituting
the default Url, so a whitespace-only configured value (e.g. a single
stray space) skipped the fallback, then got trimmed into an empty string
that passed EnsurePrintableAscii vacuously (LINQ .All() over an empty
sequence). The empty base path then reached the Akeyless SDK client with
no further validation. Switched to IsNullOrWhiteSpace, matching the
existing env-var-override path which already handled this correctly.

Also collapses four sets of near-duplicate [Fact] tests (Url/AccessId/
AccessKey/AuthType env-var unset/empty/whitespace-only, differing only in
the literal) into four [Theory]/[InlineData] tests, consistent with the
file's existing Theory usage.
BuildAkeylessConfiguration already guarantees configurationInfo.Url is
never null/whitespace, so InitClient's second "?? DefaultAkeylessApiUrl"
fallback was unreachable.
…r Theory

Was a standalone Fact with a byte-for-byte identical body to the adjacent
control-character Theory, differing only in the injected string.
Collapses the detailed per-fix bullet points into higher-level summary
entries for the release notes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support environment variable overrides for AccessId, AccessKey, and AuthType

1 participant