From 9e8fb94e87339b9908ec05fe5274ca51df3a82cf Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: May 08 2018 20:39:22 +0000 Subject: service: allow creating services without a host to manage them Add --skip-host-check option to ipa service-add command to allow creating services without corresponding host object. This is needed to cover use cases where Kerberos services created to handle client authentication in a dynamically generated environment like Kubernetes. Fixes: https://pagure.io/freeipa/issue/7514 Reviewed-By: Rob Crittenden --- diff --git a/API.txt b/API.txt index 05dec44..488452d 100644 --- a/API.txt +++ b/API.txt @@ -4457,7 +4457,7 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: service_add/1 -args: 1,13,3 +args: 1,14,3 arg: Principal('krbcanonicalname', cli_name='canonical_principal') option: Str('addattr*', cli_name='addattr') option: Flag('all', autofill=True, cli_name='all', default=False) @@ -4470,6 +4470,7 @@ option: Str('krbprincipalauthind*', cli_name='auth_ind') option: Flag('no_members', autofill=True, default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Str('setattr*', cli_name='setattr') +option: Flag('skip_host_check', autofill=True, default=False) option: Certificate('usercertificate*', cli_name='certificate') option: Str('version?') output: Entry('result') diff --git a/install/ui/src/freeipa/service.js b/install/ui/src/freeipa/service.js index 93808b0..6e377f2 100644 --- a/install/ui/src/freeipa/service.js +++ b/install/ui/src/freeipa/service.js @@ -407,6 +407,7 @@ return { other_entity: 'host', other_field: 'fqdn', label: '@i18n:objects.service.host', + editable: true, required: true, z_index: 1 }, @@ -414,6 +415,11 @@ return { $type: 'checkbox', name: 'force', metadata: '@mc-opt:service_add:force' + }, + { + $type: 'checkbox', + name: 'skip_host_check', + metadata: '@mc-opt:service_add:skip_host_check' } ] } @@ -543,6 +549,9 @@ IPA.service_adder_dialog = function(spec) { field = that.fields.get_field('force'); record['force'] = field.save(); + + field = that.fields.get_field('skip_host_check'); + record['skip_host_check'] = field.save(); }; init(); diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py index be31f81..8c8448c 100644 --- a/ipaserver/plugins/service.py +++ b/ipaserver/plugins/service.py @@ -601,9 +601,14 @@ class service_add(LDAPCreate): has_output_params = LDAPCreate.has_output_params + output_params takes_options = LDAPCreate.takes_options + ( Flag('force', - label=_('Force'), - doc=_('force principal name even if not in DNS'), + label=_('Force'), + doc=_('force principal name even if host not in DNS'), ), + Flag('skip_host_check', + label=_('Skip host check'), + doc=_('force service to be created even when host ' + 'object does not exist to manage it'), + ), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): @@ -614,12 +619,13 @@ class service_add(LDAPCreate): if principal.is_host and not options['force']: raise errors.HostService() - try: - hostresult = self.api.Command['host_show'](hostname)['result'] - except errors.NotFound: - raise errors.NotFound( - reason=_("The host '%s' does not exist to add a service to.") % - hostname) + if not options['skip_host_check']: + try: + hostresult = self.api.Command['host_show'](hostname)['result'] + except errors.NotFound: + raise errors.NotFound(reason=_( + "The host '%s' does not exist to add a service to.") % + hostname) self.obj.validate_ipakrbauthzdata(entry_attrs) @@ -628,7 +634,7 @@ class service_add(LDAPCreate): # really want to discourage creating services for hosts that # don't exist in DNS. util.verify_host_resolvable(hostname) - if not 'managedby' in entry_attrs: + if not (options['skip_host_check'] or 'managedby' in entry_attrs): entry_attrs['managedby'] = hostresult['dn'] # Enforce ipaKrbPrincipalAlias to aid case-insensitive searches diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py index c910269..c3569a5 100644 --- a/ipatests/test_xmlrpc/test_service_plugin.py +++ b/ipatests/test_xmlrpc/test_service_plugin.py @@ -47,6 +47,11 @@ service1dn = DN(('krbprincipalname',service1),('cn','services'),('cn','accounts' host1dn = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'),api.env.basedn) host2dn = DN(('fqdn',fqdn2),('cn','computers'),('cn','accounts'),api.env.basedn) host3dn = DN(('fqdn',fqdn3),('cn','computers'),('cn','accounts'),api.env.basedn) +d_service_no_realm = u'some/at.some.arbitrary.name' +d_service = u'%s@%s' % (d_service_no_realm, api.env.realm) +d_servicedn = DN(('krbprincipalname', d_service), + ('cn', 'services'), ('cn', 'accounts'), + api.env.basedn) role1 = u'Test Role' role1_dn = DN(('cn', role1), api.env.container_rolegroup, api.env.basedn) @@ -87,6 +92,7 @@ class test_service(Declarative): ('host_del', [fqdn2], {}), ('host_del', [fqdn3], {}), ('service_del', [service1], {}), + ('service_del', [d_service], {}), ] tests = [ @@ -732,6 +738,23 @@ class test_service(Declarative): ), + # Create a service disconnected from any host + dict( + desc='Try to create service %r without any host' % d_service, + command=('service_add', [d_service_no_realm], + dict(force=True, skip_host_check=True),), + expected=dict( + value=d_service, + summary=u'Added service "%s"' % d_service, + result=dict( + dn=d_servicedn, + krbprincipalname=[d_service], + krbcanonicalname=[d_service], + objectclass=objectclasses.service, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), ]