URL: https://github.com/freeipa/freeipa/pull/399 Author: dkupka Title: #399: Certificate mapping test Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/399/head:pr399 git checkout pr399
From b758cf15199a42a707c1028a29ec4772d24589eb Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud <f...@redhat.com> Date: Tue, 20 Dec 2016 16:21:58 +0100 Subject: [PATCH 1/4] Support for Certificate Identity Mapping See design http://www.freeipa.org/page/V4/Certificate_Identity_Mapping https://fedorahosted.org/freeipa/ticket/6542 --- ACI.txt | 16 +- API.txt | 154 +++++++++++++++++ VERSION.m4 | 4 +- install/share/73certmap.ldif | 17 ++ install/share/Makefile.am | 1 + install/updates/73-certmap.update | 27 +++ install/updates/Makefile.am | 1 + ipalib/constants.py | 2 + ipaserver/install/dsinstance.py | 1 + ipaserver/plugins/baseuser.py | 11 +- ipaserver/plugins/certmap.py | 345 ++++++++++++++++++++++++++++++++++++++ ipaserver/plugins/user.py | 173 ++++++++++++++++++- 12 files changed, 745 insertions(+), 7 deletions(-) create mode 100644 install/share/73certmap.ldif create mode 100644 install/updates/73-certmap.update create mode 100644 ipaserver/plugins/certmap.py diff --git a/ACI.txt b/ACI.txt index 0b47489..ec2eeca 100644 --- a/ACI.txt +++ b/ACI.txt @@ -40,6 +40,18 @@ dn: cn=caacls,cn=ca,dc=ipa,dc=example aci: (targetattr = "cn || description || ipaenabledflag")(targetfilter = "(objectclass=ipacaacl)")(version 3.0;acl "permission:System: Modify CA ACL";allow (write) groupdn = "ldap:///cn=System: Modify CA ACL,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=caacls,cn=ca,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || description || entryusn || hostcategory || ipacacategory || ipacertprofilecategory || ipaenabledflag || ipamemberca || ipamembercertprofile || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || usercategory")(targetfilter = "(objectclass=ipacaacl)")(version 3.0;acl "permission:System: Read CA ACLs";allow (compare,read,search) userdn = "ldap:///all";) +dn: cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetattr = "ipacertmappromptusername")(targetfilter = "(objectclass=ipacertmapconfigobject)")(version 3.0;acl "permission:System: Modify Certmap Configuration";allow (write) groupdn = "ldap:///cn=System: Modify Certmap Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetattr = "cn || ipacertmappromptusername")(targetfilter = "(objectclass=ipacertmapconfigobject)")(version 3.0;acl "permission:System: Read Certmap Configuration";allow (compare,read,search) userdn = "ldap:///all";) +dn: cn=certmaprules,cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Add Certmap Rules";allow (add) groupdn = "ldap:///cn=System: Add Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Delete Certmap Rules";allow (delete) groupdn = "ldap:///cn=System: Delete Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || description || ipacertmapissuer || ipacertmapmaprule || ipacertmapmatchrule || ipacertmappriority || ipaenabledflag || objectclass")(targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Modify Certmap Rules";allow (write) groupdn = "ldap:///cn=System: Modify Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,cn=ipa,cn=etc,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || createtimestamp || description || entryusn || ipacertmapissuer || ipacertmapmaprule || ipacertmapmatchrule || ipacertmappriority || ipaenabledflag || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Read Certmap Rules";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=certprofiles,cn=ca,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=System: Delete Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=certprofiles,cn=ca,dc=ipa,dc=example @@ -337,6 +349,8 @@ aci: (targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:S dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbprincipalkey || passwordhistory || sambalmpassword || sambantpassword || userpassword")(targetfilter = "(&(!(memberOf=cn=admins,cn=groups,cn=accounts,dc=ipa,dc=example))(objectclass=posixaccount))")(version 3.0;acl "permission:System: Change User password";allow (write) groupdn = "ldap:///cn=System: Change User password,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example +aci: (targetattr = "ipacertmapdata || objectclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Certificate Mappings";allow (write) groupdn = "ldap:///cn=System: Manage User Certificate Mappings,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "usercertificate")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Certificates";allow (write) groupdn = "ldap:///cn=System: Manage User Certificates,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbcanonicalname || krbprincipalname")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Principals";allow (write) groupdn = "ldap:///cn=System: Manage User Principals,cn=permissions,cn=pbac,dc=ipa,dc=example";) @@ -347,7 +361,7 @@ aci: (targetattr = "businesscategory || carlicense || cn || departmentnumber || dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example aci: (targetattr = "*")(target = "ldap:///cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read UPG Definition";allow (compare,read,search) groupdn = "ldap:///cn=System: Read UPG Definition,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber || destinationindicator || employeenumber || employeetype || facsimiletelephonenumber || homephone || homepostaladdress || inetuserhttpurl || inetuserstatus || internationalisdnnumber || jpegphoto || l || labeleduri || mail || mobile || o || ou || pager || photo || physicaldeliveryofficename || postaladdress || postalcode || postofficebox || preferreddeliverymethod || preferredlanguage || registeredaddress || roomnumber || secretary || seealso || st || street || telephonenumber || teletexterminalidentifier || telexnumber || usercertificate || usersmimecertificate || x121address || x500uniqueidentifier")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Addressbook Attributes";allow (compare,read,search) userdn = "ldap:///all";) +aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber || destinationindicator || employeenumber || employeetype || facsimiletelephonenumber || homephone || homepostaladdress || inetuserhttpurl || inetuserstatus || internationalisdnnumber || ipacertmapdata || jpegphoto || l || labeleduri || mail || mobile || o || ou || pager || photo || physicaldeliveryofficename || postaladdress || postalcode || postofficebox || preferreddeliverymethod || preferredlanguage || registeredaddress || roomnumber || secretary || seealso || st || street || telephonenumber || teletexterminalidentifier || telexnumber || usercertificate || usersmimecertificate || x121address || x500uniqueidentifier")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Addressbook Attributes";allow (compare,read,search) userdn = "ldap:///all";) dn: dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";) dn: cn=users,cn=accounts,dc=ipa,dc=example diff --git a/API.txt b/API.txt index 543cec5..ea88423 100644 --- a/API.txt +++ b/API.txt @@ -824,6 +824,119 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: certmapconfig_mod/1 +args: 0,8,3 +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('delattr*', cli_name='delattr') +option: Bool('ipacertmappromptusername?', autofill=False, cli_name='promptusername') +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmapconfig_show/1 +args: 0,4,3 +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmaprule_add/1 +args: 1,12,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('associateddomain*', cli_name='domain') +option: Str('description?', cli_name='desc') +option: DNParam('ipacertmapissuer?', cli_name='issuer') +option: Str('ipacertmapmaprule?', cli_name='maprule') +option: Str('ipacertmapmatchrule?', cli_name='matchrule') +option: Int('ipacertmappriority?', cli_name='priority') +option: Flag('ipaenabledflag?', autofill=True, default=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmaprule_del/1 +args: 1,2,3 +arg: Str('cn+', cli_name='rulename') +option: Flag('continue', autofill=True, cli_name='continue', default=False) +option: Str('version?') +output: Output('result', type=[<type 'dict'>]) +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: ListOfPrimaryKeys('value') +command: certmaprule_disable/1 +args: 1,1,3 +arg: Str('cn', cli_name='rulename') +option: Str('version?') +output: Output('result', type=[<type 'bool'>]) +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmaprule_enable/1 +args: 1,1,3 +arg: Str('cn', cli_name='rulename') +option: Str('version?') +output: Output('result', type=[<type 'bool'>]) +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmaprule_find/1 +args: 1,14,4 +arg: Str('criteria?') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('associateddomain*', autofill=False, cli_name='domain') +option: Str('cn?', autofill=False, cli_name='rulename') +option: Str('description?', autofill=False, cli_name='desc') +option: DNParam('ipacertmapissuer?', autofill=False, cli_name='issuer') +option: Str('ipacertmapmaprule?', autofill=False, cli_name='maprule') +option: Str('ipacertmapmatchrule?', autofill=False, cli_name='matchrule') +option: Int('ipacertmappriority?', autofill=False, cli_name='priority') +option: Bool('ipaenabledflag?', autofill=False, default=True) +option: Flag('pkey_only?', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Int('sizelimit?', autofill=False) +option: Int('timelimit?', autofill=False) +option: Str('version?') +output: Output('count', type=[<type 'int'>]) +output: ListOfEntries('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: Output('truncated', type=[<type 'bool'>]) +command: certmaprule_mod/1 +args: 1,14,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('associateddomain*', autofill=False, cli_name='domain') +option: Str('delattr*', cli_name='delattr') +option: Str('description?', autofill=False, cli_name='desc') +option: DNParam('ipacertmapissuer?', autofill=False, cli_name='issuer') +option: Str('ipacertmapmaprule?', autofill=False, cli_name='maprule') +option: Str('ipacertmapmatchrule?', autofill=False, cli_name='matchrule') +option: Int('ipacertmappriority?', autofill=False, cli_name='priority') +option: Flag('ipaenabledflag?', autofill=True, default=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: certmaprule_show/1 +args: 1,4,3 +arg: Str('cn', cli_name='rulename') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: certprofile_del/1 args: 1,2,3 arg: Str('cn+', cli_name='id') @@ -5752,6 +5865,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: user_add_certmap/1 +args: 1,8,3 +arg: Str('uid', cli_name='login') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('ipacertmapdata*', alwaysask=False, cli_name='data') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Bytes('usercertificate*', cli_name='certificate') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: user_add_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -5924,6 +6051,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: user_remove_certmap/1 +args: 1,8,3 +arg: Str('uid', cli_name='login') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('ipacertmapdata*', alwaysask=False, cli_name='data') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Bytes('usercertificate*', cli_name='certificate') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: user_remove_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -6307,6 +6448,17 @@ default: cert_request/1 default: cert_revoke/1 default: cert_show/1 default: cert_status/1 +default: certmapconfig/1 +default: certmapconfig_mod/1 +default: certmapconfig_show/1 +default: certmaprule/1 +default: certmaprule_add/1 +default: certmaprule_del/1 +default: certmaprule_disable/1 +default: certmaprule_enable/1 +default: certmaprule_find/1 +default: certmaprule_mod/1 +default: certmaprule_show/1 default: certprofile/1 default: certprofile_del/1 default: certprofile_find/1 @@ -6741,6 +6893,7 @@ default: trustdomain_mod/1 default: user/1 default: user_add/1 default: user_add_cert/1 +default: user_add_certmap/1 default: user_add_manager/1 default: user_add_principal/1 default: user_del/1 @@ -6749,6 +6902,7 @@ default: user_enable/1 default: user_find/1 default: user_mod/1 default: user_remove_cert/1 +default: user_remove_certmap/1 default: user_remove_manager/1 default: user_remove_principal/1 default: user_show/1 diff --git a/VERSION.m4 b/VERSION.m4 index 36929ee..187092c 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -73,8 +73,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 217) -# Last change: Add options to write lightweight CA cert or chain to file +define(IPA_API_VERSION_MINOR, 218) +# Last change: Support for Certificate Identity Mapping ######################################################## diff --git a/install/share/73certmap.ldif b/install/share/73certmap.ldif new file mode 100644 index 0000000..fb70f88 --- /dev/null +++ b/install/share/73certmap.ldif @@ -0,0 +1,17 @@ +## IPA Base OID: +## +## Attributes: 2.16.840.1.113730.3.8.22.x +## ObjectClasses: 2.16.840.1.113730.3.8.23.y +## +dn: cn=schema +attributeTypes: (2.16.840.1.113730.3.8.22.1 NAME 'ipaCertMapVersion' DESC 'IPA Certificate Mapping version' EQUALITY integerMatch ORDERING integerOrderingMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.2 NAME 'ipaCertMapPromptUsername' DESC 'Prompt for the username when multiple identities are mapped to a certificate' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.3 NAME 'ipaCertMapMapRule' DESC 'Certificate Mapping Rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.4 NAME 'ipaCertMapMatchRule' DESC 'Certificate Matching Rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.5 NAME 'ipaCertMapData' DESC 'Certificate Mapping Data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.6 NAME 'ipaCertMapIssuer' DESC 'Certificate Issuer' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.7 NAME 'ipaCertMapPriority' DESC 'Rule priority' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.23.1 NAME 'ipaCertMapContainer' DESC 'IPA Certificate Mapping container' AUXILIARY MUST ( ipaCertMapVersion ) X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.23.2 NAME 'ipaCertMapConfigObject' DESC 'IPA Certificate Mapping global config options' SUP top STRUCTURAL MAY ipaCertMapPromptUsername X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.23.3 NAME 'ipaCertMapRule' DESC 'IPA Certificate Mapping rule' SUP top STRUCTURAL MUST cn MAY ( description $ ipaCertMapIssuer $ ipaCertMapMapRule $ ipaCertMapMatchRule $ associatedDomain $ ipaCertMapPriority $ ipaEnabledFlag ) X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.23.4 NAME 'ipaCertMapObject' DESC 'IPA Object for Certificate Mapping' AUXILIARY MAY ipaCertMapData X-ORIGIN 'IPA v4.5' ) diff --git a/install/share/Makefile.am b/install/share/Makefile.am index 10de84d..6d07aec 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -26,6 +26,7 @@ dist_app_DATA = \ 70topology.ldif \ 71idviews.ldif \ 72domainlevels.ldif \ + 73certmap.ldif \ bootstrap-template.ldif \ ca-topology.uldif \ caJarSigningCert.cfg.template \ diff --git a/install/updates/73-certmap.update b/install/updates/73-certmap.update new file mode 100644 index 0000000..ede7d9b --- /dev/null +++ b/install/updates/73-certmap.update @@ -0,0 +1,27 @@ +# Configuration for Certificate Identity Mapping +dn: cn=certmap,cn=ipa,cn=etc,$SUFFIX +default:objectclass: top +default:objectclass: nsContainer +default:objectclass: ipaConfigObject +default:objectclass: ipaCertMapContainer +default:objectclass: ipaCertMapConfigObject +default:cn: certmap +default:ipaconfigstring: CertMapVersion 1 +default:ipacertmapversion: 1 +default:ipaCertMapPromptUsername: FALSE + +dn: cn=certmaprules,cn=certmap,cn=ipa,cn=etc,$SUFFIX +default:objectclass: top +default:objectclass: nsContainer +default:cn: certmaprules + +# Certificate Identity Mapping Administrators +dn: cn=Certificate Identity Mapping Administrators,cn=privileges,cn=pbac,$SUFFIX +default:objectClass: top +default:objectClass: groupofnames +default:objectClass: nestedgroup +default:cn: Certificate Identity Mapping Administrators +default:description: Certificate Identity Mapping Administrators + +dn: $SUFFIX +add:aci: (targetattr = "ipacertmapdata")(targattrfilters="add=objectclass:(objectclass=ipacertmapobject)")(version 3.0;acl "selfservice:Users can manage their own X.509 certificate identity mappings";allow (write) userdn = "ldap:///self";) diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index e8a55e1..0ff0edb 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -61,6 +61,7 @@ app_DATA = \ 72-domainlevels.update \ 73-custodia.update \ 73-winsync.update \ + 73-certmap.update \ 90-post_upgrade_plugins.update \ $(NULL) diff --git a/ipalib/constants.py b/ipalib/constants.py index 81643da..7f7a3ea 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -122,6 +122,8 @@ ('container_dnsservers', DN(('cn', 'servers'), ('cn', 'dns'))), ('container_custodia', DN(('cn', 'custodia'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_sysaccounts', DN(('cn', 'sysaccounts'), ('cn', 'etc'))), + ('container_certmap', DN(('cn', 'certmap'), ('cn', 'ipa'), ('cn', 'etc'))), + ('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'), ('cn', 'ipa'), ('cn', 'etc'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 5a28026..91ea758 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -70,6 +70,7 @@ "70topology.ldif", "71idviews.ldif", "72domainlevels.ldif", + "73certmap.ldif", "15rfc2307bis.ldif", "15rfc4876.ldif") diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py index 85ad417..7642640 100644 --- a/ipaserver/plugins/baseuser.py +++ b/ipaserver/plugins/baseuser.py @@ -134,7 +134,7 @@ class baseuser(LDAPObject): object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', - 'ipatokenradiusproxyuser' + 'ipatokenradiusproxyuser', 'ipacertmapobject' ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] @@ -146,7 +146,8 @@ class baseuser(LDAPObject): 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', 'krbprincipalexpiration', 'usercertificate;binary', - 'krbprincipalname', 'krbcanonicalname' + 'krbprincipalname', 'krbcanonicalname', + 'ipacertmapdata' ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', @@ -360,6 +361,12 @@ class baseuser(LDAPObject): label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), + Str('ipacertmapdata*', + cli_name='data', + label=_('Certificate mapping data'), + doc=_('Certificate mapping data'), + flags=['no_create', 'no_update', 'no_search'], + ), ) def normalize_and_validate_email(self, email, config=None): diff --git a/ipaserver/plugins/certmap.py b/ipaserver/plugins/certmap.py new file mode 100644 index 0000000..14f73f8 --- /dev/null +++ b/ipaserver/plugins/certmap.py @@ -0,0 +1,345 @@ +import six + +from ipalib import api, errors +from ipalib.parameters import Bool, DNParam, Flag, Int, Str +from ipalib.plugable import Registry +from ipalib.util import validate_domain_name +from .baseldap import ( + LDAPCreate, + LDAPDelete, + LDAPObject, + LDAPQuery, + LDAPRetrieve, + LDAPSearch, + LDAPUpdate, + pkey_to_value) +from ipalib import _, ngettext +from ipalib import output + + +if six.PY3: + unicode = str + +__doc__ = _(""" +Certificate Identity Mapping +""") + _(""" +Manage Certificate Identity Mapping configuration and rules. +""") + _(""" +IPA supports the use of certificates for authentication. Certificates can +either be stored in the user entry (full certificate in the usercertificate +attribute), or simply linked to the user entry through a mapping. +This code enables the management of the rules allowing to link a +certificate to a user entry. +""") + _(""" +EXAMPLES: +""") + _(""" + Display the Certificate Identity Mapping global configuration: + ipa certmapconfig-show +""") +_(""" + Modify Certificate Identity Mapping global configuration: + ipa certmapconfig-mod --promptusername=TRUE +""") +_(""" + Create a new Certificate Identity Mapping Rule: + ipa certmaprule-add myrule --desc="Link certificate with subject and issuer" +""") +_(""" + Modify a Certificate Identity Mapping Rule: + ipa certmaprule-mod myrule --maprule="<ALT-SEC-ID-I-S:altSecurityIdentities>" +""") +_(""" + Disable a Certificate Identity Mapping Rule: + ipa certmaprule-disable myrule +""") +_(""" + Enable a Certificate Identity Mapping Rule: + ipa certmaprule-enable myrule +""") +_(""" + Display information about a Certificate Identity Mapping Rule: + ipa certmaprule-show myrule +""") +_(""" + Find all Certificate Identity Mapping Rules with the specified domain: + ipa certmaprule-find --domain example.com +""") +_(""" + Delete a Certificate Identity Mapping Rule: + ipa certmaprule-del myrule +""") + +register = Registry() + +def _domain_name_validator(ugettext, value): + try: + validate_domain_name(value) + except ValueError as e: + return unicode(e) + +def _domain_name_normalizer(d): + return d.lower().rstrip('.') + + +@register() +class certmapconfig(LDAPObject): + """ + Certificate Identity Mapping configuration object + """ + object_name = _('Certificate Identity Mapping configuration options') + default_attributes = ['ipacertmappromptusername'] + + container_dn = api.env.container_certmap + + label = _('Certificate Identity Mapping Global Configuration') + label_singular = _('Certificate Identity Mapping Global Configuration') + + takes_params = ( + Bool('ipacertmappromptusername', + cli_name='promptusername', + label=_('Prompt for the username'), + doc=_('Prompt for the username when multiple identities are mapped to a certificate'), + ), + ) + + permission_filter_objectclasses = ['ipacertmapconfigobject'] + managed_permissions = { + 'System: Read Certmap Configuration': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'ipacertmappromptusername', + 'cn', + }, + }, + 'System: Modify Certmap Configuration': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'ipacertmappromptusername', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + } + + +@register() +class certmapconfig_mod(LDAPUpdate): + __doc__ = _('Modify Certificate Identity Mapping configuration.') + + +@register() +class certmapconfig_show(LDAPRetrieve): + __doc__ = _('Show the current Certificate Identity Mapping configuration.') + + +@register() +class certmaprule(LDAPObject): + """ + Certificate Identity Mapping Rules + """ + + label = _('Certificate Identity Mapping Rules') + label_singular = _('Certificate Identity Mapping Rule') + + object_name = _('Certificate Identity Mapping Rule') + object_name_plural = _('Certificate Identity Mapping Rules') + object_class = ['ipacertmaprule'] + + container_dn = api.env.container_certmaprules + default_attributes = [ + 'cn', 'description', 'ipacertmapissuer', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'associateddomain', + 'ipacertmappriority', + 'ipaenabledflag' + ] + search_attributes = [ + 'cn', 'description', 'ipacertmapissuer', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'associateddomain', + 'ipacertmappriority', + 'ipaenabledflag' + ] + + + takes_params = ( + Str('cn', + cli_name='rulename', + primary_key=True, + label=_('Rule name'), + doc=_('Certificate Identity Mapping Rule name'), + ), + Str('description?', + cli_name='desc', + label=_('Description'), + doc=_('Certificate Identity Mapping Rule description'), + ), + DNParam('ipacertmapissuer?', + cli_name='issuer', + label=_('Issuer'), + doc=_('LDAP DN of the certificate issuer (CN=Certificate Authority,O=DOMAIN.COM)'), + ), + Str('ipacertmapmaprule?', + cli_name='maprule', + label=_('Mapping rule'), + doc=_('Rule used to map the certificate with a user entry'), + ), + Str('ipacertmapmatchrule?', + cli_name='matchrule', + label=_('Matching rule'), + doc=_('Rule used to check if a certificate can be used for authentication'), + ), + Str('associateddomain*', + _domain_name_validator, + normalizer=_domain_name_normalizer, + cli_name='domain', + label=_('Domain name'), + doc=_('Domain where the user entry will be searched'), + ), + Int('ipacertmappriority?', + cli_name='priority', + label=_('Priority'), + doc=_('Priority of the rule (higher number means lower priority'), + minvalue=0, + ), + Flag('ipaenabledflag?', + label=_('Enabled'), + flags=['no_option'], + default=True + ), + ) + + permission_filter_objectclasses = ['ipacertmaprule'] + managed_permissions = { + 'System: Add Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'add'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + 'System: Read Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'description', 'ipacertmapissuer', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + 'System: Delete Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'delete'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + 'System: Modify Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'description', 'ipacertmapissuer', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + } + + def get_dn(self, *keys, **options): + rulename = keys[-1] + dn = super(certmaprule, self).get_dn(rulename, **options) + return dn + + +@register() +class certmaprule_add(LDAPCreate): + __doc__ = _('Create a new Certificate Identity Mapping Rule.') + + msg_summary = _('Added Certificate Identity Mapping Rule "%(value)s"') + + +@register() +class certmaprule_mod(LDAPUpdate): + __doc__ = _('Modify a Certificate Identity Mapping Rule.') + + msg_summary = _('Modified Certificate Identity Mapping Rule "%(value)s"') + + +@register() +class certmaprule_find(LDAPSearch): + __doc__ = _('Search for Certificate Identity Mapping Rules.') + + msg_summary = ngettext( + '%(count)d Certificate Identity Mapping Rule matched', + '%(count)d Certificate Identity Mapping Rules matched', 0 + ) + + +@register() +class certmaprule_show(LDAPRetrieve): + __doc__ = _('Display information about a Certificate Identity Mapping Rule.') + + +@register() +class certmaprule_del(LDAPDelete): + __doc__ = _('Delete a Certificate Identity Mapping Rule.') + + msg_summary = _('Deleted Certificate Identity Mapping Rule "%(value)s"') + + +@register() +class certmaprule_enable(LDAPQuery): + __doc__ = _('Enable a Certificate Identity Mapping Rule.') + + msg_summary = _('Enabled Certificate Identity Mapping Rule "%(value)s"') + has_output = output.standard_value + + def execute(self, cn, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + try: + entry_attrs = ldap.get_entry(dn, ['ipaenabledflag']) + except errors.NotFound: + self.obj.handle_not_found(cn) + + entry_attrs['ipaenabledflag'] = ['TRUE'] + + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + + return dict( + result=True, + value=pkey_to_value(cn, options), + ) + + +@register() +class certmaprule_disable(LDAPQuery): + __doc__ = _('Disable a Certificate Identity Mapping Rule.') + + msg_summary = _('Disabled Certificate Identity Mapping Rule "%(value)s"') + has_output = output.standard_value + + def execute(self, cn, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + try: + entry_attrs = ldap.get_entry(dn, ['ipaenabledflag']) + except errors.NotFound: + self.obj.handle_not_found(cn) + + entry_attrs['ipaenabledflag'] = ['FALSE'] + + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + + return dict( + result=True, + value=pkey_to_value(cn, options), + ) + diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py index 6440548..dea8173 100644 --- a/ipaserver/plugins/user.py +++ b/ipaserver/plugins/user.py @@ -22,12 +22,13 @@ from time import gmtime, strftime import posixpath import os - +from ldap.dn import str2dn, dn2str import six from ipalib import api from ipalib import errors -from ipalib import Bool, Flag, Str +from ipalib import Bool, Bytes, DNParam, Flag, Str +from ipalib import x509 from .baseuser import ( baseuser, baseuser_add, @@ -64,6 +65,7 @@ from ipapython.dn import DN from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS from ipalib.capabilities import client_has_capability +from ipaserver.plugins.service import validate_certificate if api.env.in_server: from ipaserver.plugins.ldap2 import ldap2 @@ -179,6 +181,7 @@ class user(baseuser): 'secretary', 'usercertificate', 'usersmimecertificate', 'x500uniqueidentifier', 'inetuserhttpurl', 'inetuserstatus', + 'ipacertmapdata', }, 'fixup_function': fix_addressbook_permission_bindrule, }, @@ -366,6 +369,13 @@ class user(baseuser): }, 'default_privileges': {'PassSync Service'}, }, + 'System: Manage User Certificate Mappings': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': {'ipacertmapdata', 'objectclass'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators' + }, + }, } takes_params = baseuser.takes_params + ( @@ -1200,6 +1210,165 @@ def post_callback(self, ldap, dn, entry_attrs, *keys, **options): return dn +def _convert_to_x500(name): + """ + Converts a (ipa) DN into a string representation following X500 order + """ + if name: + dn = str2dn(str(name)) + dn.reverse() + return dn2str(dn) + return name + +def _build_mapdata(subject, issuer): + issuer = _convert_to_x500(issuer) + subject = _convert_to_x500(subject) + return u'X509:{issuer}{subject}'.format( + issuer='<I>{}'.format(issuer) if issuer else '', + subject='<S>{}'.format(subject) if subject else '', + ) + +def _convert_options_to_certmap(options): + """ + Converts options to ipacertmapdata + + When --subject --issuer or --usercertificate options are used, + the value for ipacertmapdata is built from extracting subject and issuer, + converting their values to X500 ordering and using the format + X509:<I>issuer<S>subject + For instance: + X509:<I>O=DOMAIN,CN=Certificate Authority<S>O=DOMAIN,CN=user + A list of values can be returned if --usercertificate is used multiple + times, or in conjunction with --subject --issuer. + """ + data = [] + if 'ipacertmapdata' in options: + for item in options['ipacertmapdata']: + data.append(item) + + if 'issuer' in options or 'subject' in options: + issuer = options.get('issuer') + subject = options.get('subject') + data.append(_build_mapdata(subject, issuer)) + + if 'usercertificate' in options: + for dercert in options.get('usercertificate'): + cert = x509.load_certificate(dercert, x509.DER) + issuer = DN(cert.issuer) + subject = DN(cert.subject) + data.append(_build_mapdata(subject, issuer)) + + return data + + +certmap_options = ( + DNParam('issuer?', + cli_name='issuer', + label=_('Issuer'), + doc=_('Issuer of the certificate'), + flags=['virtual_attribute', 'no_create', 'no_update'] + ), + DNParam('subject?', + cli_name='subject', + label=_('Subject'), + doc=_('Subject of the certificate'), + flags=['virtual_attribute', 'no_create', 'no_update'] + ), + Bytes('usercertificate*', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded user certificate'), + ), +) + +@register() +class user_add_certmap(LDAPAddAttributeViaOption): + __doc__ = _("Add one or more certificate mappings to the user entry.") + msg_summary = _('Added certificate mappings to user "%(value)s"') + + attribute = 'ipacertmapdata' + takes_options = certmap_options + + def get_options(self): + # ipacertmapdata is not mandatory as it can be built + # from the values subject+issuer or from reading usercertificate + for option in super(user_add_certmap, self).get_options(): + if option.name in ['ipacertmapdata']: + yield option.clone(required=False, alwaysask=False) + else: + yield option.clone() + + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + # The 3 valid calls are + # --subject xx --issuer yy + # --certificate + # --data + # Check that at least one of the 3 formats is used + if 'issuer' not in options and \ + 'subject' not in options and \ + 'ipacertmapdata' not in options and \ + 'usercertificate' not in options: + raise errors.RequirementError(name='data') + + # The objectclass ipacertmapobject may not be present on + # existing user entries. We need to add it if we define a new + # value for ipacertmapdata + if 'objectclass' not in entry_attrs: + entry_attrs_old = ldap.get_entry(dn, ['objectclass']) + objclasses_lc = [x.lower() for x in entry_attrs_old['objectclass']] + if 'ipacertmapobject' not in objclasses_lc: + entry_attrs['objectclass'] = ['ipacertmapobject'] + + entry_attrs[self.attribute] = _convert_options_to_certmap(options) + + # if the command is called with --subject --issuer or --certificate + # we need to add ipacertmapdata to the attrs_list in order to + # display the resulting value in the command output + if 'ipacertmapdata' not in attrs_list: + attrs_list.append('ipacertmapdata') + + return dn + + +@register() +class user_remove_certmap(LDAPRemoveAttributeViaOption): + __doc__ = _("Remove one or more certificate mappings from the user entry.") + msg_summary = _('Removed certificate mappings from user "%(value)s"') + + attribute = 'ipacertmapdata' + takes_options = certmap_options + + def get_options(self): + # ipacertmapdata is not mandatory as it can be built + # from the values subject+issuer or from reading usercertificate + for option in super(user_remove_certmap, self).get_options(): + if option.name in ['ipacertmapdata']: + yield option.clone(required=False, alwaysask=False) + else: + yield option.clone() + + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + if 'issuer' not in options and \ + 'subject' not in options and \ + 'ipacertmapdata' not in options and \ + 'usercertificate' not in options: + raise errors.RequirementError(name='data') + + entry_attrs[self.attribute] = _convert_options_to_certmap(options) + + # if the command is called with --subject --issuer or --certificate + # we need to add ipacertmapdata to the attrs_list in order to + # display the resulting value in the command output + if 'ipacertmapdata' not in attrs_list: + attrs_list.append('ipacertmapdata') + + return dn + + @register() class user_add_manager(baseuser_add_manager): __doc__ = _("Add a manager to the user entry") From 1566d5cb6ed51ea36e7bcefa20b0f1f7005f7b38 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Fri, 13 Jan 2017 13:17:35 +0100 Subject: [PATCH 2/4] test_xmlrpc: tracker: Add enable and disable methods to tracker Prepare tracker for easier testing of *-{en,dis}able commands. --- ipatests/test_xmlrpc/tracker/base.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py index aa88e6b..d8cd3a6 100644 --- a/ipatests/test_xmlrpc/tracker/base.py +++ b/ipatests/test_xmlrpc/tracker/base.py @@ -198,6 +198,14 @@ def make_update_command(self, updates): """Make function that modifies the entry using ${CMD}_mod""" raise NotImplementedError(self._override_me_msg) + def make_enable_command(self): + """Make function that enables the entry using ${CMD}_enable""" + raise NotImplementedError(self._override_me_msg) + + def make_disable_command(self): + """Make function that disables the entry using ${CMD}_disable""" + raise NotImplementedError(self._override_me_msg) + def create(self): """Helper function to create an entry and check the result""" self.track_create() @@ -285,3 +293,21 @@ def update(self, updates, expected_updates=None): def check_update(self, result, extra_keys=()): """Check the plugin's `mod` command result""" raise NotImplementedError(self._override_me_msg) + + def enable(self): + command = self.make_enable_command() + result = command() + self.check_enable(result) + + def check_enable(self, result): + """Check the plugin's `enable` command result""" + raise NotImplementedError(self._override_me_msg) + + def disable(self): + command = self.make_disable_command() + result = command() + self.check_disable(result) + + def check_disable(self, result): + """Check the plugin's `disable` command result""" + raise NotImplementedError(self._override_me_msg) From e2f085f769f99b9b95684b41d18da653fd9ec690 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Fri, 13 Jan 2017 13:22:45 +0100 Subject: [PATCH 3/4] test: certmap: Add basic tests for certmaprule commands. https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/objectclasses.py | 5 + ipatests/test_xmlrpc/test_certmap_plugin.py | 107 ++++++++++++++++ ipatests/test_xmlrpc/tracker/certmap_plugin.py | 167 +++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 ipatests/test_xmlrpc/test_certmap_plugin.py create mode 100644 ipatests/test_xmlrpc/tracker/certmap_plugin.py diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py index 1ea020b..0a15a21 100644 --- a/ipatests/test_xmlrpc/objectclasses.py +++ b/ipatests/test_xmlrpc/objectclasses.py @@ -227,3 +227,8 @@ u'top', u'ipaca', ] + +certmaprule = [ + u'top', + u'ipacertmaprule', +] diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py new file mode 100644 index 0000000..9343f9a --- /dev/null +++ b/ipatests/test_xmlrpc/test_certmap_plugin.py @@ -0,0 +1,107 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +import itertools +import pytest + +from ipapython.dn import DN +from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test +from ipatests.test_xmlrpc.tracker.certmap_plugin import CertmapruleTracker + +certmaprule_create_params = { + u'cn': u'test_rule', + u'description': u'Certificate mapping and matching rule for test ' + u'purposes', + u'ipacertmapissuer': DN('CN=CA,O=EXAMPLE.ORG'), + u'ipacertmapmaprule': u'arbitrary free-form mapping rule defined and ' + u'consumed by SSSD', + u'ipacertmapmatchrule': u'arbitrary free-form matching rule defined ' + u'and consumed by SSSD', + u'associateddomain': u'example.org', + u'ipacertmappriority': u'1', +} + +certmaprule_update_params = { + u'description': u'Changed description', + u'ipacertmapissuer': DN('CN=Changed CA,O=OTHER.ORG'), + u'ipacertmapmaprule': u'changed arbitrary mapping rule', + u'ipacertmapmatchrule': u'changed arbitrary maching rule', + u'associateddomain': u'changed.example.org', + u'ipacertmappriority': u'5', +} + +certmaprule_optional_params = ( + 'description', + 'ipacertmapissuer', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'ipaassociateddomain', + 'ipacertmappriority', +) + +def dontfill_idfn(dont_fill): + return u"dont_fill=({})".format(', '.join([ + u"{}".format(d) for d in dont_fill + ])) + + +def update_idfn(update): + return ', '.join(["{}: {}".format(k, v) for k, v in update.items()]) + + +@pytest.fixture(scope='class') +def certmap_rule(request): + tracker = CertmapruleTracker(**certmaprule_create_params) + return tracker.make_fixture(request) + + +class TestCRUD(XMLRPC_test): + @pytest.mark.parametrize( + 'dont_fill', + itertools.chain(*[ + itertools.combinations(certmaprule_optional_params, l) + for l in range(len(certmaprule_optional_params)+1) + ]), + ids=dontfill_idfn, + ) + def test_create(self, dont_fill, certmap_rule): + certmap_rule.ensure_missing() + try: + certmap_rule.create(dont_fill) + finally: + certmap_rule.ensure_missing() + + def test_retrieve(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.retrieve() + + def test_find(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.find() + + @pytest.mark.parametrize('update', [ + dict(u) for l in range(1, len(certmaprule_update_params)+1) + for u in itertools.combinations( + certmaprule_update_params.items(), l) + ], + ids=update_idfn, + ) + def test_update(self, update, certmap_rule): + certmap_rule.ensure_missing() + certmap_rule.ensure_exists() + certmap_rule.update(update, {o: [v] for o, v in update.items()}) + + def test_delete(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.delete() + + +class TestEnableDisable(XMLRPC_test): + def test_disable(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.disable() + + def test_enable(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.enable() diff --git a/ipatests/test_xmlrpc/tracker/certmap_plugin.py b/ipatests/test_xmlrpc/tracker/certmap_plugin.py new file mode 100644 index 0000000..76022bf --- /dev/null +++ b/ipatests/test_xmlrpc/tracker/certmap_plugin.py @@ -0,0 +1,167 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +from ipapython.dn import DN +from ipatests.test_xmlrpc.tracker.base import Tracker +from ipatests.test_xmlrpc import objectclasses +from ipatests.util import assert_deepequal + + +class CertmapruleTracker(Tracker): + """ Tracker for testin certmaprule plugin """ + retrieve_keys = { + u'dn', + u'cn', + u'description', + u'ipacertmapissuer', + u'ipacertmapmaprule', + u'ipacertmapmatchrule', + u'associateddomain', + u'ipacertmappriority', + u'ipaenabledflag' + } + retrieve_all_keys = retrieve_keys | {u'objectclass'} + create_keys = retrieve_keys | {u'objectclass'} + update_keys = retrieve_keys - {u'dn'} + + def __init__(self, cn, description, ipacertmapissuer, ipacertmapmaprule, + ipacertmapmatchrule, associateddomain, ipacertmappriority, + default_version=None): + super(CertmapruleTracker, self).__init__( + default_version=default_version) + + self.dn = DN((u'cn', cn,), + self.api.env.container_certmaprules, + self.api.env.basedn) + self.options = { + u'description': description, + u'ipacertmapissuer': ipacertmapissuer, + u'ipacertmapmaprule': ipacertmapmaprule, + u'ipacertmapmatchrule': ipacertmapmatchrule, + u'associateddomain': associateddomain, + u'ipacertmappriority': ipacertmappriority, + } + + def make_create_command(self, dont_fill=()): + kwargs = {k: v for k, v in self.options.items() if k not in dont_fill} + + return self.make_command('certmaprule_add', self.name, **kwargs) + + def track_create(self, dont_fill=()): + self.attrs = { + 'dn': self.dn, + 'cn': [self.name], + 'ipaenabledflag': [u'TRUE'], + 'objectclass': objectclasses.certmaprule, + } + self.attrs.update({ + k: [v] for k, v in self.options.items() if k not in dont_fill + }) + self.exists = True + + def check_create(self, result): + assert_deepequal(dict( + value=self.name, + summary=u'Added Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=self.filter_attrs(self.create_keys), + ), result) + + def create(self, dont_fill=()): + self.track_create(dont_fill) + command = self.make_create_command(dont_fill) + result = command() + self.check_create(result) + + def make_delete_command(self): + return self.make_command('certmaprule_del', self.name) + + def check_delete(self, result): + assert_deepequal( + dict( + value=[self.name], + summary=u'Deleted Certificate Identity Mapping Rule "{}"' + ''.format(self.name), + result=dict(failed=[]), + ), + result + ) + + def make_retrieve_command(self, all=False, raw=False): + return self.make_command('certmaprule_show', self.name, all=all, + raw=raw) + + def check_retrieve(self, result, all=False, raw=False): + if all: + expected = self.filter_attrs(self.retrieve_all_keys) + else: + expected = self.filter_attrs(self.retrieve_keys) + assert_deepequal( + dict( + value=self.name, + summary=None, + result=expected, + ), + result + ) + + def make_find_command(self, *args, **kwargs): + return self.make_command('certmaprule_find', *args, **kwargs) + + def check_find(self, result, all=False, raw=False): + if all: + expected = self.filter_attrs(self.retrieve_all_keys) + else: + expected = self.filter_attrs(self.retrieve_keys) + assert_deepequal( + dict( + count=1, + truncated=False, + summary=u'1 Certificate Identity Mapping Rule matched', + result=[expected], + ), + result + ) + + def make_update_command(self, updates): + return self.make_command('certmaprule_mod', self.name, **updates) + + def check_update(self, result, extra_keys=()): + assert_deepequal( + dict( + value=self.name, + summary=u'Modified Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=self.filter_attrs(self.update_keys | set(extra_keys)), + ), + result + ) + + def make_enable_command(self): + return self.make_command('certmaprule_enable', self.name) + + def check_enable(self, result): + assert_deepequal( + dict( + value=self.name, + summary=u'Enabled Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=True, + ), + result + ) + + def make_disable_command(self): + return self.make_command('certmaprule_disable', self.name) + + def check_disable(self, result): + assert_deepequal( + dict( + value=self.name, + summary=u'Disabled Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=True, + ), + result + ) From f675c08b04f07e0f311fe2c09361bc7dab96aac3 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Tue, 24 Jan 2017 16:21:54 +0100 Subject: [PATCH 4/4] tests: certmap: Test ACI works as expected https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/test_certmap_plugin.py | 260 ++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py index 9343f9a..00f5b69 100644 --- a/ipatests/test_xmlrpc/test_certmap_plugin.py +++ b/ipatests/test_xmlrpc/test_certmap_plugin.py @@ -2,12 +2,18 @@ # Copyright (C) 2017 FreeIPA Contributors see COPYING for license # +from contextlib import contextmanager import itertools +from nose.tools import assert_raises import pytest +from ipalib import api, errors from ipapython.dn import DN from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test from ipatests.test_xmlrpc.tracker.certmap_plugin import CertmapruleTracker +from ipatests.util import assert_deepequal +from ipatests.util import change_principal, unlock_principal_password + certmaprule_create_params = { u'cn': u'test_rule', @@ -40,6 +46,17 @@ 'ipacertmappriority', ) +certmaprule_permissions = { + u'C': u'System: Add Certmap Rules', + u'R': u'System: Read Certmap Rules', + u'U': u'System: Modify Certmap Rules', + u'D': u'System: Delete Certmap Rules', +} + +CERTMAP_USER = u'cuser' +CERTMAP_PASSWD = 'Secret123' + + def dontfill_idfn(dont_fill): return u"dont_fill=({})".format(', '.join([ u"{}".format(d) for d in dont_fill @@ -105,3 +122,246 @@ def test_disable(self, certmap_rule): def test_enable(self, certmap_rule): certmap_rule.ensure_exists() certmap_rule.enable() + + +class Result(Exception): + def __init__(self, value): + self.value = value + + +@contextmanager +def execute_with_expected(user, password, perms, exps, ok_expected=None): + """ + Run command as specified user. Check exception or return value + according provided rules. + + @param user Change to this user before calling the command + @param password User to change user + @param perms User has those permissions + @param exps Iterable containing tuple + (permission, exception_class, expected_result,) + If permission is missing command must raise exception of + exception_class. If exception class is None command must + raise Result(expected_result) + @param ok_expected When no permission is missing command must raise + Result(ok_expected) + """ + with change_principal(user, password): + for perm, exception, expected in exps: + if perm not in perms: + if exception: + with assert_raises(exception): + yield + else: + try: + yield + except Result as got: + assert_deepequal(expected, got.value) + else: + if ok_expected: + assert("Command didn't raise Result") + break + else: + try: + yield + except Result as got: + if ok_expected: + assert_deepequal(ok_expected, got.value) + else: + if ok_expected: + assert("Command didn't raise Result") + + +def permissions_idfn(perms): + i = [] + for short_name, long_name in certmaprule_permissions.items(): + if long_name in perms: + i.append(short_name) + else: + i.append('-') + return ''.join(i) + + +@pytest.fixture( + scope='class', + params=itertools.chain(*[ + itertools.combinations(certmaprule_permissions.values(), l) + for l in range(len(certmaprule_permissions.values())+1) + ]), + ids=permissions_idfn, +) +def certmap_user_permissions(request): + tmp_password = u'Initial123' + + priv_name = u'test_certmap_privilege' + role_name = u'test_certmap_role' + + api.Command.user_add(CERTMAP_USER, givenname=u'Certmap', sn=u'User', + userpassword=tmp_password) + unlock_principal_password(CERTMAP_USER, tmp_password, + CERTMAP_PASSWD) + + api.Command.privilege_add(priv_name) + for perm_name in request.param: + api.Command.privilege_add_permission(priv_name, permission=perm_name) + api.Command.role_add(role_name) + api.Command.role_add_privilege(role_name, privilege=priv_name) + api.Command.role_add_member(role_name, user=CERTMAP_USER) + + def finalize(): + try: + api.Command.user_del(CERTMAP_USER) + except Exception: + pass + try: + api.Command.role_del(role_name) + except Exception: + pass + try: + api.Command.privilege_del(priv_name) + except Exception: + pass + request.addfinalizer(finalize) + + return request.param + + +class TestPermission(XMLRPC_test): + def test_create(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_missing() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Add Certmap Rules', errors.ACIError, None,), + (u'System: Read Certmap Rules', errors.NotFound, None,), + ], + ): + certmap_rule.create(), + + # Tracker sets 'exists' to True even when the create does not + # succeed so ensure_missing wouldn't be reliable here + try: + certmap_rule.delete() + except Exception: + pass + + def test_retrieve(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Read Certmap Rules', errors.NotFound, None,), + ], + ): + certmap_rule.retrieve() + + def test_find(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + expected_without_read = { + u'count': 0, + u'result': (), + u'summary': u'0 Certificate Identity Mapping Rules matched', + u'truncated': False, + } + expected_ok = { + u'count': 1, + u'result': [{ + k: (v,) for k, v in certmaprule_create_params.items() + }], + u'summary': u'1 Certificate Identity Mapping Rule matched', + u'truncated': False, + } + expected_ok[u'result'][0][u'dn'] = DN( + (u'cn', expected_ok[u'result'][0][u'cn'][0]), + api.env.container_certmaprules, + api.env.basedn, + ) + expected_ok[u'result'][0][u'ipaenabledflag'] = (u'TRUE',) + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Read Certmap Rules', None, expected_without_read,), + ], + expected_ok, + ): + find = certmap_rule.make_find_command() + got = find(**{k: v for k, v in certmaprule_create_params.items() + if k is not u'dn'}) + raise Result(got) + + def test_update(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_missing() + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Read Certmap Rules', errors.NotFound, None,), + (u'System: Modify Certmap Rules', errors.ACIError, None,), + ], + ): + certmap_rule.update( + certmaprule_update_params, + {o: [v] for o, v in certmaprule_update_params.items()}, + ) + + def test_delete(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Delete Certmap Rules', errors.ACIError, None,), + ], + ): + certmap_rule.delete() + + # Tracker sets 'exists' to False even when the delete does not + # succeed so ensure_missing wouldn't be reliable here + try: + certmap_rule.delete() + except Exception: + pass + + def test_enable(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + certmap_rule.disable() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Read Certmap Rules', errors.NotFound, None,), + (u'System: Modify Certmap Rules', errors.ACIError, None,), + ], + ): + certmap_rule.enable() + + def test_disable(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + certmap_rule.enable() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (u'System: Read Certmap Rules', errors.NotFound, None,), + (u'System: Modify Certmap Rules', errors.ACIError, None,), + ], + ): + certmap_rule.disable()
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code