From afb8305ada944293f978a20f3829d0ab93180c2d Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Jun 29 2019 08:00:28 +0000 Subject: ipaserver.plugins.service: add service-add-smb to set up an SMB service SMB service has a number of predefined properties that must be set at a creation time. Thus, we provide a special command that handles all the needed changes. In addition, since SMB principal name is predefined, it is generated automatically based on the machine hostname. Since we generate the service's object primary key, its argument/option should be removed from the list of the command's arguments and options. We also remove those options that make no sense in the context of SMB service. Most controversial would probably be a lack of the authentication indicator that could be associated with the service. However, this is intended: SMB service on the domain member is used by both humans and other SMB services in the domain. Thus, it is not possible to require a specific authentication indicator to be present: automated acquisition of the credentials by a domain controller or other domain member machine accounts is based on a single factor creds and cannot be changed. Access to SMB service should be regulated on the SMB protocol level, with access controls in share ACLs. Fixes: https://pagure.io/freeipa/issue/3999 Signed-off-by: Alexander Bokovoy Reviewed-By: Rob Crittenden Reviewed-By: Christian Heimes --- diff --git a/ACI.txt b/ACI.txt index fc77575..5ad8d12 100644 --- a/ACI.txt +++ b/ACI.txt @@ -273,6 +273,8 @@ aci: (targetattr = "krbcanonicalname || krbprincipalname")(targetfilter = "(obje dn: cn=services,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbprincipalauthind || usercertificate")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Modify Services";allow (write) groupdn = "ldap:///cn=System: Modify Services,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=services,cn=accounts,dc=ipa,dc=example +aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || ipantsecurityidentifier || loginshell || modifytimestamp || objectclass || uid || uidnumber")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Read POSIX details of SMB services";allow (compare,read,search) userdn = "ldap:///all";) +dn: cn=services,cn=accounts,dc=ipa,dc=example aci: (targetattr = "createtimestamp || entryusn || ipakrbauthzdata || ipakrbprincipalalias || ipauniqueid || krbcanonicalname || krblastpwdchange || krbobjectreferences || krbpasswordexpiration || krbprincipalaliases || krbprincipalauthind || krbprincipalexpiration || krbprincipalname || managedby || memberof || modifytimestamp || objectclass || usercertificate")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Read Services";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=services,cn=accounts,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Remove Services";allow (delete) groupdn = "ldap:///cn=System: Remove Services,cn=permissions,cn=pbac,dc=ipa,dc=example";) diff --git a/API.txt b/API.txt index 599cfa7..8fb94b1 100644 --- a/API.txt +++ b/API.txt @@ -4537,6 +4537,22 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: service_add_smb/1 +args: 2,9,3 +arg: Str('fqdn', cli_name='hostname') +arg: Str('ipantflatname?', cli_name='netbiosname') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bool('ipakrbokasdelegate?', cli_name='ok_as_delegate') +option: Bool('ipakrboktoauthasdelegate?', cli_name='ok_to_auth_as_delegate') +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: Certificate('usercertificate*', cli_name='certificate') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: service_allow_create_keytab/1 args: 1,8,3 arg: Principal('krbcanonicalname', cli_name='canonical_principal') @@ -6922,6 +6938,7 @@ default: service_add/1 default: service_add_cert/1 default: service_add_host/1 default: service_add_principal/1 +default: service_add_smb/1 default: service_allow_create_keytab/1 default: service_allow_retrieve_keytab/1 default: service_del/1 diff --git a/VERSION.m4 b/VERSION.m4 index 0f84bd6..10261fb 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 232) -# Last change: Add ipamaxhostnamelength to config +define(IPA_API_VERSION_MINOR, 233) +# Last change: Added service_add_smb command ######################################################## diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py index 626b43e..f58fe4b 100644 --- a/ipaserver/plugins/service.py +++ b/ipaserver/plugins/service.py @@ -44,14 +44,15 @@ from .baseldap import ( LDAPAddAttribute, LDAPRemoveAttribute, LDAPAddAttributeViaOption, - LDAPRemoveAttributeViaOption) + LDAPRemoveAttributeViaOption, + DNA_MAGIC) from ipalib import x509 from ipalib import _, ngettext from ipalib import util from ipalib import output from ipapython import kerberos from ipapython.dn import DN - +from ipapython.dnsutil import DNSName if six.PY3: unicode = str @@ -447,6 +448,16 @@ class service(LDAPObject): ], 'default_privileges': {'Service Administrators'}, }, + 'System: Read POSIX details of SMB services': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'uid', 'gecos', 'gidnumber', + 'homedirectory', 'loginshell', 'uidnumber', + 'ipantsecurityidentifier', + }, + } } label = _('Services') @@ -664,6 +675,133 @@ class service_add(LDAPCreate): return dn +@register() +class service_add_smb(LDAPCreate): + __doc__ = _('Add a new SMB service.') + + msg_summary = _('Added service "%(value)s"') + member_attributes = ['managedby'] + has_output_params = LDAPCreate.has_output_params + output_params + smb_takes_args = ( + Str('fqdn', util.hostname_validator, + cli_name='hostname', + label=_('Host name'), + primary_key=True, + normalizer=util.normalize_hostname, + flags={'virtual_attribute', 'no_display', 'no_update', + 'no_search'}, + ), + Str('ipantflatname?', + cli_name='netbiosname', + label=_('SMB service NetBIOS name'), + flags={'virtual_attribute', 'no_display', 'no_update', + 'no_search'}, + ), + ) + + takes_options = LDAPCreate.takes_options + + def get_args(self): + """ + Rewrite arguments to service-add-smb command to make sure we accept + hostname instead of a principal as we'll be constructing the principal + ourselves + """ + for arg in self.smb_takes_args: + yield arg + for arg in super(service_add_smb, self).get_args(): + if arg not in self.smb_takes_args and not arg.primary_key: + yield arg + + def get_options(self): + """ + Rewrite options to service-add-smb command to filter out cannonical + principal which is autoconstructed. Also filter out options which + make no sense for SMB service. + """ + excluded = ('ipakrbauthzdata', 'krbprincipalauthind', + 'ipakrbrequirespreauth') + for arg in self.takes_options: + yield arg + for arg in super(service_add_smb, self).get_options(): + check = all([arg not in self.takes_options, + not arg.primary_key, + arg.name not in excluded]) + if check: + yield arg + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, + *keys, **options): + assert isinstance(dn, DN) + hostname = keys[0] + if len(keys) == 2: + netbiosname = keys[1] + else: + # By default take leftmost label from the host name + netbiosname = DNSName.from_text(hostname)[0].decode().upper() + + # SMB service requires existence of the host object + # because DCE RPC calls authenticated with GSSAPI are using + # host/.. principal by default for validation + 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) + + # We cannot afford the host not being resolvable even for + # clustered environments with CTDB because the target name + # has to exist even in that case + util.verify_host_resolvable(hostname) + + smbaccount = '{name}$'.format(name=netbiosname) + smbprincipal = 'cifs/{hostname}'.format(hostname=hostname) + + entry_attrs['krbprincipalname'] = [ + str(kerberos.Principal(smbprincipal, realm=self.api.env.realm)), + str(kerberos.Principal(smbaccount, realm=self.api.env.realm))] + + entry_attrs['krbcanonicalname'] = entry_attrs['krbprincipalname'][0] + + # Rewrite DN using proper rdn and new canonical name because when + # LDAPCreate.execute() was called, it set DN to krbcanonicalname=$value + dn = DN(('krbprincipalname', entry_attrs['krbcanonicalname']), + DN(self.obj.container_dn, api.env.basedn)) + + # Enforce ipaKrbPrincipalAlias to aid case-insensitive searches as + # krbPrincipalName/krbCanonicalName are case-sensitive in Kerberos + # schema + entry_attrs['ipakrbprincipalalias'] = entry_attrs['krbcanonicalname'] + + for o in ('ipakrbprincipal', 'ipaidobject', 'krbprincipalaux', + 'posixaccount'): + if o not in entry_attrs['objectclass']: + entry_attrs['objectclass'].append(o) + + entry_attrs['uid'] = ['/'.join( + kerberos.Principal(smbprincipal).components)] + entry_attrs['uid'].append(smbaccount) + entry_attrs['cn'] = netbiosname + entry_attrs['homeDirectory'] = '/dev/null' + entry_attrs['uidNumber'] = DNA_MAGIC + entry_attrs['gidNumber'] = DNA_MAGIC + + self.obj.validate_ipakrbauthzdata(entry_attrs) + + if 'managedby' not in entry_attrs: + entry_attrs['managedby'] = hostresult['dn'] + + update_krbticketflags(ldap, entry_attrs, attrs_list, options, False) + + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + set_kerberos_attrs(entry_attrs, options) + rename_ipaallowedtoperform_from_ldap(entry_attrs, options) + self.obj.populate_krbcanonicalname(entry_attrs, options) + return dn + @register() class service_del(LDAPDelete):