Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed5be2c
Create _auth_method_k8s.py, add k8s auth, add role params for k8s auth
chris93111 Feb 13, 2022
795fa08
Update plugins/lookup/hashi_vault.py
chris93111 Feb 16, 2022
a035ef2
change to auth.kubernetes + switch depracated
chris93111 Feb 16, 2022
16fb27f
Bump version_added
briantist Apr 1, 2022
420cf1e
remove old conflict artifact
briantist Mar 5, 2023
88f3094
[chore] use relative imports
briantist Mar 5, 2023
8d76146
update auth method name
briantist Mar 5, 2023
bcbeb9d
nit: change to preferred style
briantist Mar 5, 2023
b4653aa
add required deprecate_callback to init
briantist Mar 5, 2023
dfb9ba5
draft setup k8s auth int/unit tests
pfeifferj Feb 13, 2022
c80f36d
rebase
pfeifferj Mar 10, 2023
dad910a
Merge branch 'ansible-collections:main' into patch-1
pfeifferj Mar 13, 2023
40b9981
Merge branch 'ansible-collections:main' into patch-1
pfeifferj May 30, 2025
73132b8
Fix Kubernetes auth implementation issues
pfeifferj May 30, 2025
50cb515
Add secret_field option for Ansible 2.17+ compatibility
pfeifferj May 30, 2025
8f017cf
Add missing canary variable for K8s integration tests
pfeifferj May 30, 2025
794e8ad
Merge branch 'main' of https://github.com/pfeifferj/community.hashi_v…
pfeifferj Jul 17, 2025
24e4947
Merge branch 'main' of https://github.com/pfeifferj/community.hashi_v…
pfeifferj Sep 13, 2025
b838dcb
Merge branch 'main' of https://github.com/pfeifferj/community.hashi_v…
pfeifferj Oct 24, 2025
24c4324
Fix Kubernetes auth integration test for Vault 1.21.0
pfeifferj Oct 26, 2025
af038c8
Fix Kubernetes auth integration test for Vault 1.21.0
pfeifferj Oct 26, 2025
df115f2
Merge branch 'patch-1' of https://github.com/pfeifferj/community.hash…
pfeifferj Oct 27, 2025
88b4cc7
chore: correct version_added
pfeifferj Nov 5, 2025
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
24 changes: 24 additions & 0 deletions plugins/doc_fragments/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ModuleDocFragment(object):
- C(aws_iam_login) was renamed C(aws_iam) in collection version C(2.1.0) and was removed in C(3.0.0).
- C(azure) auth method was added in collection version C(3.2.0).
- C(gcp) auth method was added in collection version C(7.1.0).
- C(kubernetes) auth method was added in collection version C(8.0.0).
choices:
- token
- userpass
Expand All @@ -30,6 +31,7 @@ class ModuleDocFragment(object):
- jwt
- cert
- gcp
- kubernetes
- none
default: token
type: str
Expand Down Expand Up @@ -77,6 +79,15 @@ class ModuleDocFragment(object):
jwt:
description: The JSON Web Token (JWT) to use for JWT authentication to Vault.
type: str
kubernetes_token:
description: The Kubernetes Token (JWT) to use for Kubernetes authentication to Vault.
type: str
version_added: 6.3.0
kubernetes_token_path:
description: If no kubernetes_token is specified, will try to read the token from this path.
default: '/var/run/secrets/kubernetes.io/serviceaccount/token'
type: str
version_added: 6.3.0
aws_profile:
description: The AWS profile
type: str
Expand Down Expand Up @@ -313,4 +324,17 @@ class ModuleDocFragment(object):
ini:
- section: hashi_vault_collection
key: cert_auth_private_key
kubernetes_token:
env:
- name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN
vars:
- name: ansible_hashi_vault_kubernetes_token
kubernetes_token_path:
env:
- name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN_PATH
ini:
- section: hashi_vault_collection
key: kubernetes_token_path
vars:
- name: ansible_hashi_vault_kubernetes_token_path
'''
24 changes: 19 additions & 5 deletions plugins/lookup/hashi_vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
secret:
description: Vault path to the secret being requested in the format C(path[:field]).
required: True
secret_field:
description:
- Field within the secret being requested.
- This is set automatically when using the C(secret:field) syntax.
- Use of this option is discouraged in favor of the C(:field) syntax.
required: False
version_added: 6.3.0
return_format:
description:
- Controls how multiple key/value pairs in a path are treated on return.
Expand Down Expand Up @@ -250,9 +257,18 @@

display = Display()

HAS_HVAC = False
try:
import hvac
HAS_HVAC = True
except ImportError:
HAS_HVAC = False


class LookupModule(HashiVaultLookupBase):
def run(self, terms, variables=None, **kwargs):
if not HAS_HVAC:
raise AnsibleError("Please pip install hvac to use the hashi_vault lookup module.")

ret = []

Expand Down Expand Up @@ -302,20 +318,18 @@ def field_ops(self):
field = s_f[1]
else:
field = None

self._secret_field = field
self.set_option('secret_field', field)

def get(self):
'''gets a secret. should always return a list'''

field = self._secret_field
secret = self.get_option('secret')
field = self.get_option('secret_field')
return_as = self.get_option('return_format')
hvac_exceptions = self.helper.get_hvac().exceptions

try:
data = self.client.read(secret)
except hvac_exceptions.Forbidden:
except hvac.exceptions.Forbidden:
raise AnsibleError("Forbidden: Permission Denied to secret '%s'." % secret)

if data is None:
Expand Down
62 changes: 62 additions & 0 deletions plugins/module_utils/_auth_method_k8s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 FERREIRA Christophe (@chris93111)
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)

'''Python versions supported: >=3.6'''

# FOR INTERNAL COLLECTION USE ONLY
# The interfaces in this file are meant for use within the community.hashi_vault collection
# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
# Please open an issue if you have questions about this.

from __future__ import absolute_import, division, print_function
__metaclass__ = type

from ..module_utils._hashi_vault_common import HashiVaultAuthMethodBase, HashiVaultValueError
import os


class HashiVaultAuthMethodKubernetes(HashiVaultAuthMethodBase):
'''HashiVault option group class for auth: kubernetes'''

NAME = 'kubernetes'
OPTIONS = ['kubernetes_token', 'kubernetes_token_path', 'role_id', 'mount_point']

def __init__(self, option_adapter, warning_callback, deprecate_callback):
super(HashiVaultAuthMethodKubernetes, self).__init__(option_adapter, warning_callback, deprecate_callback)

def validate(self):
self.validate_by_required_fields('role_id')

if self._options.get_option('kubernetes_token') is None and self._options.get_option('kubernetes_token_path') is not None:
token_filename = self._options.get_option('kubernetes_token_path')
if os.path.exists(token_filename):
if not os.path.isfile(token_filename):
raise HashiVaultValueError("The Kubernetes token file '%s' was found but is not a file." % token_filename)
with open(token_filename) as token_file:
self._options.set_option('kubernetes_token', token_file.read().strip())

if self._options.get_option('kubernetes_token') is None:
raise HashiVaultValueError(
self._options.get_option_default('kubernetes_token_path') + " No Kubernetes Token specified or discovered."
)

def authenticate(self, client, use_token=True):
origin_params = self._options.get_filled_options(*self.OPTIONS)
params = {
"role": origin_params.get('role_id'),
"jwt": origin_params.get('kubernetes_token'),
"use_token": use_token,
}

if 'mount_point' in origin_params:
params['mount_point'] = origin_params['mount_point']

try:
response = client.auth.kubernetes.login(**params)
except (NotImplementedError, AttributeError):
self.warn("Kubernetes authentication requires HVAC version 1.0.0 or higher. Deprecated method 'auth_kubernetes' will be used.")
response = client.auth_kubernetes(**params)

return response
5 changes: 5 additions & 0 deletions plugins/module_utils/_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_cert import HashiVaultAuthMethodCert
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_gcp import HashiVaultAuthMethodGcp
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_jwt import HashiVaultAuthMethodJwt
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_k8s import HashiVaultAuthMethodKubernetes
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_ldap import HashiVaultAuthMethodLdap
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_none import HashiVaultAuthMethodNone
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_token import HashiVaultAuthMethodToken
Expand All @@ -39,6 +40,7 @@ class HashiVaultAuthenticator():
'jwt',
'cert',
'gcp',
'kubernetes',
'none',
]),
mount_point=dict(type='str'),
Expand All @@ -51,6 +53,8 @@ class HashiVaultAuthenticator():
role_id=dict(type='str'),
secret_id=dict(type='str', no_log=True),
jwt=dict(type='str', no_log=True),
kubernetes_token=dict(type='str', no_log=True),
kubernetes_token_path=dict(type='str', default='/var/run/secrets/kubernetes.io/serviceaccount/token', no_log=False),
aws_profile=dict(type='str', aliases=['boto_profile']),
aws_access_key=dict(type='str', aliases=['aws_access_key_id'], no_log=False),
aws_secret_key=dict(type='str', aliases=['aws_secret_access_key'], no_log=True),
Expand All @@ -76,6 +80,7 @@ def __init__(self, option_adapter, warning_callback, deprecate_callback):
'cert': HashiVaultAuthMethodCert(option_adapter, warning_callback, deprecate_callback),
'gcp': HashiVaultAuthMethodGcp(option_adapter, warning_callback, deprecate_callback),
'jwt': HashiVaultAuthMethodJwt(option_adapter, warning_callback, deprecate_callback),
'kubernetes': HashiVaultAuthMethodKubernetes(option_adapter, warning_callback, deprecate_callback),
'ldap': HashiVaultAuthMethodLdap(option_adapter, warning_callback, deprecate_callback),
'none': HashiVaultAuthMethodNone(option_adapter, warning_callback, deprecate_callback),
'token': HashiVaultAuthMethodToken(option_adapter, warning_callback, deprecate_callback),
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/targets/auth_kubernetes/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vault/auth/kubernetes
context/target
needs/target/setup_vault_configure
needs/target/setup_vault_test_plugins
10 changes: 10 additions & 0 deletions tests/integration/targets/auth_kubernetes/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
ansible_hashi_vault_url: "{{ vault_test_server_http }}"
ansible_hashi_vault_auth_method: kubernetes

auth_paths:
- kubernetes

vault_kubernetes_canary:
path: cubbyhole/configure_kubernetes
value: complete # value does not matter
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdCJdLCJleHAiOjMyNDk5MDUxMzU5LCJpYXQiOjE2MDQ4MzUxMDAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6InZhdWx0LWF1dGgiLCJ1aWQiOiJkNzdjMWZlYi1jOTE5LTQxMDYtYWE4YS01MGY2N2MzYTE2MWUifX0sIm5iZiI6MTYwNDgzNTEwMCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6dmF1bHQtYXV0aCJ9.qfOfj4FeVBlBoWbaME6UnDCqKXE2FXKyil3ia1lDMaqcc87fYYE4yVByhqCSJy2pToO2MW3mYBKwfgfKxKaZj64hMh3yu1RNRASvWAVi4JM4nrBtdXei24e3WgRIjYOJnaHuypYtiBfHC-ArNJN_AhoGI00SP7Mx_ohcxtHbBcIE15Dlbh9eHOVtQSSMazaOAyQQLUybNmw9PDD0wwLnKgiky35dG03tZWd2pUttDWKlTzuIDPHyUyY9CYe-C3rVidJ6Vpw9atIO6NcffD5dvXujY1boUVO52xhcNCyL7UX1YqjcaqgUvEtUN1ridAb_rispVNzbe2w1cp41CqGHcQ
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjM0IiwidXNlcl9jbGFpbSI6InVzZXJfY2xhaW0iLCJuYmYiOjE2MDQ4MzUxMDAsImV4cCI6MzI0OTkwNTEzNTl9.etc2WSH7kR3fHFlVt4wlBYFKNn7Z4DQcRVXUK4gGF-Q
4 changes: 4 additions & 0 deletions tests/integration/targets/auth_kubernetes/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
dependencies:
- setup_vault_test_plugins
- setup_vault_configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
- name: "Setup block"
vars:
is_default_path: "{{ this_path == default_path }}"
jwt_public_key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvUJ8sf9bUQtAyESZ73mw
LCHjA8cj2ly3GTy5fxsyniUvkl6x+fVOXYqxgDYc9m14iaUXjE2eUIdQOnmRNIAz
xIhxKvXMhJVRSnjCcdQZ9FUPyvQ1bcNo6rISOEEroUdQ6ymX6/DKhW/tXPrYPFyK
502UF+YhuP+t/OdKT4qbIxHvkDyGorJDlfKDx9wIr88xY4+KFLKSkaCsO6fZvmhw
L2pVl5UfJ/mYXZdeJ8b8pJLSnWmKzXwamGNnvJK3Z4DwicL6Z9PS8ryZWMAUswFR
menCQdtebBuaKi401N9SlPl/qb3nFW3YVO1NrqGvF9nprdtBNN3++0a1fuMUmPhb
1QIDAQAB
-----END PUBLIC KEY-----
block:
- name: "Enable the Kubernetes auth method"
vault_ci_enable_auth:
method_type: kubernetes
path: "{{ omit if is_default_path else this_path }}"
config:
default_lease_ttl: 60m

- name: "Configure the Kubernetes auth method"
vault_ci_write:
path: "auth/{{ this_path }}/config"
data:
kubernetes_host: "http://mmock:8900"
pem_keys: |
{{ jwt_public_key }}
disable_local_ca_jwt: true
disable_iss_validation: true

- name: "Create a named role"
vault_ci_write:
path: "auth/{{ this_path }}/role/test-role"
data:
bound_service_account_names: "*"
bound_service_account_namespaces: "*"
policies: "{{ 'test-policy' if is_default_path else 'alt-policy' }}"
ttl: 60m
audience: "test"
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
- name: "Test block"
vars:
kubernetes_token: '{{ lookup("file", "service_account_token.jwt") }}'
kubernetes_token_invalid: '{{ lookup("file", "service_account_token_invalid.jwt") }}'
is_default_path: "{{ this_path == default_path }}"
kwargs_common:
role_id: test-role
kwargs_mount: "{{ {} if is_default_path else {'mount_point': this_path} }}"
kwargs_kubernetes_token:
kubernetes_token: "{{ kubernetes_token }}"
kwargs: "{{ kwargs_common | combine(kwargs_mount) | combine(kwargs_kubernetes_token) }}"
block:
# the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac
# we set the policy of the default mount to deny access to this secret and so we expect failure when the mount
# is default, and success when the mount is alternate
- name: Check auth mount differing result
set_fact:
response: "{{ lookup('vault_test_auth', **kwargs) }}"

- assert:
fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}"
that:
- ('test-policy' in response.login.auth.policies) | bool == is_default_path
- ('test-policy' not in response.login.auth.policies) | bool != is_default_path
- ('alt-policy' in response.login.auth.policies) | bool != is_default_path
- ('alt-policy' not in response.login.auth.policies) | bool == is_default_path

- name: Failure expected when erroneous credentials are used
vars:
kwargs_invalid: "{{ kwargs_common | combine(kwargs_mount) | combine({'kubernetes_token': kubernetes_token_invalid}) }}"
set_fact:
response: "{{ lookup('vault_test_auth', want_exception=true, **kwargs_invalid) }}"

- assert:
fail_msg: "An invalid JWT somehow did not cause a failure."
that:
- response is failed
- response.msg is search('permission denied|no known key successfully validated the token signature')
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
- name: "Test block"
vars:
is_default_path: "{{ this_path == default_path }}"
kubernetes_token: '{{ lookup("file", "service_account_token.jwt") }}'
kubernetes_token_invalid: '{{ lookup("file", "service_account_token_invalid.jwt") }}'
module_defaults:
vault_test_auth:
url: '{{ ansible_hashi_vault_url }}'
auth_method: '{{ ansible_hashi_vault_auth_method }}'
role_id: test-role
mount_point: '{{ omit if is_default_path else this_path }}'
kubernetes_token: '{{ kubernetes_token }}'
block:
# the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac
# we set the policy of the default mount to deny access to this secret and so we expect failure when the mount
# is default, and success when the mount is alternate
- name: Check auth mount differing result
register: response
vault_test_auth:

- assert:
fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}"
that:
- ('test-policy' in response.login.auth.policies) | bool == is_default_path
- ('test-policy' not in response.login.auth.policies) | bool != is_default_path
- ('alt-policy' in response.login.auth.policies) | bool != is_default_path
- ('alt-policy' not in response.login.auth.policies) | bool == is_default_path

- name: Failure expected when erroneous credentials are used
register: response
vault_test_auth:
kubernetes_token: '{{ kubernetes_token_invalid }}'
want_exception: yes

- assert:
fail_msg: "An invalid Kubernetes Service Account Token somehow did not cause a failure."
that:
- response.inner is failed
- response.msg is search('permission denied|no known key successfully validated the token signature')
Loading
Loading