Skip to content

Commit

Permalink
LDAP secrets engine enhancements (#1163)
Browse files Browse the repository at this point in the history
* Add additional mount configuration parameters

* Add method to rotate static role credentials manually

* Update documentation
  • Loading branch information
mweigel committed Apr 18, 2024
1 parent a0418e2 commit 6068338
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 4 deletions.
35 changes: 35 additions & 0 deletions docs/usage/secrets_engines/ldap.rst
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
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
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

0 comments on commit 6068338

Please sign in to comment.