Skip to content
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

LDAP secrets engine enhancements #1163

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/usage/secrets_engines/ldap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ Source reference: :py:meth:`hvac.api.secrets_engines.ldap.read_config`
config_response = client.secrets.ldap.read_config()


Rotate Root
---------------------------

Rotate the password for the binddn entry used to manage LDAP. This generated password will only be known to Vault and will not be retrievable once rotated.

Source reference: :py:meth:`hvac.api.secrets_engines.ldap.rotate_root`

.. code:: python

import hvac
client = hvac.Client()

# Authenticate to Vault using client.auth.x

rotate_response = client.secrets.ldap.rotate_root()


Create or Update Static Role
----------------------------

Expand Down Expand Up @@ -122,6 +139,7 @@ Source reference: :py:meth:`hvac.api.secrets_engines.ldap.delete_static_role`

deletion_response = client.secrets.ldap.delete_static_role(name='sql-service-account')


Generate Static Credentials
---------------------------

Expand All @@ -144,3 +162,20 @@ Source reference: :py:meth:`hvac.api.secrets_engines.ldap.generate_static_creden
access=gen_creds_response['data']['current_password'],
secret=gen_creds_response['data']['old_password'],
))


Rotate Static Credentials
---------------------------

Manually rotate the password of an existing role.

Source reference: :py:meth:`hvac.api.secrets_engines.ldap.rotate_static_credentials`

.. code:: python

import hvac
client = hvac.Client()

# Authenticate to Vault using client.auth.x

rotate_response = client.secrets.ldap.rotate_static_credentials(name='hvac-role')
51 changes: 47 additions & 4 deletions hvac/api/secrets_engines/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ def configure(
userdn=None,
userattr=None,
upndomain=None,
connection_timeout=None,
request_timeout=None,
starttls=None,
insecure_tls=None,
certificate=None,
client_tls_cert=None,
client_tls_key=None,
mount_point=DEFAULT_MOUNT_POINT,
):
"""Configure shared information for the ldap secrets engine.
Expand All @@ -43,6 +50,20 @@ def configure(
:type password_policy: str | unicode
:param schema: The LDAP schema to use when storing entry passwords. Valid schemas include ``openldap``, ``ad``, and ``racf``.
:type schema: str | unicode
:param connection_timeout: Timeout, in seconds, when attempting to connect to the LDAP server before trying the next URL in the configuration.
:type connection_timeout: int | str
:param request_timeout: Timeout, in seconds, for the connection when making requests against the server before returning back an error.
:type request_timeout: int | str
:param starttls: If true, issues a StartTLS command after establishing an unencrypted connection.
:type starttls: bool
:param insecure_tls: If true, skips LDAP server SSL certificate verification - insecure, use with caution!
:type insecure_tls: bool
:param certificate: CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded.
:type certificate: str | unicode
:param client_tls_cert: Client certificate to provide to the LDAP server, must be x509 PEM encoded.
:type client_tls_cert: str | unicode
:param client_tls_key: Client key to provide to the LDAP server, must be x509 PEM encoded.
:type client_tls_key: str | unicode
:param mount_point: The "path" the method/backend was mounted on.
:type mount_point: str | unicode
:return: The response of the request.
Expand All @@ -58,6 +79,13 @@ def configure(
"upndomain": upndomain,
"password_policy": password_policy,
"schema": schema,
"connection_timeout": connection_timeout,
"request_timeout": request_timeout,
"starttls": starttls,
"insecure_tls": insecure_tls,
"certificate": certificate,
"client_tls_cert": client_tls_cert,
"client_tls_key": client_tls_key,
}
)

Expand Down Expand Up @@ -123,7 +151,7 @@ def create_or_update_static_role(
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).
: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
Expand All @@ -141,7 +169,7 @@ def read_static_role(self, name, mount_point=DEFAULT_MOUNT_POINT):
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).
: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
Expand All @@ -166,7 +194,7 @@ def delete_static_role(self, name, mount_point=DEFAULT_MOUNT_POINT):
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).
: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
Expand All @@ -182,7 +210,7 @@ def generate_static_credentials(self, name, mount_point=DEFAULT_MOUNT_POINT):

: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).
: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
Expand All @@ -191,3 +219,18 @@ def generate_static_credentials(self, name, mount_point=DEFAULT_MOUNT_POINT):
return self._adapter.get(
url=api_path,
)

def rotate_static_credentials(self, name, mount_point=DEFAULT_MOUNT_POINT):
"""This endpoint rotates the password of an existing static role.

:param name: Specifies the name of the static role to rotate credentials for.
:type name: 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
"""
api_path = utils.format_url("/v1/{}/rotate-role/{}", mount_point, name)
return self._adapter.post(
url=api_path,
)
33 changes: 33 additions & 0 deletions tests/unit_tests/api/secrets_engines/test_ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def test_configure(self, test_label, mount_point, requests_mocker):
userattr=None,
schema=None,
userdn="ou=users,dc=example,dc=com",
connection_timeout="60s",
request_timeout="30s",
starttls=False,
insecure_tls=False,
)

self.assertEqual(
Expand Down Expand Up @@ -244,6 +248,35 @@ def test_generate_static_credentials(
second=response,
)

@parameterized.expand(
[
("default mount point", DEFAULT_MOUNT_POINT),
("custom mount point", "other-ldap-tree"),
]
)
@requests_mock.Mocker()
def test_rotate_static_credentials(self, test_label, mount_point, requests_mocker):
expected_status_code = 204
role_name = "hvac"
mock_url = "http://localhost:8200/v1/{mount_point}/rotate-role/{name}".format(
mount_point=mount_point,
name=role_name,
)
requests_mocker.register_uri(
method="POST",
url=mock_url,
status_code=expected_status_code,
)
ldap = Ldap(adapter=JSONAdapter())
response = ldap.rotate_static_credentials(
name=role_name,
mount_point=mount_point,
)
self.assertEqual(
first=expected_status_code,
second=response.status_code,
)

@parameterized.expand(
[
("default mount point", DEFAULT_MOUNT_POINT),
Expand Down