Source code for insights.combiners.identity_domain

"""
Identity Domain - Combiner for domain enrollment
================================================

The combiner detects enrollment into identity domains such as IPA,
Active Directory, generic Kerberos realm, and generic LDAP. It parses
domains and realms from SSSD, KRB5, IPA, and Samba configuration.

Supported domain types
----------------------

* IPA (RHEL IdM, FreeIPA)
* Active Directory (SSSD)
* Active Directory (Samba winbind)
* generic LDAP domain (SSSD)
* generic LDAP domain with Kerberos authentication (SSSD)
* generic Kerberos realm (from ``krb5.conf``)

The combiner cannot detect generic Kerberos realms that solely rely upon
DNS realm lookup (``dns_lookup_realm``).

Examples::

    DomainInfo(
        name="ipa.test",
        domain_type="IPA",
        server_software="IPA",
        client_software="SSSD",
        domain="ipa.test",
        realm="IPA.TEST",
        workgroup=None,
        ipa_mode="client",
    )

    DomainInfo(
        name="ad-winbind.test",
        domain_type="Active Directory (winbind)",
        server_software="Active Directory",
        client_software="winbind",
        domain="ad-winbind.test",
        realm="AD-WINBIND.TEST",
        workgroup="AD-WINBIND",
        ipa_mode=None,
    )
"""
import collections

from insights.core.exceptions import SkipComponent
from insights.core.plugins import combiner
from insights.combiners.ipa import IPA
from insights.parsers.samba import SambaConfigs
from insights.parsers.sssd_conf import SSSD_Config
from insights.combiners.krb5 import AllKrb5Conf


class DomainTypes(object):
    """Human readable domain type"""

    # SSSD domain types
    IPA = "IPA"
    AD_SSSD = "Active Directory (SSSD)"
    LDAP = "LDAP"
    LDAP_KRB5 = "LDAP/Kerberos"
    # krb5.conf but not in sssd.conf
    KRB5 = "Kerberos"
    # Samba winbind
    AD_WINBIND = "Active Directory (winbind)"


class ServerSoftware(object):
    """Server software"""

    IPA = "IPA"
    AD = "Active Directory"
    LDAP = "generic LDAP"
    LDAP_KRB5 = "generic LDAP/Kerberos"
    KRB5 = "generic Kerberos"


class ClientSoftware(object):
    """Client software"""

    SSSD = "SSSD"
    WINBIND = "winbind"
    KRB5 = "Kerberos"


class IPAMode(object):
    """IPA mode (server or client-only)"""

    IPA_CLIENT = "client"
    IPA_SERVER = "server"


DomainInfo = collections.namedtuple(
    "DomainInfo",
    [
        "name",
        "domain_type",
        "server_software",
        "client_software",
        "domain",
        "realm",
        "workgroup",
        "ipa_mode",
    ],
)
"""Identity domain information

Attributes:
    name (str): user-friendly name
        either SSSD's domain name, domain name, or lower-case realm name
    domain_type (str): domain type, e.g. ``IPA`` or ``Active Directory (SSSD)``
    server_software (str): name of the server software, e.g. ``Active Directory``
    client_software (str): name of the client software, e.g. ``SSSD`` or ``winbind``
    domain (str, None): name of the identity domain,
        not set for generic Kerberos or LDAP
    realm (str, None): Kerberos realm name,
        not set for generic LDAP
    workgroup (str, None): workgroup name,
        only set for AD with winbind
    ipa_mode (str, None): IPA mode (server or client),
        only set for IPA
"""


