summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2011-11-23 16:59:21 -0500
committerAlexander Bokovoy <abokovoy@redhat.com>2011-12-09 16:46:25 +0200
commit55512dc938eb4a9a6655e473beab587e340af55c (patch)
tree27805ce2bcbd5b9fbb759cfab781ad3575a83792
parenta1c9e3618c9d0e03fc926031f2c65d92da7a8b03 (diff)
downloadfreeipa-55512dc938eb4a9a6655e473beab587e340af55c.tar.gz
freeipa-55512dc938eb4a9a6655e473beab587e340af55c.tar.xz
freeipa-55512dc938eb4a9a6655e473beab587e340af55c.zip
Add SELinux user mapping framework.
This will allow one to define what SELinux context a given user gets on a given machine. A rule can contain a set of users and hosts or it can point to an existing HBAC rule that defines them. https://fedorahosted.org/freeipa/ticket/755
-rw-r--r--API.txt130
-rw-r--r--install/share/60basev3.ldif2
-rw-r--r--install/share/60ipaconfig.ldif4
-rw-r--r--install/share/bootstrap-template.ldif15
-rw-r--r--install/updates/40-delegation.update38
-rw-r--r--install/updates/50-ipaconfig.update4
-rw-r--r--install/updates/Makefile.am1
-rw-r--r--ipalib/constants.py1
-rw-r--r--ipalib/errors.py16
-rw-r--r--ipalib/plugins/config.py41
-rw-r--r--ipalib/plugins/hbacrule.py8
-rw-r--r--ipalib/plugins/selinuxusermap.py441
-rw-r--r--tests/test_xmlrpc/objectclasses.py12
-rw-r--r--tests/test_xmlrpc/test_selinuxusermap_plugin.py600
14 files changed, 1309 insertions, 4 deletions
diff --git a/API.txt b/API.txt
index ed8b5553d..c2f4863fc 100644
--- a/API.txt
+++ b/API.txt
@@ -444,7 +444,7 @@ args: 1,0,1
arg: Str('request_id')
output: Output('result', None, None)
command: config_mod
-args: 0,20,3
+args: 0,22,3
option: Int('ipamaxusernamelength', attribute=True, autofill=False, cli_name='maxusername', minvalue=1, multivalue=False, required=False)
option: IA5Str('ipahomesrootdir', attribute=True, autofill=False, cli_name='homedirectory', multivalue=False, required=False)
option: Str('ipadefaultloginshell', attribute=True, autofill=False, cli_name='defaultshell', multivalue=False, required=False)
@@ -458,6 +458,8 @@ option: Bool('ipamigrationenabled', attribute=True, autofill=False, cli_name='en
option: Str('ipagroupobjectclasses', attribute=True, autofill=False, cli_name='groupobjectclasses', csv=True, multivalue=True, required=False)
option: Str('ipauserobjectclasses', attribute=True, autofill=False, cli_name='userobjectclasses', csv=True, multivalue=True, required=False)
option: Int('ipapwdexpadvnotify', attribute=True, autofill=False, cli_name='pwdexpnotify', minvalue=0, multivalue=False, required=False)
+option: Str('ipaselinuxusermaporder', attribute=True, autofill=False, cli_name='ipaselinuxusermaporder', multivalue=False, required=False)
+option: Str('ipaselinuxusermapdefault', attribute=True, autofill=False, cli_name='ipaselinuxusermapdefault', multivalue=False, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Str('delattr*', cli_name='delattr', exclude='webui')
@@ -2314,6 +2316,132 @@ option: Str('version?', exclude='webui')
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_add
+args: 1,10,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
+option: Str('ipaselinuxuser', attribute=True, cli_name='selinuxuser', multivalue=False, required=True)
+option: Str('seealso', attribute=True, cli_name='hbacrule', multivalue=False, required=False)
+option: StrEnum('usercategory', attribute=True, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
+option: StrEnum('hostcategory', attribute=True, cli_name='hostcat', multivalue=False, required=False, values=(u'all',))
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_add_host
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+option: Str('host*', alwaysask=True, cli_name='hosts', csv=True)
+option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('failed', <type 'dict'>, None)
+output: Output('completed', <type 'int'>, None)
+command: selinuxusermap_add_user
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('failed', <type 'dict'>, None)
+output: Output('completed', <type 'int'>, None)
+command: selinuxusermap_del
+args: 1,1,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('result', <type 'dict'>, None)
+output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_disable
+args: 1,0,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('result', <type 'bool'>, None)
+output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_enable
+args: 1,0,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('result', <type 'bool'>, None)
+output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_find
+args: 1,12,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Str('cn', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('ipaselinuxuser', attribute=True, autofill=False, cli_name='selinuxuser', multivalue=False, query=True, required=False)
+option: Str('seealso', attribute=True, autofill=False, cli_name='hbacrule', multivalue=False, query=True, required=False)
+option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, query=True, required=False, values=(u'all',))
+option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostcat', multivalue=False, query=True, required=False, values=(u'all',))
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+option: Flag('pkey_only?', autofill=True, default=False)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('count', <type 'int'>, None)
+output: Output('truncated', <type 'bool'>, None)
+command: selinuxusermap_mod
+args: 1,12,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('ipaselinuxuser', attribute=True, autofill=False, cli_name='selinuxuser', multivalue=False, required=False)
+option: Str('seealso', attribute=True, autofill=False, cli_name='hbacrule', multivalue=False, required=False)
+option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
+option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostcat', multivalue=False, required=False, values=(u'all',))
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('value', <type 'unicode'>, None)
+command: selinuxusermap_remove_host
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+option: Str('host*', alwaysask=True, cli_name='hosts', csv=True)
+option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('failed', <type 'dict'>, None)
+output: Output('completed', <type 'int'>, None)
+command: selinuxusermap_remove_user
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('failed', <type 'dict'>, None)
+output: Output('completed', <type 'int'>, None)
+command: selinuxusermap_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('rights', autofill=True, default=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('value', <type 'unicode'>, None)
command: service_add
args: 1,5,3
arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, required=True)
diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif
index 104cffb2b..eec1aea83 100644
--- a/install/share/60basev3.ldif
+++ b/install/share/60basev3.ldif
@@ -26,6 +26,7 @@ attributeTypes: ( 2.16.840.1.113730.3.8.11.19 NAME 'ipaNTSupportedEncryptionType
attributeTypes: ( 2.16.840.1.113730.3.8.11.20 NAME 'memberPrincipal' DESC 'Principal names member of a groupOfPrincipals group' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA-v3')
attributeTypes: ( 2.16.840.1.113730.3.8.11.21 NAME 'ipaAllowToImpersonate' DESC 'Principals that can be impersonated' SUP distinguishedName X-ORIGIN 'IPA-v3')
attributeTypes: ( 2.16.840.1.113730.3.8.11.22 NAME 'ipaAllowedTarget' DESC 'Target principals alowed to get a ticket for' SUP distinguishedName X-ORIGIN 'IPA-v3')
+attributeTypes: (2.16.840.1.113730.3.8.11.30 NAME 'ipaSELinuxUser' DESC 'An SELinux user' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
objectClasses: (2.16.840.1.113730.3.8.12.1 NAME 'ipaExternalGroup' SUP top STRUCTURAL MUST ( cn ) MAY ( ipaExternalMember $ memberOf $ description $ owner) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.2 NAME 'ipaNTUserAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) MAY ( ipaNTHash $ ipaNTLogonScript $ ipaNTProfilePath $ ipaNTHomeDirectory $ ipaNTHomeDirectoryDrive ) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.3 NAME 'ipaNTGroupAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) X-ORIGIN 'IPA v3' )
@@ -33,3 +34,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.4 NAME 'ipaNTDomainAttrs' SUP top AUXIL
objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $ ipaNTTrustAttributes $ ipaNTTrustDirection $ ipaNTTrustPartner $ ipaNTFlatName $ ipaNTTrustAuthOutgoing $ ipaNTTrustAuthIncoming $ ipaNTSecurityIdentifier $ ipaNTTrustForestTrustInfo $ ipaNTTrustPosixOffset $ ipaNTSupportedEncryptionTypes) )
objectClasses: (2.16.840.1.113730.3.8.12.6 NAME 'groupOfPrincipals' SUP top AUXILIARY MUST ( cn ) MAY ( memberPrincipal ) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.7 NAME 'ipaKrb5DelegationACL' SUP groupOfPrincipals STRUCTURAL MAY ( ipaAllowToImpersonate $ ipaAllowedTarget ) X-ORIGIN 'IPA v3' )
+objectClasses: (2.16.840.1.113730.3.8.12.10 NAME 'ipaSELinuxUserMap' SUP ipaAssociation STRUCTURAL MUST ipaSELinuxUser MAY ( accessTime $ seeAlso ) X-ORIGIN 'IPA v3')
diff --git a/install/share/60ipaconfig.ldif b/install/share/60ipaconfig.ldif
index 767bfa94f..1b3a42b8a 100644
--- a/install/share/60ipaconfig.ldif
+++ b/install/share/60ipaconfig.ldif
@@ -41,11 +41,13 @@ attributeTypes: ( 2.16.840.1.113730.3.8.3.22 NAME 'ipaMigrationEnabled' DESC 'En
attributetypes: ( 2.16.840.1.113730.3.8.3.23 NAME 'ipaCertificateSubjectBase' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
## ipaConfigString - can hold any string to be used as configuration for something (it is multivalued)
attributeTypes: (2.16.840.1.113730.3.8.3.16 NAME 'ipaConfigString' DESC 'Generic configuration stirng' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+attributeTypes: ( 2.16.840.1.113730.3.8.3.26 NAME 'ipaSELinuxUserMapDefault' DESC 'Default SELinux user' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
+attributeTypes: ( 2.16.840.1.113730.3.8.3.27 NAME 'ipaSELinuxUserMapOrder' DESC 'Available SELinux user context ordering' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
###############################################
##
## ObjectClasses
##
## ipaGuiConfig - GUI config parameters objectclass
-objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase) )
+objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder) )
## ipaConfigObject - Generic config strings object holder
objectClasses: (2.16.840.1.113730.3.8.4.13 NAME 'ipaConfigObject' DESC 'generic config object for IPA' AUXILIARY MAY ( ipaConfigString ) X-ORIGIN 'IPA v2' )
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index fbad4abaa..4f6bc3c97 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -346,6 +346,8 @@ ipaUserObjectClasses: ipaobject
ipaDefaultEmailDomain: $DOMAIN
ipaMigrationEnabled: FALSE
ipaConfigString: AllowNThash
+ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0-s0:c0.c1023$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023
+ipaSELinuxUserMapDefault: guest_u:s0
dn: cn=cosTemplates,cn=accounts,$SUFFIX
changetype: add
@@ -364,3 +366,16 @@ objectClass: cosClassicDefinition
cosTemplateDn: cn=cosTemplates,cn=accounts,$SUFFIX
cosAttribute: krbPwdPolicyReference override
cosSpecifier: memberOf
+
+dn: cn=selinux,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: nsContainer
+cn: selinux
+
+dn: cn=usermap,cn=selinux,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: nsContainer
+cn: usermap
+
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 3502ce89a..cd5b498a8 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -267,3 +267,41 @@ dn: $SUFFIX
replace:aci:'(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Add DNS entries";allow (add) groupdn = "ldap:///cn=add dns entries,cn=permissions,cn=pbac,$SUFFIX";)::(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:add dns entries";allow (add) groupdn = "ldap:///cn=add dns entries,cn=permissions,cn=pbac,$SUFFIX";)'
replace:aci:'(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Remove DNS entries";allow (delete) groupdn = "ldap:///cn=remove dns entries,cn=permissions,cn=pbac,$SUFFIX";)::(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:remove dns entries";allow (delete) groupdn = "ldap:///cn=remove dns entries,cn=permissions,cn=pbac,$SUFFIX";)'
replace:aci:'(targetattr = "idnsname || cn || idnsallowdynupdate || dnsttl || dnsclass || arecord || aaaarecord || a6record || nsrecord || cnamerecord || ptrrecord || srvrecord || txtrecord || mxrecord || mdrecord || hinforecord || minforecord || afsdbrecord || sigrecord || keyrecord || locrecord || nxtrecord || naptrrecord || kxrecord || certrecord || dnamerecord || dsrecord || sshfprecord || rrsigrecord || nsecrecord || idnsname || idnszoneactive || idnssoamname || idnssoarname || idnssoaserial || idnssoarefresh || idnssoaretry || idnssoaexpire || idnssoaminimum || idnsupdatepolicy")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Update DNS entries";allow (write) groupdn = "ldap:///cn=update dns entries,cn=permissions,cn=pbac,$SUFFIX";)::(targetattr = "idnsname || cn || idnsallowdynupdate || dnsttl || dnsclass || arecord || aaaarecord || a6record || nsrecord || cnamerecord || ptrrecord || srvrecord || txtrecord || mxrecord || mdrecord || hinforecord || minforecord || afsdbrecord || sigrecord || keyrecord || locrecord || nxtrecord || naptrrecord || kxrecord || certrecord || dnamerecord || dsrecord || sshfprecord || rrsigrecord || nsecrecord || idnsname || idnszoneactive || idnssoamname || idnssoarname || idnssoaserial || idnssoarefresh || idnssoaretry || idnssoaexpire || idnssoaminimum || idnsupdatepolicy")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:update dns entries";allow (write) groupdn = "ldap:///cn=update dns entries,cn=permissions,cn=pbac,$SUFFIX";)'
+
+# SELinux User Mapping
+dn: cn=SELinux User Map Administrators,cn=privileges,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: nestedgroup
+default:cn: SELinux User Map Administrators
+default:description: SELinux User Map Administrators
+
+dn: cn=Add SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Add SELinux User Maps
+default:member: cn=SELinux User Map Administrators,cn=privileges,cn=pbac,$SUFFIX
+
+dn: cn=Remove SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Remove SELinux User Maps
+default:member: cn=SELinux User Map Administrators,cn=privileges,cn=pbac,$SUFFIX
+
+dn: cn=Modify SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Modify SELinux User Maps
+default:member: cn=SELinux User Map Administrators,cn=privileges,cn=pbac,$SUFFIX
+
+dn: $SUFFIX
+add:aci:'(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Add SELinux User Maps";allow (add) groupdn = "ldap:///cn=Add SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)'
+
+dn: $SUFFIX
+add:aci:'(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Remove SELinux User Maps";allow (delete) groupdn = "ldap:///cn=Remove SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)'
+
+dn: $SUFFIX
+add:aci:'(targetattr = "cn || memberuser || memberhost || seealso || ipaselinuxuser || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Modify SELinux User Maps";allow (write) groupdn = "ldap:///cn=Modify SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)'
diff --git a/install/updates/50-ipaconfig.update b/install/updates/50-ipaconfig.update
new file mode 100644
index 000000000..9ed24d6fc
--- /dev/null
+++ b/install/updates/50-ipaconfig.update
@@ -0,0 +1,4 @@
+dn: cn=ipaConfig,cn=etc,$SUFFIX
+default:ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0-s0:c0.c1023$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023
+default:ipaSELinuxUserMapDefault: guest_u:s0
+
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 40e8e26d0..cc7117614 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -26,6 +26,7 @@ app_DATA = \
50-groupuuid.update \
50-hbacservice.update \
50-nis.update \
+ 50-ipaconfig.update \
55-pbacmemberof.update \
$(NULL)
diff --git a/ipalib/constants.py b/ipalib/constants.py
index ba5f470b0..96cf3ba60 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -96,6 +96,7 @@ DEFAULT_CONFIG = (
('container_sudocmdgroup', 'cn=sudocmdgroups,cn=sudo'),
('container_entitlements', 'cn=entitlements,cn=etc'),
('container_automember', 'cn=automember,cn=etc'),
+ ('container_selinux', 'cn=usermap,cn=selinux'),
# Ports, hosts, and URIs:
# FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
diff --git a/ipalib/errors.py b/ipalib/errors.py
index 3434c26be..4463fee70 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -1512,6 +1512,22 @@ class NotRegisteredError(ExecutionError):
format = _('Not registered yet')
+class DependentEntry(ExecutionError):
+ """
+ **4307** Raised when an entry being deleted has dependencies
+
+ For example:
+ >>> raise DependentEntry(label=u'SELinux User Map', key=u'test', dependent=u'test1')
+ Traceback (most recent call last):
+ ...
+ DependentEntry: Not registered yet
+
+ """
+
+ errno = 4307
+ format = _('%(key)s cannot be deleted because %(label)s %(dependent)s requires it')
+
+
##############################################################################
# 5000 - 5999: Generic errors
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
index 20c4eda72..0c238ac98 100644
--- a/ipalib/plugins/config.py
+++ b/ipalib/plugins/config.py
@@ -47,6 +47,9 @@ Certificate Subject base: the configured certificate subject base,
Password plug-in features: currently defines additional hashes that the
password will generate (there may be other conditions).
+When setting the order list for mapping SELinux users you may need to
+quote the value so it isn't interpreted by the shell.
+
EXAMPLES:
Show basic server configuration:
@@ -66,6 +69,9 @@ EXAMPLES:
Enable migration mode to make "ipa migrate-ds" command operational:
ipa config-mod --enable-migration=TRUE
+
+ Define SELinux user map order:
+ ipa config-mod --ipaselinuxusermaporder='guest_u:s0$xguest_u:s0$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
""")
def validate_searchtimelimit(ugettext, limit):
@@ -83,7 +89,7 @@ class config(LDAPObject):
'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit',
'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields',
'ipamigrationenabled', 'ipacertificatesubjectbase',
- 'ipapwdexpadvnotify',
+ 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault',
]
label = _('Configuration')
@@ -172,6 +178,14 @@ class config(LDAPObject):
doc=_('Extra hashes to generate in password plug-in'),
flags=['no_update'],
),
+ Str('ipaselinuxusermaporder?',
+ label=_('SELinux user map order'),
+ doc=_('Order in increasing priority of SELinux users, delimited by $'),
+ ),
+ Str('ipaselinuxusermapdefault?',
+ label=_('Default SELinux user'),
+ doc=_('Default SELinux user when no match is found in SELinux map rule'),
+ ),
)
def get_dn(self, *keys, **kwargs):
@@ -228,6 +242,31 @@ class config_mod(LDAPUpdate):
error=_('%s default attribute %s would not be allowed!') \
% (obj, obj_attr))
+ if 'ipaselinuxusermapdefault' in options and options['ipaselinuxusermapdefault'] is None:
+ raise errors.ValidationError(name='ipaselinuxusermapdefault',
+ error=_('SELinux user map default user may not be empty'))
+
+ # Make sure the default user is in the list
+ if 'ipaselinuxusermapdefault' in options or \
+ 'ipaselinuxusermaporder' in options:
+ config = None
+ if 'ipaselinuxusermapdefault' in options:
+ defaultuser = options['ipaselinuxusermapdefault']
+ else:
+ config = ldap.get_ipa_config()[1]
+ defaultuser = config['ipaselinuxusermapdefault']
+
+ if 'ipaselinuxusermaporder' in options:
+ order = options['ipaselinuxusermaporder']
+ else:
+ if not config:
+ config = ldap.get_ipa_config()[1]
+ order = config['ipaselinuxusermaporder']
+ userlist = order[0].split('$')
+ if defaultuser not in userlist:
+ raise errors.ValidationError(name='ipaselinuxusermaporder',
+ error=_('Default SELinux user map default user not in order list'))
+
return dn
api.register(config_mod)
diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
index f9f04529f..92b656d66 100644
--- a/ipalib/plugins/hbacrule.py
+++ b/ipalib/plugins/hbacrule.py
@@ -239,6 +239,14 @@ class hbacrule_del(LDAPDelete):
msg_summary = _('Deleted HBAC rule "%(value)s"')
+ def pre_callback(self, ldap, dn, *keys, **options):
+ kw = dict(seealso=dn)
+ _entries = api.Command.selinuxusermap_find(None, **kw)
+ if _entries['count']:
+ raise errors.DependentEntry(key=keys[0], label=self.api.Object['selinuxusermap'].label_singular, dependent=_entries['result'][0]['cn'][0])
+
+ return dn
+
api.register(hbacrule_del)
diff --git a/ipalib/plugins/selinuxusermap.py b/ipalib/plugins/selinuxusermap.py
new file mode 100644
index 000000000..475376f6e
--- /dev/null
+++ b/ipalib/plugins/selinuxusermap.py
@@ -0,0 +1,441 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2011 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipalib import api, errors
+from ipalib import Str, StrEnum
+from ipalib.plugins.baseldap import *
+from ipalib import _, ngettext
+from ipalib.plugins.hbacrule import is_all
+
+__doc__ = _("""
+SELinux User Mapping
+
+Map IPA users to SELinux users by host.
+
+Hosts, hostgroups, users and groups can be either defined within
+the rule or it may point to an existing HBAC rule.
+
+EXAMPLES:
+
+ Create a rule, "test1", that sets all users to xguest_u:s0 on the host "server":
+ ipa selinuxusermap-add --usercat=all --selinuxuser=xguest_u:s0 test1
+ ipa selinuxusermap-add-host --hosts=server.example.com test1
+
+ Create a rule, "test2", that sets all users to guest_u:s0 and uses an existing HBAC rule for users and hosts:
+ ipa selinuxusermap-add --usercat=all --hbacrule=webserver --selinuxuser=guest_u:s0 test1
+
+ Display the properties of a named HBAC rule:
+ ipa selinuxusermap-show test1
+
+ Create a rule for a specific user. This sets the SELinux context for
+ user john to unconfined_u:s0-s0:c0.c1023 on any machine:
+ ipa selinuxusermap-add --hostcat=all --selinuxuser=unconfined_u:s0-s0:c0.c1023 john_unconfined
+ ipa selinuxusermap-add-user --users=john john_unconfined
+
+ Disable a named rule:
+ ipa selinuxusermap-disable test1
+
+ Enable a named rule:
+ ipa selinuxusermap-enable test1
+
+ Remove a named rule:
+ ipa selinuxusermap-del john_unconfined
+
+SEEALSO:
+
+ The list controlling the order in which the SELinux user map is applied
+ and the default SELinux user are available in the config-show commond.
+""")
+
+notboth_err = _('HBAC rule and local members cannot both be set')
+
+def validate_selinuxuser(ugettext, user):
+ """
+ An SELinux user has 3 components: user:MLS:MCS
+ user traditionally ends with _u but this is not mandatory. Regex is ^[a-zA-Z][a-zA-Z_]*
+ The MLS part can only be
+ Level: s[0-15](-s[0-15])
+ Then MCS could be c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123]
+ Meaning
+ s0 s0-s1 s0-s15:c0.c1023 s0-s1:c0,c2,c15.c26 s0-s0:c0.c1023
+
+ Returns a message on invalid, returns nothing on valid.
+ """
+ regex_name = re.compile(r'^[a-zA-Z][a-zA-Z_]*$')
+ regex_mls = re.compile(r'^s[0-9][1-5]{0,1}(-s[0-9][1-5]{0,1}){0,1}$')
+ regex_mcs = re.compile(r'^c(\d+)([.,-]c(\d+))*?$')
+
+ # If we add in ::: we don't have to check to see if some values are
+ # empty
+ (name, mls, mcs, ignore) = (user + ':::').split(':',3)
+
+ if not regex_name.match(name):
+ return _('Invalid SELinux user name, only a-Z and _ are allowed')
+ if mls and not regex_mls.match(mls):
+ return _('Invalid MLS value, must match s[0-15](-s[0-15])')
+ if mcs and not regex_mcs.match(mcs):
+ return _('Invalid MCS value, must match c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123]')
+
+ return None
+
+def validate_selinuxuser_inlist(ldap, user):
+ """
+ Ensure the user is in the list of allowed SELinux users.
+
+ Returns nothing if the user is found, raises an exception otherwise.
+ """
+ config = ldap.get_ipa_config()[1]
+ item = config.get('ipaselinuxusermaporder', [])
+ if len(item) != 1:
+ raise errors.NotFound(reason=_('SELinux user map list not found in configuration'))
+ userlist = item[0].split('$')
+ if user not in userlist:
+ raise errors.NotFound(reason=_('SELinux user %(user)s not found in ordering list (in config)') % dict(user=user))
+
+ return
+
+class selinuxusermap(LDAPObject):
+ """
+ SELinux User Map object.
+ """
+ container_dn = api.env.container_selinux
+ object_name = _('SELinux User Map rule')
+ object_name_plural = _('SELinux User Map rules')
+ object_class = ['ipaassociation', 'ipaselinuxusermap']
+ default_attributes = [
+ 'cn', 'ipaenabledflag',
+ 'description', 'usercategory', 'hostcategory',
+ 'ipaenabledflag', 'memberuser', 'memberhost',
+ 'memberhostgroup', 'seealso', 'ipaselinuxuser',
+ ]
+ uuid_attribute = 'ipauniqueid'
+ rdn_attribute = 'ipauniqueid'
+ attribute_members = {
+ 'memberuser': ['user', 'group'],
+ 'memberhost': ['host', 'hostgroup'],
+ }
+
+ # These maps will not show as members of other entries
+
+ label = _('SELinux User Maps')
+ label_singular = _('SELinux User Map')
+
+ takes_params = (
+ Str('cn',
+ cli_name='name',
+ label=_('Rule name'),
+ primary_key=True,
+ ),
+ Str('ipaselinuxuser', validate_selinuxuser,
+ cli_name='selinuxuser',
+ label=_('SELinux User'),
+ ),
+ Str('seealso?',
+ cli_name='hbacrule',
+ label=_('HBAC Rule'),
+ doc=_('HBAC Rule that defines the users, groups and hostgroups'),
+ ),
+ StrEnum('usercategory?',
+ cli_name='usercat',
+ label=_('User category'),
+ doc=_('User category the rule applies to'),
+ values=(u'all', ),
+ ),
+ StrEnum('hostcategory?',
+ cli_name='hostcat',
+ label=_('Host category'),
+ doc=_('Host category the rule applies to'),
+ values=(u'all', ),
+ ),
+ Str('description?',
+ cli_name='desc',
+ label=_('Description'),
+ ),
+ Flag('ipaenabledflag?',
+ label=_('Enabled'),
+ flags=['no_create', 'no_update', 'no_search'],
+ ),
+ Str('memberuser_user?',
+ label=_('Users'),
+ flags=['no_create', 'no_update', 'no_search'],
+ ),
+ Str('memberuser_group?',
+ label=_('User Groups'),
+ flags=['no_create', 'no_update', 'no_search'],
+ ),
+ Str('memberhost_host?',
+ label=_('Hosts'),
+ flags=['no_create', 'no_update', 'no_search'],
+ ),
+ Str('memberhost_hostgroup?',
+ label=_('Host Groups'),
+ flags=['no_create', 'no_update', 'no_search'],
+ ),
+ )
+
+ def _normalize_seealso(self, seealso):
+ """
+ Given a HBAC rule name verify its existence and return the dn.
+ """
+ if not seealso:
+ return None
+
+ try:
+ dn = DN(seealso)
+ return str(dn)
+ except ValueError:
+ try:
+ (dn, entry_attrs) = self.backend.find_entry_by_attr(
+ self.api.Object['hbacrule'].primary_key.name, seealso, self.api.Object['hbacrule'].object_class, [''], self.api.Object['hbacrule'].container_dn)
+ seealso = dn
+ except errors.NotFound:
+ raise errors.NotFound(reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso))
+
+ return seealso
+
+ def _convert_seealso(self, ldap, entry_attrs, **options):
+ """
+ Convert an HBAC rule dn into a name
+ """
+ if options.get('raw', False):
+ return
+
+ if 'seealso' in entry_attrs:
+ (hbac_dn, hbac_attrs) = ldap.get_entry(entry_attrs['seealso'][0], ['cn'])
+ entry_attrs['seealso'] = hbac_attrs['cn'][0]
+
+api.register(selinuxusermap)
+
+
+class selinuxusermap_add(LDAPCreate):
+ __doc__ = _('Create a new SELinux User Map.')
+
+ msg_summary = _('Added SELinux User Map "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ # rules are enabled by default
+ entry_attrs['ipaenabledflag'] = 'TRUE'
+ validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser'])
+ if 'seealso' in options:
+ entry_attrs['seealso'] = self.obj._normalize_seealso(options['seealso'])
+
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ self.obj._convert_seealso(ldap, entry_attrs, **options)
+
+ return dn
+
+api.register(selinuxusermap_add)
+
+
+class selinuxusermap_del(LDAPDelete):
+ __doc__ = _('Delete a SELinux User Map.')
+
+ msg_summary = _('Deleted SELinux User Map "%(value)s"')
+
+api.register(selinuxusermap_del)
+
+
+class selinuxusermap_mod(LDAPUpdate):
+ __doc__ = _('Modify a SELinux User Map.')
+
+ msg_summary = _('Modified SELinux User Map "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ try:
+ (_dn, _entry_attrs) = ldap.get_entry(dn, attrs_list)
+ except errors.NotFound:
+ self.obj.handle_not_found(*keys)
+
+ if 'seealso' in options and ('usercategory' in _entry_attrs or
+ 'hostcategory' in _entry_attrs or
+ 'memberuser' in _entry_attrs or
+ 'memberhost' in _entry_attrs):
+ raise errors.MutuallyExclusiveError(reason=notboth_err)
+
+ if is_all(options, 'usercategory') and 'memberuser' in entry_attrs:
+ raise errors.MutuallyExclusiveError(reason="user category cannot be set to 'all' while there are allowed users")
+ if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs:
+ raise errors.MutuallyExclusiveError(reason="host category cannot be set to 'all' while there are allowed hosts")
+
+ if 'ipaselinuxuser' in options:
+ validate_selinuxuser_inlist(ldap, options['ipaselinuxuser'])
+
+ if 'seealso' in options:
+ entry_attrs['seealso'] = self.obj._normalize_seealso(options['seealso'])
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ self.obj._convert_seealso(ldap, entry_attrs, **options)
+ return dn
+
+api.register(selinuxusermap_mod)
+
+
+class selinuxusermap_find(LDAPSearch):
+ __doc__ = _('Search for SELinux User Maps.')
+
+ msg_summary = ngettext(
+ '%(count)d SELinux User Map matched', '%(count)d SELinux User Maps matched', 0
+ )
+
+ def execute(self, *args, **options):
+ # If searching on hbacrule we need to find the uuid to search on
+ if 'seealso' in options:
+ kw = dict(cn=options['seealso'], all=True)
+ _entries = api.Command.hbacrule_find(None, **kw)['result']
+ del options['seealso']
+ if _entries:
+ options['seealso'] = _entries[0]['dn']
+
+ return super(selinuxusermap_find, self).execute(*args, **options)
+
+ def post_callback(self, ldap, entries, truncated, *args, **options):
+ if options.get('pkey_only', False):
+ return
+ for entry in entries:
+ (dn, attrs) = entry
+ self.obj._convert_seealso(ldap, attrs, **options)
+
+api.register(selinuxusermap_find)
+
+
+class selinuxusermap_show(LDAPRetrieve):
+ __doc__ = _('Display the properties of a SELinux User Map rule.')
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ self.obj._convert_seealso(ldap, entry_attrs, **options)
+ return dn
+
+api.register(selinuxusermap_show)
+
+
+class selinuxusermap_enable(LDAPQuery):
+ __doc__ = _('Enable an SELinux User Map rule.')
+
+ msg_summary = _('Enabled SELinux User Map "%(value)s"')
+ has_output = output.standard_value
+
+ def execute(self, cn):
+ ldap = self.obj.backend
+
+ dn = self.obj.get_dn(cn)
+ entry_attrs = {'ipaenabledflag': 'TRUE'}
+
+ try:
+ ldap.update_entry(dn, entry_attrs)
+ except errors.EmptyModlist:
+ raise errors.AlreadyActive()
+ except errors.NotFound:
+ self.obj.handle_not_found(cn)
+
+ return dict(
+ result=True,
+ value=cn,
+ )
+
+api.register(selinuxusermap_enable)
+
+
+class selinuxusermap_disable(LDAPQuery):
+ __doc__ = _('Disable an SELinux User Map rule.')
+
+ msg_summary = _('Disabled SELinux User Map "%(value)s"')
+ has_output = output.standard_value
+
+ def execute(self, cn):
+ ldap = self.obj.backend
+
+ dn = self.obj.get_dn(cn)
+ entry_attrs = {'ipaenabledflag': 'FALSE'}
+
+ try:
+ ldap.update_entry(dn, entry_attrs)
+ except errors.EmptyModlist:
+ raise errors.AlreadyInactive()
+ except errors.NotFound:
+ self.obj.handle_not_found(cn)
+
+ return dict(
+ result=True,
+ value=cn,
+ )
+
+api.register(selinuxusermap_disable)
+
+
+class selinuxusermap_add_user(LDAPAddMember):
+ __doc__ = _('Add users and groups to an SELinux User Map rule.')
+
+ member_attributes = ['memberuser']
+ member_count_out = ('%i object added.', '%i objects added.')
+
+ def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+ try:
+ (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
+ except errors.NotFound:
+ self.obj.handle_not_found(*keys)
+ if 'usercategory' in entry_attrs and \
+ entry_attrs['usercategory'][0].lower() == 'all':
+ raise errors.MutuallyExclusiveError(reason="users cannot be added when user category='all'")
+ if 'seealso' in entry_attrs:
+ raise errors.MutuallyExclusiveError(reason=notboth_err)
+ return dn
+
+api.register(selinuxusermap_add_user)
+
+
+class selinuxusermap_remove_user(LDAPRemoveMember):
+ __doc__ = _('Remove users and groups from an SELinux User Map rule.')
+
+ member_attributes = ['memberuser']
+ member_count_out = ('%i object removed.', '%i objects removed.')
+
+api.register(selinuxusermap_remove_user)
+
+
+class selinuxusermap_add_host(LDAPAddMember):
+ __doc__ = _('Add target hosts and hostgroups to an SELinux User Map rule.')
+
+ member_attributes = ['memberhost']
+ member_count_out = ('%i object added.', '%i objects added.')
+
+ def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+ try:
+ (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
+ except errors.NotFound:
+ self.obj.handle_not_found(*keys)
+ if 'hostcategory' in entry_attrs and \
+ entry_attrs['hostcategory'][0].lower() == 'all':
+ raise errors.MutuallyExclusiveError(reason="hosts cannot be added when host category='all'")
+ if 'seealso' in entry_attrs:
+ raise errors.MutuallyExclusiveError(reason=notboth_err)
+ return dn
+
+api.register(selinuxusermap_add_host)
+
+
+class selinuxusermap_remove_host(LDAPRemoveMember):
+ __doc__ = _('Remove target hosts and hostgroups from an SELinux User Map rule.')
+
+ member_attributes = ['memberhost']
+ member_count_out = ('%i object removed.', '%i objects removed.')
+
+api.register(selinuxusermap_remove_host)
diff --git a/tests/test_xmlrpc/objectclasses.py b/tests/test_xmlrpc/objectclasses.py
index ce904d81e..3569579ef 100644
--- a/tests/test_xmlrpc/objectclasses.py
+++ b/tests/test_xmlrpc/objectclasses.py
@@ -123,5 +123,15 @@ netgroup = [
automember = [
u'top',
- u'automemberregexrule'
+ u'automemberregexrule',
+]
+
+selinuxusermap = [
+ u'ipaassociation',
+ u'ipaselinuxusermap',
+]
+
+hbacrule = [
+ u'ipaassociation',
+ u'ipahbacrule',
]
diff --git a/tests/test_xmlrpc/test_selinuxusermap_plugin.py b/tests/test_xmlrpc/test_selinuxusermap_plugin.py
new file mode 100644
index 000000000..368037dbe
--- /dev/null
+++ b/tests/test_xmlrpc/test_selinuxusermap_plugin.py
@@ -0,0 +1,600 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2011 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Test the `ipalib/plugins/selinuxusermap.py` module.
+"""
+
+from ipalib import api, errors
+from tests.test_xmlrpc import objectclasses
+from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid
+from ipalib.dn import *
+from tests.util import Fuzzy
+
+rule1 = u'selinuxrule1'
+selinuxuser1 = u'guest_u:s0'
+selinuxuser2 = u'xguest_u:s0'
+
+user1 = u'tuser1'
+group1 = u'testgroup1'
+host1 = u'testhost1.%s' % api.env.domain
+hostdn1 = DN(('fqdn',host1),('cn','computers'),('cn','accounts'),
+ api.env.basedn)
+hbacrule1 = u'testhbacrule1'
+
+fuzzy_selinuxusermapdn = Fuzzy(
+ 'ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_selinux, api.env.basedn)
+)
+fuzzy_hbacruledn = Fuzzy(
+ 'ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_hbac, api.env.basedn)
+)
+
+class test_selinuxusermap(Declarative):
+ cleanup_commands = [
+ ('selinuxusermap_del', [rule1], {}),
+ ('group_del', [group1], {}),
+ ('user_del', [user1], {}),
+ ('host_del', [host1], {}),
+ ('hbacrule_del', [hbacrule1], {}),
+ ]
+
+ tests = [
+
+ dict(
+ desc='Try to retrieve non-existent %r' % rule1,
+ command=('selinuxusermap_show', [rule1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to update non-existent %r' % rule1,
+ command=('selinuxusermap_mod', [rule1], dict(description=u'Foo')),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to delete non-existent %r' % rule1,
+ command=('selinuxusermap_del', [rule1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Create rule %r' % rule1,
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=selinuxuser1)
+ ),
+ expected=dict(
+ value=rule1,
+ summary=u'Added SELinux User Map "%s"' % rule1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser1],
+ objectclass=objectclasses.selinuxusermap,
+ ipauniqueid=[fuzzy_uuid],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Try to create duplicate %r' % rule1,
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=selinuxuser1)
+ ),
+ expected=errors.DuplicateEntry(),
+ ),
+
+
+ dict(
+ desc='Retrieve rule %r' % rule1,
+ command=('selinuxusermap_show', [rule1], {}),
+ expected=dict(
+ value=rule1,
+ summary=None,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser1],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Update rule %r' % rule1,
+ command=(
+ 'selinuxusermap_mod', [rule1], dict(ipaselinuxuser=selinuxuser2)
+ ),
+ expected=dict(
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ ),
+ summary=u'Modified SELinux User Map "%s"' % rule1,
+ value=rule1,
+ ),
+ ),
+
+
+ dict(
+ desc='Retrieve %r to verify update' % rule1,
+ command=('selinuxusermap_show', [rule1], {}),
+ expected=dict(
+ value=rule1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ summary=None,
+ ),
+ ),
+
+
+ dict(
+ desc='Search for rule %r' % rule1,
+ command=('selinuxusermap_find', [], dict(cn=rule1)),
+ expected=dict(
+ count=1,
+ truncated=False,
+ result=[
+ dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ ],
+ summary=u'1 SELinux User Map matched',
+ ),
+ ),
+
+
+ ###############
+ # Create additional entries needed for testing
+ dict(
+ desc='Create %r' % user1,
+ command=(
+ 'user_add', [], dict(givenname=u'Test', sn=u'User1')
+ ),
+ expected=dict(
+ value=user1,
+ summary=u'Added user "%s"' % user1,
+ result=dict(
+ gecos=[u'Test User1'],
+ givenname=[u'Test'],
+ homedirectory=[u'/home/%s' % user1],
+ krbprincipalname=[u'%s@%s' % (user1, api.env.realm)],
+ loginshell=[u'/bin/sh'],
+ objectclass=objectclasses.user,
+ sn=[u'User1'],
+ uid=[user1],
+ uidnumber=[fuzzy_digits],
+ gidnumber=[fuzzy_digits],
+ displayname=[u'Test User1'],
+ cn=[u'Test User1'],
+ initials=[u'TU'],
+ ipauniqueid=[fuzzy_uuid],
+ krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \
+ [DN(('cn','global_policy'),('cn',api.env.realm),
+ ('cn','kerberos'),api.env.basedn)],
+ mepmanagedentry=lambda x: [DN(i) for i in x] == \
+ [DN(('cn',user1),('cn','groups'),('cn','accounts'),
+ api.env.basedn)],
+ memberof_group=[u'ipausers'],
+ dn=lambda x: DN(x) == \
+ DN(('uid',user1),('cn','users'),('cn','accounts'),
+ api.env.basedn),
+ has_keytab=False,
+ has_password=False,
+ ),
+ ),
+ ),
+
+ dict(
+ desc='Create group %r' % group1,
+ command=(
+ 'group_add', [group1], dict(description=u'Test desc 1')
+ ),
+ expected=dict(
+ value=group1,
+ summary=u'Added group "%s"' % group1,
+ result=dict(
+ cn=[group1],
+ description=[u'Test desc 1'],
+ gidnumber=[fuzzy_digits],
+ objectclass=objectclasses.group + [u'posixgroup'],
+ ipauniqueid=[fuzzy_uuid],
+ dn=lambda x: DN(x) == \
+ DN(('cn',group1),('cn','groups'),('cn','accounts'),
+ api.env.basedn),
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Add member %r to %r' % (user1, group1),
+ command=(
+ 'group_add_member', [group1], dict(user=user1)
+ ),
+ expected=dict(
+ completed=1,
+ failed=dict(
+ member=dict(
+ group=tuple(),
+ user=tuple(),
+ ),
+ ),
+ result={
+ 'dn': lambda x: DN(x) == \
+ DN(('cn',group1),('cn','groups'),('cn','accounts'),
+ api.env.basedn),
+ 'member_user': (user1,),
+ 'gidnumber': [fuzzy_digits],
+ 'cn': [group1],
+ 'description': [u'Test desc 1'],
+ },
+ ),
+ ),
+
+
+ dict(
+ desc='Create host %r' % host1,
+ command=('host_add', [host1],
+ dict(
+ description=u'Test host 1',
+ l=u'Undisclosed location 1',
+ force=True,
+ ),
+ ),
+ expected=dict(
+ value=host1,
+ summary=u'Added host "%s"' % host1,
+ result=dict(
+ dn=lambda x: DN(x) == hostdn1,
+ fqdn=[host1],
+ description=[u'Test host 1'],
+ l=[u'Undisclosed location 1'],
+ krbprincipalname=[u'host/%s@%s' % (host1, api.env.realm)],
+ objectclass=objectclasses.host,
+ ipauniqueid=[fuzzy_uuid],
+ managedby_host=[host1],
+ has_keytab=False,
+ has_password=False,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Create HBAC rule %r' % hbacrule1,
+ command=(
+ 'hbacrule_add', [hbacrule1], {}
+ ),
+ expected=dict(
+ value=hbacrule1,
+ summary=u'Added HBAC rule "%s"' % hbacrule1,
+ result=dict(
+ cn=[hbacrule1],
+ objectclass=objectclasses.hbacrule,
+ ipauniqueid=[fuzzy_uuid],
+ accessruletype=[u'allow'],
+ ipaenabledflag=[u'TRUE'],
+ dn=fuzzy_hbacruledn,
+ ),
+ ),
+ ),
+
+
+ ###############
+ # Fill out rule with members and/or pointers to HBAC rules
+ dict(
+ desc='Add user to %r' % rule1,
+ command=('selinuxusermap_add_user', [rule1], dict(user=user1)),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ memberuser_user = [user1],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Add non-existent user to %r' % rule1,
+ command=('selinuxusermap_add_user', [rule1], dict(user=u'notfound')),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[(u'notfound', u'no such entry')])),
+ completed=0,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ memberuser_user = [user1],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Remove user from %r' % rule1,
+ command=('selinuxusermap_remove_user', [rule1], dict(user=user1)),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Remove non-existent user to %r' % rule1,
+ command=('selinuxusermap_remove_user', [rule1], dict(user=u'notfound')),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[(u'notfound', u'This entry is not a member')])),
+ completed=0,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Add group to %r' % rule1,
+ command=('selinuxusermap_add_user', [rule1], dict(group=group1)),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ memberuser_group = [group1],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Add host to %r' % rule1,
+ command=('selinuxusermap_add_host', [rule1], dict(host=host1)),
+ expected=dict(
+ failed=dict(memberhost=dict(hostgroup=[], host=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ memberhost_host = [host1],
+ memberuser_group = [group1],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ ###############
+ # Test enabling and disabling
+ dict(
+ desc='Disable %r' % rule1,
+ command=('selinuxusermap_disable', [rule1], {}),
+ expected=dict(
+ result=True,
+ value=rule1,
+ summary=u'Disabled SELinux User Map "%s"' % rule1,
+ )
+ ),
+
+
+ dict(
+ desc='Disable %r again' % rule1,
+ command=('selinuxusermap_disable', [rule1], {}),
+ expected=errors.AlreadyInactive(),
+ ),
+
+
+ dict(
+ desc='Enable %r' % rule1,
+ command=('selinuxusermap_enable', [rule1], {}),
+ expected=dict(
+ result=True,
+ value=rule1,
+ summary=u'Enabled SELinux User Map "%s"' % rule1,
+ )
+ ),
+
+
+ dict(
+ desc='Re-enable %r again' % rule1,
+ command=('selinuxusermap_enable', [rule1], {}),
+ expected=errors.AlreadyActive(),
+ ),
+
+
+ # Point to an HBAC Rule
+ dict(
+ desc='Add an HBAC rule to %r that has other members' % rule1,
+ command=(
+ 'selinuxusermap_mod', [rule1], dict(seealso=hbacrule1)
+ ),
+ expected=errors.MutuallyExclusiveError(reason=u'cannot have both'),
+ ),
+
+
+ dict(
+ desc='Remove host from %r' % rule1,
+ command=('selinuxusermap_remove_host', [rule1], dict(host=host1)),
+ expected=dict(
+ failed=dict(memberhost=dict(hostgroup=[], host=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ memberuser_group = [group1],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Remove group from %r' % rule1,
+ command=('selinuxusermap_remove_user', [rule1], dict(group=group1)),
+ expected=dict(
+ failed=dict(memberuser=dict(group=[], user=[])),
+ completed=1,
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ dn=fuzzy_selinuxusermapdn,
+ ),
+ )
+ ),
+
+
+ dict(
+ desc='Add non-existent HBAC rule to %r' % rule1,
+ command=(
+ 'selinuxusermap_mod', [rule1], dict(seealso=u'notfound')
+ ),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Add an HBAC rule to %r' % rule1,
+ command=(
+ 'selinuxusermap_mod', [rule1], dict(seealso=hbacrule1)
+ ),
+ expected=dict(
+ result=dict(
+ cn=[rule1],
+ ipaselinuxuser=[selinuxuser2],
+ ipaenabledflag = [u'TRUE'],
+ seealso = hbacrule1,
+ ),
+ summary=u'Modified SELinux User Map "%s"' % rule1,
+ value=rule1,
+ ),
+ ),
+
+
+ dict(
+ desc='Add user to %r that has HBAC' % rule1,
+ command=('selinuxusermap_add_user', [rule1], dict(user=user1)),
+ expected=errors.MutuallyExclusiveError(reason=u'cannot have both'),
+ ),
+
+
+ dict(
+ desc='Add host to %r that has HBAC' % rule1,
+ command=('selinuxusermap_add_host', [rule1], dict(host=host1)),
+ expected=errors.MutuallyExclusiveError(reason=u'cannot have both'),
+ ),
+
+
+ dict(
+ desc='Try to delete HBAC rule pointed to by %r' % rule1,
+ command=('hbacrule_del', [hbacrule1], {}),
+ expected=errors.DependentEntry(key=hbacrule1, label=u'SELinux User Map', dependent=rule1)
+ ),
+
+
+ # Test clean up
+ dict(
+ desc='Delete %r' % rule1,
+ command=('selinuxusermap_del', [rule1], {}),
+ expected=dict(
+ result=dict(failed=u''),
+ value=rule1,
+ summary=u'Deleted SELinux User Map "%s"' % rule1,
+ )
+ ),
+
+
+ dict(
+ desc='Try to delete non-existent %r' % rule1,
+ command=('selinuxusermap_del', [rule1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ # Some negative tests
+ dict(
+ desc='Create rule with unknown user%r' % rule1,
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=u'notfound')
+ ),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Create rule with invalid user bad+user',
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=u'bad+user')
+ ),
+ expected=errors.ValidationError(name='ipaselinuxuser', error='invalid name'),
+ ),
+
+
+ dict(
+ desc='Create rule with invalid MCS xguest_u:s999',
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=u'xguest_u:s999')
+ ),
+ expected=errors.ValidationError(name='ipaselinuxuser', error='invalid name'),
+ ),
+
+
+ dict(
+ desc='Create rule with invalid MLS xguest_u:s0:p88',
+ command=(
+ 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=u'xguest_u:s0:p88')
+ ),
+ expected=errors.ValidationError(name='ipaselinuxuser', error='invalid name'),
+ ),
+
+ ]