LDAP Inventory guide
This guide covers information about the LDAP inventory plugin included in this collection. This inventory plugin can be used to build an inventory from an LDAP server source, like Microsoft Active Directory.
Connection info
Details on how to configure an LDAP connection can be found under the LDAP connection guide. Each of the connection options described by the plugin documentation are specified in the inventory yml configuration file like the below.
plugin: microsoft.ad.ldap
# LDAP connection options can be defined in the yaml config.
auth_protocol: simple
username: UserName
password: MyPassword123
tls_mode: ldaps
Attributes
The LDAP inventory plugin can be used to set custom facts for each host it retrieves based on the computer object’s LDAP attributes. Retrieving custom attributes is done through the attributes
option in the inventory plugin definition. The value is set to one of the three following types:
Empty string or null
A template string
A dictionary
Note
While an individual attribute can only be set to one of these types, it is possible to use the different value types for different attributes.
It is also possible to use the compose
inventory option to use the builtin compose templating provided by inventory plugins but the LDAP attributes must first be requested through the attributes
option and referenced in the compose
template through the host fact the attributes
set it on.
Empty string or null
attributes:
comment:
objectSid: ''
ms-Mcs-AdmPwd:
In this case each of the attribute values will be set as a host fact as they are coerced by the LDAP schema, see value types and templating. The name of each fact will be based on the attribute name with -
being replaced by _
. In the above example the host facts comment
, objectSid
, and ms_Mcs_AdmPwd
will be set to the coerced values.
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
comment: test comment
ms_Mcs_AdmPwd: Password123!
objectSid: S-1-5-21-1234-1108
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
Template string
attributes:
comment: this
objectSid: raw | microsoft.ad.as_sid
ms-Mcs-AdmPwd: raw | first
This format will set the host fact based on the template value specified. Each template is implicitly wrapped with {{ ... }}
and processed through Jinja2 to produce a result. This means the template string can contain filters provided by Ansible and other collections to convert the raw LDAP value into something more useful. The this
variable refers to the coerced LDAP attribute value and raw
refers to a list of base64 encoded byte strings of the raw LDAP attribute value. See value types and templating for more information around what can be done inside the templates. Each host fact will be named after the attribute name with -
being replaced by _
. In the above example the host facts command
, objectSid
, and ms_Mcs_AdmPwd
will be set to the template results.
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
comment: test comment
ms_Mcs_AdmPwd: UGFzc3dvcmQxMjMh
objectSid:
- S-1-5-21-1234-1108
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
Dictionary
attributes:
comment:
# Jinja2 native types will automatically convert this to a dict as
# the value is a json string.
my_comment:
other_var: this | from_json
objectSid:
sid: raw | microsoft.ad.as_sid | first
ms-Mcs-AdmPwd:
ansible_password: this
The final value that can be set on each attribute values is a dictionary where the keys are the host facts to set and the value is the template used to dervice the final value. It can be null or an empty string to refer to the LDAP coerced value of that attribute (this
) or a template string to template a new value based on the requirements at hand. See the above two formats for more info on null/empty string vs a string template value. In the above example there are 4 host facts set:
my_command
- the coerced value for thecomment
attributeother_var
- a dictionary created from the coerced value ofcomment
if it was a json stringsid
- the computer SID value as a string derived fromobjectSid
ansible_password
- the LAPS password coerced value derived fromms-Mcs-AdmPwd
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
ansible_password: Password123!
my_comment:
foo: bar
other_var:
foo: bar
sid: S-1-5-21-1234-1108
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
Note
The host fact names are used literally, there are no conversions from -
to _
when using this format.
Inventory hostname
By default the inventory_hostname
for a found host will be based on the name
LDAP attribute value. If the dNSHostName
attribute is set for the computer account found, it will be set as the ansible_host
fact. To define a custom inventory_hostname
or ansible_host
either set it in the attributes
or compose
plugin option under that key. For example this will set the inventory_hostname
to the value of sAMAccountName
without the ending $
rather than the computer account LDAP name
attribute.
attributes:
sAMAccountName:
inventory_hostname: sAMAccountName[:-1]
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
microsoft_ad_distinguished_name: CN=OtherName,CN=Computers,DC=domain,DC=com
sAMAccountName: MYHOST$
It is also possible to set inventory_hostname
under the compose
key. The following will produce the same output as the above.
attributes:
sAMAccountName:
compose:
inventory_hostname: sAMAccountName[:-1]
An example of setting a custom ansible_host
fact that is used as the connection host but leaving the default inventory_hostname
of the computer account name is:
attributes:
sAMAccountName:
ansible_host: sAMAccountName[:-1]
Value types and templating
Each LDAP attribute value is stored as a list of bytes but the schema supplied in the LDAP database can describe how those raw list of bytes are represented as a proper type, like a string, integer, boolean, etc. Currently only these four types are used when coercing LDAP attribute values
Booleans
Integers
Bytes
Strings
Booleans, integers, and strings are coerced into those specific Python types but bytes are coerced into a base64 string encoding of those bytes.
Note
The objectGuid
and objectSid
attributes are always coerced into strings representing the security identifier and guid respectively. These are the only attributes that have special coercion rules outside of the LDAP schema syntax.
LDAP attribute values may also be marked as a a single or multi valued attribute. A single value contains just the coerced value, or None/null
if it has not been set while a multi valued attribute will be set as a list of coerced values. For example the comment
is a single valued string while servicePrincipalName
is a multi valued string. Using this inventory configuration that requests comment
, and servicePrincipalName
we get the following inventory host definition:
plugin: microsoft.ad.ldap
attributes:
comment:
servicePrincipalName:
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
comment: test comment
servicePrincipalName:
- WSMAN/MYHOST
- WSMAN/MYHOST.domain.com
- TERMSRV/MYHOST
- TERMSRV/MYHOST.domain.com
- RestrictedKrbHost/MYHOST
- HOST/MYHOST
- RestrictedKrbHost/MYHOST.domain.com
- HOST/MYHOST.domain.com
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
Some attributes like pwdLastSet
are typically represented as a datetime value but internally are stored as integers. As there is no metadata in the LDAP schema to denote these integer values as datetime objects they will only be coerced into integer values by default.
The following filters can be used as an easy way to further convert the coerced values into something more readable:
An example of these filters being used in the attributes
option can be seen below:
plugin: microsoft.ad.ldap
attributes:
pwdLastSet:
password_last_set_int: this
password_last_set_datetime: this | microsoft.ad.as_datetime
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
password_last_set_datetime: 2023-02-06T07:39:09.195321+0000
password_last_set_int: 133201427491953218
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
The templates can also reference other filters that exist outside the collection, like the Ansible builtin from_json
and more. The value is simply what would be placed inside {{ ... }}
during a normal template operation.
Note
Lookups cannot be used in the attribute value templates, only filters.
Each template used in the attributes
inventory option can reference the following variables:
this
raw
Any previously defined attributes
The this
variable refers to the coerced LDAP attribute value while raw
refers to the list of base64 encoded strings representing the raw LDAP value that hasn’t been coerced. As each attribute host fact is processed, it is also available in the subsequent templates under that host fact name. Here is an example of a more complex set of attributes:
plugin: microsoft.ad.ldap
attributes:
objectSid:
sid: this
sid_raw: raw
sid_raw_filtered: raw | microsoft.ad.as_sid | first
objectGuid:
sAMAccountName:
computer_name:
comment:
comment: this
# Can refer to previously set attributes above
description: computer_name ~ " - " ~ sid ~ " - " ~ objectGuid ~ " - " ~ this
# Can also be used as a template and refer to the vars retrieved above
compose:
comment2: comment
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
comment: test comment
comment2: test comment
computer_name: MYHOST$
description: MYHOST$ - S-1-5-21-1234-1108 - 51cc490f-1de0-41ae-98ad-dc065d5b33e2 - test comment
objectGuid: 51cc490f-1de0-41ae-98ad-dc065d5b33e2
sid: S-1-5-21-1234-1108
sid_raw:
- AQMAAAAAAAUVAAAA0gQAAFQEAAA=
sid_raw_filtered: S-1-5-21-1234-1108
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
LAPS
Local Administrator Administrator Password Solution (LAPS) can be used to automatically change the password of the local administrator account on domain joined hosts. The LDAP connection plugin can retrieve the LAPS-managed value and assign it as the connection password for the target host.
There are three different attributes that can be used by LAPS to store the password information:
ms-Mcs-AdmPwd
- The legacy LAPS attribute containing the passwordmsLAPS-Password
- The Windows LAPS attribute containing the username and passwordmsLAPS-EncryptedPassword
- The Windows LAPS attribute containing the encrypted username and password
If using the legacy LAPS setup, the following will retrieve and assign the connection username and password to the LAPS-managed value:
plugin: microsoft.ad.ldap
attributes:
ms-Mcs-AdmPwd:
ansible_user: '"Administrator"'
ansible_password: this
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
ansible_password: aR$lmrqK1l622H
ansible_user: Administrator
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
Note
Legacy LAPS does not store the username, the above example hardcodes the user name Administrator
.
If using Windows LAPS without encryption, the following will assign the connection username and password to the LAPS-managed values:
plugin: microsoft.ad.ldap
attributes:
msLAPS-Password:
ansible_user: (this | from_json).n
ansible_password: (this | from_json).p
raw_example: raw
this_example: this
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
ansible_password: AWznso@ZJ+J6p9
ansible_user: Administrator
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
raw_example:
- eyJuIjoiQWRtaW5pc3RyYXRvciIsInQiOiIxZDk4MmI0MzdiN2E1YzYiLCJwIjoiQVd6bnNvQFpKK0o2cDkifQ==
this_example:
n: Administrator
p: AWznso@ZJ+J6p9
t: 1d982b437b7a5c6
Unlike Legacy LAPS, the attribute value is a json string that contains the keys:
n
- The account name the password was encrypted forp
- The password for the accountt
- The time the password was set encoded as a FILETIME in base16
Note
It is recommended to use the from_json
filter (as shown in the example above) on the this
value to ensure consistent behavior in the presence or absence of Jinja2 native type support.
Getting an encrypted Windows LAPS value requires the dpapi-ng
Python library to be installed. See the LDAP connection requirements for more information on this optional package and how to debug whether it’s installed or not.
Note
Using Windows LAPS encrypted password is currently an experimental feature.
With the dpapi-ng
package installed, an authorized LDAP user can decrypt and assign the LAPS-managed username and password to the target host connection as follows:
plugin: microsoft.ad.ldap
attributes:
msLAPS-EncryptedPassword:
ansible_user: (this.value | from_json).n
ansible_password: (this.value | from_json).p
raw_example: raw
this_example: this
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
ansible_password: 6jr&}yK++{0Q}&
ansible_user: Administrator
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
raw_example:
- toLZAWR7rgfk...
this_example:
encrypted_value: MIIETgYJKoZI...
flags: 0
info: ''
update_timestamp: 133281382308674404
value: '{"n":"Administrator","t":"1d982b607ae7b64","p":"6jr&}yK++{0Q}&"}'
The raw
value contains the raw base64 encoded value as stored in AD. The this
value contains a dictionary with the following keys:
encrypted_value
: The encrypted password blob as a base64 stringflags
: The flags set as a bitwise int value, currently these are undocumented by Microsoftupdate_timestamp
: The FILETIME value of when thevalue
: The decrypted value containing the username and password as a JSON stringdebug
: Debug information that indicates why it failed to decrypt the value
The value
key will only be present if the decryption process was successful. If it failed, the debug
key will be present and contain the reason why it failed to be decrypted.
If the dpapi-ng
library is not installed this is what the output would look like:
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
raw_example:
- toLZAWR7rgfk...
this_example:
debug: Cannot decrypt value as the Python library dpapi-ng is not installed
encrypted_value: MIIETgYJKoZI...
flags: 0
update_timestamp: 133281382308674404
The value
key is no longer present and debug
contains the message that dpapi-ng
is not installed.
If dpapi-ng
library was installed but the connection user is not authorized to decrypt the value this is what the output would look like:
# ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
ansible_host: MYHOST.domain.com
microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
raw_example:
- toLZAWR7rgfk...
this_example:
debug: Failed to decrypt value due to error - ValueError GetKey failed 0x80070005
encrypted_value: MIIETgYJKoZI...
flags: 0
update_timestamp: 133281382308674404
A simple way to test that the connection user is able to decrypt the password is to run Get-LapsADPassword -Identity MYHOST
on a Windows host as that user.