diff options
-rw-r--r-- | API.txt | 130 | ||||
-rw-r--r-- | install/share/60basev3.ldif | 2 | ||||
-rw-r--r-- | install/share/60ipaconfig.ldif | 4 | ||||
-rw-r--r-- | install/share/bootstrap-template.ldif | 15 | ||||
-rw-r--r-- | install/updates/40-delegation.update | 38 | ||||
-rw-r--r-- | install/updates/50-ipaconfig.update | 4 | ||||
-rw-r--r-- | install/updates/Makefile.am | 1 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/errors.py | 16 | ||||
-rw-r--r-- | ipalib/plugins/config.py | 41 | ||||
-rw-r--r-- | ipalib/plugins/hbacrule.py | 8 | ||||
-rw-r--r-- | ipalib/plugins/selinuxusermap.py | 441 | ||||
-rw-r--r-- | tests/test_xmlrpc/objectclasses.py | 12 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_selinuxusermap_plugin.py | 600 |
14 files changed, 1309 insertions, 4 deletions
@@ -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 104cffb2..eec1aea8 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 767bfa94..1b3a42b8 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 fbad4aba..4f6bc3c9 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 3502ce89..cd5b498a 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 00000000..9ed24d6f --- /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 40e8e26d..cc711761 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 ba5f470b..96cf3ba6 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 3434c26b..4463fee7 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 20c4eda7..0c238ac9 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 f9f04529..92b656d6 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 00000000..475376f6 --- /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 ce904d81..3569579e 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 00000000..368037db --- /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'), + ), + + ] |