-
Notifications
You must be signed in to change notification settings - Fork 396
LDAP secret engine support (#1032) #1033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
5d526ba
Initial commit for LDAP secrets engine
JordanStopford 1b5aaad
Fix docs and linting issues
JordanStopford 682d5eb
Fix linting error
JordanStopford df51d22
Run tests with docker container so we don't need to install vault
JordanStopford 2dc7358
More tests
JordanStopford d1f5fd2
Merge branch 'main' into ldap-secrets
JordanStopford 922bc96
Fix indentation
JordanStopford 72258d3
Fix client not being available
JordanStopford 1161ea4
Various test fixes
JordanStopford 3adf09f
Merge branch 'hvac:main' into ldap-secrets
JordanStopford 0feed5c
Reverting the changes prior to implementing unit tests
JordanStopford deb7d5b
Reverting the changes prior to implementing unit tests
JordanStopford c6221ae
Reverting the changes prior to implementing unit tests
JordanStopford a6a2602
Unit tests for LDAP secrets
JordanStopford f8e56cd
Reverting the changes prior to implementing unit tests
JordanStopford cda852f
Linting
JordanStopford 0dc0cc4
Fix newline?
JordanStopford 776db59
Fix newline?
JordanStopford 777f961
Fix linting
JordanStopford 476f6bc
Merge branch 'hvac:main' into ldap-secrets
JordanStopford a53faba
Apply suggestions from code review
briantist 6a5d830
Update hvac/api/secrets_engines/ldap.py
briantist bd4b2a9
nit: remove docs character
briantist c705eb9
remove use of arbitrary kwargs
briantist d07671a
use example.com in tests
briantist cbe3f83
add unit test for generate_static_credentials
briantist f199003
Merge branch 'main' into pr/JordanStopford/1033
briantist d166125
Merge branch 'main' into pr/JordanStopford/1033
briantist File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ Secrets Engines | |
database | ||
gcp | ||
identity | ||
ldap | ||
pki | ||
kv | ||
kv_v1 | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
LDAP | ||
================ | ||
|
||
.. contents:: | ||
|
||
Configure LDAP Secrets Secrets Engine | ||
------------------------------------- | ||
|
||
Configure the LDAP secrets engine to either manage service accounts or service account libraries. | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.configure` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
# Not all these settings may apply to your setup, refer to Vault | ||
# documentation for context of what to use here | ||
|
||
config_response = client.secrets.ldap.configure( | ||
binddn='username@domain.fqdn', # A upn or DN can be used for this value, Vault resolves the user to a dn silently | ||
bindpass='***********', | ||
url='ldaps://domain.fqdn', | ||
userdn='cn=Users,dn=domain,dn=fqdn', | ||
upndomain='domain.fqdn', | ||
userattr="cn", | ||
schema="openldap" | ||
) | ||
print(config_response) | ||
|
||
|
||
Read Config | ||
----------- | ||
|
||
Return the LDAP Secret Engine configuration. | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.read_config` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
config_response = client.secrets.ldap.read_config() | ||
|
||
|
||
Create or Update Static Role | ||
---------------------------- | ||
|
||
Create or Update a role which allows the retrieval and rotation of an LDAP account. Retrieve and rotate the actual credential via generate_static_credentials(). | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.create_or_update_static_role` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
role_response = client.secrets.ldap.create_or_update_static_role( | ||
name='hvac-role', | ||
username='sql-service-account', | ||
dn='cn=sql-service-account,dc=petshop,dc=com', | ||
rotation_period="60s") | ||
|
||
|
||
Read Static Role | ||
---------------- | ||
|
||
Retrieve the role configuration which allows the retrieval and rotation of an LDAP account. Retrieve and rotate the actual credential via generate_static_credentials(). | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.read_static_role` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
role_response = client.secrets.ldap.read_static_role(name='sql-service-account') | ||
|
||
|
||
List Static Roles | ||
----------------- | ||
|
||
List all configured roles which allows the retrieval and rotation of an LDAP account. Retrieve and rotate the actual credential via generate_static_credentials(). | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.list_static_roles` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
all_static_roles = client.secrets.ldap.list_static_roles() | ||
|
||
|
||
Delete Static Role | ||
------------------ | ||
|
||
Remove the role configuration which allows the retrieval and rotation of an LDAP account. | ||
|
||
The account is retained in Active Directory, but the password will be whatever Vault had rotated it to last. | ||
To regain control, the password will need to be reset via Active Directory. | ||
briantist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.delete_static_role` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
deletion_response = client.secrets.ldap.delete_static_role(name='sql-service-account') | ||
|
||
Generate Static Credentials | ||
--------------------------- | ||
|
||
Retrieve a service account password from LDAP. Return the previous password (if known). Vault shall rotate | ||
the password before returning it, if it has breached its configured ttl. | ||
|
||
Source reference: :py:meth:`hvac.api.secrets_engines.ldap.generate_static_credentials` | ||
|
||
.. code:: python | ||
|
||
import hvac | ||
client = hvac.Client() | ||
|
||
# Authenticate to Vault using client.auth.x | ||
|
||
gen_creds_response = client.secrets.ldap.generate_static_credentials( | ||
name='hvac-role', | ||
) | ||
print('Retrieved Service Account Password: {access} (Current) / {secret} (Old)'.format( | ||
access=gen_creds_response['data']['current_password'], | ||
secret=gen_creds_response['data']['old_password'], | ||
)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
#!/usr/bin/env python | ||
"""LDAP methods module.""" | ||
|
||
from hvac import utils | ||
from hvac.api.vault_api_base import VaultApiBase | ||
|
||
DEFAULT_MOUNT_POINT = "ldap" | ||
|
||
|
||
class Ldap(VaultApiBase): | ||
"""LDAP Secrets Engine (API). | ||
Reference: https://www.vaultproject.io/api/secret/ldap/index.html | ||
""" | ||
|
||
def configure( | ||
self, | ||
binddn=None, | ||
bindpass=None, | ||
url=None, | ||
password_policy=None, | ||
schema=None, | ||
userdn=None, | ||
userattr=None, | ||
upndomain=None, | ||
mount_point=DEFAULT_MOUNT_POINT, | ||
*args, | ||
**kwargs | ||
briantist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): | ||
"""Configure shared information for the ldap secrets engine. | ||
|
||
Supported methods: | ||
POST: /{mount_point}/config. Produces: 204 (empty body) | ||
|
||
:param binddn: Distinguished name of object to bind when performing user and group search. | ||
:type binddn: str | unicode | ||
:param bindpass: Password to use along with binddn when performing user search. | ||
:type bindpass: str | unicode | ||
:param url: Base DN under which to perform user search. | ||
:type url: str | unicode | ||
:param userdn: Base DN under which to perform user search. | ||
:type userdn: str | unicode | ||
:param upndomain: userPrincipalDomain used to construct the UPN string for the authenticating user. | ||
:type upndomain: str | unicode | ||
:param password_policy: – The name of the password policy to use to generate passwords. | ||
:type password_policy: str | unicode | ||
:param schema: The LDAP schema to use when storing entry passwords. Valid schemas include openldap, ad, and racf. | ||
briantist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
integer number of seconds or Go duration format string.** | ||
briantist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:type schema: str | unicode | ||
:param mount_point: The "path" the method/backend was mounted on. | ||
:type mount_point: str | unicode | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
params = utils.remove_nones( | ||
{ | ||
"binddn": binddn, | ||
"bindpass": bindpass, | ||
"url": url, | ||
"userdn": userdn, | ||
"userattr": userattr, | ||
"upndomain": upndomain, | ||
"password_policy": password_policy, | ||
"schema": schema, | ||
} | ||
) | ||
|
||
params.update(kwargs) | ||
|
||
api_path = utils.format_url("/v1/{mount_point}/config", mount_point=mount_point) | ||
return self._adapter.post( | ||
url=api_path, | ||
json=params, | ||
) | ||
|
||
def read_config(self, mount_point=DEFAULT_MOUNT_POINT): | ||
"""Read the configured shared information for the ldap secrets engine. | ||
|
||
Credentials will be omitted from returned data. | ||
|
||
Supported methods: | ||
GET: /{mount_point}/config. Produces: 200 application/json | ||
|
||
:param mount_point: The "path" the method/backend was mounted on. | ||
:type mount_point: str | unicode | ||
:return: The JSON response of the request. | ||
:rtype: dict | ||
""" | ||
api_path = utils.format_url("/v1/{mount_point}/config", mount_point=mount_point) | ||
return self._adapter.get( | ||
url=api_path, | ||
) | ||
|
||
def rotate_root(self, mount_point=DEFAULT_MOUNT_POINT): | ||
"""Rotate the root password for the binddn entry used to manage the ldap secrets engine. | ||
|
||
Supported methods: | ||
POST: /{mount_point}/rotate root. Produces: 200 application/json | ||
|
||
:param mount_point: The "path" the method/backend was mounted on. | ||
:type mount_point: str | unicode | ||
:return: The JSON response of the request. | ||
:rtype: dict | ||
""" | ||
api_path = utils.format_url( | ||
"/v1/{mount_point}/rotate-root", mount_point=mount_point | ||
) | ||
return self._adapter.post(url=api_path) | ||
|
||
def create_or_update_static_role( | ||
self, | ||
name, | ||
username=None, | ||
dn=None, | ||
rotation_period=None, | ||
mount_point=DEFAULT_MOUNT_POINT, | ||
): | ||
"""This endpoint creates or updates the ldap static role definition. | ||
|
||
:param name: Specifies the name of an existing static role against which to create this ldap credential. | ||
:type name: str | unicode | ||
:param username: The name of a pre-existing service account in LDAP that maps to this static role. | ||
This value is required on create and cannot be updated. | ||
:type username: str | unicode | ||
:param dn: Distinguished name of the existing LDAP entry to manage password rotation for (takes precedence over username). | ||
Optional but cannot be modified after creation. The name of a pre-existing service account in Active Directory that maps to this role. | ||
briantist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:type dn: str | unicode | ||
:param rotation_period: How often Vault should rotate the password. | ||
This is provided as a string duration with a time suffix like "30s" or "1h" or as seconds. | ||
If not provided, the default Vault rotation_period is used. | ||
:type rotation_period: str | unicode | ||
:param mount_point: Specifies the place where the secrets engine will be accessible (default: ad). | ||
:type mount_point: str | unicode | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
api_path = utils.format_url("/v1/{}/static-role/{}", mount_point, name) | ||
params = {"username": username, "rotation_period": rotation_period} | ||
params.update(utils.remove_nones({"dn": dn})) | ||
return self._adapter.post( | ||
url=api_path, | ||
json=params, | ||
) | ||
|
||
def read_static_role(self, name, mount_point=DEFAULT_MOUNT_POINT): | ||
"""This endpoint queries for information about an ldap static role with the given name. | ||
If no role exists with that name, a 404 is returned. | ||
:param name: Specifies the name of the static role to query. | ||
:type name: str | unicode | ||
:param mount_point: Specifies the place where the secrets engine will be accessible (default: ad). | ||
:type mount_point: str | unicode | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
api_path = utils.format_url("/v1/{}/static-role/{}", mount_point, name) | ||
return self._adapter.get( | ||
url=api_path, | ||
) | ||
|
||
def list_static_roles(self, mount_point=DEFAULT_MOUNT_POINT): | ||
"""This endpoint lists all existing static roles in the secrets engine. | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
api_path = utils.format_url("/v1/{}/static-role", mount_point) | ||
return self._adapter.list( | ||
url=api_path, | ||
) | ||
|
||
def delete_static_role(self, name, mount_point=DEFAULT_MOUNT_POINT): | ||
"""This endpoint deletes an ldap static role with the given name. | ||
Even if the role does not exist, this endpoint will still return a successful response. | ||
:param name: Specifies the name of the role to delete. | ||
:type name: str | unicode | ||
:param mount_point: Specifies the place where the secrets engine will be accessible (default: ad). | ||
:type mount_point: str | unicode | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
api_path = utils.format_url("/v1/{}/static-role/{}", mount_point, name) | ||
return self._adapter.delete( | ||
url=api_path, | ||
) | ||
|
||
def generate_static_credentials(self, name, mount_point=DEFAULT_MOUNT_POINT): | ||
"""This endpoint retrieves the previous and current LDAP password for | ||
the associated account (or rotate if required) | ||
|
||
:param name: Specifies the name of the static role to request credentials from. | ||
:type name: str | unicode | ||
:param mount_point: Specifies the place where the secrets engine will be accessible (default: ad). | ||
:type mount_point: str | unicode | ||
:return: The response of the request. | ||
:rtype: requests.Response | ||
""" | ||
api_path = utils.format_url("/v1/{}/static-cred/{}", mount_point, name) | ||
return self._adapter.get( | ||
url=api_path, | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.