URL: https://github.com/freeipa/freeipa/pull/5438
Author: tiran
 Title: #5438: [PoC] Add support for subuid and subgid
Action: opened

PR body:
"""
See: https://pagure.io/freeipa/issue/8361
Signed-off-by: Christian Heimes <chei...@redhat.com>
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/5438/head:pr5438
git checkout pr5438
From 103464cc299ffc0b7ecea8c054b2aaf25a4a86ee Mon Sep 17 00:00:00 2001
From: Christian Heimes <chei...@redhat.com>
Date: Mon, 18 Jan 2021 14:13:43 +0100
Subject: [PATCH] Add support for subuid and subgid

See: https://pagure.io/freeipa/issue/8361
Signed-off-by: Christian Heimes <chei...@redhat.com>
---
 install/share/60basev3.ldif       |   5 ++
 install/ui/src/freeipa/user.js    |  29 +++++++
 install/updates/20-indices.update |  16 ++++
 ipalib/constants.py               |   5 ++
 ipaserver/plugins/baseuser.py     | 134 +++++++++++++++++++++++++++++-
 ipaserver/plugins/internal.py     |   7 ++
 6 files changed, 194 insertions(+), 2 deletions(-)

diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif
index efc6c8afb60..c5b64195744 100644
--- a/install/share/60basev3.ldif
+++ b/install/share/60basev3.ldif
@@ -58,6 +58,10 @@ attributeTypes: (2.16.840.1.113730.3.8.11.70 NAME 'ipaPermTargetTo' DESC 'Destin
 attributeTypes: (2.16.840.1.113730.3.8.11.71 NAME 'ipaPermTargetFrom' DESC 'Source location from where moving an entry IPA permission ACI' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.0' )
 attributeTypes: ( 2.16.840.1.113730.3.8.11.75 NAME 'ipaNTAdditionalSuffixes' DESC 'Suffix for the user principal name associated with the domain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
 attributeTypes: (2.16.840.1.113730.3.8.11.77 NAME 'ipaDomainResolutionOrder' DESC 'List of domains used to resolve a short name' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'IPA v4.5')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.78 NAME 'ipaSubUidNumber' DESC 'numerical subordinate user ID' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.79 NAME 'ipaSubUidCount' DESC 'numerical subordinate user ID count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.80 NAME 'ipaSubGidNumber' DESC 'numerical subordinate user ID' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.81 NAME 'ipaSubGidCount' DESC 'numerical subordinate user ID count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
 attributeTypes: (2.16.840.1.113730.3.8.18.2.1 NAME 'ipaVaultType' DESC 'IPA vault type' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.2')
 attributeTypes: (2.16.840.1.113730.3.8.18.2.2 NAME 'ipaVaultSalt' DESC 'IPA vault salt' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'IPA v4.2' )
 # FIXME: https://bugzilla.redhat.com/show_bug.cgi?id=1267782
@@ -86,5 +90,6 @@ objectClasses: (2.16.840.1.113730.3.8.12.25 NAME 'ipaPrivateKeyObject' DESC 'Wra
 objectClasses: (2.16.840.1.113730.3.8.12.26 NAME 'ipaSecretKeyObject' DESC 'Wrapped secret keys' SUP top AUXILIARY MUST ( ipaSecretKey $ ipaWrappingKey $ ipaWrappingMech ) X-ORIGIN 'IPA v4.1' )
 objectClasses: (2.16.840.1.113730.3.8.12.34 NAME 'ipaSecretKeyRefObject' DESC 'Indirect storage for encoded key material' SUP top AUXILIARY MUST ( ipaSecretKeyRef ) X-ORIGIN 'IPA v4.1' )
 objectClasses: (2.16.840.1.113730.3.8.12.39 NAME 'ipaNameResolutionData' DESC 'Data used to resolve short names to fully-qualified form' SUP top AUXILIARY MAY ( ipaDomainResolutionOrder ) X-ORIGIN 'IPA v4.5')
+objectClasses: (2.16.840.1.113730.3.8.12.40 NAME 'ipaUserSubordinate' DESC 'Subordinate uid and gid for users' SUP posixAccount AUXILIARY MUST ( ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
 objectClasses: (2.16.840.1.113730.3.8.18.1.1 NAME 'ipaVault' DESC 'IPA vault' SUP top STRUCTURAL MUST ( cn ) MAY ( description $ ipaVaultType $ ipaVaultSalt $ ipaVaultPublicKey $ owner $ member ) X-ORIGIN 'IPA v4.2' )
 objectClasses: (2.16.840.1.113730.3.8.18.1.2 NAME 'ipaVaultContainer' DESC 'IPA vault container' SUP top STRUCTURAL MUST ( cn ) MAY ( description $ owner ) X-ORIGIN 'IPA v4.2' )
diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
index a4eb390b7d9..d161449fb96 100644
--- a/install/ui/src/freeipa/user.js
+++ b/install/ui/src/freeipa/user.js
@@ -259,6 +259,35 @@ return {
                         }
                     ]
                 },
+                {
+                    name: 'subordinate',
+                    label: '@i18n:objects.subordinate.identity',
+                    fields: [
+                        {
+                            name: 'ipasubuidnumber',
+                            label: '@i18n:objects.subordinate.subuidnumber',
+                            flags: ['w_if_no_aci']
+                        },
+                        {
+                            name: 'ipasubuidcount',
+                            label: '@i18n:objects.subordinate.subuidcount',
+                            hidden_if_empty: true,
+                            read_only: true
+
+                        },
+                        {
+                            name: 'ipasubgidnumber',
+                            label: '@i18n:objects.subordinate.subgidnumber',
+                            flags: ['w_if_no_aci']
+                        },
+                        {
+                            name: 'ipasubgidcount',
+                            label: '@i18n:objects.subordinate.subgidcount',
+                            hidden_if_empty: true,
+                            read_only: true
+                        }
+                    ]
+                },
                 {
                     name: 'pwpolicy',
                     label: '@i18n:objects.pwpolicy.identity',
diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
index 6632f105a98..b47b3851c55 100644
--- a/install/updates/20-indices.update
+++ b/install/updates/20-indices.update
@@ -272,6 +272,22 @@ add:nsIndexType: eq
 add:nsIndexType: pres
 add:nsIndexType: sub
 
+dn: cn=ipaSubGidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+only:cn: ipaSubGidNumber
+default:objectClass: nsIndex
+default:objectClass: top
+default:nsSystemIndex: false
+add:nsIndexType: eq
+add:nsMatchingRule: integerOrderingMatch
+
+dn: cn=ipaSubUidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+only:cn: ipaSubUidNumber
+default:objectClass: nsIndex
+default:objectClass: top
+default:nsSystemIndex: false
+add:nsIndexType: eq
+add:nsMatchingRule: integerOrderingMatch
+
 dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 only:cn: ipasudorunasgroup
 default:objectClass: nsIndex
diff --git a/ipalib/constants.py b/ipalib/constants.py
index a622f640b24..78f3a0288bc 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -340,3 +340,8 @@
 # Apache's mod_ssl SSLVerifyDepth value (Maximum depth of CA
 # Certificates in Client Certificate verification)
 MOD_SSL_VERIFY_DEPTH = '5'
+
+# subuid / subgid counts are hard-coded
+# An interval of 65536 uids/gids is required to map nobody (65534).
+SUBUID_COUNT = 65536
+SUBGID_COUNT = 65536
diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
index e1b7763f0fd..4edb31a744b 100644
--- a/ipaserver/plugins/baseuser.py
+++ b/ipaserver/plugins/baseuser.py
@@ -33,7 +33,9 @@
 from ipaserver.plugins.service import (validate_realm, normalize_principal)
 from ipalib.request import context
 from ipalib import _
-from ipalib.constants import PATTERN_GROUPUSER_NAME
+from ipalib.constants import (
+    PATTERN_GROUPUSER_NAME, SUBUID_COUNT, SUBGID_COUNT
+)
 from ipapython import kerberos
 from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
 from ipapython.ipavalidate import Email
@@ -175,13 +177,17 @@ class baseuser(LDAPObject):
         'krbprincipalexpiration', 'usercertificate;binary',
         'krbprincipalname', 'krbcanonicalname',
         'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath',
-        'ipanthomedirectory', 'ipanthomedirectorydrive'
+        'ipanthomedirectory', 'ipanthomedirectorydrive',
+        'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
+        'ipasubgidcount',
     ]
     search_display_attributes = [
         'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname',
         'krbprincipalname', 'loginshell',
         'mail', 'telephonenumber', 'title', 'nsaccountlock',
         'uidnumber', 'gidnumber', 'sshpubkeyfp',
+        'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
+        'ipasubgidcount',
     ]
     uuid_attribute = 'ipauniqueid'
     attribute_members = {
@@ -429,6 +435,32 @@ class baseuser(LDAPObject):
                     'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
                     'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'),
                 ),
+        Int('ipasubuidnumber?',
+            label=_('SubUID'),
+            cli_name='subuid',
+            doc=_('Subordinate user ID'),
+            ),
+        Int('ipasubuidcount?',
+            label=_('SubUID count'),
+            cli_name='subuidcount',
+            doc=_('Subordinate user ID count'),
+            flags=['no_create', 'no_update', 'no_search'],
+            minvalue=SUBUID_COUNT,
+            maxvalue=SUBUID_COUNT,
+            ),
+        Int('ipasubgidnumber?',
+            label=_('SubGID'),
+            cli_name='subgid',
+            doc=_('Subordinate group ID'),
+            ),
+        Int('ipasubgidcount?',
+            label=_('SubGID count'),
+            cli_name='subgidcount',
+            doc=_('Subordinate group ID count'),
+            flags=['no_create', 'no_update', 'no_search'],
+            minvalue=SUBGID_COUNT,
+            maxvalue=SUBGID_COUNT,
+            ),
     )
 
     def normalize_and_validate_email(self, email, config=None):
@@ -526,6 +558,102 @@ def convert_attribute_members(self, entry_attrs, *keys, **options):
         except KeyError:
             pass
 
+    def find_subordinate_conflicts(
+        self, ldap, dn, entry_attrs, extra_filters=(), **kwargs
+    ):
+        id_interval_filters = []
+        single = entry_attrs.single_value
+        if 'ipasubuidnumber' in single:
+            subuid = single['ipasubuidnumber']
+            sulow = subuid - SUBUID_COUNT + 1
+            suhigh = subuid + SUBUID_COUNT - 1
+
+            # any uid within our interval
+            id_interval_filters.append(
+                f"(&(uidNumber>={subuid})(uidNumber<={suhigh}))"
+            )
+            # any other subuid within our interval
+            # and our subuid within the interval of any other subuid
+            id_interval_filters.append(
+                f"(&(ipaSubUidNumber>={sulow})(ipaSubUidNumber<={suhigh}))",
+            )
+
+        if 'ipasubgidnumber' in single:
+            subgid = single['ipasubgidnumber']
+            sglow = subgid - SUBGID_COUNT + 1
+            sghigh = subgid + SUBGID_COUNT - 1
+
+            id_interval_filters.append(
+                f"(&(gidNumber>={subgid})(gidNumber<={sghigh}))"
+            )
+            id_interval_filters.append(
+                f"(&(ipaSubGidNumber>={sglow})(ipaSubGidNumber<={sghigh}))",
+            )
+
+        if not id_interval_filters:
+            # no filter
+            return []
+
+        filters = [
+            ldap.make_filter_from_attr(
+                "objectClass",
+                ["posixAccount", "posixGroup", "ipaIDObject"],
+                ldap.MATCH_ANY
+            ),
+            ldap.combine_filters(id_interval_filters, ldap.MATCH_ANY),
+            # exclude this entry (exclude by DN does not work)
+            ldap.make_filter_from_attr(
+                "uid", dn["uid"], rules=ldap.MATCH_NONE
+            )
+        ]
+        filters.extend(extra_filters)
+        id_filter = ldap.combine_filters(filters, ldap.MATCH_ALL)
+
+        return ldap.find_entries(
+            filter=id_filter,
+            base_dn=DN(api.env.container_accounts, api.env.basedn),
+            **kwargs
+        )
+
+    def handle_subordinate_ids(self, ldap, dn, entry_attrs):
+        new_subuid = entry_attrs.get('ipasubuidnumber')
+        new_subgid = entry_attrs.get('ipasubgidnumber')
+        if new_subuid is None and new_subgid is None:
+            # nothing to do
+            return
+
+        # if new_subuid is None:
+        #     raise errors.RequirementError(name='ipasubuidnumber')
+        # if new_subgid is None:
+        #     raise errors.RequirementError(name='ipasubgidnumber')
+
+        if 'objectclass' in entry_attrs:
+            obj_classes = entry_attrs['objectclass']
+        else:
+            _entry_attrs = ldap.get_entry(dn, ['objectclass'])
+            entry_attrs['objectclass'] = _entry_attrs['objectclass']
+        if 'ipausersubordinate' not in obj_classes:
+            obj_classes.append('ipausersubordinate')
+
+        # hard-coded constants
+        entry_attrs['ipasubuidcount'] = SUBUID_COUNT
+        entry_attrs['ipasubgidcount'] = SUBGID_COUNT
+
+        try:
+            conflicts = self.find_subordinate_conflicts(
+                ldap, dn, entry_attrs, size_limit=10
+            )
+        except errors.NotFound:
+            pass
+        else:
+            raise errors.ValidationError(
+                name='ipasubuidnumber',
+                error=_(
+                    "subuid interval or subgid interval conflicts with "
+                    "existing intervals, users, or groups."
+                ) + repr(conflicts)
+            )
+
 
 class baseuser_add(LDAPCreate):
     """
@@ -536,6 +664,7 @@ def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
         assert isinstance(dn, DN)
         set_krbcanonicalname(entry_attrs)
         self.obj.convert_usercertificate_pre(entry_attrs)
+        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
         if entry_attrs.get('ipatokenradiususername', None):
             add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
                                      entry_attrs, update=False)
@@ -683,6 +812,7 @@ def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
 
         self.check_objectclass(ldap, dn, entry_attrs)
         self.obj.convert_usercertificate_pre(entry_attrs)
+        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
         self.preserve_krbprincipalname_pre(ldap, entry_attrs, *keys, **options)
         update_samba_attrs(ldap, dn, entry_attrs, **options)
 
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index 7b61e527eb7..d6a38944c56 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -1546,6 +1546,13 @@ class i18n_messages(Command):
                     "Drive to mount a home directory"
                 ),
             },
+            "subordinate": {
+                "identity": _("Subordinate user and group id"),
+                "subuidnumber": _("Subordinate user id"),
+                "subuidcount": _("Subordinate user id count"),
+                "subgidnumber": _("Subordinate group id"),
+                "subgidcount": _("Subordinate group id count"),
+            },
             "trustconfig": {
                 "options": _("Options"),
             },
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-devel@lists.fedorahosted.org

Reply via email to