Skip to content

Commit

Permalink
first draft of LAPS lookup plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
no-12 committed Feb 28, 2024
1 parent 2c488fb commit 247b93f
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
Empty file added plugins/lookup/__init__.py
Empty file.
193 changes: 193 additions & 0 deletions plugins/lookup/laps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Copyright: (c) 2024, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

DOCUMENTATION = r"""
name: laps
author: Nico Ohnezat (@no-12)
short_description: Inventory plugin for Active Directory
version_added: 2.2.0
description:
- Lookup plugin that retrieves the LAPS password information for multiple hosts from an Active Directory server.
options:
_terms:
description: One or multiple C(dNSHostName) of the computer objects to search for.
required: True
laps:
description:
- The LAPS password type to retrieve.
- Defaults to the C(auto). This will attempt to retrieve the LAPS password in the following order: C(windows_encrypted), C(windows_plain_text), C(legacy_microsoft).
type: str
choices:
- auto
- windows_encrypted
- windows_plain_text
- legacy_microsoft
default: auto
search_base:
description:
- The LDAP search base to find the computer objects in.
- Defaults to the C(defaultNamingContext) of the Active Directory server
if not specified.
- If searching a larger Active Directory database, it is recommended to
narrow the search base to speed up the queries.
type: str
search_scope:
description:
- The scope of the LDAP search to perform.
- C(base) will search only the current path or object specified by
I(search_base). This is typically not useful for inventory plugins.
- C(one_level) will search only the immediate child objects in
I(search_base).
- C(subtree) will search the immediate child objects and any nested
objects in I(search_base).
choices:
- base
- one_level
- subtree
default: subtree
type: str
notes:
- This plugin is a tech preview and the module options are subject to change
based on feedback received.
extends_documentation_fragment:
- microsoft.ad.ldap_connection
"""

import json

from ansible.errors import AnsibleError
from ansible.module_utils.basic import missing_required_lib
from ansible.plugins.lookup import LookupBase

try:
import sansldap

from ..plugin_utils._ldap import create_ldap_connection
from ..plugin_utils._ldap.schema import LDAPSchema
from ..plugin_utils._ldap.laps import LAPSDecryptor
from ..filter.ldap_converters import as_datetime

HAS_LDAP = True
LDAP_IMP_ERR = None
except Exception as e:
HAS_LDAP = False
LDAP_IMP_ERR = e


class LookupModule(LookupBase):
NAME = "microsoft.ad.laps"

def _parse_value(self, parser, values):
if values:
return parser(values[0])
return None

def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)

if not HAS_LDAP:
msg = missing_required_lib(
"sansldap and pyspnego",
url="https://pypi.org/project/sansldap/ and https://pypi.org/project/pyspnego/",
reason="for ldap lookups",
)
raise AnsibleError(f"{msg}: {LDAP_IMP_ERR}") from LDAP_IMP_ERR

laps = self.get_option("laps")
search_base = self.get_option("search_base")
search_scope = self.get_option("search_scope")
ldap_search_scope = {
"base": sansldap.SearchScope.BASE,
"one_level": sansldap.SearchScope.ONE_LEVEL,
"subtree": sansldap.SearchScope.SUBTREE,
}[search_scope]

computer_filter = sansldap.FilterEquality("objectClass", b"computer")
dnshostname_filter = sansldap.FilterOr(
filters=[
sansldap.FilterEquality("dnshostname", bytes(t, "utf-8")) for t in terms
]
)
final_filter = sansldap.FilterAnd(filters=[computer_filter, dnshostname_filter])

attributes = {
"dnshostname",
"mslaps-encryptedpassword",
"mslaps-password",
"mslaps-passwordexpirationtime",
"ms-mcs-admpwd",
"ms-mcs-admpwdexpirationtime",
}

connection_options = self.get_options()
laps_decryptor = LAPSDecryptor(**connection_options)
return_value = []
with create_ldap_connection(**connection_options) as client:
for _, info in client.search(
filter=final_filter,
attributes=list(attributes),
search_base=search_base,
search_scope=ldap_search_scope,
).items():
insensitive_info = {k.lower(): v for k, v in info.items()}

hostname = self._parse_value(
bytes.decode, insensitive_info.get("dnshostname")
)

result = {
"hostname": hostname,
"laps": None,
}

if laps in ["auto", "windows_encrypted"]:
raw_mslaps_encrypted_password = self._parse_value(
laps_decryptor.decrypt,
insensitive_info.get("mslaps-encryptedpassword"),
)
if raw_mslaps_encrypted_password:
result["laps"] = "windows_encrypted"
parsed_mslaps_password = json.loads(raw_mslaps_password)
result["laps_username"] = parsed_mslaps_password.get("n")
result["laps_password"] = parsed_mslaps_password.get("p")
result["laps_password_expiration_time"] = self._parse_value(
as_datetime,
insensitive_info.get("mslaps-passwordexpirationtime"),
)
return_value.append(result)
continue

if laps in ["auto", "windows_plain_text"]:
raw_mslaps_password = self._parse_value(
bytes.decode, insensitive_info.get("mslaps-password")
)
if raw_mslaps_password:
result["laps"] = "windows_plain_text"
parsed_mslaps_password = json.loads(raw_mslaps_password)
result["laps_username"] = parsed_mslaps_password.get("n")
result["laps_password"] = parsed_mslaps_password.get("p")
result["laps_password_expiration_time"] = self._parse_value(
as_datetime,
insensitive_info.get("mslaps-passwordexpirationtime"),
)
return_value.append(result)
continue

if laps in ["auto", "legacy_microsoft"]:
legacy_laps_password = self._parse_value(
bytes.decode, insensitive_info.get("ms-mcs-admpwd")
)
if legacy_laps_password:
result["laps"] = "legacy_microsoft"
result["laps_username"] = "Administrator"
result["laps_password"] = legacy_laps_password
result["laps_password_expiration_time"] = self._parse_value(
as_datetime,
insensitive_info.get("ms-mcs-admpwdexpirationtime"),
)
return_value.append(result)
continue

return_value.append(result)

return return_value

0 comments on commit 247b93f

Please sign in to comment.