Skip to content

Add Akeyless credential plugins, with OIDC workload identity support - #194

Open
kgal-akl wants to merge 2 commits into
ansible:develfrom
akeyless-community:feat/akeyless-oidc
Open

Add Akeyless credential plugins, with OIDC workload identity support#194
kgal-akl wants to merge 2 commits into
ansible:develfrom
akeyless-community:feat/akeyless-oidc

Conversation

@kgal-akl

@kgal-akl kgal-akl commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Adds four Akeyless credential plugins:

Entry point Credential type Authentication
akeyless Akeyless API key
akeyless_ssh Akeyless SSH API key
akeyless-oidc Akeyless (OIDC) Workload identity token
akeyless-ssh-oidc Akeyless SSH (OIDC) Workload identity token

The API key plugins cover static secret lookup (text, JSON and key-value formats) and signed SSH certificate issuance.

The OIDC plugins follow the hashivault-kv-oidc / hashivault-ssh-oidc pattern: they reuse the same backends and differ only in their inputs, exposing the internal workload_identity_token field, dropping access_key, and naming the endpoint field url since the controller reads the audience of the minted token from the input of that exact name. access_id on those types identifies the Akeyless JWT auth method that trusts the platform as an OIDC issuer.

Authentication selects the auth material at call time, preferring a workload identity token (exchanged via the SDK's jwt access type) and falling back to the API key, so the API key plugins are unchanged.

This supersedes #147, which could not be reopened.

Companion PR

The OIDC types also need ansible/awx#16562. _is_oidc_namespace_disabled() filters against the hardcoded OIDC_CREDENTIAL_TYPE_NAMESPACES list, so without that change these types stay visible when FEATURE_OIDC_WORKLOAD_IDENTITY_ENABLED is off and fail at job launch instead of being hidden.

Test plan

  • Unit tests for both auth paths, secret formats, SSH issuance and error handling (24 tests, 90% coverage of the module)
  • A test pinning the two contract details the controller depends on: the internal workload_identity_token field and the input named url
  • pre-commit clean: flake8/wemake, pylint, mypy on 3.11/3.12/3.13
  • End to end on AAP 2.7.1 (controller 4.8.3, awx-plugins-core 0.1.dev3822+g5d80fc116):
    • POST /api/v2/credentials/<id>/test/ returns 202 with the expected aud and aap_controller_* claims
    • A job resolves a Machine credential's password from Akeyless via the workload identity token
    • Negative control: an invalid secret path fails the job with a traceback terminating in akeyless_backend, confirming the lookup is in the execution path

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added Akeyless credential integrations for retrieving secrets and generating SSH certificates.
    • Added support for access-key and OIDC/workload-identity authentication.
    • Added support for text, JSON, and key-value secret formats, including optional CA verification and SSH certificate TTLs.
    • Added plugin discovery and installation options for the new integrations.
  • Documentation

    • Updated documentation tooling to recognize Akeyless terminology and references.
  • Tests

    • Added comprehensive coverage for secret retrieval, authentication, SSH certificates, OIDC, and error handling.

kgal-akl and others added 2 commits July 27, 2026 12:21
Add `akeyless` and `akeyless_ssh` credential plugins backed by the Akeyless
SDK, covering static secret lookup (text, JSON and key-value formats) and
signed SSH certificate issuance.

Both authenticate with an Akeyless API key (access ID and access key) and
support supplying a CA certificate for gateways that terminate TLS with a
private CA.

Type stubs for the `akeyless` SDK are vendored under `_type_stubs/` because
the SDK does not ship any.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add `akeyless-oidc` and `akeyless-ssh-oidc`, which authenticate to Akeyless
with the workload identity token issued by the controller instead of a stored
API key, following the `hashivault-kv-oidc` / `hashivault-ssh-oidc` pattern.

Both reuse the existing backends and differ only in their inputs: they expose
the internal `workload_identity_token` field, drop `access_key`, and name the
endpoint field `url`, since the controller reads the audience of the token it
mints from the input of that exact name.

Authentication now selects the auth material at call time, preferring a
workload identity token (exchanged via the SDK's `jwt` access type) and
falling back to the API key, so the API key plugins are unaffected.

`access_id` on the OIDC types identifies the Akeyless JWT auth method that
trusts the platform as an OIDC issuer, rather than an API key access ID.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Akeyless credential plugins for static secrets, SSH certificates, and OIDC authentication, including typed SDK stubs, package entry points, optional dependencies, comprehensive tests, and supporting lint, typing, documentation, and environment configuration.

Changes

Akeyless credential integration

Layer / File(s) Summary
Akeyless SDK type contracts
_type_stubs/akeyless/*
Adds typed declarations for Akeyless clients, authentication, secret and certificate requests, responses, and API exceptions.
Backend implementation and registration
src/awx_plugins/credentials/akeyless.py, pyproject.toml
Implements secret retrieval, SSH certificate issuance, access-key and workload-identity authentication, error handling, plugin variants, entry points, and optional dependencies.
Backend behavior validation
tests/akeyless_test.py, tests/importable_test.py, tox.ini
Tests secret formats, authentication, OIDC precedence, SSH certificates, TTL handling, failure cases, plugin contracts, discovery, and test extras.
Repository tooling and documentation
.flake8, .gitignore, .mypy.ini, .pre-commit-config.yaml, .pylintrc.toml, .ruff.toml, docs/*
Updates linting, typing, dependency, source-discovery, spelling, and Sphinx configuration for Akeyless code and tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CredentialPlugin
  participant AkeylessBackend
  participant AkeylessAPI
  participant AkeylessGateway
  CredentialPlugin->>AkeylessBackend: provide credential inputs
  AkeylessBackend->>AkeylessAPI: authenticate with access key or JWT
  AkeylessAPI->>AkeylessGateway: send authentication request
  AkeylessGateway-->>AkeylessAPI: return authentication token
  AkeylessBackend->>AkeylessAPI: retrieve secret or request certificate
  AkeylessAPI->>AkeylessGateway: execute credential operation
  AkeylessGateway-->>AkeylessAPI: return secret or signed certificate
  AkeylessAPI-->>AkeylessBackend: return operation result
  AkeylessBackend-->>CredentialPlugin: return credential data
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: new Akeyless credential plugins with OIDC workload identity support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/awx_plugins/credentials/akeyless.py (1)

476-503: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collapse the duplicated ApiExceptionRuntimeError wrapping.

The same wrap block appears four times across both backends. A small context manager would remove the duplication and keep the message format in one place.

♻️ Suggested helper
import contextlib


`@contextlib.contextmanager`
def _wrapped_api_errors() -> _t.Iterator[None]:
    try:
        yield
    except _ApiException as api_exc:
        raise RuntimeError(
            f'Akeyless API error: {api_exc.reason}'
            f' (Status: {api_exc.status})',
        ) from api_exc
    except ValueError as val_err:
        raise RuntimeError(str(val_err)) from val_err
     with _plugin.CertFiles(kwargs.get('ca_cert')) as ca_cert_path:
         api_instance = _setup_client(
             _resolve_gateway_url(kwargs),
             ca_cert_path,
         )
-        try:
+        with _wrapped_api_errors():
             token = _authenticate(api_instance, kwargs)
-        except _ApiException as api_exc:
-            ...

Note this also normalizes the current asymmetry: akeyless_backend does not wrap ValueError from the fetch path while akeyless_ssh_backend does.

Also applies to: 536-566

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/awx_plugins/credentials/akeyless.py` around lines 476 - 503, Collapse the
duplicated exception wrapping in a shared _wrapped_api_errors context manager,
defining the Akeyless API and ValueError-to-RuntimeError conversions in one
place. Use this context manager around both authentication and secret-fetch
operations in akeyless_backend and the corresponding operations in
akeyless_ssh_backend, preserving the existing error message format and
normalizing ValueError handling for fetches.
tests/akeyless_test.py (1)

27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tighten the object annotations so attribute access is type-checked.

_MockApiFactory returns object yet callers do mock_api.get_secret_value.side_effect = ..., and plugin: object is followed by plugin.inputs[...]. Annotating as unittest.mock.Mock and _plugin.CredentialPlugin respectively keeps these assertions checkable without widening the module-level mypy suppressions at line 2.

Also applies to: 463-463

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/akeyless_test.py` at line 27, Update the _MockApiFactory return
annotation from object to unittest.mock.Mock, and change the plugin annotation
at the referenced later usage from object to _plugin.CredentialPlugin. Preserve
the existing behavior while enabling type checking for mock_api attribute access
and plugin.inputs access without broadening mypy suppressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/awx_plugins/credentials/akeyless.py`:
- Around line 476-503: Collapse the duplicated exception wrapping in a shared
_wrapped_api_errors context manager, defining the Akeyless API and
ValueError-to-RuntimeError conversions in one place. Use this context manager
around both authentication and secret-fetch operations in akeyless_backend and
the corresponding operations in akeyless_ssh_backend, preserving the existing
error message format and normalizing ValueError handling for fetches.

In `@tests/akeyless_test.py`:
- Line 27: Update the _MockApiFactory return annotation from object to
unittest.mock.Mock, and change the plugin annotation at the referenced later
usage from object to _plugin.CredentialPlugin. Preserve the existing behavior
while enabling type checking for mock_api attribute access and plugin.inputs
access without broadening mypy suppressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d1a45178-1944-4b53-9794-68ab47a0683e

📥 Commits

Reviewing files that changed from the base of the PR and between 7c6a7cf and ae2671c.

📒 Files selected for processing (17)
  • .flake8
  • .gitignore
  • .mypy.ini
  • .pre-commit-config.yaml
  • .pylintrc.toml
  • .ruff.toml
  • _type_stubs/akeyless/__init__.pyi
  • _type_stubs/akeyless/models/__init__.pyi
  • _type_stubs/akeyless/models/get_ssh_certificate.pyi
  • _type_stubs/akeyless/rest.pyi
  • docs/conf.py
  • docs/spelling_wordlist.txt
  • pyproject.toml
  • src/awx_plugins/credentials/akeyless.py
  • tests/akeyless_test.py
  • tests/importable_test.py
  • tox.ini

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.

1 participant