[docs] @combiner(optional=[SSSD_Config, AllKrb5Conf, IPA, SambaConfigs]) class IdentityDomain(object): """ A combiner for identity domains. Raises: SkipComponent: When no identity domains are detected. Attributes: domains (list): List of the namedtuple `DomainInfo` default_realm (str, None): default realm name (if configured) dns_lookup_realm (bool): is Kerberos realm DNS lookup enabled? dns_lookup_kdc (bool): is Kerberos KDC DNS lookup enabled? """ def __init__(self, sssd=None, krb5=None, ipa=None, smb=None): if sssd is None and krb5 is None and smb is None: # ipa depends on sssd raise SkipComponent("KRB5, SSSD, and Samba are not configured") self.domains = [] self._realms = set() if krb5 is not None: self.default_realm = krb5.default_realm self.dns_lookup_realm = krb5.dns_lookup_realm self.dns_lookup_kdc = krb5.dns_lookup_kdc else: self.default_realm = None # krb5.conf default is True self.dns_lookup_realm = True self.dns_lookup_kdc = True if sssd is not None: self._parse_sssd(sssd, ipa) if smb is not None: self._parse_smb(smb) if krb5 is not None: # parse /etc/krb5.conf last to skip SSSD and Samba realms self._parse_krb5(krb5) if not self.domains: raise SkipComponent("No identity domains detected") def _add_domaininfo( self, name, dtype, srv, clnt, domain, realm, workgroup=None, ipa_mode=None ): if realm is not None: if realm in self._realms: # already configured return self._realms.add(realm) di = DomainInfo(name, dtype, srv, clnt, domain, realm, workgroup, ipa_mode) self.domains.append(di) def _parse_sssd(self, sssd, ipa): """Extract domains from sssd.conf Supports id_providers "ad", "ipa", and "ldap". """ id_auth_providers = set(["ldap", "krb5", "ipa", "ad", "proxy"]) for name in sssd.domains: if "/" in name: # Ignore trusted domain (subdomain) configuration. Subdomain # settings are configured as # `[domain/parent.example/subdomain.example]`. continue conf = sssd.domain_config(name) id_provider = conf.get("id_provider") ipa_mode = None auth_provider = conf.get("auth_provider") if auth_provider is None and id_provider in id_auth_providers: # most id providers are also an auth providers auth_provider = id_provider elif auth_provider == "none": auth_provider = None if id_provider == "ad": dtype = DomainTypes.AD_SSSD srv = ServerSoftware.AD domain = conf.get("ad_domain", name) realm = conf.get("krb5_domain", domain.upper()) elif id_provider == "ipa": if ipa is None or not ipa.is_client: # unsupported configuration continue dtype = DomainTypes.IPA srv = ServerSoftware.IPA domain = conf.get("ipa_domain", name) realm = conf.get("krb5_domain", domain.upper()) ipa_mode = IPAMode.IPA_SERVER if ipa.is_server else IPAMode.IPA_CLIENT elif id_provider == "ldap": if auth_provider == "ldap": dtype = DomainTypes.LDAP srv = ServerSoftware.LDAP domain = None realm = None elif auth_provider == "krb5": dtype = DomainTypes.LDAP_KRB5 srv = ServerSoftware.LDAP_KRB5 domain = None # krb5_domain is required realm = conf.get("krb5_realm", name.upper()) else: # unsupported configuration continue elif id_provider in ("proxy", "files"): # not an identity domain continue else: # unsupported configuration continue self._add_domaininfo( name, dtype, srv, ClientSoftware.SSSD, domain, realm, ipa_mode=ipa_mode ) def _parse_smb(self, smb): """Parse smb.conf to detect AD with winbind We ignore IPA DC here as the information is already provided by `sssd.conf`. IPA DC has either server role `ROLE_IPA_DC` (Samba >= 4.16.0) or `ROLE_DOMAIN_PDC`, and always `security=user`. """ if ( smb.server_role != "ROLE_DOMAIN_MEMBER" or not smb.has_option("global", "security") or smb.get("global", "security").upper() != "ADS" or not smb.has_option("global", "realm") ): return realm = smb.get("global", "realm") domain = realm.lower() if smb.has_option("global", "workgroup"): workgroup = smb.get("global", "workgroup") else: workgroup = realm.split(".", 1)[0] self._add_domaininfo( domain, DomainTypes.AD_WINBIND, ServerSoftware.AD, ClientSoftware.WINBIND, domain, realm, workgroup, ) def _parse_krb5(self, krb5): """Parse krb5.conf to detect additional generic Kerberos realms""" for realm in krb5.realms: self._add_domaininfo( realm.lower(), DomainTypes.KRB5, ServerSoftware.KRB5, ClientSoftware.KRB5, None, realm, )