On 05/26/2015 01:51 PM, Tomas Babej wrote: > > > On 05/26/2015 12:39 PM, Tomas Babej wrote: >> >> >> On 05/26/2015 11:57 AM, Jan Cholasta wrote: >>> Dne 25.5.2015 v 17:15 Tomas Babej napsal(a): >>>> >>>> >>>> On 05/25/2015 12:42 PM, Tomas Babej wrote: >>>>> >>>>> >>>>> On 05/25/2015 07:30 AM, Jan Cholasta wrote: >>>>>> Dne 22.5.2015 v 12:36 Petr Vobornik napsal(a): >>>>>>> On 05/22/2015 07:08 AM, Jan Cholasta wrote: >>>>>>>> Dne 21.5.2015 v 18:18 Tomas Babej napsal(a): >>>>>>>>> >>>>>>>>> >>>>>>>>> On 05/19/2015 04:07 PM, Tomas Babej wrote: >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> On 05/19/2015 03:59 PM, Martin Kosek wrote: >>>>>>>>>>> On 05/19/2015 03:56 PM, Tomas Babej wrote: >>>>>>>>>>>> >>>>>>>>>>>> On 05/19/2015 03:51 PM, Martin Kosek wrote: >>>>>>>>>>>>> On 05/19/2015 03:49 PM, Ludwig Krispenz wrote: >>>>>>>>>>>>>> On 05/19/2015 03:36 PM, Martin Kosek wrote: >>>>>>>>>>>>>>> On 05/19/2015 03:22 PM, Tomas Babej wrote: >>>>>>>>>>>>>>> ... >>>>>>>>>>>>>>>>> 3) Domain level is just a single integer and it should be >>>>>>>>>>>>>>>>> treated as such, >>>>>>>>>>>>>>>>> there's no need for an LDAPObject plugin and other >>>>>>>>>>>>>>>>> unnecessary >>>>>>>>>>>>>>>>> complexities. >>>>>>>>>>>>>>>>> The implemetation could be as simple as (from top of my >>>>>>>>>>>>>>>>> head, >>>>>>>>>>>>>>>>> untested): >>>>>>>>>>>>>>>> That's right, I also considered this approach, but as far >>>>>>>>>>>>>>>> as I >>>>>>>>>>>>>>>> know you do >>>>>>>>>>>>>>>> not >>>>>>>>>>>>>>>> get the permission handling for the global DomainLevel entry >>>>>>>>>>>>>>>> otherwise. >>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>> Ludwig, I changed the path for the global entry to >>>>>>>>>>>>>>>> cn=DomainLevel. >>>>>>>>>>>>>>> I know this particular DN was added to the design by Simo, but >>>>>>>>>>>>>>> why do we want >>>>>>>>>>>>>>> to use CamelCase with LDAP object? >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> Wouldn't "cn=Domain Level,cn=ipa,cn=etc,SUFFIX" be a better >>>>>>>>>>>>>>> place >>>>>>>>>>>>>>> for it? This >>>>>>>>>>>>>>> is the last time we can change it, so I am asking now. >>>>>>>>>>>>>>> Then, we >>>>>>>>>>>>>>> will be stuck >>>>>>>>>>>>>>> with this DN forever. >>>>>>>>>>>>>> I don't mind using ""cn=Domain Level" , >>>>>>>>>>>>>> >>>>>>>>>>>>>> but where does the entry live, here you say >>>>>>>>>>>>>> >>>>>>>>>>>>>> cn=Domain Level,cn=ipa,cn=etc,SUFFIX" >>>>>>>>>>>>>> >>>>>>>>>>>>>> and in the design page it is: >>>>>>>>>>>>>> >>>>>>>>>>>>>> cn=DomainLevel,cn=etc,SUFFIX >>>>>>>>>>>>>> >>>>>>>>>>>>>> The current version of the topology plugin is looking for >>>>>>>>>>>>>> >>>>>>>>>>>>>> cn=DomainLevel,cn=ipa,cn=etc,SUFFIX" >>>>>>>>>>>>>> but I want to change it to do a search on >>>>>>>>>>>>>> objectclass=ipaDomainLevelConfig >>>>>>>>>>>>> I see - we all need to unify the location apparently. I >>>>>>>>>>>>> updated the >>>>>>>>>>>>> design page >>>>>>>>>>>>> to use "cn=Domain Level,cn=ipa,cn=etc,SUFFIX". Tomas, please >>>>>>>>>>>>> send >>>>>>>>>>>>> the updated >>>>>>>>>>>>> patch set, it should be an extremely simple change :-) >>>>>>>>>>>> I prefer the ipa parent and the space in the name, so I'm glad we >>>>>>>>>>>> could agree >>>>>>>>>>>> on this without much bikeshedding. >>>>>>>>>>>> >>>>>>>>>>>> Updated patch attaced. >>>>>>>>>>>> >>>>>>>>>>>> Tomas >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>> I still see >>>>>>>>>>> >>>>>>>>>>> +# Create default Domain Level entry if it does not exist >>>>>>>>>>> +dn: cn=DomainLevel,cn=ipa,cn=etc,$SUFFIX >>>>>>>>>>> +default: objectClass: top >>>>>>>>>>> +default: objectClass: nsContainer >>>>>>>>>>> +default: objectClass: ipaDomainLevelConfig >>>>>>>>>>> +default: ipaDomainLevel: 0 >>>>>>>>>>> >>>>>>>>>>> ... >>>>>>>>>> >>>>>>>>>> Right, the space eluded me there, thanks for the catch. >>>>>>>>>> >>>>>>>>>> Tomas >>>>>>>>> >>>>>>>>> A new iteration of the patch, including the server-side checks >>>>>>>>> for the >>>>>>>>> installers. >>>>>>>>> >>>>>>>>> Tomas >>>>>>>> >>>>>>>> 1) >>>>>>>> https://www.redhat.com/archives/freeipa-devel/2015-May/msg00228.html >>>>>>>> - I still don't agree that the plugin should be based on LDAPObject. >>>>>>> >>>>>>> On the other hand, with LDAPObject base, Web UI for this feature is >>>>>>> much >>>>>>> more simpler because it can rely on existing conventions. >>>>>> >>>>>> Following this logic, should *everything* be based on LDAPObject, >>>>>> because it would satisfy the convetion? I don't think so. The convetion >>>>>> should not apply here, because domain level is conceptually *not* an >>>>>> object, it is a property. IMHO having a clean API should be preferred >>>>>> over implementation convenience. >>>>>> >>>>> >>>>> I do not have strong opinions over this. Attached version implements >>>>> a lightweight approach to the domainlevel related commands. >>>>> >>>>> Tomas >>>>> >>>>> >>>>> >>>> >>>> Fixes a slight schema glitch. >>>> >>> >>> Thanks for the patch! >>> >>> 1) >>> >>> + # Detect the current domain level >>> + try: >>> + current = remote_api.Command['domainlevel_show']['result'] >>> + except KeyError: >>> + # If we're joining an older master, domainlevel_show is >>> not >>> + # available >>> + current = 0 >>> >>> KeyError? That does not look right. remote_api differs from api only in >>> that it sets up ldap2 to connect to the remote server, but it uses local >>> plugins and everything, so domainlevel_show should always be available. >>> >>> >>> 2) Could you also set supported domain levels in >>> install/share/master-entry.ldif? I think it makes sense to have them >>> there right from the beginning of server install. >>> >>> >>> 3) I think you should use the per-plugin api object instead of >>> ipalib.api when constructing DNs (domainlevel_dn, container_masters). >>> >>> >>> 4) I would say the opposite of "domainlevel-set" should be >>> "domainlevel-get", not "domainlevel-show". IMO it's OK since property >>> commands are an uncharted territory and don't have to (maybe even >>> shouldn't) use the same convention as objects. >>> >>> >>> 5) >>> >>> + 'System: Read Domain Level': { >>> + 'ipapermlocation': DN('cn=masters,cn=ipa,cn=etc', api.env.basedn), >>> + 'ipapermtargetfilter': {'(objectclass=ipadomainlevelconfig)'}, >>> + 'ipapermbindruletype': 'all', >>> + 'ipapermright': {'read', 'search', 'compare'}, >>> + 'ipapermdefaultattr': { >>> + 'ipadomainlevel', 'objectclass', >>> + }, >>> + }, >>> >>> Shouldn't ipapermlocation say "cn=Domain Level,cn=ipa,cn=etc"? >>> >> >> Thanks for the review, I fixed all the issues raised. >> >> Tomas >> > > Added a small fix for replca install, to avoid duplicated creation of > the domainlevel entry. > > Tomas >
Aand the correct patch. Tomas
>From 4d4b6bae3e64b9e1b311bf36c76f304066fb34be Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 14 May 2015 10:49:55 +0200 Subject: [PATCH] Add Domain Level feature https://fedorahosted.org/freeipa/ticket/5018 --- ACI.txt | 2 + API.txt | 9 ++ install/share/72domainlevels.ldif | 6 + install/share/Makefile.am | 2 + install/share/domainlevel.ldif | 7 ++ install/share/master-entry.ldif | 6 +- install/tools/ipa-replica-install | 32 ++++- install/tools/ipa-server-install | 22 +++- install/updates/72-domainlevels.update | 14 +++ install/updates/Makefile.am | 1 + ipalib/constants.py | 3 + ipalib/errors.py | 16 +++ ipalib/plugins/domainlevel.py | 138 +++++++++++++++++++++ ipaserver/install/dsinstance.py | 16 ++- ipaserver/install/ldapupdate.py | 5 + .../install/plugins/update_managed_permissions.py | 11 +- 16 files changed, 278 insertions(+), 12 deletions(-) create mode 100644 install/share/72domainlevels.ldif create mode 100644 install/share/domainlevel.ldif create mode 100644 install/updates/72-domainlevels.update create mode 100644 ipalib/plugins/domainlevel.py diff --git a/ACI.txt b/ACI.txt index bf539892910f14ebc3fbee88a72d2b57c0d1327b..3c4ebde5b3ac2eb0b8e9465c5f2bd74f5bdbfb01 100644 --- a/ACI.txt +++ b/ACI.txt @@ -322,6 +322,8 @@ dn: cn=dna,cn=ipa,cn=etc,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || dnahostname || dnaportnum || dnaremainingvalues || dnaremotebindmethod || dnaremoteconnprotocol || dnasecureportnum || entryusn || modifytimestamp || objectclass")(targetfilter = "(objectclass=dnasharedconfig)")(version 3.0;acl "permission:System: Read DNA Configuration";allow (compare,read,search) userdn = "ldap:///all";) dn: ou=profile,dc=ipa,dc=example aci: (targetattr = "attributemap || authenticationmethod || bindtimelimit || cn || createtimestamp || credentiallevel || defaultsearchbase || defaultsearchscope || defaultserverlist || dereferencealiases || entryusn || followreferrals || modifytimestamp || objectclass || objectclassmap || ou || preferredserverlist || profilettl || searchtimelimit || serviceauthenticationmethod || servicecredentiallevel || servicesearchdescriptor")(targetfilter = "(|(objectclass=organizationalUnit)(objectclass=DUAConfigProfile))")(version 3.0;acl "permission:System: Read DUA Profile";allow (compare,read,search) userdn = "ldap:///anyone";) +dn: cn=Domain Level,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetattr = "createtimestamp || entryusn || ipadomainlevel || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipadomainlevelconfig)")(version 3.0;acl "permission:System: Read Domain Level";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=masters,cn=ipa,cn=etc,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || ipaconfigstring || modifytimestamp || objectclass")(targetfilter = "(objectclass=nscontainer)")(version 3.0;acl "permission:System: Read IPA Masters";allow (compare,read,search) groupdn = "ldap:///cn=System: Read IPA Masters,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=config diff --git a/API.txt b/API.txt index 38deafefa57942bf242f989d79b1e93ee2c2013e..66f55e2d1f72857a02a0f2e1a3ba9472334524b7 100644 --- a/API.txt +++ b/API.txt @@ -1283,6 +1283,15 @@ option: Str('version?', exclude='webui') output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: PrimaryKey('value', None, None) +command: domainlevel_get +args: 0,1,1 +option: Str('version?', exclude='webui') +output: Output('result', <type 'int'>, None) +command: domainlevel_set +args: 1,1,1 +arg: Int('ipadomainlevel', cli_name='level', minvalue=0) +option: Str('version?', exclude='webui') +output: Output('result', <type 'int'>, None) command: env args: 1,3,4 arg: Str('variables*') diff --git a/install/share/72domainlevels.ldif b/install/share/72domainlevels.ldif new file mode 100644 index 0000000000000000000000000000000000000000..184e1cb220e80395bbe6ea063df9957ebde752ce --- /dev/null +++ b/install/share/72domainlevels.ldif @@ -0,0 +1,6 @@ +dn: cn=schema +attributeTypes: (2.16.840.1.113730.3.8.19.2.1 NAME 'ipaDomainLevel' DESC 'Domain Level value' EQUALITY numericStringMatch ORDERING numericStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.19.2.2 NAME 'ipaMinDomainLevel' DESC 'Minimal supported Domain Level value' EQUALITY numericStringMatch ORDERING numericStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.19.2.3 NAME 'ipaMaxDomainLevel' DESC 'Maximal supported Domain Level value' EQUALITY numericStringMatch ORDERING numericStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4') +objectClasses: (2.16.840.1.113730.3.8.19.1.1 NAME 'ipaDomainLevelConfig' SUP ipaConfigObject AUXILIARY DESC 'Domain Level Configuration' MUST (ipaDomainLevel) X-ORIGIN 'IPA v4') +objectClasses: (2.16.840.1.113730.3.8.19.1.2 NAME 'ipaSupportedDomainLevelConfig' SUP ipaConfigObject AUXILIARY DESC 'Supported Domain Level Configuration' MUST (ipaMinDomainLevel $ ipaMaxDomainLevel) X-ORIGIN 'IPA v4') diff --git a/install/share/Makefile.am b/install/share/Makefile.am index c39352caae69f9aa60ccb4f2ffce7ec01da26da1..8d336690f184025f8199ed1d2c57d8274f0d3886 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -22,6 +22,7 @@ app_DATA = \ 70ipaotp.ldif \ 70topology.ldif \ 71idviews.ldif \ + 72domainlevels.ldif \ anonymous-vlv.ldif \ bootstrap-template.ldif \ caJarSigningCert.cfg.template \ @@ -34,6 +35,7 @@ app_DATA = \ ds-nfiles.ldif \ dns.ldif \ dnssec.ldif \ + domainlevel.ldif \ kerberos.ldif \ indices.ldif \ bind.named.conf.template \ diff --git a/install/share/domainlevel.ldif b/install/share/domainlevel.ldif new file mode 100644 index 0000000000000000000000000000000000000000..21ed6a4738f25ce6668806cd44483e2d291dc361 --- /dev/null +++ b/install/share/domainlevel.ldif @@ -0,0 +1,7 @@ +# Create default Domain Level for new masters +dn: cn=Domain Level,cn=ipa,cn=etc,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +objectClass: ipaDomainLevelConfig +ipaDomainLevel: $DOMAIN_LEVEL diff --git a/install/share/master-entry.ldif b/install/share/master-entry.ldif index 34e5b34437eef663d37942b2f5d51c33ca999151..321b8c3681e2b86f3cb35cc512c04e45f34ea022 100644 --- a/install/share/master-entry.ldif +++ b/install/share/master-entry.ldif @@ -3,5 +3,9 @@ changetype: add objectclass: top objectclass: nsContainer objectclass: ipaReplTopoManagedServer -ipaReplTopoManagedSuffix: $SUFFIX +objectClass: ipaConfigObject +objectClass: ipaSupportedDomainLevelConfig cn: $FQDN +ipaReplTopoManagedSuffix: $SUFFIX +ipaMinDomainLevel: $MIN_DOMAIN_LEVEL +ipaMaxDomainLevel: $MAX_DOMAIN_LEVEL diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install index c75848b1ada91254a41245df240ede24c477d5b1..1df782b7304b3303f8e67ad943f0f2fbf1e96f9c 100755 --- a/install/tools/ipa-replica-install +++ b/install/tools/ipa-replica-install @@ -43,7 +43,7 @@ from ipaserver.install import cainstance from ipaserver.install import kra from ipaserver.install import dns as dns_installer from ipalib import api, create_api, errors, util, certstore, x509 -from ipalib.constants import CACERT +from ipalib import constants from ipapython import version from ipapython.config import IPAOptionParser from ipapython import sysrestore @@ -224,12 +224,12 @@ def install_ca_cert(ldap, base_dn, realm, cafile): try: certs = certstore.get_ca_certs(ldap, base_dn, realm, False) except errors.NotFound: - shutil.copy(cafile, CACERT) + shutil.copy(cafile, constants.CACERT) else: certs = [c[0] for c in certs if c[2] is not False] - x509.write_certificate_list(certs, CACERT) + x509.write_certificate_list(certs, constants.CACERT) - os.chmod(CACERT, 0444) + os.chmod(constants.CACERT, 0444) except Exception, e: print "error copying files: " + str(e) sys.exit(1) @@ -569,6 +569,30 @@ def main(): print " %% ipa-replica-manage del %s --force" % config.host_name exit(3) + # Detect the current domain level + try: + current = remote_api.Command['domainlevel_get']()['result'] + except errors.NotFound: + # If we're joining an older master, domain entry is not + # available + current = 0 + + # Detect if current level is out of supported range + # for this IPA version + under_lower_bound = current < constants.MIN_DOMAIN_LEVEL + above_upper_bound = current > constants.MAX_DOMAIN_LEVEL + + if under_lower_bound or above_upper_bound: + message = ("This version of FreeIPA does not support " + "the Domain Level which is currently set for " + "this domain. The Domain Level needs to be " + "raised before installing a replica with " + "this version is allowed to be installed " + "within this domain.") + root_logger.error(message) + print(message) + exit(3) + # Check pre-existing host entry try: entry = conn.find_entries(u'fqdn=%s' % config.host_name, ['fqdn'], DN(api.env.container_host, api.env.basedn)) diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install index 9bb8955dc15d1682edf33d7652de0829771267f3..c7d7c7bff7d5a5e818eaa4a8cb93be94eae7d0c1 100755 --- a/install/tools/ipa-server-install +++ b/install/tools/ipa-server-install @@ -70,7 +70,7 @@ from ipapython import sysrestore from ipapython.ipautil import * from ipapython import ipautil from ipapython import dogtag -from ipalib import api, errors, util, x509 +from ipalib import api, errors, util, x509, constants from ipapython.config import IPAOptionParser from ipalib.util import validate_domain_name from ipalib.constants import CACERT @@ -176,6 +176,8 @@ def parse_options(): help="create home directories for users " "on their first login") basic_group.add_option("--hostname", dest="host_name", help="fully qualified name of server") + basic_group.add_option("--domain-level", dest="domainlevel", help="IPA domain level", + default=constants.MAX_DOMAIN_LEVEL, type=int) basic_group.add_option("--ip-address", dest="ip_addresses", type="ip", ip_local=True, action="append", default=[], help="Master Server IP Address. This option can be used multiple times", @@ -327,6 +329,15 @@ def parse_options(): except ValueError, e: parser.error("invalid domain: " + unicode(e)) + # Check that Domain Level is within the allowed range + if not options.uninstall: + if options.domainlevel < constants.MIN_DOMAIN_LEVEL: + parser.error("Domain Level cannot be lower than {0}" + .format(constants.MIN_DOMAIN_LEVEL)) + elif options.domainlevel > constants.MAX_DOMAIN_LEVEL: + parser.error("Domain Level cannot be higher than {0}" + .format(constants.MAX_DOMAIN_LEVEL)) + if not options.setup_dns: if options.forwarders: parser.error("You cannot specify a --forwarder option without the --setup-dns option") @@ -1143,21 +1154,24 @@ def main(): ntp.create_instance() if options.dirsrv_cert_files: - ds = dsinstance.DsInstance(fstore=fstore) + ds = dsinstance.DsInstance(fstore=fstore, + domainlevel=options.domainlevel) ds.create_instance(realm_name, host_name, domain_name, dm_password, dirsrv_pkcs12_info, idstart=options.idstart, idmax=options.idmax, subject_base=options.subject, hbac_allow=not options.hbac_allow) else: - ds = dsinstance.DsInstance(fstore=fstore) + ds = dsinstance.DsInstance(fstore=fstore, + domainlevel=options.domainlevel) ds.create_instance(realm_name, host_name, domain_name, dm_password, idstart=options.idstart, idmax=options.idmax, subject_base=options.subject, hbac_allow=not options.hbac_allow) else: - ds = dsinstance.DsInstance(fstore=fstore) + ds = dsinstance.DsInstance(fstore=fstore, + domainlevel=options.domainlevel) ds.init_info( realm_name, host_name, domain_name, dm_password, options.subject, 1101, 1100, None) diff --git a/install/updates/72-domainlevels.update b/install/updates/72-domainlevels.update new file mode 100644 index 0000000000000000000000000000000000000000..2e83c7be9b200121081470a80a3a9303d685a789 --- /dev/null +++ b/install/updates/72-domainlevels.update @@ -0,0 +1,14 @@ +# Create default Domain Level entry if it does not exist +dn: cn=Domain Level,cn=ipa,cn=etc,$SUFFIX +default: objectClass: top +default: objectClass: nsContainer +default: objectClass: ipaDomainLevelConfig +default: ipaDomainLevel: 0 + +# Create entry proclaiming Domain Level support of this master +# This will update the supported Domain Levels during upgrade +dn: cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX +add: objectClass: ipaConfigObject +add: objectClass: ipaSupportedDomainLevelConfig +only: ipaMinDomainLevel: $MIN_DOMAIN_LEVEL +only: ipaMaxDomainLevel: $MAX_DOMAIN_LEVEL diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index 66f6b9d37971f8b8501d73fc6ddca21b6686ff4b..4e2da05d61a41543914e79c4634331df6018c041 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -49,6 +49,7 @@ app_DATA = \ 61-trusts-s4u2proxy.update \ 62-ranges.update \ 71-idviews.update \ + 72-domainlevels.update \ 90-post_upgrade_plugins.update \ $(NULL) diff --git a/ipalib/constants.py b/ipalib/constants.py index 195938a355d1b24c02aa0a5833c1725c76e85c76..b99306eaec1d7bcbec4612a8aa4a599d02ac73e5 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -224,3 +224,6 @@ LDAP_GENERALIZED_TIME_FORMAT = "%Y%m%d%H%M%SZ" IPA_ANCHOR_PREFIX = ':IPA:' SID_ANCHOR_PREFIX = ':SID:' + +MIN_DOMAIN_LEVEL = 0 +MAX_DOMAIN_LEVEL = 1 diff --git a/ipalib/errors.py b/ipalib/errors.py index 89b1ef2e0dc1d7346a69fb813bd71990746c620c..63ec22269467b769d276c443f6b3dbed38cd766e 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1344,6 +1344,22 @@ class EmptyResult(NotFound): errno = 4031 +class InvalidDomainLevelError(ExecutionError): + """ + **4032** Raised when a operation could not be completed due to a invalid + domain level. + + For example: + + >>> raise InvalidDomainLevelError(reason='feature requires domain level 4') + Traceback (most recent call last): + ... + InvalidDomainLevelError: feature requires domain level 4 + + """ + + errno = 4032 + class BuiltinError(ExecutionError): """ **4100** Base class for builtin execution errors (*4100 - 4199*). diff --git a/ipalib/plugins/domainlevel.py b/ipalib/plugins/domainlevel.py new file mode 100644 index 0000000000000000000000000000000000000000..64e383006722fb2f32f5300d627b18b6daf051d4 --- /dev/null +++ b/ipalib/plugins/domainlevel.py @@ -0,0 +1,138 @@ +# +# Copyright (C) 2015 FreeIPA Contributors see COPYING for license +# + +from collections import namedtuple + +from ipalib import _ +from ipalib import Command +from ipalib import errors +from ipalib import output +from ipalib.parameters import Int +from ipalib.plugable import Registry +from ipalib.plugins.baseldap import LDAPObject, LDAPUpdate, LDAPRetrieve + +from ipapython.dn import DN + + +__doc__ = _(""" +Raise the IPA Domain Level. +""") + +register = Registry() + +DomainLevelRange = namedtuple('DomainLevelRange', ['min', 'max']) + +domainlevel_output = ( + output.Output('result', int, _('Current domain level:')), +) + + +def get_domainlevel_dn(api): + domainlevel_dn = DN( + ('cn', 'Domain Level'), + ('cn', 'ipa'), + ('cn', 'etc'), + api.env.basedn + ) + + return domainlevel_dn + + +def get_domainlevel_range(master_entry): + try: + return DomainLevelRange( + int(master_entry['ipaMinDomainLevel'][0]), + int(master_entry['ipaMaxDomainLevel'][0]) + ) + except KeyError: + return DomainLevelRange(0, 0) + + +def get_master_entries(ldap, api): + """ + Returns list of LDAPEntries representing IPA masters. + """ + + container_masters = DN( + ('cn', 'masters'), + ('cn', 'ipa'), + ('cn', 'etc'), + api.env.basedn + ) + + masters, _ = ldap.find_entries( + filter="(cn=*)", + base_dn=container_masters, + scope=ldap.SCOPE_ONELEVEL, + paged_search=True, # we need to make sure to get all of them + ) + + return masters + + +@register() +class domainlevel_get(Command): + __doc__ = _('Query current Domain Level.') + + has_output = domainlevel_output + + def execute(self, *args, **options): + ldap = self.api.Backend.ldap2 + entry = ldap.get_entry( + get_domainlevel_dn(self.api), + ['ipaDomainLevel'] + ) + + return {'result': int(entry.single_value['ipaDomainLevel'])} + + +@register() +class domainlevel_set(Command): + __doc__ = _('Change current Domain Level.') + + has_output = domainlevel_output + + takes_args = ( + Int('ipadomainlevel', + cli_name='level', + label=_('Domain Level'), + minvalue=0, + ), + ) + + def execute(self, *args, **options): + """ + Checks all the IPA masters for supported domain level ranges. + + If the desired domain level is within the supported range of all + masters, it will be raised. + + Domain level cannot be lowered. + """ + + ldap = self.api.Backend.ldap2 + + current_entry = ldap.get_entry(get_domainlevel_dn(self.api)) + current_value = int(current_entry.single_value['ipadomainlevel']) + desired_value = int(args[0]) + + # Domain level cannot be lowered + if int(desired_value) < int(current_value): + message = _("Domain Level cannot be lowered.") + raise errors.InvalidDomainLevelError(message) + + # Check if every master supports the desired level + for master in get_master_entries(ldap, self.api): + supported = get_domainlevel_range(master) + + if supported.min > desired_value or supported.max < desired_value: + message = _("Domain Level cannot be raised to {0}, server {1} " + "does not support it." + .format(desired_value, master['cn'][0])) + raise errors.InvalidDomainLevelError(message) + + current_entry.single_value['ipaDomainLevel'] = desired_value + ldap.update_entry(current_entry) + + return {'result': int(current_entry.single_value['ipaDomainLevel'])} diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 09139405dfebae0ee3f82848a1b6b73e49e49687..064a2ab1db61b465638a77e13e1d9ea43b1cce63 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -40,6 +40,7 @@ from ipaserver.install import upgradeinstance from ipalib import api from ipalib import certstore from ipalib import errors +from ipalib import constants from ipaplatform.tasks import tasks from ipalib.constants import CACERT from ipapython.dn import DN @@ -62,6 +63,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif", "70ipaotp.ldif", "70topology.ldif", "71idviews.ldif", + "72domainlevels.ldif", "15rfc2307bis.ldif", "15rfc4876.ldif") @@ -186,7 +188,7 @@ info: IPA V2.0 class DsInstance(service.Service): def __init__(self, realm_name=None, domain_name=None, dm_password=None, - fstore=None): + fstore=None, domainlevel=None): service.Service.__init__(self, "dirsrv", service_desc="directory server", dm_password=dm_password, @@ -209,6 +211,7 @@ class DsInstance(service.Service): self.subject_base = None self.open_ports = [] self.run_init_memberof = True + self.domainlevel = domainlevel if realm_name: self.suffix = ipautil.realm_to_suffix(self.realm) self.__setup_sub_dict() @@ -254,6 +257,7 @@ class DsInstance(service.Service): def __common_post_setup(self): self.step("initializing group membership", self.init_memberof) self.step("adding master entry", self.__add_master_entry) + self.step("initializing domain level", self.__set_domain_level) self.step("configuring Posix uid/gid generation", self.__config_uidgid_gen) self.step("adding replication acis", self.__add_replication_acis) @@ -395,7 +399,10 @@ class DsInstance(service.Service): IDMAX=self.idmax, HOST=self.fqdn, ESCAPED_SUFFIX=str(self.suffix), GROUP=DS_GROUP, - IDRANGE_SIZE=idrange_size + IDRANGE_SIZE=idrange_size, + DOMAIN_LEVEL=self.domainlevel, + MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL, + MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL, ) def __create_instance(self): @@ -1011,3 +1018,8 @@ class DsInstance(service.Service): root_logger.debug('Unable to find certificate subject base in ' 'certmap.conf') return None + + def __set_domain_level(self): + # Create global domain level entry and set the domain level + if self.domainlevel is not None: + self._ldap_mod("domainlevel.ldif", self.sub_dict) diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index 2f5bcc748eb546b4dad7e1aeeb7a2882a40d8d35..4aa463152b9ec05fb5e1de9e1a5e386f6fc46e6f 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -39,6 +39,7 @@ from ipaserver.install import installutils from ipapython import ipautil, ipaldap from ipalib import errors from ipalib import api, create_api +from ipalib import constants from ipaplatform.paths import paths from ipaplatform import services from ipapython.dn import DN @@ -305,6 +306,10 @@ class LDAPUpdate: self.sub_dict["TIME"] = int(time.time()) if not self.sub_dict.get("DOMAIN") and domain is not None: self.sub_dict["DOMAIN"] = domain + if not self.sub_dict.get("MIN_DOMAIN_LEVEL"): + self.sub_dict["MIN_DOMAIN_LEVEL"] = str(constants.MIN_DOMAIN_LEVEL) + if not self.sub_dict.get("MAX_DOMAIN_LEVEL"): + self.sub_dict["MAX_DOMAIN_LEVEL"] = str(constants.MAX_DOMAIN_LEVEL) self.api = create_api(mode=None) self.api.bootstrap(in_server=True, context='updates') self.api.finalize() diff --git a/ipaserver/install/plugins/update_managed_permissions.py b/ipaserver/install/plugins/update_managed_permissions.py index 1fbfd9993fa2c871690b58cdce7000cd3deba0d5..11765fba33342eb0168cfffa2f354f5ffc8cf4ef 100644 --- a/ipaserver/install/plugins/update_managed_permissions.py +++ b/ipaserver/install/plugins/update_managed_permissions.py @@ -338,7 +338,16 @@ NONOBJECT_PERMISSIONS = { 'serviceAuthenticationMethod', 'objectclassMap', 'attributeMap', 'profileTTL' }, - } + }, + 'System: Read Domain Level': { + 'ipapermlocation': DN('cn=Domain Level,cn=ipa,cn=etc', api.env.basedn), + 'ipapermtargetfilter': {'(objectclass=ipadomainlevelconfig)'}, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'ipadomainlevel', 'objectclass', + }, + }, } -- 2.1.0
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code