URL: https://github.com/freeipa/freeipa/pull/516 Author: flo-renaud Title: #516: IdM Server: list all Employees with matching Smart Card Action: opened
PR body: """ Implement a new IPA command allowing to retrieve the list of users matching the provided certificate. The command is using SSSD Dbus interface, thus including users from IPA domain and from trusted domains. This requires sssd-dbus package to be installed on IPA server. https://fedorahosted.org/freeipa/ticket/6646 """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/516/head:pr516 git checkout pr516
From 05f93e155e44aeb00d7af67f02af4e1d5a96bda8 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/2] 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 | 181 +++++++++++++++++ VERSION.m4 | 4 +- install/share/73certmap.ldif | 14 ++ install/share/Makefile.am | 1 + install/updates/73-certmap.update | 23 +++ install/updates/Makefile.am | 1 + ipalib/constants.py | 2 + ipapython/dn.py | 8 +- ipaserver/install/dsinstance.py | 1 + ipaserver/plugins/baseuser.py | 174 ++++++++++++++++- ipaserver/plugins/certmap.py | 391 +++++++++++++++++++++++++++++++++++++ ipaserver/plugins/stageuser.py | 16 +- ipaserver/plugins/user.py | 23 ++- ipatests/test_ipapython/test_dn.py | 20 ++ 15 files changed, 862 insertions(+), 13 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..a36d460 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,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,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,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,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,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || description || 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,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || createtimestamp || description || entryusn || ipacertmapmaprule || ipacertmapmatchrule || ipacertmappriority || ipaenabledflag || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Read Certmap Rules";allow (compare,read,search) userdn = "ldap:///all";) 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 3ebebab..a8f8ff1 100644 --- a/API.txt +++ b/API.txt @@ -824,6 +824,116 @@ 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,11,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', cli_name='domain') +option: Str('description?', cli_name='desc') +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,13,4 +arg: Str('criteria?') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', autofill=False, cli_name='domain') +option: Str('cn?', autofill=False, cli_name='rulename') +option: Str('description?', autofill=False, cli_name='desc') +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,13,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', autofill=False, cli_name='domain') +option: Str('delattr*', cli_name='delattr') +option: Str('description?', autofill=False, cli_name='desc') +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') @@ -4762,6 +4872,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: stageuser_add_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +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: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: stageuser_add_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -4915,6 +5039,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: stageuser_remove_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +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: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: stageuser_remove_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -5796,6 +5934,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: user_add_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +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: 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') @@ -5968,6 +6120,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: user_remove_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +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: 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') @@ -6351,6 +6517,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 @@ -6706,12 +6883,14 @@ default: stageuser/1 default: stageuser_activate/1 default: stageuser_add/1 default: stageuser_add_cert/1 +default: stageuser_add_certmapdata/1 default: stageuser_add_manager/1 default: stageuser_add_principal/1 default: stageuser_del/1 default: stageuser_find/1 default: stageuser_mod/1 default: stageuser_remove_cert/1 +default: stageuser_remove_certmapdata/1 default: stageuser_remove_manager/1 default: stageuser_remove_principal/1 default: stageuser_show/1 @@ -6789,6 +6968,7 @@ default: trustdomain_mod/1 default: user/1 default: user_add/1 default: user_add_cert/1 +default: user_add_certmapdata/1 default: user_add_manager/1 default: user_add_principal/1 default: user_del/1 @@ -6797,6 +6977,7 @@ default: user_enable/1 default: user_find/1 default: user_mod/1 default: user_remove_cert/1 +default: user_remove_certmapdata/1 default: user_remove_manager/1 default: user_remove_principal/1 default: user_show/1 diff --git a/VERSION.m4 b/VERSION.m4 index 8d66718..8c93277 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, 218) -# Last change: Remove no_option flag for nsaccountlock and add cli_name='disabled' +define(IPA_API_VERSION_MINOR, 219) +# 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..9c67ccb --- /dev/null +++ b/install/share/73certmap.ldif @@ -0,0 +1,14 @@ +## IPA Base OID: +## +## Attributes: 2.16.840.1.113730.3.8.22.1.x +## ObjectClasses: 2.16.840.1.113730.3.8.22.2.y +## +dn: cn=schema +attributeTypes: (2.16.840.1.113730.3.8.22.1.1 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.1.2 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.1.3 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.1.4 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.1.5 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.22.2.1 NAME 'ipaCertMapConfigObject' DESC 'IPA Certificate Mapping global config options' AUXILIARY MAY ipaCertMapPromptUsername X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.22.2.2 NAME 'ipaCertMapRule' DESC 'IPA Certificate Mapping rule' SUP top STRUCTURAL MUST cn MAY ( description $ ipaCertMapMapRule $ ipaCertMapMatchRule $ associatedDomain $ ipaCertMapPriority $ ipaEnabledFlag ) X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.22.2.3 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 c58e1d2..bbf6ce1 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -27,6 +27,7 @@ dist_app_DATA = \ 70topology.ldif \ 71idviews.ldif \ 72domainlevels.ldif \ + 73certmap.ldif \ anon-princ-aci.ldif \ bootstrap-template.ldif \ ca-topology.uldif \ diff --git a/install/updates/73-certmap.update b/install/updates/73-certmap.update new file mode 100644 index 0000000..ecb3db3 --- /dev/null +++ b/install/updates/73-certmap.update @@ -0,0 +1,23 @@ +# Configuration for Certificate Identity Mapping +dn: cn=certmap,$SUFFIX +default:objectclass: top +default:objectclass: nsContainer +default:objectclass: ipaCertMapConfigObject +default:cn: certmap +default:ipaCertMapPromptUsername: FALSE + +dn: cn=certmaprules,cn=certmap,$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 e64324f..e7e8048 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'))), + ('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipapython/dn.py b/ipapython/dn.py index 4e8c22b..a54629e 100644 --- a/ipapython/dn.py +++ b/ipapython/dn.py @@ -1155,9 +1155,15 @@ def __deepcopy__(self, memo): def _get_rdn(self, rdn): return self.RDN_type(*rdn, **{'raw': True}) - def __str__(self): + def ldap_text(self): return dn2str(self.rdns) + def x500_text(self): + return dn2str(reversed(self.rdns)) + + def __str__(self): + return self.ldap_text() + def __repr__(self): return "%s.%s('%s')" % (self.__module__, self.__class__.__name__, self.__str__()) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 9172b65..da44814 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 75cf7d8..44adc76 100644 --- a/ipaserver/plugins/baseuser.py +++ b/ipaserver/plugins/baseuser.py @@ -19,14 +19,17 @@ import six -from ipalib import api, errors -from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes +from ipalib import api, errors, x509 +from ipalib import ( + Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes, DNParam) from ipalib.parameters import Principal from ipalib.plugable import Registry from .baseldap import ( DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete, - LDAPRetrieve, LDAPAddAttribute, LDAPRemoveAttribute, LDAPAddMember, - LDAPRemoveMember, LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption) + LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute, + LDAPAddMember, LDAPRemoveMember, + LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption, + add_missing_object_class) from ipaserver.plugins.service import ( validate_certificate, validate_realm, normalize_principal) from ipalib.request import context @@ -134,7 +137,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 +149,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 +364,13 @@ class baseuser(LDAPObject): label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), + Str( + 'ipacertmapdata*', + cli_name='certmapdata', + label=_('Certificate mapping data'), + doc=_('Certificate mapping data'), + flags=['no_create', 'no_update', 'no_search'], + ), ) def normalize_and_validate_email(self, email, config=None): @@ -728,3 +739,154 @@ def post_callback(self, ldap, dn, entry_attrs, *keys, **options): self.obj.convert_usercertificate_post(entry_attrs, **options) return dn + + +class ModCertMapData(LDAPModAttribute): + attribute = 'ipacertmapdata' + takes_options = ( + DNParam( + 'issuer?', + cli_name='issuer', + label=_('Issuer'), + doc=_('Issuer of the certificate'), + flags=['virtual_attribute'] + ), + DNParam( + 'subject?', + cli_name='subject', + label=_('Subject'), + doc=_('Subject of the certificate'), + flags=['virtual_attribute'] + ), + Bytes( + 'certificate*', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded user certificate'), + flags=['virtual_attribute'] + ), + ) + + @staticmethod + def _build_mapdata(subject, issuer): + return u'X509:<I>{issuer}<S>{subject}'.format( + issuer=issuer.x500_text(), subject=subject.x500_text()) + + @classmethod + def _convert_options_to_certmap(cls, entry_attrs, issuer=None, + subject=None, certificates=()): + """ + Converts options to ipacertmapdata + + When --subject --issuer or --certificate 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 --certificate is used multiple + times, or in conjunction with --subject --issuer. + """ + data = [] + data.extend(entry_attrs.get(cls.attribute, list())) + + if issuer or subject: + data.append(cls._build_mapdata(subject, issuer)) + + for dercert in certificates: + cert = x509.load_certificate(dercert, x509.DER) + issuer = DN(cert.issuer) + subject = DN(cert.subject) + if not subject: + raise errors.ValidationError( + name='certificate', + error=_('cannot have an empty subject')) + data.append(cls._build_mapdata(subject, issuer)) + + entry_attrs[cls.attribute] = data + + def get_args(self): + # ipacertmapdata is not mandatory as it can be built + # from the values subject+issuer or from reading certificate + for arg in super(ModCertMapData, self).get_args(): + if arg.name == 'ipacertmapdata': + yield arg.clone(required=False, alwaysask=False) + else: + yield arg.clone() + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + # The 3 valid calls are + # ipa user-add-certmapdata LOGIN --subject xx --issuer yy + # ipa user-add-certmapdata LOGIN [DATA] --certificate xx + # ipa user-add-certmapdata LOGIN DATA + # Check that at least one of the 3 formats is used + + try: + certmapdatas = keys[1] or [] + except IndexError: + certmapdatas = [] + issuer = options.get('issuer') + subject = options.get('subject') + certificates = options.get('certificate', []) + + # If only LOGIN is supplied, then we need either subject or issuer or + # certificate + if (not certmapdatas and not issuer and not subject and + not certificates): + raise errors.RequirementError(name='ipacertmapdata') + + # If subject or issuer is provided, other options are not allowed + if subject or issuer: + if certificates: + raise errors.MutuallyExclusiveError( + reason=_('cannot specify both subject/issuer ' + 'and certificate')) + if certmapdatas: + raise errors.MutuallyExclusiveError( + reason=_('cannot specify both subject/issuer ' + 'and ipacertmapdata')) + # If subject or issuer is provided, then the other one is required + if not subject: + raise errors.RequirementError(name='subject') + if not issuer: + raise errors.RequirementError(name='issuer') + + # 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') + + self._convert_options_to_certmap( + entry_attrs, + issuer=issuer, + subject=subject, + certificates=certificates) + + return dn + + +class baseuser_add_certmapdata(ModCertMapData, LDAPAddAttribute): + __doc__ = _("Add one or more certificate mappings to the user entry.") + msg_summary = _('Added certificate mappings to user "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + + dn = super(baseuser_add_certmapdata, self).pre_callback( + ldap, dn, entry_attrs, attrs_list, *keys, **options) + + # The objectclass ipacertmapobject may not be present on + # existing user entries. We need to add it if we define a new + # value for ipacertmapdata + add_missing_object_class(ldap, u'ipacertmapobject', dn) + + return dn + + +class baseuser_remove_certmapdata(ModCertMapData, + LDAPRemoveAttribute): + __doc__ = _("Remove one or more certificate mappings from the user entry.") + msg_summary = _('Removed certificate mappings from user "%(value)s"') diff --git a/ipaserver/plugins/certmap.py b/ipaserver/plugins/certmap.py new file mode 100644 index 0000000..c37eae3 --- /dev/null +++ b/ipaserver/plugins/certmap.py @@ -0,0 +1,391 @@ +# Authors: +# Florence Blanc-Renaud <f...@redhat.com> +# +# Copyright (C) 2017 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/>. + +import six + +from ipalib import api, errors +from ipalib.parameters import Bool, DNSNameParam, Flag, Int, Str +from ipalib.plugable import Registry +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 rule1 --desc="Link certificate with subject and issuer" +""") + _(""" + Modify a Certificate Identity Mapping Rule: + ipa certmaprule-mod rule1 --maprule="<ALT-SEC-ID-I-S:altSecurityIdentities>" +""") + _(""" + Disable a Certificate Identity Mapping Rule: + ipa certmaprule-disable rule1 +""") + _(""" + Enable a Certificate Identity Mapping Rule: + ipa certmaprule-enable rule1 +""") + _(""" + Display information about a Certificate Identity Mapping Rule: + ipa certmaprule-show rule1 +""") + _(""" + 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 rule1 +""") + +register = Registry() + + +def check_associateddomain_is_trusted(api_inst, options): + """ + Check that the associateddomain in options are either IPA domain or + a trusted domain. + + :param api_inst: API instance + :param associateddomain: domains to be checked + + :raises: ValidationError if the domain is neither IPA domain nor trusted + """ + domains = options.get('associateddomain') + if domains: + trust_suffix_namespace = set() + trust_suffix_namespace.add(api_inst.env.domain.lower()) + + trust_objects = api_inst.Command.trust_find(sizelimit=0)['result'] + for obj in trust_objects: + trustdomains = api_inst.Command.trustdomain_find( + obj['cn'][0], sizelimit=0)['result'] + for domain in trustdomains: + trust_suffix_namespace.add(domain['cn'][0].lower()) + + for dom in domains: + if not str(dom).lower() in trust_suffix_namespace: + raise errors.ValidationError( + name=_('domain'), + error=_('The domain %s is neither IPA domain nor a trusted' + 'domain.') % dom + ) + + +@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', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'associateddomain', + 'ipacertmappriority', + 'ipaenabledflag' + ] + search_attributes = [ + 'cn', 'description', + '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'), + ), + 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'), + ), + DNSNameParam( + 'associateddomain*', + 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, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'description', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + }, + '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', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + } + + +@register() +class certmaprule_add(LDAPCreate): + __doc__ = _('Create a new Certificate Identity Mapping Rule.') + + msg_summary = _('Added Certificate Identity Mapping Rule "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + check_associateddomain_is_trusted(self.api, options) + return dn + + +@register() +class certmaprule_mod(LDAPUpdate): + __doc__ = _('Modify a Certificate Identity Mapping Rule.') + + msg_summary = _('Modified Certificate Identity Mapping Rule "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + check_associateddomain_is_trusted(self.api, options) + return dn + + +@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/stageuser.py b/ipaserver/plugins/stageuser.py index 5602514..c7ea478 100644 --- a/ipaserver/plugins/stageuser.py +++ b/ipaserver/plugins/stageuser.py @@ -44,7 +44,9 @@ baseuser_add_principal, baseuser_remove_principal, baseuser_add_manager, - baseuser_remove_manager) + baseuser_remove_manager, + baseuser_add_certmapdata, + baseuser_remove_certmapdata) from ipalib.request import context from ipalib.util import set_krbcanonicalname from ipalib import _, ngettext @@ -772,3 +774,15 @@ class stageuser_add_principal(baseuser_add_principal): class stageuser_remove_principal(baseuser_remove_principal): __doc__ = _('Remove principal alias from the stageuser entry') msg_summary = _('Removed aliases from stageuser "%(value)s"') + + +@register() +class stageuser_add_certmapdata(baseuser_add_certmapdata): + __doc__ = _("Add one or more certificate mappings to the stage user" + " entry.") + + +@register() +class stageuser_remove_certmapdata(baseuser_remove_certmapdata): + __doc__ = _("Remove one or more certificate mappings from the stage user" + " entry.") diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py index 88171cf..2d29dfb 100644 --- a/ipaserver/plugins/user.py +++ b/ipaserver/plugins/user.py @@ -22,7 +22,6 @@ from time import gmtime, strftime import posixpath import os - import six from ipalib import api @@ -46,7 +45,9 @@ baseuser_add_cert, baseuser_remove_cert, baseuser_add_principal, - baseuser_remove_principal) + baseuser_remove_principal, + baseuser_add_certmapdata, + baseuser_remove_certmapdata) from .idviews import remove_ipaobject_overrides from ipalib.plugable import Registry from .baseldap import ( @@ -179,6 +180,7 @@ class user(baseuser): 'secretary', 'usercertificate', 'usersmimecertificate', 'x500uniqueidentifier', 'inetuserhttpurl', 'inetuserstatus', + 'ipacertmapdata', }, 'fixup_function': fix_addressbook_permission_bindrule, }, @@ -366,6 +368,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 + ( @@ -1185,6 +1194,16 @@ class user_remove_cert(baseuser_remove_cert): @register() +class user_add_certmapdata(baseuser_add_certmapdata): + __doc__ = _("Add one or more certificate mappings to the user entry.") + + +@register() +class user_remove_certmapdata(baseuser_remove_certmapdata): + __doc__ = _("Remove one or more certificate mappings from the user entry.") + + +@register() class user_add_manager(baseuser_add_manager): __doc__ = _("Add a manager to the user entry") diff --git a/ipatests/test_ipapython/test_dn.py b/ipatests/test_ipapython/test_dn.py index 3ca3b57..24b6093 100644 --- a/ipatests/test_ipapython/test_dn.py +++ b/ipatests/test_ipapython/test_dn.py @@ -1184,6 +1184,26 @@ def test_hashing(self): self.assertFalse(dn3_a in s) self.assertFalse(dn3_b in s) + def test_x500_text(self): + # null DN x500 ordering and LDAP ordering are the same + nulldn = DN() + self.assertEqual(nulldn.ldap_text(), nulldn.x500_text()) + + # reverse a DN with a single RDN + self.assertEqual(self.dn1.ldap_text(), self.dn1.x500_text()) + + # reverse a DN with 2 RDNs + dn3_x500 = self.dn3.x500_text() + dn3_rev = DN(self.rdn2, self.rdn1) + self.assertEqual(dn3_rev.ldap_text(), dn3_x500) + + # reverse a longer DN + longdn_x500 = self.base_container_dn.x500_text() + longdn_rev = DN(longdn_x500) + l = len(self.base_container_dn) + for i in range(l): + self.assertEquals(longdn_rev[i], self.base_container_dn[l-1-i]) + class TestEscapes(unittest.TestCase): def setUp(self): From f07858477a7c7cf5fed3ac6383207add303c8404 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud <f...@redhat.com> Date: Thu, 23 Feb 2017 18:04:47 +0100 Subject: [PATCH 2/2] IdM Server: list all Employees with matching Smart Card Implement a new IPA command allowing to retrieve the list of users matching the provided certificate. The command is using SSSD Dbus interface, thus including users from IPA domain and from trusted domains. This requires sssd-dbus package to be installed on IPA server. https://fedorahosted.org/freeipa/ticket/6646 --- API.txt | 8 +++ freeipa.spec.in | 2 + ipaclient/plugins/certmap.py | 65 ++++++++++++++++++++++++ ipaserver/plugins/certmap.py | 118 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 ipaclient/plugins/certmap.py diff --git a/API.txt b/API.txt index a8f8ff1..22cab15 100644 --- a/API.txt +++ b/API.txt @@ -824,6 +824,13 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: PrimaryKey('value') +command: certmap_match/1 +args: 0,2,3 +option: Bytes('certificate', cli_name='certificate') +option: Str('version?') +output: Output('count', type=[<type 'int'>]) +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: Output('uids', type=[<type 'list'>, <type 'tuple'>, <type 'NoneType'>]) command: certmapconfig_mod/1 args: 0,8,3 option: Str('addattr*', cli_name='addattr') @@ -6517,6 +6524,7 @@ default: cert_request/1 default: cert_revoke/1 default: cert_show/1 default: cert_status/1 +default: certmap_match/1 default: certmapconfig/1 default: certmapconfig_mod/1 default: certmapconfig_show/1 diff --git a/freeipa.spec.in b/freeipa.spec.in index 5c835ca..5e66a2f 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -269,6 +269,8 @@ Requires: gzip Requires: oddjob # Require 0.6.0 for the new delegation access control features Requires: gssproxy >= 0.6.0 +# Require 1.15.1 for the certificate identity mapping feature +Requires: sssd-dbus >= 1.15.1 Provides: %{alt_name}-server = %{version} Conflicts: %{alt_name}-server diff --git a/ipaclient/plugins/certmap.py b/ipaclient/plugins/certmap.py new file mode 100644 index 0000000..7cedea5 --- /dev/null +++ b/ipaclient/plugins/certmap.py @@ -0,0 +1,65 @@ +# Authors: +# Florence Blanc-Renaud <f...@redhat.com> +# +# Copyright (C) 2017 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 ipaclient.frontend import CommandOverride +from ipalib.plugable import Registry + +import six + +if six.PY3: + unicode = str + +register = Registry() + + +@register(override=True, no_fail=True) +class certmap_match(CommandOverride): + """ + Search for users matching the provided certificate. + """ + + def output_for_cli(self, textui, output, *args, **options): + """ + Custom output for certmap-match command. + """ + + rv = 0 + for o in self.output: + outp = self.output[o] + if 'no_display' in outp.flags: + continue + result = output[o] + + if o.lower() == 'count' and result == 0: + rv = 1 + + if isinstance(result, (list, tuple)): + textui.print_attribute(unicode(outp.doc), result, + '%s: %s', 1, True) + elif isinstance(result, unicode): + if o == 'summary': + textui.print_summary(result) + else: + textui.print_indented(result) + + elif isinstance(result, int): + textui.print_count(result, + '%s %%d' % unicode(self.output[o].doc)) + + return rv diff --git a/ipaserver/plugins/certmap.py b/ipaserver/plugins/certmap.py index c37eae3..9e6f646 100644 --- a/ipaserver/plugins/certmap.py +++ b/ipaserver/plugins/certmap.py @@ -17,9 +17,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import base64 +import dbus import six -from ipalib import api, errors +from ipalib import api, errors, x509 +from ipalib import Command, Bytes from ipalib.parameters import Bool, DNSNameParam, Flag, Int, Str from ipalib.plugable import Registry from .baseldap import ( @@ -33,6 +36,7 @@ pkey_to_value) from ipalib import _, ngettext from ipalib import output +from ipaserver.plugins.service import validate_certificate if six.PY3: @@ -389,3 +393,115 @@ def execute(self, cn, **options): result=True, value=pkey_to_value(cn, options), ) + + +DBUS_SSSD_NAME = 'org.freedesktop.sssd.infopipe' +DBUS_PROPERTY_IF = 'org.freedesktop.DBus.Properties' +DBUS_SSSD_USERS_PATH = '/org/freedesktop/sssd/infopipe/Users' +DBUS_SSSD_USERS_IF = 'org.freedesktop.sssd.infopipe.Users' +DBUS_SSSD_USER_IF = 'org.freedesktop.sssd.infopipe.Users.User' + + +class _sssd(object): + """ + Auxiliary class for SSSD infopipe DBus. + """ + def __init__(self, log): + """ + Initialize the Users object and interface. + + :raise RemoteRetrieveError: if DBus error occurs + """ + try: + self.log = log + self._bus = dbus.SystemBus() + self._users_obj = self._bus.get_object( + DBUS_SSSD_NAME, DBUS_SSSD_USERS_PATH) + self._users_iface = dbus.Interface( + self._users_obj, DBUS_SSSD_USERS_IF) + except dbus.DBusException as e: + self.log.error( + 'Failed to initialize DBus interface {iface}. DBus ' + 'exception is {exc}.'.format(iface=DBUS_SSSD_USERS_IF, exc=e) + ) + raise errors.RemoteRetrieveError( + reason=_('Failed to connect to sssd over SystemBus. ' + 'See details in the error_log')) + + def list_users_by_cert(self, cert): + """ + Look for users matching the cert. + + Call Users.ListByCertificate interface and return a list of names + corresponding to the users matching the provided cert + :param cert: DER cert + :raise RemoteRetrieveError: if DBus error occurs + """ + try: + pem = x509.make_pem(base64.b64encode(cert)) + # bug 3306 in sssd returns 0 entry when max_entries = 0 + # Temp workaround is to use a non-null value, not too high + # to avoid reserving unneeded memory + max_entries = dbus.UInt32(100) + user_paths = self._users_iface.ListByCertificate(pem, max_entries) + users = list() + for user_path in user_paths: + user_obj = self._bus.get_object(DBUS_SSSD_NAME, user_path) + user_iface = dbus.Interface(user_obj, DBUS_PROPERTY_IF) + user_login = user_iface.Get(DBUS_SSSD_USER_IF, 'name') + users.append(user_login) + return users + except dbus.DBusException as e: + err_name = e.get_dbus_name() + # If there is no matching user, do not consider this as an + # exception and return an empty list + if err_name == 'org.freedesktop.sssd.Error.NotFound': + return list() + self.log.error( + 'Failed to use interface {iface}. DBus ' + 'exception is {exc}.'.format(iface=DBUS_SSSD_USERS_IF, exc=e)) + raise errors.RemoteRetrieveError( + reason=_('Failed to find users over SystemBus. ' + ' See details in the error_log')) + + +@register() +class certmap_match(Command): + __doc__ = _('Search for users matching the provided certificate.') + + has_output = ( + output.summary, + output.Output('uids', (list, tuple, type(None)), _('Matched uid')), + output.Output('count', int, _('Number of entries returned')), + ) + + takes_options = ( + Bytes( + 'certificate', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded user certificate') + ), + ) + + def execute(self, *args, **options): + """ + Search for users matching the provided certificate. + + The search is performed using SSSD's DBus interface + Users.ListByCertificate. + SSSD does the lookup based on certificate mapping rules, using + FreeIPA domain and trusted domains. + :raise RemoteRetrieveError: if DBus returns an exception + """ + sssd = _sssd(self.log) + result = dict() + + cert = options['certificate'] + users = sssd.list_users_by_cert(cert) + result['count'] = len(users) + result['uids'] = users + result['summary'] = u'{count} user{plural} matched'.format( + count=len(users), plural='' if len(users) == 1 else 's') + + return result
-- 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