2 Commits
Author SHA1 Message Date
zpericic 1115ee1e9a ipaserver: Move /etc/httpd/ipa to /etc/ipa/httpd 2022-03-25 00:03:56 +01:00
zpericic fa1325fc8b ipaserver: Use apache VirtualHost for IPA UI 2022-03-25 00:03:56 +01:00
340 changed files with 10398 additions and 18377 deletions

No files matched your search

+1 -10
View File
@@ -1,14 +1,5 @@
component: Subject
# component: Subject is a single-line summary
Explanation
# Explanation must describe the fix or feature + the method
# chosen to implement it, and can span across multiple lines.
Fixes: https://pagure.io/freeipa/issue/XXXX
or
Related: https://pagure.io/freeipa/issue/XXXX
# Fixes: means that the commit fixes the referenced issue(s).
# Related: means that the commit is related to the issue(s)
# in some way, but does not resolve it/them.
https://pagure.io/freeipa/issue/XXXX
-6
View File
@@ -57,12 +57,6 @@ py-compile
freeipa2-dev-doc
~/doc/guide/Makefile
# Workshop
doc/workshop/.vagrant/
doc/workshop/packer_cache/
doc/workshop/output-*/
doc/workshop/anaconda-ks.cfg
# Root directory
/freeipa.spec
/dist/
-30
View File
@@ -1,30 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.8"
apt_packages:
- plantuml
- graphviz
- fonts-ipafont
- fonts-ipaexfont
# Build documentation in the doc/ directory with Sphinx
sphinx:
configuration: doc/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
# formats:
# - pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: doc/requirements.txt
+2 -2
View File
@@ -10,6 +10,6 @@ ipaserver == @VERSION@
ipatests == @VERSION@
# keep pylint version in sync with current Fedora release
# F36 has 2.12.2
# F34 has 2.6
# https://pagure.io/freeipa/issue/8818 added pylint 2.8 support
pylint ~= 2.12.2
pylint < 2.9
+2 -12
View File
@@ -180,16 +180,6 @@ dn: cn=views,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || gidnumber || ipaanchoruuid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaGroupOverride)")(version 3.0;acl "permission:System: Read Group ID Overrides";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=views,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "createtimestamp || description || entryusn || gecos || gidnumber || homedirectory || ipaanchoruuid || ipaoriginaluid || ipasshpubkey || loginshell || memberof || modifytimestamp || objectclass || uid || uidnumber || usercertificate")(targetfilter = "(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User ID Overrides";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=idp,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipaidp)")(version 3.0;acl "permission:System: Add External IdP server";allow (add) groupdn = "ldap:///cn=System: Add External IdP server,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=idp,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipaidp)")(version 3.0;acl "permission:System: Delete External IdP server";allow (delete) groupdn = "ldap:///cn=System: Delete External IdP server,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=idp,dc=ipa,dc=example
aci: (targetattr = "cn || ipaidpauthendpoint || ipaidpclientid || ipaidpclientsecret || ipaidpdevauthendpoint || ipaidpissuerurl || ipaidpkeysendpoint || ipaidpscope || ipaidpsub || ipaidptokenendpoint || ipaidpuserinfoendpoint || objectclass")(targetfilter = "(objectclass=ipaidp)")(version 3.0;acl "permission:System: Modify External IdP server";allow (write) groupdn = "ldap:///cn=System: Modify External IdP server,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=idp,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipaidpauthendpoint || ipaidpclientid || ipaidpdevauthendpoint || ipaidpissuerurl || ipaidpkeysendpoint || ipaidpscope || ipaidpsub || ipaidptokenendpoint || ipaidpuserinfoendpoint || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidp)")(version 3.0;acl "permission:System: Read External IdP server";allow (compare,read,search) groupdn = "ldap:///cn=System: Read External IdP server,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=idp,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipaidpauthendpoint || ipaidpclientid || ipaidpclientsecret || ipaidpdevauthendpoint || ipaidpissuerurl || ipaidpkeysendpoint || ipaidpscope || ipaidpsub || ipaidptokenendpoint || ipaidpuserinfoendpoint || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidp)")(version 3.0;acl "permission:System: Read External IdP server client secret";allow (compare,read,search) groupdn = "ldap:///cn=System: Read External IdP server client secret,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=ranges,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipaautoprivategroups || ipabaseid || ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || ipasecondarybaserid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID Ranges";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=views,cn=accounts,dc=ipa,dc=example
@@ -241,9 +231,9 @@ aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=System: Delete Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetattr = "ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetattr = "ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=radiusproxy,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || ipatokenradiusretries || ipatokenradiusserver || ipatokenradiustimeout || ipatokenusermapattribute || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipatokenradiusconfiguration)")(version 3.0;acl "permission:System: Read Radius Servers";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Radius Servers,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=Realm Domains,cn=ipa,cn=etc,dc=ipa,dc=example
+29 -143
View File
@@ -136,8 +136,9 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: automember_default_group_remove/1
args: 0,4,3
args: 0,5,3
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('description?', autofill=False, cli_name='desc')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: StrEnum('type', values=[u'group', u'hostgroup'])
option: Str('version?')
@@ -145,9 +146,10 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('value', type=[<type 'unicode'>])
command: automember_default_group_set/1
args: 0,5,3
args: 0,6,3
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('automemberdefaultgroup', cli_name='default_group')
option: Str('description?', autofill=False, cli_name='desc')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: StrEnum('type', values=[u'group', u'hostgroup'])
option: Str('version?')
@@ -1098,7 +1100,7 @@ option: Int('ipasearchrecordslimit?', autofill=False, cli_name='searchrecordslim
option: Int('ipasearchtimelimit?', autofill=False, cli_name='searchtimelimit')
option: Str('ipaselinuxusermapdefault?', autofill=False)
option: Str('ipaselinuxusermaporder?', autofill=False)
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp', u'disabled'])
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'disabled'])
option: Bool('ipauserdefaultsubordinateid?', autofill=False, cli_name='user_default_subid')
option: Str('ipauserobjectclasses*', autofill=False, cli_name='userobjectclasses')
option: IA5Str('ipausersearchfields?', autofill=False, cli_name='usersearch')
@@ -2477,7 +2479,7 @@ option: Bool('ipakrbokasdelegate?', cli_name='ok_as_delegate')
option: Bool('ipakrboktoauthasdelegate?', cli_name='ok_to_auth_as_delegate')
option: Bool('ipakrbrequirespreauth?', cli_name='requires_pre_auth')
option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Str('l?', cli_name='locality')
option: Str('macaddress*')
option: Flag('no_members', autofill=True, default=False)
@@ -2613,7 +2615,7 @@ option: Str('in_netgroup*', cli_name='in_netgroups')
option: Str('in_role*', cli_name='in_roles')
option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('ipaassignedidview?', autofill=False)
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Str('l?', autofill=False, cli_name='locality')
option: Str('macaddress*', autofill=False)
option: Str('man_by_host*', cli_name='man_by_hosts')
@@ -2653,7 +2655,7 @@ option: Bool('ipakrbokasdelegate?', autofill=False, cli_name='ok_as_delegate')
option: Bool('ipakrboktoauthasdelegate?', autofill=False, cli_name='ok_to_auth_as_delegate')
option: Bool('ipakrbrequirespreauth?', autofill=False, cli_name='requires_pre_auth')
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Principal('krbprincipalname*', autofill=False)
option: Str('l?', autofill=False, cli_name='locality')
option: Str('macaddress*', autofill=False)
@@ -3051,96 +3053,6 @@ option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: idp_add/1
args: 1,18,3
arg: Str('cn', cli_name='name')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('ipaidpauthendpoint?', alwaysask=False, cli_name='auth_uri')
option: Str('ipaidpbaseurl?', cli_name='base_url')
option: Str('ipaidpclientid', cli_name='client_id')
option: Password('ipaidpclientsecret?', cli_name='secret', confirm=True)
option: Str('ipaidpdevauthendpoint?', alwaysask=False, cli_name='dev_auth_uri')
option: Str('ipaidpissuerurl?', cli_name='issuer_url')
option: Str('ipaidpkeysendpoint?', alwaysask=False, cli_name='keys_uri')
option: Str('ipaidporg?', cli_name='organization')
option: StrEnum('ipaidpprovider?', cli_name='provider', values=[u'google', u'github', u'microsoft', u'okta', u'keycloak'])
option: Str('ipaidpscope?', cli_name='scope')
option: Str('ipaidpsub?', cli_name='idp_user_id')
option: Str('ipaidptokenendpoint?', alwaysask=False, cli_name='token_uri')
option: Str('ipaidpuserinfoendpoint?', alwaysask=False, cli_name='userinfo_uri')
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: idp_del/1
args: 1,2,3
arg: Str('cn+', cli_name='name')
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: idp_find/1
args: 1,17,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cn?', autofill=False, cli_name='name')
option: Str('ipaidpauthendpoint?', autofill=False, cli_name='auth_uri')
option: Str('ipaidpclientid?', autofill=False, cli_name='client_id')
option: Password('ipaidpclientsecret?', autofill=False, cli_name='secret', confirm=True)
option: Str('ipaidpdevauthendpoint?', autofill=False, cli_name='dev_auth_uri')
option: Str('ipaidpissuerurl?', autofill=False, cli_name='issuer_url')
option: Str('ipaidpkeysendpoint?', autofill=False, cli_name='keys_uri')
option: Str('ipaidpscope?', autofill=False, cli_name='scope')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipaidptokenendpoint?', autofill=False, cli_name='token_uri')
option: Str('ipaidpuserinfoendpoint?', autofill=False, cli_name='userinfo_uri')
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: idp_mod/1
args: 1,18,3
arg: Str('cn', cli_name='name')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('delattr*', cli_name='delattr')
option: Str('ipaidpauthendpoint?', autofill=False, cli_name='auth_uri')
option: Str('ipaidpclientid?', autofill=False, cli_name='client_id')
option: Password('ipaidpclientsecret?', autofill=False, cli_name='secret', confirm=True)
option: Str('ipaidpdevauthendpoint?', autofill=False, cli_name='dev_auth_uri')
option: Str('ipaidpissuerurl?', autofill=False, cli_name='issuer_url')
option: Str('ipaidpkeysendpoint?', autofill=False, cli_name='keys_uri')
option: Str('ipaidpscope?', autofill=False, cli_name='scope')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipaidptokenendpoint?', autofill=False, cli_name='token_uri')
option: Str('ipaidpuserinfoendpoint?', autofill=False, cli_name='userinfo_uri')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('rename?', cli_name='rename')
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: idp_show/1
args: 1,4,3
arg: Str('cn', cli_name='name')
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: idrange_add/1
args: 1,13,3
arg: Str('cn', cli_name='name')
@@ -3326,18 +3238,16 @@ output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: krbtpolicy_mod/1
args: 1,19,3
args: 1,17,3
arg: Str('uid?', cli_name='user')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('delattr*', cli_name='delattr')
option: Int('krbauthindmaxrenewableage_hardened?', autofill=False, cli_name='hardened_maxrenew')
option: Int('krbauthindmaxrenewableage_idp?', autofill=False, cli_name='idp_maxrenew')
option: Int('krbauthindmaxrenewableage_otp?', autofill=False, cli_name='otp_maxrenew')
option: Int('krbauthindmaxrenewableage_pkinit?', autofill=False, cli_name='pkinit_maxrenew')
option: Int('krbauthindmaxrenewableage_radius?', autofill=False, cli_name='radius_maxrenew')
option: Int('krbauthindmaxticketlife_hardened?', autofill=False, cli_name='hardened_maxlife')
option: Int('krbauthindmaxticketlife_idp?', autofill=False, cli_name='idp_maxlife')
option: Int('krbauthindmaxticketlife_otp?', autofill=False, cli_name='otp_maxlife')
option: Int('krbauthindmaxticketlife_pkinit?', autofill=False, cli_name='pkinit_maxlife')
option: Int('krbauthindmaxticketlife_radius?', autofill=False, cli_name='radius_maxlife')
@@ -4058,7 +3968,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: pwpolicy_add/1
args: 1,19,3
args: 1,18,3
arg: Str('cn', cli_name='group')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -4075,7 +3985,6 @@ option: Int('krbpwdlockoutduration?', cli_name='lockouttime')
option: Int('krbpwdmaxfailure?', cli_name='maxfail')
option: Int('krbpwdmindiffchars?', cli_name='minclasses')
option: Int('krbpwdminlength?', cli_name='minlength')
option: Int('passwordgracelimit?', cli_name='gracelimit', default=-1)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('version?')
@@ -4091,7 +4000,7 @@ output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value')
command: pwpolicy_find/1
args: 1,21,4
args: 1,20,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cn?', autofill=False, cli_name='group')
@@ -4108,7 +4017,6 @@ option: Int('krbpwdlockoutduration?', autofill=False, cli_name='lockouttime')
option: Int('krbpwdmaxfailure?', autofill=False, cli_name='maxfail')
option: Int('krbpwdmindiffchars?', autofill=False, cli_name='minclasses')
option: Int('krbpwdminlength?', autofill=False, cli_name='minlength')
option: Int('passwordgracelimit?', autofill=False, cli_name='gracelimit', default=-1)
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Int('sizelimit?', autofill=False)
@@ -4119,7 +4027,7 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: pwpolicy_mod/1
args: 1,21,3
args: 1,20,3
arg: Str('cn?', cli_name='group')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -4137,7 +4045,6 @@ option: Int('krbpwdlockoutduration?', autofill=False, cli_name='lockouttime')
option: Int('krbpwdmaxfailure?', autofill=False, cli_name='maxfail')
option: Int('krbpwdmindiffchars?', autofill=False, cli_name='minclasses')
option: Int('krbpwdminlength?', autofill=False, cli_name='minlength')
option: Int('passwordgracelimit?', autofill=False, cli_name='gracelimit', default=-1)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr')
@@ -4683,7 +4590,7 @@ option: StrEnum('ipakrbauthzdata*', cli_name='pac_type', values=[u'MS-PAC', u'PA
option: Bool('ipakrbokasdelegate?', cli_name='ok_as_delegate')
option: Bool('ipakrboktoauthasdelegate?', cli_name='ok_to_auth_as_delegate')
option: Bool('ipakrbrequirespreauth?', cli_name='requires_pre_auth')
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
@@ -4819,7 +4726,7 @@ arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: StrEnum('ipakrbauthzdata*', autofill=False, cli_name='pac_type', values=[u'MS-PAC', u'PAD', u'NONE'])
option: Principal('krbcanonicalname?', autofill=False, cli_name='canonical_principal')
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
option: Str('man_by_host*', cli_name='man_by_hosts')
option: Flag('no_members', autofill=True, default=True)
@@ -4843,7 +4750,7 @@ option: StrEnum('ipakrbauthzdata*', autofill=False, cli_name='pac_type', values=
option: Bool('ipakrbokasdelegate?', autofill=False, cli_name='ok_as_delegate')
option: Bool('ipakrboktoauthasdelegate?', autofill=False, cli_name='ok_to_auth_as_delegate')
option: Bool('ipakrbrequirespreauth?', autofill=False, cli_name='requires_pre_auth')
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened'])
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@@ -5071,7 +4978,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: stageuser_add/1
args: 1,47,3
args: 1,45,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -5088,12 +4995,10 @@ option: Int('gidnumber?')
option: Str('givenname', cli_name='first')
option: Str('homedirectory?', cli_name='homedir')
option: Str('initials?', autofill=True)
option: Str('ipaidpconfiglink?', cli_name='idp')
option: Str('ipaidpsub?', cli_name='idp_user_id')
option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
option: Str('ipatokenradiususername?', cli_name='radius_username')
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=True, cli_name='principal')
@@ -5179,7 +5084,7 @@ output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value')
command: stageuser_find/1
args: 1,62,4
args: 1,60,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('carlicense*', autofill=False)
@@ -5200,15 +5105,13 @@ option: Str('in_role*', cli_name='in_roles')
option: Str('in_subid*', cli_name='in_subids')
option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('initials?', autofill=False)
option: Str('ipaidpconfiglink?', autofill=False, cli_name='idp')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', autofill=False, cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', autofill=False, cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
@@ -5248,7 +5151,7 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: stageuser_mod/1
args: 1,53,3
args: 1,51,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -5265,8 +5168,6 @@ option: Int('gidnumber?', autofill=False)
option: Str('givenname?', autofill=False, cli_name='first')
option: Str('homedirectory?', autofill=False, cli_name='homedir')
option: Str('initials?', autofill=False)
option: Str('ipaidpconfiglink?', autofill=False, cli_name='idp')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
@@ -5274,7 +5175,7 @@ option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', autofill=False, cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', autofill=False, cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
@@ -6257,7 +6158,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: user_add/1
args: 1,48,3
args: 1,46,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -6273,12 +6174,10 @@ option: Int('gidnumber?')
option: Str('givenname', cli_name='first')
option: Str('homedirectory?', cli_name='homedir')
option: Str('initials?', autofill=True)
option: Str('ipaidpconfiglink?', cli_name='idp')
option: Str('ipaidpsub?', cli_name='idp_user_id')
option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
option: Str('ipatokenradiususername?', cli_name='radius_username')
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=True, cli_name='principal')
@@ -6381,7 +6280,7 @@ output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: user_find/1
args: 1,65,4
args: 1,63,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('carlicense*', autofill=False)
@@ -6402,15 +6301,13 @@ option: Str('in_role*', cli_name='in_roles')
option: Str('in_subid*', cli_name='in_subids')
option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('initials?', autofill=False)
option: Str('ipaidpconfiglink?', autofill=False, cli_name='idp')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', autofill=False, cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', autofill=False, cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
@@ -6453,7 +6350,7 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: user_mod/1
args: 1,54,3
args: 1,52,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -6470,8 +6367,6 @@ option: Int('gidnumber?', autofill=False)
option: Str('givenname?', autofill=False, cli_name='first')
option: Str('homedirectory?', autofill=False, cli_name='homedir')
option: Str('initials?', autofill=False)
option: Str('ipaidpconfiglink?', autofill=False, cli_name='idp')
option: Str('ipaidpsub?', autofill=False, cli_name='idp_user_id')
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
@@ -6479,7 +6374,7 @@ option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
option: DateTime('krbpasswordexpiration?', autofill=False, cli_name='password_expiration')
option: DateTime('krbprincipalexpiration?', autofill=False, cli_name='principal_expiration')
option: Principal('krbprincipalname*', autofill=False, cli_name='principal')
@@ -6655,7 +6550,7 @@ output: Output('completed', type=[<type 'int'>])
output: Output('failed', type=[<type 'dict'>])
output: Entry('result')
command: vault_archive_internal/1
args: 1,10,3
args: 1,9,3
arg: Str('cn', cli_name='name')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Bytes('nonce')
@@ -6666,7 +6561,6 @@ option: Flag('shared?', autofill=True, default=False)
option: Str('username?', cli_name='user')
option: Bytes('vault_data')
option: Str('version?')
option: StrEnum('wrapping_algo?', autofill=True, default=u'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc'])
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
@@ -6757,7 +6651,7 @@ output: Output('completed', type=[<type 'int'>])
output: Output('failed', type=[<type 'dict'>])
output: Entry('result')
command: vault_retrieve_internal/1
args: 1,8,3
args: 1,7,3
arg: Str('cn', cli_name='name')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@@ -6766,7 +6660,6 @@ option: Bytes('session_key')
option: Flag('shared?', autofill=True, default=False)
option: Str('username?', cli_name='user')
option: Str('version?')
option: StrEnum('wrapping_algo?', autofill=True, default=u'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc'])
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
@@ -7130,12 +7023,6 @@ default: idoverrideuser_find/1
default: idoverrideuser_mod/1
default: idoverrideuser_remove_cert/1
default: idoverrideuser_show/1
default: idp/1
default: idp_add/1
default: idp_del/1
default: idp_find/1
default: idp_mod/1
default: idp_show/1
default: idrange/1
default: idrange_add/1
default: idrange_del/1
@@ -7442,7 +7329,6 @@ default: vaultcontainer_del/1
default: vaultcontainer_remove_owner/1
default: vaultcontainer_show/1
default: whoami/1
capability: vault_aes_keywrap 2.246
capability: messages 2.52
capability: optional_uid_params 2.54
capability: permissions2 2.69
-4
View File
@@ -31,7 +31,6 @@ Developers:
Benjamin Drung
Brian Cook
Brian J. Murrell
Brian Turek
Carl George
Cédric Jeanneret
Changmin Teng
@@ -81,7 +80,6 @@ Developers:
Jayesh Garg
Jeremy Frasier
Jérôme Fenal
jh23453
Jim Meyering
Jochen Kellner
JoeDrane
@@ -90,7 +88,6 @@ Developers:
John Morris
Jr Aquino
Julian Gethmann
Julien Rische
Justin Stephenson
Kaleemullah Siddiqui
Karl MacMillan
@@ -113,7 +110,6 @@ Developers:
Martin Košek
Martin Nagy
Matt Rogers
Matthew Davis
Michael Simacek
Michal Polovka
Michal Reznik
+4 -3
View File
@@ -21,7 +21,7 @@
########################################################
define(IPA_VERSION_MAJOR, 4)
define(IPA_VERSION_MINOR, 9)
define(IPA_VERSION_RELEASE, 10)
define(IPA_VERSION_RELEASE, 8)
########################################################
# For 'pre' releases the version will be #
@@ -86,8 +86,9 @@ define(IPA_DATA_VERSION, 20100614120000)
# #
########################################################
define(IPA_API_VERSION_MAJOR, 2)
# Last change: add graceperiodlimit
define(IPA_API_VERSION_MINOR, 248)
# Last change: add enable_sid to config
define(IPA_API_VERSION_MINOR, 245)
########################################################
# Following values are auto-generated from values above
+18 -9
View File
@@ -743,7 +743,8 @@ jsonrpc_request(const char *ipaserver, const json_t *json, curl_buffer *response
json_str = json_dumps(json, 0);
if (!json_str) {
fprintf(stderr, _("json_dumps() failed\n"));
if (debug)
fprintf(stderr, _("json_dumps() failed\n"));
rval = 17;
goto cleanup;
@@ -757,7 +758,8 @@ jsonrpc_request(const char *ipaserver, const json_t *json, curl_buffer *response
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
fprintf(stderr, _("JSON-RPC call failed: %s\n"), curl_easy_strerror(res));
if (debug)
fprintf(stderr, _("JSON-RPC call failed: %s\n"), curl_easy_strerror(res));
rval = 17;
goto cleanup;
@@ -767,7 +769,8 @@ jsonrpc_request(const char *ipaserver, const json_t *json, curl_buffer *response
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp_code);
if (resp_code != 200) {
fprintf(stderr, _("JSON-RPC call failed with status code: %li\n"), resp_code);
if (debug)
fprintf(stderr, _("JSON-RPC call failed with status code: %li\n"), resp_code);
if (!quiet && resp_code == 401)
fprintf(stderr, _("JSON-RPC call was unauthorized. Check your credentials.\n"));
@@ -845,7 +848,8 @@ jsonrpc_parse_response(const char *payload, json_t** j_result_obj, bool quiet) {
j_root = json_loads(payload, 0, &j_error);
if (!j_root) {
fprintf(stderr, _("Parsing JSON-RPC response failed: %s\n"), j_error.text);
if (debug)
fprintf(stderr, _("Parsing JSON-RPC response failed: %s\n"), j_error.text);
rval = 17;
goto cleanup;
@@ -860,7 +864,8 @@ jsonrpc_parse_response(const char *payload, json_t** j_result_obj, bool quiet) {
*j_result_obj = json_object_get(j_root, "result");
if (!*j_result_obj) {
fprintf(stderr, _("Parsing JSON-RPC response failed: no 'result' value found.\n"));
if (debug)
fprintf(stderr, _("Parsing JSON-RPC response failed: no 'result' value found.\n"));
rval = 17;
goto cleanup;
@@ -892,7 +897,8 @@ jsonrpc_parse_join_response(const char *payload, join_info *join_i, bool quiet)
&tmp_hostdn,
"krbprincipalname", &tmp_princ,
"krblastpwdchange", &tmp_pwdch) != 0) {
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
if (debug)
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
rval = 17;
goto cleanup;
@@ -935,7 +941,8 @@ join_krb5_jsonrpc(const char *ipaserver, const char *hostname, char **hostdn, co
"nshardwareplatform", uinfo.machine);
if (!json_req) {
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
if (debug)
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
rval = 17;
goto cleanup;
@@ -983,7 +990,8 @@ jsonrpc_parse_unenroll_response(const char *payload, bool* result, bool quiet) {
if (json_unpack_ex(j_result_obj, &j_error, 0, "{s:b}",
"result", result) != 0) {
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
if (debug)
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
rval = 20;
goto cleanup;
@@ -1013,7 +1021,8 @@ jsonrpc_unenroll_host(const char *ipaserver, const char *host, bool quiet) {
host);
if (!json_req) {
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
if (debug)
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
rval = 17;
goto cleanup;
+2 -16
View File
@@ -78,7 +78,7 @@ Specifies the insecure CA end user port. The default is 8080.
The time to wait for a certmonger request to complete during installation. The default value is 300 seconds.
.TP
.B context <context>
Specifies the context that IPA is being executed in. IPA may operate differently depending on the context. The current defined contexts are cli, server and dns. Additionally this value is used to load /etc/ipa/\fBcontext\fR.conf to provide context\-specific configuration. For example, if you want to always perform client requests in verbose mode but do not want to have verbose enabled on the server, add the verbose option to \fI/etc/ipa/cli.conf\fR.
Specifies the context that IPA is being executed in. IPA may operate differently depending on the context. The current defined contexts are cli and server. Additionally this value is used to load /etc/ipa/\fBcontext\fR.conf to provide context\-specific configuration. For example, if you want to always perform client requests in verbose mode but do not want to have verbose enabled on the server, add the verbose option to \fI/etc/ipa/cli.conf\fR.
.TP
.B debug <boolean>
When True provides detailed information. Specifically this set the global log level to "debug". Default is False.
@@ -119,9 +119,6 @@ Enable a per-request LDAP cache. The default is True.
.B ldap_cache_size <integer>
The maximum number of entries cached if ldap_cache is True. Since this cache is per-request it is not expected to be very large. The default is 100. Setting the value < 1 effectively disables the cache regardless of the ldap_cache setting
.TP
.B ldap_cache_debug <boolean>
Log details on hits, misses, etc. for the LDAP cache if the cache is enabled.
.TP
.B ldap_uri <URI>
Specifies the URI of the IPA LDAP server to connect to. The URI scheme may be one of \fBldap\fR or \fBldapi\fR. The default is to use ldapi, e.g. ldapi://%2fvar%2frun%2fslapd\-EXAMPLE\-COM.socket
.TP
@@ -163,9 +160,6 @@ Specifies the mode the server is running in. The currently support values are \f
.B mount_ipa <URI>
Specifies the mount point that the development server will register. The default is /ipa/
.TP
.B oidc_child_debug_level <debuglevel>
Specifies the debug level of \fBoidc_child\fR, a helper process used by \fBipa-otpd\fR for OIDC/OAuth2 authentication. Level can be between 0 and 10, the higher the more details. If the level is 6 or higher HTTP debug output is added as well.
.TP
.B prompt_all <boolean>
Specifies that all options should be prompted for in the IPA client, even optional values. Default is False.
.TP
@@ -253,20 +247,12 @@ system\-wide IPA configuration file
.I $HOME/.ipa/default.conf
user IPA configuration file
.TP
It is also possible to define context\-specific configuration files. The \fBcontext\fR is set when the IPA api is initialized. The currently defined contexts in IPA are \fBcli\fR, \fBserver\fR and \fBdns\fR. This is helpful, for example, if you only want \fBdebug\fR enabled on the server and not in the client. If this is set to True in \fIdefault.conf\fR it will affect both the ipa client tool and the IPA server. If it is only set in \fIserver.conf\fR then only the server will have \fBdebug\fR set. These files will be loaded if they exist:
It is also possible to define context\-specific configuration files. The \fBcontext\fR is set when the IPA api is initialized. The two currently defined contexts in IPA are \fBcli\fR and \fBserver\fR. This is helpful, for example, if you only want \fBdebug\fR enabled on the server and not in the client. If this is set to True in \fIdefault.conf\fR it will affect both the ipa client tool and the IPA server. If it is only set in \fIserver.conf\fR then only the server will have \fBdebug\fR set. These files will be loaded if they exist:
.TP
.I /etc/ipa/cli.conf
system\-wide IPA client configuration file
.TP
.I /etc/ipa/server.conf
system\-wide IPA server configuration file
.SH "EXAMPLES"
.TP
An example of a context-specific configuration file is \fB/etc/ipa/dns.conf\fR to be used to increase debug output of the IPA DNSSEC daemons.
.TP
.RS L
[global]
debug = True
.RE
.SH "SEE ALSO"
.BR ipa (1)
-3
View File
@@ -89,9 +89,6 @@ Specifies the From: e-mail address value in the e-mails sent. The default is nor
.B notify_ttls <list of days>
This is the list of days before a password expiration when ipa-epn should notify a user that their password will soon require a reset. If this value is not specified then the default list will be used: 28, 14, 7, 3, 1.
.TP
.B msg_subject <subject>
Specifies the subject of the e-mails sent. The default is "Your password will expire soon."
.TP
.B msg_charset <type>
Set the character set of the message. The default is utf8. This will result in he body of the message being base64-encoded.
.TP
+18 -4
View File
@@ -24,12 +24,14 @@ ipa\-client\-automount [\fIOPTION\fR]... <location>
.SH "DESCRIPTION"
Configures automount for IPA.
The automount configuration consists of two files:
The automount configuration consists of three files:
.PP
.IP o
/etc/nsswitch.conf
.IP o
/etc/sysconfig/autofs
.IP o
/etc/autofs_ldap_auth.conf
.TP
By default this will use DNS discovery to attempt to determine the IPA server(s) to use. If IPA servers are discovered then the automount client will be configured to use DNS discovery.
@@ -40,9 +42,9 @@ The default automount location is named default. To specify a different one use
.TP
The IPA client must already be configured in order to configure automount. The IPA client is configured as part of a server installation.
.TP
SSSD is configured to manage the automount maps.
There are two ways to configure automount. The default is to use sssd to manage the automount maps. Alternatively autofs can configured to bind to LDAP over GSSAPI and authenticate using the machine's host principal.
.TP
The nss automount service is configured to use sss and files.
The nsswitch automount service is configured to use either sss or ldap and files depending on whether SSSD is configured or not.
.TP
NFSv4 is also configured. The rpc.gssd and rpc.idmapd are started on clients to support Kerberos\-secured mounts.
.SH "OPTIONS"
@@ -52,6 +54,9 @@ Set the FQDN of the IPA server to connect to.
\fB\-\-location\fR=\fILOCATION\fR
Automount location.
.TP
\fB\-S\fR, \fB\-\-no\-sssd\fR
Do not configure the client to use SSSD for automount.
.TP
\fB\-\-idmap\-domain\fR=\fIIDMAP_DOMAIN\fR
NFS domain for idmapd.conf. If unset, defaults to the IPA domain. If set to DNS, let idmapd or nfsidmap determine the domain from DNS (see idmapd(8) or nfsidmap(5) for details). If set to anything else, set idmapd.conf's Domain entry to that value.
.TP
@@ -66,12 +71,21 @@ Restore the automount configuration files.
.SH "FILES"
.TP
Files that will be configured:
Files that will be always be configured:
/etc/nsswitch.conf
.TP
Files that will be configured when SSSD is the automount client (default):
/etc/sssd/sssd.conf
.TP
Files that will be configured when using the ldap automount client:
/etc/sysconfig/autofs
/etc/autofs_ldap_auth.conf
.SH "EXIT STATUS"
0 if the installation was successful
-3
View File
@@ -136,9 +136,6 @@ Do not configure OpenSSH server.
\fB\-\-no\-sudo\fR
Do not configure SSSD as a data source for sudo.
.TP
\fB\-\-subid\fR
Configure SSSD as data source for subid.
.TP
\fB\-\-no\-dns\-sshfp\fR
Do not automatically create DNS SSHFP records.
.TP
-3
View File
@@ -64,9 +64,6 @@ smtp_delay = 0
# a user that their password will soon require a reset.
notify_ttls = 28, 14, 7, 3, 1
# Set the subject of the message
# msg_subject =
# Set the character set of the message.
msg_charset = utf8
+3 -17
View File
@@ -101,13 +101,9 @@ dnl ---------------------------------------------------------------------------
SAVE_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
SAVE_LIBS="$LIBS"
LIBS=
AC_SEARCH_LIBS([ldap_search], [ldap_r ldap], [], [AC_MSG_ERROR([libldap or libldap_r not found])])
AC_SEARCH_LIBS([ber_peek_tag], [lber], [], [AC_MSG_ERROR([liblber not found])])
LDAP_LIBS="$LIBS"
LDAP_CFLAGS=""
LIBS="$SAVE_LIBS"
AC_CHECK_LIB([ldap_r], [ldap_search], [ ], AC_MSG_ERROR([libldap_r not found]))
AC_CHECK_LIB([lber], [ber_peek_tag], [ ], AC_MSG_ERROR([liblber not found]))
LDAP_LIBS="-lldap_r -llber"
LDAP_CFLAGS=""
AC_SUBST(LDAP_LIBS)
AC_SUBST(LDAP_CFLAGS)
@@ -293,9 +289,6 @@ AM_COND_IF([BUILD_IPA_CERTAUTH_PLUGIN], [
AM_CONDITIONAL([BUILD_IPA_KDCPOLICY_PLUGIN],
[test x$have_kdcpolicy_plugin = xyes])
AM_CONDITIONAL([BUILD_IPA_ISSUE_PAC],
[test x$have_kdb_issue_pac = xyes])
dnl ---------------------------------------------------------------------------
dnl - Check for program paths
dnl ---------------------------------------------------------------------------
@@ -391,7 +384,6 @@ AC_SUBST([IPAPLATFORM])
AC_MSG_RESULT([${IPAPLATFORM}])
if test "x${IPAPLATFORM}" == "xdebian"; then
HTTPD_GROUP="www-data"
KRB5KDC_SERVICE="krb5-kdc.service"
NAMED_GROUP="bind"
ODS_USER="opendnssec"
@@ -399,7 +391,6 @@ if test "x${IPAPLATFORM}" == "xdebian"; then
# see https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html
PYTHON_INSTALL_EXTRA_OPTIONS="--install-layout=deb"
else
HTTPD_GROUP="apache"
KRB5KDC_SERVICE="krb5kdc.service"
NAMED_GROUP="named"
ODS_USER="ods"
@@ -407,10 +398,6 @@ else
PYTHON_INSTALL_EXTRA_OPTIONS=""
fi
AC_MSG_CHECKING([HTTPD_GROUP])
AC_SUBST([HTTPD_GROUP])
AC_MSG_RESULT([${HTTPD_GROUP}])
AC_SUBST([KRB5KDC_SERVICE])
AC_MSG_CHECKING([NAMED_GROUP])
@@ -646,7 +633,6 @@ AC_CONFIG_FILES([
daemons/ipa-slapi-plugins/ipa-cldap/Makefile
daemons/ipa-slapi-plugins/ipa-dns/Makefile
daemons/ipa-slapi-plugins/ipa-enrollment/Makefile
daemons/ipa-slapi-plugins/ipa-graceperiod/Makefile
daemons/ipa-slapi-plugins/ipa-lockout/Makefile
daemons/ipa-slapi-plugins/ipa-otp-counter/Makefile
daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile
+1 -6
View File
@@ -19,7 +19,6 @@ from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL
from ipalib.install.kinit import kinit_keytab
from ipapython.dn import DN
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython.ipautil import get_config_debug
from ipapython import ipaldap
from ipaplatform.paths import paths
from ipaserver.dnssec.abshsm import (sync_pkcs11_metadata,
@@ -146,11 +145,7 @@ def ldap2replica_zone_keys_sync(ldapkeydb, localhsm):
# IPA framework initialization
debug = get_config_debug('dns')
standard_logging_setup(debug=debug, verbose=True)
if not debug:
logger.info("To increase debugging set debug=True in dns.conf "
"See default.conf(5) for details")
standard_logging_setup(debug=True)
ipalib.api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
ipalib.api.finalize()
+1 -18
View File
@@ -16,31 +16,14 @@ from ipalib.install.kinit import kinit_keytab
from ipapython.dn import DN
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython import ipaldap
from ipapython.ipautil import get_config_debug
from ipaplatform.paths import paths
from ipaserver.dnssec.keysyncer import KeySyncer
logger = logging.getLogger(os.path.basename(__file__))
def fixup_dnssec_utils(self):
try:
os.stat(self.DNSSEC_KEYFROMLABEL)
except FileNotFoundError:
try:
os.stat(self.DNSSEC_KEYFROMLABEL_9_17)
except FileNotFoundError:
pass
else:
self.DNSSEC_KEYFROMLABEL = self.DNSSEC_KEYFROMLABEL_9_17
fixup_dnssec_utils(paths)
# IPA framework initialization
debug = get_config_debug('dns')
standard_logging_setup(debug=debug, verbose=True)
if not debug:
logger.info("To increase debugging set debug=True in dns.conf "
"See default.conf(5) for details")
standard_logging_setup(debug=True)
api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
api.finalize()
+1 -6
View File
@@ -36,7 +36,6 @@ from ipalib.install.kinit import kinit_keytab
from ipapython.dn import DN
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython import ipaldap
from ipapython.ipautil import get_config_debug
from ipaplatform.paths import paths
from ipaserver import p11helper
from ipaserver.dnssec.abshsm import sync_pkcs11_metadata, wrappingmech_name2id
@@ -676,11 +675,7 @@ def cleanup_ldap_zone(ldap, dns_dn, zone_name):
# IPA framework initialization
debug = get_config_debug('dns')
standard_logging_setup(debug=debug, verbose=True)
if not debug:
logger.info("To increase debugging set debug=True in dns.conf "
"See default.conf(5) for details")
standard_logging_setup(debug=True)
ipalib.api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
ipalib.api.finalize()
+1 -13
View File
@@ -33,7 +33,7 @@ ipadb_la_SOURCES = \
ipa_kdb_passwords.c \
ipa_kdb_principals.c \
ipa_kdb_pwdpolicy.c \
ipa_kdb_mspac.c \
ipa_kdb_mspac.c \
ipa_kdb_mspac_private.h \
ipa_kdb_delegation.c \
ipa_kdb_audit_as.c \
@@ -41,12 +41,6 @@ ipadb_la_SOURCES = \
dist_noinst_DATA = ipa_kdb.exports
if BUILD_IPA_ISSUE_PAC
ipadb_la_SOURCES += ipa_kdb_mspac_v9.c
else
ipadb_la_SOURCES += ipa_kdb_mspac_v6.c
endif
if BUILD_IPA_CERTAUTH_PLUGIN
ipadb_la_SOURCES += ipa_kdb_certauth.c
endif
@@ -90,12 +84,6 @@ ipa_kdb_tests_SOURCES = \
ipa_kdb_audit_as.c \
$(NULL)
if BUILD_IPA_ISSUE_PAC
ipa_kdb_tests_SOURCES += ipa_kdb_mspac_v9.c
else
ipa_kdb_tests_SOURCES += ipa_kdb_mspac_v6.c
endif
if BUILD_IPA_CERTAUTH_PLUGIN
ipa_kdb_tests_SOURCES += ipa_kdb_certauth.c
endif
+1 -45
View File
@@ -199,7 +199,6 @@ static const struct {
{ "otp", IPADB_USER_AUTH_OTP },
{ "pkinit", IPADB_USER_AUTH_PKINIT },
{ "hardened", IPADB_USER_AUTH_HARDENED },
{ "idp", IPADB_USER_AUTH_IDP },
{ }
};
@@ -222,10 +221,6 @@ void ipadb_parse_user_auth(LDAP *lcontext, LDAPMessage *le,
}
}
}
/* If password auth is enabled, enable hardened policy too. */
if (*userauth & IPADB_USER_AUTH_PASSWORD) {
*userauth |= IPADB_USER_AUTH_HARDENED;
}
ldap_value_free_len(vals);
}
@@ -797,47 +792,8 @@ kdb_vftabl kdb_function_table = {
};
#endif
#if (KRB5_KDB_DAL_MAJOR_VERSION == 9)
#error DAL version 9 is not supported yet
/* Version 9 removes sign_authdata and adds issue_pac method. It is a complete
* revamp of how PAC is issued, so we need to implement it differently to previous
* versions. */
kdb_vftabl kdb_function_table = {
.maj_ver = KRB5_KDB_DAL_MAJOR_VERSION,
.min_ver = 0,
.init_library = ipadb_init_library,
.fini_library = ipadb_fini_library,
.init_module = ipadb_init_module,
.fini_module = ipadb_fini_module,
.create = ipadb_create,
.get_age = ipadb_get_age,
.get_principal = ipadb_get_principal,
.put_principal = ipadb_put_principal,
.delete_principal = ipadb_delete_principal,
.iterate = ipadb_iterate,
.create_policy = ipadb_create_pwd_policy,
.get_policy = ipadb_get_pwd_policy,
.put_policy = ipadb_put_pwd_policy,
.iter_policy = ipadb_iterate_pwd_policy,
.delete_policy = ipadb_delete_pwd_policy,
.fetch_master_key = ipadb_fetch_master_key,
.store_master_key_list = ipadb_store_master_key_list,
.change_pwd = ipadb_change_pwd,
.check_transited_realms = ipadb_check_transited_realms,
.check_policy_as = ipadb_check_policy_as,
.audit_as_req = ipadb_audit_as_req,
.check_allowed_to_delegate = ipadb_check_allowed_to_delegate,
.free_principal_e_data = ipadb_free_principal_e_data,
.get_s4u_x509_principal = NULL,
.allowed_to_delegate_from = NULL,
.issue_pac = NULL,
};
#endif
#if (KRB5_KDB_DAL_MAJOR_VERSION != 6) && \
(KRB5_KDB_DAL_MAJOR_VERSION != 7) && \
(KRB5_KDB_DAL_MAJOR_VERSION != 8) && \
(KRB5_KDB_DAL_MAJOR_VERSION != 9)
(KRB5_KDB_DAL_MAJOR_VERSION != 8)
#error unsupported DAL major version
#endif
-2
View File
@@ -89,7 +89,6 @@ enum ipadb_user_auth {
IPADB_USER_AUTH_OTP = 1 << 3,
IPADB_USER_AUTH_PKINIT = 1 << 4,
IPADB_USER_AUTH_HARDENED = 1 << 5,
IPADB_USER_AUTH_IDP = 1 << 6,
};
enum ipadb_user_auth_idx {
@@ -97,7 +96,6 @@ enum ipadb_user_auth_idx {
IPADB_USER_AUTH_IDX_RADIUS,
IPADB_USER_AUTH_IDX_PKINIT,
IPADB_USER_AUTH_IDX_HARDENED,
IPADB_USER_AUTH_IDX_IDP,
IPADB_USER_AUTH_IDX_MAX,
};
+4 -14
View File
@@ -40,7 +40,7 @@ jitter(krb5_deltat baseline, krb5_deltat *lifetime_out)
return;
}
*lifetime_out = baseline - abs(offset) % JITTER_WINDOW_SECONDS;
*lifetime_out = baseline - offset % JITTER_WINDOW_SECONDS;
}
static krb5_error_code
@@ -89,9 +89,8 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
ua = ied->user_auth;
/* If no mechanisms are set, or it is anonymous PKINIT, allow every auth method */
if ((ua == IPADB_USER_AUTH_NONE) ||
(request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS)) {
/* If no mechanisms are set, allow every auth method */
if (ua == IPADB_USER_AUTH_NONE) {
jitter(ONE_DAY_SECONDS, lifetime_out);
kerr = 0;
goto done;
@@ -134,15 +133,6 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
goto done;
}
pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_HARDENED]);
} else if (strcmp(auth_indicator, "idp") == 0) {
valid_auth_indicators++;
/* Allow hardened even if only password pre-auth is allowed */
if (!(ua & IPADB_USER_AUTH_IDP)) {
*status = "IdP pre-authentication not allowed for this user.";
kerr = KRB5KDC_ERR_POLICY;
goto done;
}
pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_IDP]);
}
}
@@ -162,7 +152,7 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
if (pol_limits->max_life != 0) {
jitter(pol_limits->max_life, lifetime_out);
} else {
jitter(client->max_life, lifetime_out);
jitter(ONE_DAY_SECONDS, lifetime_out);
}
if (pol_limits->max_renewable_life != 0) {
+311 -54
View File
@@ -982,11 +982,11 @@ static krb5_error_code ipadb_get_pac_attrs_blob(TALLOC_CTX *mem_ctx,
#endif
krb5_error_code ipadb_get_pac(krb5_context kcontext,
krb5_db_entry *client,
unsigned int flags,
krb5_timestamp authtime,
krb5_pac *pac)
static krb5_error_code ipadb_get_pac(krb5_context kcontext,
krb5_db_entry *client,
unsigned int flags,
krb5_timestamp authtime,
krb5_pac *pac)
{
TALLOC_CTX *tmpctx;
struct ipadb_e_data *ied;
@@ -1148,8 +1148,7 @@ krb5_error_code ipadb_get_pac(krb5_context kcontext,
#endif
#ifdef HAVE_PAC_REQUESTER_SID
/* MS-KILE 3.3.5.6.4.8: add PAC_REQUESTER_SID only in TGT case */
if ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0) {
{
union PAC_INFO pac_requester_sid;
/* == Package PAC_REQUESTER_SID == */
memset(&pac_requester_sid, 0, sizeof(pac_requester_sid));
@@ -1181,7 +1180,7 @@ done:
return kerr;
}
bool ipadb_is_cross_realm_krbtgt(krb5_const_principal princ)
static bool is_cross_realm_krbtgt(krb5_const_principal princ)
{
if ((princ->length != 2) ||
(princ->data[0].length != 6) ||
@@ -1697,7 +1696,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
"local [%s], PAC [%s]",
dom ? dom : "<failed to display>",
sid ? sid : "<failed to display>");
return KRB5KDC_ERR_TGT_REVOKED;
return KRB5KDC_ERR_POLICY;
}
}
@@ -1709,7 +1708,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
kerr = ipadb_get_principal(context, client_princ, flags, &client_actual);
if (kerr != 0) {
krb5_klog_syslog(LOG_ERR, "PAC issue: ipadb_get_principal failed.");
return KRB5KDC_ERR_TGT_REVOKED;
return KRB5KDC_ERR_POLICY;
}
ied = (struct ipadb_e_data *)client_actual->e_data;
@@ -1743,7 +1742,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
"local [%s] vs PAC [%s]",
local_sid ? local_sid : "<failed to display>",
pac_sid ? pac_sid : "<failed to display>");
kerr = KRB5KDC_ERR_TGT_REVOKED;
kerr = KRB5KDC_ERR_POLICY;
goto done;
}
@@ -2005,43 +2004,22 @@ static krb5_error_code ipadb_check_logon_info(krb5_context context,
/* Check that requester SID is the same as in the PAC entry */
if (requester_sid != NULL) {
struct dom_sid client_sid;
bool is_from_trusted_domain = false;
kerr = ipadb_get_sid_from_pac(tmpctx, info.info, &client_sid);
if (kerr) {
goto done;
}
result = dom_sid_check(&client_sid, requester_sid, true);
if (!result) {
struct ipadb_context *ipactx = ipadb_get_context(context);
if (!ipactx || !ipactx->mspac) {
return KRB5_KDB_DBNOTINITED;
}
/* In S4U case we might be dealing with the PAC issued by the trusted domain */
if (is_s4u && (ipactx->mspac->trusts != NULL)) {
/* Iterate through list of trusts and check if this SID belongs to
* one of the domains we trust */
for(int i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
result = dom_sid_check(&ipactx->mspac->trusts[i].domsid,
requester_sid, false);
if (result) {
is_from_trusted_domain = true;
break;
}
}
}
if (!is_from_trusted_domain) {
/* memctx is freed by the caller */
char *pac_sid = dom_sid_string(tmpctx, &client_sid);
char *req_sid = dom_sid_string(tmpctx, requester_sid);
krb5_klog_syslog(LOG_ERR, "PAC issue: PAC has a SID "
"different from what PAC requester claims. "
"PAC [%s] vs PAC requester [%s]",
pac_sid ? pac_sid : "<failed to display>",
req_sid ? req_sid : "<failed to display>");
kerr = KRB5KDC_ERR_TGT_REVOKED;
goto done;
}
/* memctx is freed by the caller */
char *pac_sid = dom_sid_string(tmpctx, &client_sid);
char *req_sid = dom_sid_string(tmpctx, requester_sid);
krb5_klog_syslog(LOG_ERR, "PAC issue: PAC has a SID "
"different from what PAC requester claims. "
"PAC [%s] vs PAC requester [%s]",
pac_sid ? pac_sid : "<failed to display>",
req_sid ? req_sid : "<failed to display>");
kerr = KRB5KDC_ERR_POLICY;
goto done;
}
}
@@ -2224,17 +2202,17 @@ done:
return kerr;
}
krb5_error_code ipadb_verify_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *proxy,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_timestamp authtime,
krb5_authdata **authdata,
krb5_pac *pac)
static krb5_error_code ipadb_verify_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *proxy,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_timestamp authtime,
krb5_authdata **authdata,
krb5_pac *pac)
{
krb5_keyblock *srv_key = NULL;
krb5_keyblock *priv_key = NULL;
@@ -2264,7 +2242,7 @@ krb5_error_code ipadb_verify_pac(krb5_context context,
* But when a trusted realm passes us a PAC the kdc checksum is
* generated with that realm krbtgt key, so we need to use the cross
* realm krbtgt to check the 'server' checksum instead. */
if (ipadb_is_cross_realm_krbtgt(krbtgt->princ)) {
if (is_cross_realm_krbtgt(krbtgt->princ)) {
/* krbtgt from a trusted realm */
is_cross_realm = true;
@@ -2425,6 +2403,104 @@ done:
return kerr;
}
static krb5_error_code ipadb_sign_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_timestamp authtime,
krb5_pac pac,
krb5_data *pac_data)
{
krb5_keyblock *right_krbtgt_signing_key = NULL;
krb5_key_data *right_krbtgt_key;
krb5_db_entry *right_krbtgt = NULL;
krb5_principal krbtgt_princ = NULL;
krb5_error_code kerr;
char *princ = NULL;
bool is_issuing_referral = false;
int ret;
/* for cross realm trusts cases we need to sign with the right key.
* we need to fetch the right key on our own until the DAL is fixed
* to pass us separate check tgt keys and sign tgt keys */
/* We can only ever create the kdc checksum with our realm tgt key.
* So, if we get a cross realm tgt we have to fetch our realm tgt
* instead. */
if (is_cross_realm_krbtgt(krbtgt->princ)) {
ret = asprintf(&princ, "krbtgt/%.*s@%.*s",
server->princ->realm.length,
server->princ->realm.data,
server->princ->realm.length,
server->princ->realm.data);
if (ret == -1) {
princ = NULL;
kerr = ENOMEM;
goto done;
}
kerr = krb5_parse_name(context, princ, &krbtgt_princ);
if (kerr) {
goto done;
}
kerr = ipadb_get_principal(context, krbtgt_princ, 0, &right_krbtgt);
if (kerr) {
goto done;
}
kerr = krb5_dbe_find_enctype(context, right_krbtgt,
-1, -1, 0, &right_krbtgt_key);
if (kerr) {
goto done;
}
if (!right_krbtgt_key) {
kerr = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto done;
}
right_krbtgt_signing_key = malloc(sizeof(krb5_keyblock));
if (!right_krbtgt_signing_key) {
kerr = ENOMEM;
goto done;
}
kerr = krb5_dbe_decrypt_key_data(context, NULL, right_krbtgt_key,
right_krbtgt_signing_key, NULL);
if (kerr) {
goto done;
}
} else {
right_krbtgt_signing_key = krbtgt_key;
}
#ifdef KRB5_KDB_FLAG_ISSUING_REFERRAL
is_issuing_referral = (flags & KRB5_KDB_FLAG_ISSUING_REFERRAL) != 0;
#endif
/* only pass with_realm TRUE when it is cross-realm ticket and S4U2Self
* was requested */
kerr = krb5_pac_sign_ext(context, pac, authtime, client_princ, server_key,
right_krbtgt_signing_key,
(is_issuing_referral &&
(flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION)),
pac_data);
done:
free(princ);
krb5_free_principal(context, krbtgt_princ);
ipadb_free_principal(context, right_krbtgt);
if (right_krbtgt_signing_key != krbtgt_key) {
krb5_free_keyblock(context, right_krbtgt_signing_key);
}
return kerr;
}
void get_authz_data_types(krb5_context context, krb5_db_entry *entry,
bool *_with_pac, bool *_with_pad)
{
@@ -2554,6 +2630,187 @@ done:
}
krb5_error_code ipadb_sign_authdata(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *client,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *client_key,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_keyblock *session_key,
krb5_timestamp authtime,
krb5_authdata **tgt_auth_data,
krb5_authdata ***signed_auth_data)
{
krb5_const_principal ks_client_princ;
krb5_authdata **pac_auth_data = NULL;
krb5_authdata *authdata[2] = { NULL, NULL };
krb5_authdata ad;
krb5_boolean is_as_req;
krb5_error_code kerr;
krb5_pac pac = NULL;
krb5_data pac_data;
struct ipadb_context *ipactx;
bool with_pac;
bool with_pad;
bool make_ad = false;
int result;
krb5_db_entry *client_entry = NULL;
krb5_boolean is_equal;
bool force_reinit_mspac = false;
is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0);
/* When using s4u2proxy client_princ actually refers to the proxied user
* while client->princ to the proxy service asking for the TGS on behalf
* of the proxied user. So always use client_princ in preference */
if (client_princ != NULL) {
ks_client_princ = client_princ;
if (!is_as_req) {
is_equal = false;
if ((client != NULL) && (client->princ != NULL)) {
is_equal = krb5_principal_compare(context, client_princ, client->princ);
}
if (!is_equal) {
kerr = ipadb_get_principal(context, client_princ, flags, &client_entry);
/* If we didn't find client_princ in our database, it might be:
* - a principal from another realm, handle it down in ipadb_get/verify_pac()
*/
if (kerr != 0) {
client_entry = NULL;
}
}
}
} else {
if (client == NULL) {
*signed_auth_data = NULL;
return 0;
}
ks_client_princ = client->princ;
}
if (client_entry == NULL) client_entry = client;
if (is_as_req) {
get_authz_data_types(context, client_entry, &with_pac, &with_pad);
} else {
get_authz_data_types(context, server, &with_pac, &with_pad);
}
if (with_pad) {
krb5_klog_syslog(LOG_ERR, "PAD authorization data is requested but " \
"currently not supported.");
}
/* we need to create a PAC if we are requested one and this is an AS REQ,
* or we are doing protocol transition (S4USelf) but not over cross-realm
*/
if ((is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC)) ||
((flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && (client != NULL))) {
make_ad = true;
}
if (with_pac && make_ad) {
ipactx = ipadb_get_context(context);
if (!ipactx) {
kerr = ENOMEM;
goto done;
}
/* Be aggressive here: special case for discovering range type
* immediately after establishing the trust by IPA framework. For all
* other cases call ipadb_reinit_mspac() with force_reinit_mspac set
* to 'false' to make sure the information about trusted domains is
* updated on a regular basis for all worker processes. */
if ((krb5_princ_size(context, ks_client_princ) == 2) &&
(strncmp(krb5_princ_component(context, ks_client_princ, 0)->data, "HTTP",
krb5_princ_component(context, ks_client_princ, 0)->length) == 0) &&
(ulc_casecmp(krb5_princ_component(context, ks_client_princ, 1)->data,
krb5_princ_component(context, ks_client_princ, 1)->length,
ipactx->kdc_hostname, strlen(ipactx->kdc_hostname),
NULL, NULL, &result) == 0)) {
force_reinit_mspac = true;
}
(void)ipadb_reinit_mspac(ipactx, force_reinit_mspac);
kerr = ipadb_get_pac(context, client, flags, authtime, &pac);
if (kerr != 0 && kerr != ENOENT) {
goto done;
}
} else if (with_pac && !is_as_req) {
/* find the existing PAC, if present */
kerr = krb5_find_authdata(context, tgt_auth_data, NULL,
KRB5_AUTHDATA_WIN2K_PAC, &pac_auth_data);
if (kerr != 0) {
goto done;
}
/* check or generate pac data */
if ((pac_auth_data == NULL) || (pac_auth_data[0] == NULL)) {
if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
kerr = ipadb_get_pac(context, client_entry, flags, authtime, &pac);
if (kerr != 0 && kerr != ENOENT) {
goto done;
}
}
} else {
if (pac_auth_data[1] != NULL) {
kerr = KRB5KDC_ERR_BADOPTION; /* FIXME: right error ? */
goto done;
}
kerr = ipadb_verify_pac(context, flags, ks_client_princ, client,
server, krbtgt, server_key, krbtgt_key,
authtime, pac_auth_data, &pac);
if (kerr != 0) {
goto done;
}
}
}
if (pac == NULL) {
/* No PAC to deal with, proceed */
*signed_auth_data = NULL;
kerr = 0;
goto done;
}
kerr = ipadb_sign_pac(context, flags, ks_client_princ, server, krbtgt,
server_key, krbtgt_key, authtime, pac, &pac_data);
if (kerr != 0) {
goto done;
}
/* put in signed data */
ad.magic = KV5M_AUTHDATA;
ad.ad_type = KRB5_AUTHDATA_WIN2K_PAC;
ad.contents = (krb5_octet *)pac_data.data;
ad.length = pac_data.length;
authdata[0] = &ad;
kerr = krb5_encode_authdata_container(context,
KRB5_AUTHDATA_IF_RELEVANT,
authdata,
signed_auth_data);
krb5_free_data_contents(context, &pac_data);
if (kerr != 0) {
goto done;
}
kerr = 0;
done:
if (client_entry != NULL && client_entry != client) {
ipadb_free_principal(context, client_entry);
}
krb5_pac_free(context, pac);
return kerr;
}
static char *get_server_netbios_name(struct ipadb_context *ipactx)
{
+1 -19
View File
@@ -55,22 +55,4 @@ char *dom_sid_string(TALLOC_CTX *memctx, const struct dom_sid *dom_sid);
krb5_error_code filter_logon_info(krb5_context context, TALLOC_CTX *memctx,
krb5_data realm, struct PAC_LOGON_INFO_CTR *info);
void get_authz_data_types(krb5_context context, krb5_db_entry *entry,
bool *_with_pac, bool *_with_pad);
bool ipadb_is_cross_realm_krbtgt(krb5_const_principal princ);
krb5_error_code ipadb_get_pac(krb5_context kcontext,
krb5_db_entry *client,
unsigned int flags,
krb5_timestamp authtime,
krb5_pac *pac);
krb5_error_code ipadb_verify_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *proxy,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_timestamp authtime,
krb5_authdata **authdata,
krb5_pac *pac);
bool *_with_pac, bool *_with_pad);
-315
View File
@@ -1,315 +0,0 @@
/*
* MIT Kerberos KDC database backend for FreeIPA
*
* Authors: Simo Sorce <ssorce@redhat.com>
*
* Copyright (C) 2011 Simo Sorce, 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/>.
*/
#include "config.h"
#include "ipa_hostname.h"
#include "ipa_kdb.h"
#include <talloc.h>
#include <unicase.h>
#include "util/time.h"
#include "gen_ndr/ndr_krb5pac.h"
#include "ipa_kdb_mspac_private.h"
static krb5_error_code ipadb_sign_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_timestamp authtime,
krb5_pac pac,
krb5_data *pac_data)
{
krb5_keyblock *right_krbtgt_signing_key = NULL;
krb5_key_data *right_krbtgt_key;
krb5_db_entry *right_krbtgt = NULL;
krb5_principal krbtgt_princ = NULL;
krb5_error_code kerr;
char *princ = NULL;
bool is_issuing_referral = false;
int ret;
/* for cross realm trusts cases we need to sign with the right key.
* we need to fetch the right key on our own until the DAL is fixed
* to pass us separate check tgt keys and sign tgt keys */
/* We can only ever create the kdc checksum with our realm tgt key.
* So, if we get a cross realm tgt we have to fetch our realm tgt
* instead. */
if (ipadb_is_cross_realm_krbtgt(krbtgt->princ)) {
ret = asprintf(&princ, "krbtgt/%.*s@%.*s",
server->princ->realm.length,
server->princ->realm.data,
server->princ->realm.length,
server->princ->realm.data);
if (ret == -1) {
princ = NULL;
kerr = ENOMEM;
goto done;
}
kerr = krb5_parse_name(context, princ, &krbtgt_princ);
if (kerr) {
goto done;
}
kerr = ipadb_get_principal(context, krbtgt_princ, 0, &right_krbtgt);
if (kerr) {
goto done;
}
kerr = krb5_dbe_find_enctype(context, right_krbtgt,
-1, -1, 0, &right_krbtgt_key);
if (kerr) {
goto done;
}
if (!right_krbtgt_key) {
kerr = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto done;
}
right_krbtgt_signing_key = malloc(sizeof(krb5_keyblock));
if (!right_krbtgt_signing_key) {
kerr = ENOMEM;
goto done;
}
kerr = krb5_dbe_decrypt_key_data(context, NULL, right_krbtgt_key,
right_krbtgt_signing_key, NULL);
if (kerr) {
goto done;
}
} else {
right_krbtgt_signing_key = krbtgt_key;
}
#ifdef KRB5_KDB_FLAG_ISSUING_REFERRAL
is_issuing_referral = (flags & KRB5_KDB_FLAG_ISSUING_REFERRAL) != 0;
#endif
/* only pass with_realm TRUE when it is cross-realm ticket and S4U2Self
* was requested */
kerr = krb5_pac_sign_ext(context, pac, authtime, client_princ, server_key,
right_krbtgt_signing_key,
(is_issuing_referral &&
(flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION)),
pac_data);
done:
free(princ);
krb5_free_principal(context, krbtgt_princ);
ipadb_free_principal(context, right_krbtgt);
if (right_krbtgt_signing_key != krbtgt_key) {
krb5_free_keyblock(context, right_krbtgt_signing_key);
}
return kerr;
}
krb5_error_code ipadb_sign_authdata(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
krb5_db_entry *client,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
krb5_keyblock *client_key,
krb5_keyblock *server_key,
krb5_keyblock *krbtgt_key,
krb5_keyblock *session_key,
krb5_timestamp authtime,
krb5_authdata **tgt_auth_data,
krb5_authdata ***signed_auth_data)
{
krb5_const_principal ks_client_princ;
krb5_authdata **pac_auth_data = NULL;
krb5_authdata *authdata[2] = { NULL, NULL };
krb5_authdata ad;
krb5_boolean is_as_req;
krb5_error_code kerr;
krb5_pac pac = NULL;
krb5_data pac_data;
struct ipadb_context *ipactx;
bool with_pac;
bool with_pad;
bool make_ad = false;
int result;
krb5_db_entry *client_entry = NULL;
krb5_boolean is_equal;
bool force_reinit_mspac = false;
is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0);
/* When using s4u2proxy client_princ actually refers to the proxied user
* while client->princ to the proxy service asking for the TGS on behalf
* of the proxied user. So always use client_princ in preference */
if (client_princ != NULL) {
ks_client_princ = client_princ;
if (!is_as_req) {
is_equal = false;
if ((client != NULL) && (client->princ != NULL)) {
is_equal = krb5_principal_compare(context, client_princ, client->princ);
}
if (!is_equal) {
kerr = ipadb_get_principal(context, client_princ, flags, &client_entry);
/* If we didn't find client_princ in our database, it might be:
* - a principal from another realm, handle it down in ipadb_get/verify_pac()
*/
if (kerr != 0) {
client_entry = NULL;
}
}
}
} else {
if (client == NULL) {
*signed_auth_data = NULL;
return 0;
}
ks_client_princ = client->princ;
}
if (client_entry == NULL) client_entry = client;
if (is_as_req) {
get_authz_data_types(context, client_entry, &with_pac, &with_pad);
} else {
get_authz_data_types(context, server, &with_pac, &with_pad);
}
if (with_pad) {
krb5_klog_syslog(LOG_ERR, "PAD authorization data is requested but " \
"currently not supported.");
}
/* we need to create a PAC if we are requested one and this is an AS REQ,
* or we are doing protocol transition (S4USelf) but not over cross-realm
*/
if ((is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC)) ||
((flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && (client != NULL))) {
make_ad = true;
}
if (with_pac && make_ad) {
ipactx = ipadb_get_context(context);
if (!ipactx) {
kerr = ENOMEM;
goto done;
}
/* Be aggressive here: special case for discovering range type
* immediately after establishing the trust by IPA framework. For all
* other cases call ipadb_reinit_mspac() with force_reinit_mspac set
* to 'false' to make sure the information about trusted domains is
* updated on a regular basis for all worker processes. */
if ((krb5_princ_size(context, ks_client_princ) == 2) &&
(strncmp(krb5_princ_component(context, ks_client_princ, 0)->data, "HTTP",
krb5_princ_component(context, ks_client_princ, 0)->length) == 0) &&
(ulc_casecmp(krb5_princ_component(context, ks_client_princ, 1)->data,
krb5_princ_component(context, ks_client_princ, 1)->length,
ipactx->kdc_hostname, strlen(ipactx->kdc_hostname),
NULL, NULL, &result) == 0)) {
force_reinit_mspac = true;
}
(void)ipadb_reinit_mspac(ipactx, force_reinit_mspac);
kerr = ipadb_get_pac(context, client, flags, authtime, &pac);
if (kerr != 0 && kerr != ENOENT) {
goto done;
}
} else if (with_pac && !is_as_req) {
/* find the existing PAC, if present */
kerr = krb5_find_authdata(context, tgt_auth_data, NULL,
KRB5_AUTHDATA_WIN2K_PAC, &pac_auth_data);
if (kerr != 0) {
goto done;
}
/* check or generate pac data */
if ((pac_auth_data == NULL) || (pac_auth_data[0] == NULL)) {
if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
kerr = ipadb_get_pac(context, client_entry, flags, authtime, &pac);
if (kerr != 0 && kerr != ENOENT) {
goto done;
}
}
} else {
if (pac_auth_data[1] != NULL) {
kerr = KRB5KDC_ERR_BADOPTION; /* FIXME: right error ? */
goto done;
}
kerr = ipadb_verify_pac(context, flags, ks_client_princ, client,
server, krbtgt, server_key, krbtgt_key,
authtime, pac_auth_data, &pac);
if (kerr != 0) {
goto done;
}
}
}
if (pac == NULL) {
/* No PAC to deal with, proceed */
*signed_auth_data = NULL;
kerr = 0;
goto done;
}
kerr = ipadb_sign_pac(context, flags, ks_client_princ, server, krbtgt,
server_key, krbtgt_key, authtime, pac, &pac_data);
if (kerr != 0) {
goto done;
}
/* put in signed data */
ad.magic = KV5M_AUTHDATA;
ad.ad_type = KRB5_AUTHDATA_WIN2K_PAC;
ad.contents = (krb5_octet *)pac_data.data;
ad.length = pac_data.length;
authdata[0] = &ad;
kerr = krb5_encode_authdata_container(context,
KRB5_AUTHDATA_IF_RELEVANT,
authdata,
signed_auth_data);
krb5_free_data_contents(context, &pac_data);
if (kerr != 0) {
goto done;
}
kerr = 0;
done:
if (client_entry != NULL && client_entry != client) {
ipadb_free_principal(context, client_entry);
}
krb5_pac_free(context, pac);
return kerr;
}
-34
View File
@@ -1,34 +0,0 @@
/*
* MIT Kerberos KDC database backend for FreeIPA
*
* Authors: Simo Sorce <ssorce@redhat.com>
*
* Copyright (C) 2011 Simo Sorce, 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/>.
*/
#include "config.h"
#include "ipa_hostname.h"
#include "ipa_kdb.h"
#include <talloc.h>
#include <unicase.h>
#include "util/time.h"
#include "gen_ndr/ndr_krb5pac.h"
#include "ipa_kdb_mspac_private.h"
+14 -67
View File
@@ -77,7 +77,6 @@ static char *std_principal_attrs[] = {
IPA_KRB_AUTHZ_DATA_ATTR,
IPA_USER_AUTH_TYPE,
"ipatokenRadiusConfigLink",
"ipaIdpConfigLink",
"krbAuthIndMaxTicketLife",
"krbAuthIndMaxRenewableAge",
"ipaNTSecurityIdentifier",
@@ -317,31 +316,8 @@ static void ipadb_validate_radius(struct ipadb_context *ipactx,
"ipatokenRadiusConfigLink");
if (vals == NULL || vals[0] == NULL)
*ua &= ~IPADB_USER_AUTH_RADIUS;
else {
/* OTP use implies presence of password in IPA LDAP,
* this is incompatible with RADIUS proxy case where
* a password in LDAP is not used anymore. */
*ua &= ~IPADB_USER_AUTH_OTP;
}
if (vals != NULL)
ldap_value_free_len(vals);
}
static void ipadb_validate_idp(struct ipadb_context *ipactx,
LDAPMessage *lentry,
enum ipadb_user_auth *ua)
{
struct berval **vals;
if (!(*ua & IPADB_USER_AUTH_IDP))
return;
/* Ensure that the user has a link to an IdP config. */
vals = ldap_get_values_len(ipactx->lcontext, lentry,
"ipaIdpConfigLink");
if (vals == NULL || vals[0] == NULL)
*ua &= ~IPADB_USER_AUTH_IDP;
else
*ua = IPADB_USER_AUTH_RADIUS;
if (vals != NULL)
ldap_value_free_len(vals);
@@ -379,7 +355,6 @@ static enum ipadb_user_auth ipadb_get_user_auth(struct ipadb_context *ipactx,
/* Perform flag validation. */
ipadb_validate_otp(ipactx, lentry, &ua);
ipadb_validate_radius(ipactx, lentry, &ua);
ipadb_validate_idp(ipactx, lentry, &ua);
return ua;
}
@@ -562,8 +537,6 @@ static void ipadb_parse_authind_policies(krb5_context kcontext,
IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT},
{"krbAuthIndMaxTicketLife;hardened",
IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED},
{"krbAuthIndMaxTicketLife;idp",
IPADB_USER_AUTH_IDP, IPADB_USER_AUTH_IDX_IDP},
{NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX},
}, age_authind_map[] = {
{"krbAuthIndMaxRenewableAge;otp",
@@ -574,8 +547,6 @@ static void ipadb_parse_authind_policies(krb5_context kcontext,
IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT},
{"krbAuthIndMaxRenewableAge;hardened",
IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED},
{"krbAuthIndMaxRenewableAge;idp",
IPADB_USER_AUTH_IDP, IPADB_USER_AUTH_IDX_IDP},
{NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX},
};
@@ -585,13 +556,7 @@ static void ipadb_parse_authind_policies(krb5_context kcontext,
}
for (size_t i = 0; life_authind_map[i].attribute != NULL; i++) {
/* Only change max_life/max_renewable_life per indicator
* if the value wasn't set yet. This function gets called twice:
* - for the principal entry
* - for the associated policy lookup */
if ((ua & life_authind_map[i].flag) &&
(ied->pol_limits[life_authind_map[i].idx].max_life == 0)) {
if (ua & life_authind_map[i].flag) {
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
life_authind_map[i].attribute,
&result);
@@ -618,7 +583,6 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
{
const krb5_octet rad_string[] = "otp\0[{\"indicators\": [\"radius\"]}]";
const krb5_octet otp_string[] = "otp\0[{\"indicators\": [\"otp\"]}]";
const krb5_octet idp_string[] = "idp\0[{\"type\":\"oauth2\",\"indicators\": [\"idp\"]}]";
struct ipadb_context *ipactx;
enum ipadb_user_auth ua;
LDAP *lcontext;
@@ -824,18 +788,17 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
&res_key_data, &result, &mkvno);
switch (ret) {
case 0:
/* Only set a principal's key if password or hardened auth can be used.
* Otherwise the KDC would add pre-authentication methods to the
* NEEDED_PREAUTH reply for AS-REQs which indicate the password
* authentication is available. This might confuse applications like
* e.g. SSSD which try to determine suitable authentication methods and
* corresponding prompts with the help of MIT Kerberos' responder
* interface which acts on the returned pre-authentication methods. A
* typical example is enforced OTP authentication where of course keys
* are available for the first factor but password authentication
* should not be advertised by the KDC. */
if (!(ua & (IPADB_USER_AUTH_PASSWORD | IPADB_USER_AUTH_HARDENED)) &&
(ua != IPADB_USER_AUTH_NONE)) {
/* Only set a principal's key if password auth can be used. Otherwise
* the KDC would add pre-authentication methods to the NEEDED_PREAUTH
* reply for AS-REQs which indicate the password authentication is
* available. This might confuse applications like e.g. SSSD which try
* to determine suitable authentication methods and corresponding
* prompts with the help of MIT Kerberos' responder interface which
* acts on the returned pre-authentication methods. A typical example
* is enforced OTP authentication where of course keys are available
* for the first factor but password authentication should not be
* advertised by the KDC. */
if (!(ua & IPADB_USER_AUTH_PASSWORD) && (ua != IPADB_USER_AUTH_NONE)) {
/* This is the same behavior as ENOENT below. */
ipa_krb5_free_key_data(res_key_data, result);
break;
@@ -997,11 +960,6 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
sizeof(rad_string), rad_string);
if (kerr)
goto done;
} else if (ua & IPADB_USER_AUTH_IDP) {
kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
sizeof(idp_string), idp_string);
if (kerr)
goto done;
}
if (ua & ~IPADB_USER_AUTH_NONE) {
@@ -1293,8 +1251,6 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
if (!first) {
kerr = KRB5_KDB_NOENTRY;
} else {
struct ipadb_e_data *ied;
if (polmask & MAXTKTLIFE_BIT) {
ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
"krbmaxticketlife", &result);
@@ -1322,15 +1278,6 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
entry->attributes |= maybe_require_preauth(ipactx, entry);
}
}
ied = (struct ipadb_e_data *)entry->e_data;
if (ied && ied->ipa_user == true) {
/* Apply default policy to indicators, if any */
if (ied->user_auth & ~IPADB_USER_AUTH_NONE) {
ipadb_parse_authind_policies(kcontext, ipactx->lcontext,
first, entry, ied->user_auth);
}
}
}
}
+1 -2
View File
@@ -9,8 +9,7 @@ ipa_otpd_LDADD = $(top_builddir)/util/libutil.la
dist_noinst_DATA = ipa-otpd.socket.in ipa-otpd@.service.in test.py
systemdsystemunit_DATA = ipa-otpd.socket ipa-otpd@.service
ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c \
oauth2.c
ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c
%.socket: %.socket.in
@sed -e 's|@krb5rundir[@]|$(krb5rundir)|g' \
-58
View File
@@ -28,13 +28,6 @@
#include <errno.h>
#ifndef UCHAR_MAX
#define UCHAR_MAX 255
#endif
/* RFC 2865 */
#define MAX_ATTRSIZE (UCHAR_MAX - 2)
#define SECRET ""
#define otpd_log_req(req, ...) \
otpd_log_req_(__FILE__, __LINE__, (req), __VA_ARGS__)
@@ -43,28 +36,11 @@
struct otpd_queue_iter;
enum ldap_query {
LDAP_QUERY_EMPTY = 0,
LDAP_QUERY_USER,
LDAP_QUERY_RADIUS,
LDAP_QUERY_RADIUS_USERMAP,
LDAP_QUERY_IDP,
LDAP_QUERY_END
};
enum oauth2_state {
OAUTH2_NO = 0,
OAUTH2_GET_ISSUER,
OAUTH2_GET_DEVICE_CODE,
OAUTH2_GET_ACCESS_TOKEN
};
struct otpd_queue_item {
struct otpd_queue_item *next;
krad_packet *req;
krad_packet *rsp;
size_t sent;
enum ldap_query ldap_query;
char *error;
struct {
@@ -72,9 +48,6 @@ struct otpd_queue_item {
char *uid;
char *ipatokenRadiusUserName;
char *ipatokenRadiusConfigLink;
char *ipaidpSub;
char *ipaidpConfigLink;
char **ipauserauthtypes;
char *other;
} user;
@@ -85,28 +58,6 @@ struct otpd_queue_item {
time_t ipatokenRadiusTimeout;
size_t ipatokenRadiusRetries;
} radius;
struct {
char *name;
char *ipaidpIssuerURL;
char *ipaidpDevAuthEndpoint;
char *ipaidpTokenEndpoint;
char *ipaidpUserInfoEndpoint;
char *ipaidpKeysEndpoint;
char *ipaidpClientID;
char *ipaidpClientSecret;
char *ipaidpScope;
char *ipaidpSub;
krb5_boolean valid;
char* ipaidpDebugLevelStr;
krb5_boolean ipaidpDebugCurl;
} idp;
struct {
char *device_code_reply;
krb5_data state;
} oauth2;
int msgid;
};
@@ -147,10 +98,6 @@ struct otpd_context {
struct otpd_queue requests;
struct otpd_queue responses;
} bind;
struct {
struct otpd_queue states;
} oauth2_state;
};
extern struct otpd_context ctx;
@@ -196,13 +143,8 @@ krb5_error_code otpd_forward(struct otpd_queue_item **i);
const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item);
const char *otpd_parse_idp(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item);
const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item);
const char *otpd_parse_radius_username(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item);
int oauth2(struct otpd_queue_item **item, enum oauth2_state);
+2 -2
View File
@@ -242,8 +242,7 @@ int main(int argc, char **argv)
goto error;
}
ctx.vctx = verto_default(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL
| VERTO_EV_TYPE_CHILD);
ctx.vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL);
if (ctx.vctx == NULL) {
otpd_log_err(ENOMEM, "Unable to initialize event loop");
goto error;
@@ -343,3 +342,4 @@ error:
krb5_free_context(ctx.kctx);
return ctx.exitstatus;
}
-614
View File
@@ -1,614 +0,0 @@
/*
* FreeIPA 2FA companion daemon
*
* Authors: Sumit Bose <sbose@redhat.com>
*
* Copyright (C) 2021 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/>.
*/
/*
* This file reaches out to a third-party IdP to handle an OAuth2
* authentication request (stdio.c/query.c) if the user is configured
* accordingly. The result is placed in the stdout queue (stdio.c).
*/
#include <krb5/krb5.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/random.h>
#include "internal.h"
#define OIDC_CHILD_PATH "/usr/libexec/sssd/oidc_child"
struct child_ctx {
int read_from_child;
int write_to_child;
verto_ev *read_ev;
verto_ev *write_ev;
verto_ev *child_ev;
struct otpd_queue_item *item;
struct otpd_queue_item *saved_item;
enum oauth2_state oauth2_state;
};
static int set_fd_nonblocking(int fd)
{
int flags;
int ret;
flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
ret = errno;
return ret;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
ret = errno;
return ret;
}
return 0;
}
static void free_child_ctx(verto_ctx *vctx, verto_ev *ev)
{
(void)vctx; /* Unused */
struct child_ctx *child_ctx;
child_ctx = verto_get_private(ev);
free(child_ctx);
}
static void oauth2_on_child_exit(verto_ctx *vctx, verto_ev *ev)
{
(void)vctx; /* Unused */
verto_proc_status st;
st = verto_get_proc_status(ev);
/* The krad req might not be available at this stage anymore, so
* otpd_log_err() is used. */
otpd_log_err(0, "Child finished with status [%d].", WEXITSTATUS(st));
}
static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev)
{
(void)vctx; /* Unused */
ssize_t io;
struct child_ctx *child_ctx;
child_ctx = verto_get_private(ev);
if (child_ctx == NULL) {
otpd_log_err(EINVAL, "Lost child context");
verto_del(ev);
return;
}
if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
/* no input needed */
verto_del(ev);
return;
}
io = write(verto_get_fd(ev),
child_ctx->saved_item->oauth2.device_code_reply,
strlen(child_ctx->saved_item->oauth2.device_code_reply));
otpd_queue_item_free(child_ctx->saved_item);
if (io < 0) {
switch (errno) {
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN - EWOULDBLOCK != 0)
case EWOULDBLOCK:
#endif
#if defined(EAGAIN)
case EAGAIN:
#endif
case ENOBUFS:
case EINTR:
/* In this case, we just need to try again. */
return;
default:
/* Unrecoverable. */
break;
}
otpd_log_err(errno, "Failed to send to child");
}
verto_del(ev);
}
#define min(a,b) ((a) > (b) ? (b) : (a))
static int add_krad_attr_to_set(struct child_ctx *child_ctx,
krad_attrset *attrset,
krb5_data *datap,
krad_attr attr, const char *message)
{
krb5_data state = {0};
char *p = datap->data;
unsigned int len = datap->length;
int ret = 0;
do {
state.data = p;
state.length = min(MAX_ATTRSIZE - 5, len);
p += state.length;
ret = krad_attrset_add(attrset, attr, &(state));
if (ret != 0) {
otpd_log_req(child_ctx->item->req, message);
break;
}
len -= state.length;
} while (len > 0);
return ret;
}
/* Most attributes have limited length (MAX_ATTRSIZE). In order to accept longer
* values, we will concatenate all the attribute values to single krb5_data. */
static int get_krad_attr_from_packet(const krad_packet *rres,
krad_attr attr, krb5_data *_data)
{
const krb5_data *rmsg;
krb5_data data = {0};
unsigned int memindex;
unsigned int i;
i = 0;
do {
rmsg = krad_packet_get_attr(rres, attr, i);
if (rmsg != NULL) {
data.length += rmsg->length;
}
i++;
} while (rmsg != NULL);
if (data.length == 0) {
return ENOENT;
}
data.data = malloc(data.length);
if (data.data == NULL) {
return ENOMEM;
}
i = 0;
memindex = 0;
do {
rmsg = krad_packet_get_attr(rres, attr, i);
if (rmsg != NULL) {
memcpy(&data.data[memindex], rmsg->data, rmsg->length);
memindex += rmsg->length;
}
i++;
} while (rmsg != NULL);
if (memindex != data.length) {
free(data.data);
return ERANGE;
}
*_data = data;
return 0;
}
/* oidc_child will return two lines.
* The first is a JSON formatted string containing the device code and other
* data needed to get the access token in the second round. This will be
* returned to the caller as Radius Proxy-State so that the caller will send
* it back in the next round.
* The second line is the string expected by the krb5 oauth2 pre-auth plugin
* and will be send to the caller as Radius Reply-Message.
*/
static int handle_device_code_reply(struct child_ctx *child_ctx,
const char *dc_reply, char *rad_reply)
{
krad_attrset *attrset = NULL;
int ret;
krb5_data data = { 0 };
struct otpd_queue_item *state_item;
ret = otpd_queue_item_new(NULL, &state_item);
if (ret != 0) {
otpd_log_req(child_ctx->item->req, "Failed to allocate state item");
goto done;
}
state_item->oauth2.device_code_reply = strdup(dc_reply);
if (state_item->oauth2.device_code_reply == NULL) {
otpd_log_req(child_ctx->item->req, "Failed to copy device code reply.");
goto done;
}
ret = krad_attrset_new(ctx.kctx, &attrset);
if (ret != 0) {
otpd_log_req(child_ctx->item->req,
"Failed to create radius attribute set");
goto done;
}
state_item->oauth2.state.magic = 0;
state_item->oauth2.state.data = strdup(dc_reply);
if (state_item->oauth2.state.data == NULL) {
otpd_log_req(child_ctx->item->req,
"Failed to copy device code reply to krad.");
goto done;
}
state_item->oauth2.state.length = strlen(dc_reply);
ret = add_krad_attr_to_set(child_ctx, attrset, &(state_item->oauth2.state),
krad_attr_name2num("Proxy-State"),
"Failed to serialize state to attribute set");
if (ret != 0) {
goto done;
}
data.magic = 0;
data.data = rad_reply;
data.length = strlen(rad_reply);
ret = add_krad_attr_to_set(child_ctx, attrset, &data,
krad_attr_name2num("Reply-Message"),
"Failed to serialize reply to attribute set");
if (ret != 0) {
goto done;
}
ret = krad_packet_new_response(ctx.kctx, SECRET,
krad_code_name2num("Access-Challenge"),
attrset,
child_ctx->item->req, &child_ctx->item->rsp);
if (ret != 0) {
otpd_log_err(ret, "Failed to create radius response");
child_ctx->item->rsp = NULL;
}
otpd_queue_push(&ctx.oauth2_state.states, state_item);
ret = 0;
done:
krad_attrset_free(attrset);
if (ret != 0) {
if (state_item != NULL) {
free(state_item->oauth2.state.data);
free(state_item->oauth2.device_code_reply);
free(state_item);
}
}
return ret;
}
static int check_access_token_reply(struct child_ctx *child_ctx,
const char *buf, size_t len)
{
int ret;
if (strlen(child_ctx->item->user.ipaidpSub) != len
|| memcmp(child_ctx->item->user.ipaidpSub, buf, len) != 0) {
return EPERM;
}
ret = krad_packet_new_response(ctx.kctx, SECRET,
krad_code_name2num("Access-Accept"), NULL,
child_ctx->item->req, &child_ctx->item->rsp);
if (ret != 0) {
otpd_log_err(ret, "Failed to create radius response");
child_ctx->item->rsp = NULL;
}
return ret;
}
static void oauth2_on_child_readable(verto_ctx *, verto_ev *ev)
{
static char buf[10240];
ssize_t io = 0;
struct child_ctx *child_ctx = NULL;
int ret;
char *rad_reply;
char *end;
child_ctx = (struct child_ctx *) verto_get_private(ev);
if (child_ctx == NULL) {
otpd_log_err(EINVAL, "Lost child context");
verto_del(ev);
return;
}
/* Make sure ctx.stdio.responses will at least return an error */
child_ctx->item->rsp = NULL;
child_ctx->item->sent = 0;
io = read(verto_get_fd(ev), buf, 10240);
if (io < 0) {
otpd_log_err(errno, "Failed to read from child");
goto done;
}
if (io >= 0) {
buf[io] = '\0';
otpd_log_req(child_ctx->item->req, "Received: [%s]", buf);
}
verto_del(ev);
if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
/* expect 2 lines of output. First the orginal JSON string return by
* the IdP from the devicecode request which will be used as input to
* the child process in the second run. Second the JSON string returned
* in the radius reply. */
rad_reply = memchr(buf, '\n', io);
if (rad_reply != NULL) {
*rad_reply = '\0';
rad_reply++;
end = memchr(rad_reply, '\n', io - (rad_reply - 1 - buf));
if (end == NULL) {
otpd_log_req(child_ctx->item->req, "Missing second new-line.");
goto done;
}
*end = '\0';
ret = handle_device_code_reply(child_ctx, buf, rad_reply);
if (ret != 0) {
otpd_log_req(child_ctx->item->req,
"Failed to handle device code reply.");
}
}
} else if (child_ctx->oauth2_state == OAUTH2_GET_ACCESS_TOKEN) {
ret = check_access_token_reply(child_ctx, buf, (size_t) io);
if (ret != 0) {
otpd_log_req(child_ctx->item->req,
"Failed to check access token reply.");
}
} else {
/* error */
otpd_log_req(child_ctx->item->req, "Unexpected state [%d].",
child_ctx->oauth2_state);
}
done:
otpd_queue_push(&ctx.stdio.responses, child_ctx->item);
verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
VERTO_EV_FLAG_IO_ERROR |
VERTO_EV_FLAG_IO_READ |
VERTO_EV_FLAG_IO_WRITE);
}
static const char *oauth2_state_to_str(enum oauth2_state oauth2_state)
{
switch (oauth2_state) {
case OAUTH2_NO:
return "OAuth2 not available";
break;
case OAUTH2_GET_ISSUER:
return "Get issuer from LDAP";
break;
case OAUTH2_GET_DEVICE_CODE:
return "Get device code";
break;
case OAUTH2_GET_ACCESS_TOKEN:
return "Get access token";
break;
default:
return "Unknown OAuth2 state";
}
}
int oauth2(struct otpd_queue_item **item, enum oauth2_state oauth2_state)
{
int ret;
pid_t child_pid;
int pipefd_to_child[2] = { -1, -1};
int pipefd_from_child[2] = { -1, -1};
struct child_ctx *child_ctx;
/* Up to 50 arguments to the helper supported. The amount of arguments
* is controlled inside this function. Right now max used is below 20 */
char *args[50] = {NULL};
size_t args_idx = 0;
krb5_data data_state = {0};
struct otpd_queue_item *saved_item;
if (oauth2_state != OAUTH2_GET_DEVICE_CODE
&& oauth2_state != OAUTH2_GET_ACCESS_TOKEN) {
otpd_log_req((*item)->req, "Unexpected OAuth2 state [%d][%s]",
oauth2_state, oauth2_state_to_str(oauth2_state));
return EINVAL;
}
if (oauth2_state == OAUTH2_GET_ACCESS_TOKEN) {
ret = get_krad_attr_from_packet((*item)->req,
krad_attr_name2num("Proxy-State"),
&data_state);
if ((ret != 0) || (data_state.length == 0)) {
otpd_log_req((*item)->req, "Missing Radius Proxy-State attribute");
return EINVAL;
}
saved_item = calloc(sizeof(struct otpd_queue_item), 1);
if (saved_item == NULL) {
otpd_log_req((*item)->req, "No matching saved state found");
return EINVAL;
}
saved_item->oauth2.device_code_reply = strndup(data_state.data,
data_state.length);
if (saved_item->oauth2.device_code_reply == NULL) {
otpd_log_req((*item)->req, "Failed to copy device code reply");
return EINVAL;
}
krb5_free_data_contents(NULL, &data_state);
}
child_ctx = calloc(sizeof(struct child_ctx), 1);
if (child_ctx == NULL) {
ret = ENOMEM;
goto done;
}
child_ctx->item = (*item);
child_ctx->saved_item = saved_item;
child_ctx->oauth2_state = oauth2_state;
otpd_log_req((*item)->req, "oauth2 start: %s",
oauth2_state_to_str(oauth2_state));
args[args_idx++] = OIDC_CHILD_PATH;
if (oauth2_state == OAUTH2_GET_DEVICE_CODE) {
args[args_idx++] = "--get-device-code";
} else {
args[args_idx++] = "--get-access-token";
}
if ((*item)->idp.ipaidpIssuerURL != NULL) {
args[args_idx++] = "--issuer-url";
args[args_idx++] = (*item)->idp.ipaidpIssuerURL;
} else {
args[args_idx++] = "--device-auth-endpoint";
args[args_idx++] = (*item)->idp.ipaidpDevAuthEndpoint;
args[args_idx++] = "--token-endpoint";
args[args_idx++] = (*item)->idp.ipaidpTokenEndpoint;
args[args_idx++] = "--userinfo-endpoint";
args[args_idx++] = (*item)->idp.ipaidpUserInfoEndpoint;
if ((*item)->idp.ipaidpKeysEndpoint) {
args[args_idx++] = "--jwks-uri";
args[args_idx++] = (*item)->idp.ipaidpKeysEndpoint;
}
}
args[args_idx++] = "--client-id";
args[args_idx++] = (*item)->idp.ipaidpClientID;
if ((*item)->idp.ipaidpClientSecret) {
args[args_idx++] = "--client-secret";
args[args_idx++] = (*item)->idp.ipaidpClientSecret;
}
if ((*item)->idp.ipaidpScope) {
args[args_idx++] = "--scope";
args[args_idx++] = (*item)->idp.ipaidpScope;
}
if ((*item)->idp.ipaidpSub) {
args[args_idx++] = "--user-identifier-attribute";
args[args_idx++] = (*item)->idp.ipaidpSub;
}
if ((*item)->idp.ipaidpDebugLevelStr != NULL) {
args[args_idx++] = "--debug-level";
args[args_idx++] = (*item)->idp.ipaidpDebugLevelStr;
}
if ((*item)->idp.ipaidpDebugCurl) {
args[args_idx++] = "--libcurl-debug";
}
#if 0
for (int i; args[i]; i++) {
otpd_log_req((*item)->req, "oidc_child exec: %s", args[i]);
}
#endif
ret = pipe(pipefd_from_child);
if (ret == -1) {
ret = errno;
goto done;
}
ret = pipe(pipefd_to_child);
if (ret == -1) {
ret = errno;
goto done;
}
child_pid = fork();
if (child_pid == 0) { /* child */
close(pipefd_to_child[1]);
ret = dup2(pipefd_to_child[0], STDIN_FILENO);
if (ret == -1) {
exit(EXIT_FAILURE);
}
close(pipefd_from_child[0]);
ret = dup2(pipefd_from_child[1], STDOUT_FILENO);
if (ret == -1) {
exit(EXIT_FAILURE);
}
execv(OIDC_CHILD_PATH, args);
exit(EXIT_FAILURE);
} else if (child_pid > 0) { /* parent */
close(pipefd_to_child[0]);
set_fd_nonblocking(pipefd_to_child[1]);
child_ctx->write_to_child = pipefd_to_child[1];
close(pipefd_from_child[1]);
set_fd_nonblocking(pipefd_from_child[0]);
child_ctx->read_from_child = pipefd_from_child[0];
child_ctx->write_ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
VERTO_EV_FLAG_IO_CLOSE_FD |
VERTO_EV_FLAG_IO_ERROR |
VERTO_EV_FLAG_IO_WRITE,
oauth2_on_child_writable,
child_ctx->write_to_child);
if (child_ctx->write_ev == NULL) {
ret = ENOMEM;
otpd_log_err(ret, "Unable to initialize oauth2 writer event");
goto done;
}
verto_set_private(child_ctx->write_ev, child_ctx, NULL);
child_ctx->read_ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
VERTO_EV_FLAG_IO_CLOSE_FD |
VERTO_EV_FLAG_IO_ERROR |
VERTO_EV_FLAG_IO_READ,
oauth2_on_child_readable,
child_ctx->read_from_child);
if (child_ctx->read_ev == NULL) {
ret = ENOMEM;
otpd_log_err(ret, "Unable to initialize oauth2 writer event");
goto done;
}
verto_set_private(child_ctx->read_ev, child_ctx, NULL);
child_ctx->child_ev = verto_add_child(ctx.vctx, VERTO_EV_FLAG_NONE,
oauth2_on_child_exit, child_pid);
verto_set_private(child_ctx->child_ev, child_ctx, free_child_ctx);
} else { /* error */
ret = errno;
otpd_log_err(ret, "Failed to fork oidc_child");
goto done;
}
ret = 0;
done:
if (ret == 0) {
*item = NULL;
}
return ret;
}
-176
View File
@@ -24,11 +24,8 @@
* This file parses the user's configuration received from LDAP (see query.c).
*/
#define _GNU_SOURCE /* for asprintf() */
#include "internal.h"
#include <asm-generic/errno-base.h>
#include <ctype.h>
#include <krb5/krb5.h>
#define DEFAULT_TIMEOUT 15
#define DEFAULT_RETRIES 3
@@ -68,69 +65,6 @@ static int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
return 0;
}
/* Convert an LDAP entry into an allocated string array. */
static int get_string_array(LDAP *ldp, LDAPMessage *entry, const char *name,
char ***out)
{
struct berval **vals;
ber_len_t i;
char **buf;
int tmp;
size_t count;
size_t c;
int ret;
vals = ldap_get_values_len(ldp, entry, name);
if (vals == NULL)
return ENOENT;
tmp = ldap_count_values_len(vals);
if (tmp < 0) {
ret = ENOENT;
goto done;
}
count = (size_t) tmp;
buf = calloc(count + 1, sizeof(char *));
if (buf == NULL) {
ret = ENOMEM;
goto done;
}
for (c = 0; c < count; c++) {
buf[c] = calloc(vals[c]->bv_len + 1, sizeof(char));
if (buf[c] == NULL) {
ret = ENOMEM;
goto done;
}
for (i = 0; i < vals[c]->bv_len; i++) {
if (!isprint(vals[c]->bv_val[i])) {
ret = EINVAL;
goto done;
}
buf[c][i] = vals[c]->bv_val[i];
}
}
if (*out != NULL)
free(*out);
*out = buf;
ret = 0;
done:
if (ret != 0 && buf != NULL) {
for (c = 0; buf[c] != NULL; c++) {
free(buf[c]);
}
free(buf);
}
ldap_value_free_len(vals);
return ret;
}
/* Convert an LDAP entry into an unsigned long. */
static int get_ulong(LDAP *ldp, LDAPMessage *entry, const char *name,
unsigned long *out)
@@ -178,21 +112,6 @@ const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
if (i != 0 && i != ENOENT)
return strerror(i);
i = get_string(ldp, entry, "ipaidpSub",
&item->user.ipaidpSub);
if (i != 0 && i != ENOENT)
return strerror(i);
i = get_string(ldp, entry, "ipaidpConfigLink",
&item->user.ipaidpConfigLink);
if (i != 0 && i != ENOENT)
return strerror(i);
i = get_string_array(ldp, entry, "ipauserauthtype",
&item->user.ipauserauthtypes);
if (i != 0 && i != ENOENT)
return strerror(i);
/* Get the DN. */
item->user.dn = ldap_get_dn(ldp, entry);
if (item->user.dn == NULL) {
@@ -203,101 +122,6 @@ const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
return NULL;
}
#define ENV_OIDC_CHILD_DEBUG_LEVEL "oidc_child_debug_level"
/* Parse the IdP configuration */
const char *otpd_parse_idp(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item)
{
int i;
long dbg_lvl = 0;
const char *dbg_env = NULL;
char *endptr = NULL;
item->idp.valid = FALSE;
i = get_string(ldp, entry, "cn", &item->idp.name);
if (i != 0) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpIssuerURL", &item->idp.ipaidpIssuerURL);
if ((i != 0) && (i != ENOENT)) {
return strerror(i);
}
/* We support either passing issuer URL or individual end-points */
if (i == ENOENT) {
i = get_string(ldp, entry, "ipaidpDevAuthEndpoint", &item->idp.ipaidpDevAuthEndpoint);
if (i != 0) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpTokenEndpoint", &item->idp.ipaidpTokenEndpoint);
if (i != 0) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpUserInfoEndpoint", &item->idp.ipaidpUserInfoEndpoint);
if (i != 0) {
return strerror(i);
}
/* JWKS end-point may be optional */
i = get_string(ldp, entry, "ipaidpKeysEndpoint", &item->idp.ipaidpKeysEndpoint);
if ((i != 0) && (i != ENOENT)) {
return strerror(i);
}
}
i = get_string(ldp, entry, "ipaidpClientID", &item->idp.ipaidpClientID);
if (i != 0) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpClientSecret", &item->idp.ipaidpClientSecret);
if ((i != 0) && (i != ENOENT)) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpScope", &item->idp.ipaidpScope);
if ((i != 0) && (i != ENOENT)) {
return strerror(i);
}
i = get_string(ldp, entry, "ipaidpSub", &item->idp.ipaidpSub);
if ((i != 0) && (i != ENOENT)) {
return strerror(i);
}
item->idp.ipaidpDebugLevelStr = NULL;
item->idp.ipaidpDebugCurl = FALSE;
dbg_env = getenv(ENV_OIDC_CHILD_DEBUG_LEVEL);
if (dbg_env != NULL && *dbg_env != '\0') {
errno = 0;
dbg_lvl = strtoul(dbg_env, &endptr, 10);
if (errno == 0 && *endptr == '\0') {
if (dbg_lvl < 0) {
dbg_lvl = 0;
} else if (dbg_lvl > 10) {
dbg_lvl = 10;
}
if (asprintf(&item->idp.ipaidpDebugLevelStr, "%ld", dbg_lvl) != -1) {
if (dbg_lvl > 5) {
item->idp.ipaidpDebugCurl = TRUE;
}
} else {
otpd_log_req(item->req, "Failed to copy debug level");
}
} else {
otpd_log_req(item->req,
"Cannot parse value [%s] from environment variable [%s]",
dbg_env, ENV_OIDC_CHILD_DEBUG_LEVEL);
}
}
item->idp.valid = TRUE;
return NULL;
}
/* Parse the user's RADIUS configuration. */
const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
struct otpd_queue_item *item)
+11 -159
View File
@@ -31,7 +31,6 @@
#define _GNU_SOURCE 1 /* for asprintf() */
#include "internal.h"
#include <ctype.h>
#include <stdbool.h>
#define DEFAULT_TIMEOUT 15
#define DEFAULT_RETRIES 3
@@ -40,9 +39,6 @@ static char *user[] = {
"uid",
"ipatokenRadiusUserName",
"ipatokenRadiusConfigLink",
"ipaidpSub",
"ipaidpConfigLink",
"ipauserauthtype",
NULL
};
@@ -55,37 +51,6 @@ static char *radius[] = {
NULL
};
static char *idp[] = {
"ipaidpClientID",
"ipaidpClientSecret",
"ipaidpIssuerURL",
"ipaidpDevAuthEndpoint",
"ipaidpTokenEndpoint",
"ipaidpUserInfoEndpoint",
"ipaidpKeysEndpoint",
"ipaidpScope",
"ipaidpSub",
"cn",
NULL
};
static bool auth_type_is(char **auth_types, const char *check)
{
size_t c;
if (auth_types == NULL || check == NULL) {
return false;
}
for(c = 0; auth_types[c] != NULL; c++) {
if (strcasecmp(auth_types[c], check) == 0) {
return true;
}
}
return false;
}
/* Send queued LDAP requests to the server. */
static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
{
@@ -111,7 +76,6 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
goto error;
otpd_log_req(item->req, "user query start");
item->ldap_query = LDAP_QUERY_USER;
if (asprintf(&filter, "(&(objectClass=Person)(krbPrincipalName=%*s))",
princ->length, princ->data) < 0)
@@ -122,20 +86,9 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
NULL, NULL, 1, &item->msgid);
free(filter);
} else if (auth_type_is(item->user.ipauserauthtypes, "idp")) {
otpd_log_req(item->req, "idp query start: %s",
item->user.ipaidpConfigLink);
item->ldap_query = LDAP_QUERY_IDP;
i = ldap_search_ext(verto_get_private(ev),
item->user.ipaidpConfigLink,
LDAP_SCOPE_BASE, NULL, idp, 0, NULL,
NULL, NULL, 1, &item->msgid);
} else if (item->radius.ipatokenRadiusSecret == NULL) {
otpd_log_req(item->req, "radius query start: %s",
item->user.ipatokenRadiusConfigLink);
item->ldap_query = LDAP_QUERY_RADIUS;
i = ldap_search_ext(verto_get_private(ev),
item->user.ipatokenRadiusConfigLink,
@@ -145,7 +98,6 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
} else if (item->radius.ipatokenUserMapAttribute != NULL) {
otpd_log_req(item->req, "username query start: %s",
item->radius.ipatokenUserMapAttribute);
item->ldap_query = LDAP_QUERY_RADIUS_USERMAP;
attrs[0] = item->radius.ipatokenUserMapAttribute;
attrs[1] = NULL;
@@ -155,6 +107,7 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
}
if (i == LDAP_SUCCESS) {
item->sent++;
push = &ctx.query.responses;
}
@@ -162,78 +115,6 @@ error:
otpd_queue_push(push, item);
}
static enum oauth2_state get_oauth2_state(enum ldap_query ldap_query,
struct otpd_queue_item *item)
{
const krb5_data *data_pwd;
const krb5_data *data_state;
enum oauth2_state oauth2_state = OAUTH2_NO;
data_pwd = krad_packet_get_attr(item->req,
krad_attr_name2num("User-Password"), 0);
data_state = krad_packet_get_attr(item->req,
krad_attr_name2num("Proxy-State"), 0);
if (data_pwd == NULL && data_state == NULL) {
oauth2_state = OAUTH2_GET_DEVICE_CODE;
} else if (data_pwd == NULL && data_state != NULL) {
oauth2_state = OAUTH2_GET_ACCESS_TOKEN;
}
/* Looks like caller does not expect oauth2 authentication */
if (oauth2_state == OAUTH2_NO) {
return oauth2_state;
}
if (ldap_query == LDAP_QUERY_USER) {
/* Check the user entry for required attributes */
if (item->user.ipaidpSub == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing 'sub' in user entry");
}
if (item->user.ipaidpConfigLink == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing issuer in user entry");
}
if (oauth2_state != OAUTH2_NO) {
/* Next step is to lookup IdP data */
oauth2_state = OAUTH2_GET_ISSUER;
}
} else if (ldap_query == LDAP_QUERY_IDP) {
/* Check the idp entry for required attributes */
if (item->idp.ipaidpIssuerURL == NULL) {
if (item->idp.ipaidpDevAuthEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing authentication end-point in idp entry");
}
if (item->idp.ipaidpTokenEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing access token end-point in idp entry");
}
if (item->idp.ipaidpUserInfoEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing userinfo end-point in idp entry");
}
}
if (item->idp.ipaidpClientID == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing client ID in idp entry");
}
}
return oauth2_state;
}
/* Read LDAP responses from the server. */
static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
{
@@ -245,7 +126,6 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
LDAP *ldp;
int i;
(void)vctx;
enum oauth2_state oauth2_state;
ldp = verto_get_private(ev);
@@ -270,19 +150,16 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
goto egress;
err = NULL;
switch (item->ldap_query) {
case LDAP_QUERY_USER:
switch (item->sent) {
case 1:
err = otpd_parse_user(ldp, entry, item);
break;
case LDAP_QUERY_RADIUS:
case 2:
err = otpd_parse_radius(ldp, entry, item);
break;
case LDAP_QUERY_RADIUS_USERMAP:
case 3:
err = otpd_parse_radius_username(ldp, entry, item);
break;
case LDAP_QUERY_IDP:
err = otpd_parse_idp(ldp, entry, item);
break;
default:
ldap_msgfree(entry);
goto egress;
@@ -304,14 +181,14 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
item->msgid = -1;
switch (item->ldap_query) {
case LDAP_QUERY_USER:
switch (item->sent) {
case 1:
otpd_log_req(item->req, "user query end: %s",
item->error == NULL ? item->user.dn : item->error);
if (item->user.dn == NULL || item->user.uid == NULL)
goto egress;
break;
case LDAP_QUERY_RADIUS:
case 2:
otpd_log_req(item->req, "radius query end: %s",
item->error == NULL
? item->radius.ipatokenRadiusServer
@@ -320,47 +197,22 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
item->radius.ipatokenRadiusSecret == NULL)
goto egress;
break;
case LDAP_QUERY_RADIUS_USERMAP:
case 3:
otpd_log_req(item->req, "username query end: %s",
item->error == NULL ? item->user.other : item->error);
break;
case LDAP_QUERY_IDP:
otpd_log_req(item->req, "idp query end: %s",
item->error == NULL ? item->idp.name : item->error);
if (!item->idp.valid) {
goto egress;
}
break;
default:
goto egress;
}
/* Check for oauth2 */
oauth2_state = get_oauth2_state(item->ldap_query, item);
if (oauth2_state == OAUTH2_GET_ISSUER) {
push = &ctx.query.requests;
event = ctx.query.io;
goto egress;
} else if (oauth2_state != OAUTH2_NO) {
i = oauth2(&item, oauth2_state);
if (i != 0) {
goto egress;
} else {
/* oauth2 will call ctx.stdio.writer, so we can return here */
return;
}
}
if (item->error != NULL)
goto egress;
if (item->ldap_query == LDAP_QUERY_USER &&
item->user.ipatokenRadiusConfigLink != NULL) {
if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) {
push = &ctx.query.requests;
event = ctx.query.io;
goto egress;
} else if (item->ldap_query == LDAP_QUERY_RADIUS &&
} else if (item->sent == 2 &&
item->radius.ipatokenUserMapAttribute != NULL &&
item->user.ipatokenRadiusUserName == NULL) {
push = &ctx.query.requests;
-27
View File
@@ -46,8 +46,6 @@ krb5_error_code otpd_queue_item_new(krad_packet *req,
void otpd_queue_item_free(struct otpd_queue_item *item)
{
size_t c;
if (item == NULL)
return;
@@ -56,34 +54,9 @@ void otpd_queue_item_free(struct otpd_queue_item *item)
free(item->user.ipatokenRadiusUserName);
free(item->user.ipatokenRadiusConfigLink);
free(item->user.other);
free(item->user.ipaidpSub);
free(item->user.ipaidpConfigLink);
if (item->user.ipauserauthtypes != NULL) {
for (c = 0; item->user.ipauserauthtypes[c] != NULL; c++) {
free(item->user.ipauserauthtypes[c]);
}
free(item->user.ipauserauthtypes);
}
free(item->radius.ipatokenRadiusServer);
free(item->radius.ipatokenRadiusSecret);
free(item->radius.ipatokenUserMapAttribute);
free(item->idp.ipaidpIssuerURL);
free(item->idp.ipaidpDevAuthEndpoint);
free(item->idp.ipaidpTokenEndpoint);
free(item->idp.ipaidpUserInfoEndpoint);
free(item->idp.ipaidpKeysEndpoint);
free(item->idp.name);
free(item->idp.ipaidpClientID);
if (item->idp.ipaidpClientSecret != NULL) {
size_t len = strlen(item->idp.ipaidpClientSecret);
(void*) memset(item->idp.ipaidpClientSecret, 0, len);
free(item->idp.ipaidpClientSecret);
}
free(item->idp.ipaidpScope);
free(item->idp.ipaidpSub);
free(item->idp.ipaidpDebugLevelStr);
free(item->oauth2.device_code_reply);
free(item->oauth2.state.data);
free(item->error);
krad_packet_free(item->req);
krad_packet_free(item->rsp);
-2
View File
@@ -166,7 +166,6 @@ void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
/* Send the packet. */
data = krad_packet_encode(item->rsp);
otpd_log_req(item->req, "sent: %d data: %d", item->sent, data->length);
i = write(verto_get_fd(ev), data->data + item->sent,
data->length - item->sent);
if (i < 0) {
@@ -192,7 +191,6 @@ void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
/* If the packet was completely sent, free the response. */
item->sent += i;
otpd_log_req(item->req, "..sent: %d data: %d", item->sent, data->length);
if (item->sent == data->length) {
otpd_log_req(item->req, "response sent: %s",
krad_code_num2name(krad_packet_get_code(item->rsp)));
+1 -1
View File
@@ -59,7 +59,7 @@ def main():
rsp.DecodePacket(buf)
pkt.VerifyReply(rsp)
proc.terminate()
proc.terminate() # pylint: disable=E1101
proc.wait()
if __name__ == '__main__':
+41 -118
View File
@@ -2728,7 +2728,7 @@ static NTSTATUS get_trust_pwd(TALLOC_CTX *mem_ctx, const DATA_BLOB *auth_blob,
if (iopw.count != 0 && iopw.current.count != 0 &&
iopw.current.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) {
if (pwd != NULL) {
if (!convert_string_talloc(tmp_ctx, CH_UTF16LE, CH_UNIX,
if (!convert_string_talloc(tmp_ctx, CH_UTF16, CH_UNIX,
iopw.current.array[0].AuthInfo.clear.password,
iopw.current.array[0].AuthInfo.clear.size,
&trustpw, &converted_size)) {
@@ -3581,14 +3581,12 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
bool ret = false;
bool retval = false;
bool machine_account = false;
bool trusted_domain = false;
int status;
int len = 0;
int idx = 0;
size_t conv_size = 0;
DATA_BLOB nthash;
struct dom_sid *group_sid;
uint32_t acct_flags = 0;
TALLOC_CTX *tmp_ctx = talloc_init("init_sam_from_ldap");
if (!tmp_ctx) {
@@ -3615,15 +3613,17 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
}
len = ldap_count_values_len(usernames);
/* Extract machine account as a user name if exists.
* If not, extract the first returned value */
for (int i=0; i < len; i++) {
if (usernames[i] != NULL &&
usernames[i]->bv_len > 0 &&
usernames[i]->bv_val[usernames[i]->bv_len-1] == '$') {
idx = i;
machine_account = true;
break;
if (len > 1) {
/* Extract machine account as a user name if exists.
* If not, extract the first returned value */
for (int i=0; i < len; i++) {
if (usernames[i] != NULL &&
usernames[i]->bv_len > 0 &&
usernames[i]->bv_val[usernames[i]->bv_len-1] == '$') {
idx = i;
machine_account = true;
break;
}
}
}
@@ -3644,31 +3644,6 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
DEBUG(2, ("init_sam_from_ldap: Entry found for user: %s\n", username));
if (machine_account) {
/* it may also be a TDO account */
struct berval **oclasses = NULL;
oclasses = ldap_get_values_len(priv2ld(ipasam_state), entry,
LDAP_ATTRIBUTE_OBJECTCLASS);
int c = 0;
if (oclasses == NULL) {
DEBUG(1, ("Cannot find any objectclasses.\n"));
goto fn_exit;
}
for (c = 0; oclasses[c] != NULL; c++) {
if (strncasecmp(LDAP_OBJ_TRUSTED_DOMAIN, oclasses[c]->bv_val,
oclasses[c]->bv_len) == 0) {
break;
}
}
if (oclasses[c] != NULL) {
trusted_domain = true;
}
ldap_value_free_len(oclasses);
}
nt_username = talloc_strdup(tmp_ctx, username);
if (!nt_username) {
goto fn_exit;
@@ -3737,94 +3712,42 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
}
/* set correct account type */
acct_flags = trusted_domain ? ACB_DOMTRUST :
(machine_account ? ACB_WSTRUST : ACB_NORMAL);
pdb_set_acct_ctrl(sampass, acct_flags, PDB_SET);
/* Force machine accounts to be workstation trust type */
pdb_set_acct_ctrl(sampass, machine_account ? ACB_WSTRUST : ACB_NORMAL,
PDB_SET);
if (trusted_domain) {
DATA_BLOB trust_auth_incoming = {0};
char *pass = NULL;
NTSTATUS xstatus;
retval = false;
if (smbldap_talloc_single_blob(tmp_ctx,
priv2ld(ipasam_state), entry,
LDAP_ATTRIBUTE_TRUST_AUTH_INCOMING,
&trust_auth_incoming)) {
xstatus = get_trust_pwd(tmp_ctx, &trust_auth_incoming,
&pass, NULL);
if (NT_STATUS_IS_OK(xstatus)) {
uint8_t key[NT_HASH_LEN];
volatile char *p = (void *) pass;
size_t plen = strlen(pass);
if (E_md4hash(pass, key)) {
volatile char *q = (void *) key;
size_t qlen = NT_HASH_LEN;
nthash.data = talloc_memdup(tmp_ctx, key, NT_HASH_LEN);
nthash.length = NT_HASH_LEN;
retval = nthash.data != NULL;
while (qlen--) {
*q++ = '\0';
}
}
while (plen--) {
*p++ = '\0';
}
}
} else {
DEBUG(9, ("Failed to get incoming auth info.\n"));
}
} else {
retval = smbldap_talloc_single_blob(tmp_ctx,
priv2ld(ipasam_state),
entry, LDAP_ATTRIBUTE_NTHASH,
&nthash);
if (!retval) {
/* NT Hash is not in place. Attempt to retrieve it from
* the RC4-HMAC key if that exists in Kerberos credentials.
* IPA 389-ds plugin allows to ask for it by setting
* ipaNTHash to MagicRegen value.
* */
temp = smbldap_talloc_dn(tmp_ctx, priv2ld(ipasam_state), entry);
if (temp) {
retval = ipasam_nthash_regen(ipasam_state,
tmp_ctx, temp);
if (retval) {
retval = ipasam_nthash_retrieve(ipasam_state,
tmp_ctx, temp, &nthash);
}
retval = smbldap_talloc_single_blob(tmp_ctx,
priv2ld(ipasam_state),
entry, LDAP_ATTRIBUTE_NTHASH,
&nthash);
if (!retval) {
/* NT Hash is not in place. Attempt to retrieve it from
* the RC4-HMAC key if that exists in Kerberos credentials.
* IPA 389-ds plugin allows to ask for it by setting
* ipaNTHash to MagicRegen value.
* */
temp = smbldap_talloc_dn(tmp_ctx, priv2ld(ipasam_state), entry);
if (temp) {
retval = ipasam_nthash_regen(ipasam_state,
tmp_ctx, temp);
if (retval) {
retval = ipasam_nthash_retrieve(ipasam_state,
tmp_ctx, temp, &nthash);
}
}
}
if (retval) {
if (nthash.length != NT_HASH_LEN && nthash.length != 0) {
DEBUG(5, ("NT hash from LDAP has the wrong size. "
"Perhaps password was not re-set?\n"));
} else {
volatile char *q = (void *) nthash.data;
size_t qlen = nthash.length;
if (!pdb_set_nt_passwd(sampass, nthash.data, PDB_SET)) {
DEBUG(5, ("Failed to set NT hash.\n"));
}
if (q != NULL) {
/* nthash.length might be 0 on this path,
* we want to skip in this case */
while (nthash.length && qlen--) {
*q++ = '\0';
}
}
}
} else {
if (!retval) {
DEBUG(5, ("Failed to read NT hash form LDAP response.\n"));
}
if (nthash.length != NT_HASH_LEN && nthash.length != 0) {
DEBUG(5, ("NT hash from LDAP has the wrong size. Perhaps password was not re-set?\n"));
} else {
if (!pdb_set_nt_passwd(sampass, nthash.data, PDB_SET)) {
DEBUG(5, ("Failed to set NT hash.\n"));
}
}
/* FIXME: */
if (!pdb_set_pass_last_set_time(sampass, (time_t) 1, PDB_SET)) {
DEBUG(5, ("Failed to set last time set.\n"));
-1
View File
@@ -5,7 +5,6 @@ SUBDIRS = \
ipa-cldap \
ipa-dns \
ipa-enrollment \
ipa-graceperiod \
ipa-lockout \
ipa-modrdn \
ipa-otp-counter \
@@ -287,7 +287,6 @@ done:
ipa_cldap_respond(ctx, req, &reply);
ipa_cldap_free_kvps(&req->kvps);
free(reply.bv_val);
free(req);
return;
}
@@ -49,14 +49,6 @@
#include "back_extdom.h"
#include "util.h"
#if defined HAVE_DECL_SSS_NSS_GETORIGBYUSERNAME_TIMEOUT && !HAVE_DECL_SSS_NSS_GETORIGBYUSERNAME_TIMEOUT
#define sss_nss_getorigbyusername_timeout sss_nss_getorigbyname_timeout
#endif
#if defined HAVE_DECL_SSS_NSS_GETORIGBYGROUPNAME_TIMEOUT && !HAVE_DECL_SSS_NSS_GETORIGBYGROUPNAME_TIMEOUT
#define sss_nss_getorigbygroupname_timeout sss_nss_getorigbyname_timeout
#endif
#define SSSD_DOMAIN_SEPARATOR '@'
int get_buffer(size_t *_buf_len, char **_buf)
@@ -894,8 +886,8 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx,
}
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbyusername_timeout(pwd.pw_name, get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(pwd.pw_name, get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
|| id_type == SSS_ID_TYPE_BOTH)) {
set_err_msg(req, "Failed to read original data");
@@ -973,8 +965,8 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx,
}
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbygroupname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
|| id_type == SSS_ID_TYPE_BOTH)) {
set_err_msg(req, "Failed to read original data");
@@ -1123,8 +1115,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
}
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbyusername_timeout(pwd.pw_name, get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(pwd.pw_name, get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
|| id_type == SSS_ID_TYPE_BOTH)) {
set_err_msg(req, "Failed to read original data");
@@ -1160,8 +1152,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
}
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbygroupname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
|| id_type == SSS_ID_TYPE_BOTH)) {
set_err_msg(req, "Failed to read original data");
@@ -1274,9 +1266,9 @@ static int handle_username_request(struct ipa_extdom_ctx *ctx,
switch(ret) {
case 0:
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbyusername_timeout(pwd.pw_name,
get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(pwd.pw_name,
get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
|| id_type == SSS_ID_TYPE_BOTH)) {
set_err_msg(req, "Failed to read original data");
@@ -1365,8 +1357,8 @@ static int handle_groupname_request(struct ipa_extdom_ctx *ctx,
}
if (request_type == REQ_FULL_WITH_GROUPS) {
ret = sss_nss_getorigbygroupname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
&kv_list, &id_type);
if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
|| id_type == SSS_ID_TYPE_BOTH)) {
if (ret == ENOENT) {
@@ -1,42 +0,0 @@
NULL =
PLUGIN_COMMON_DIR = $(srcdir)/../common
AM_CPPFLAGS = \
-I$(srcdir) \
-I$(PLUGIN_COMMON_DIR) \
-DPREFIX=\""$(prefix)"\" \
-DBINDIR=\""$(bindir)"\" \
-DLIBDIR=\""$(libdir)"\" \
-DLIBEXECDIR=\""$(libexecdir)"\" \
-DDATADIR=\""$(datadir)"\" \
-I$(top_srcdir)/util \
$(DIRSRV_CFLAGS) \
$(LDAP_CFLAGS) \
$(WARN_CFLAGS) \
$(NULL)
plugindir = $(libdir)/dirsrv/plugins
plugin_LTLIBRARIES = \
libipa_graceperiod.la \
$(NULL)
libipa_graceperiod_la_SOURCES = \
ipa_graceperiod.c \
$(NULL)
libipa_graceperiod_la_LDFLAGS = -avoid-version
libipa_graceperiod_la_LIBADD = \
$(LDAP_LIBS) \
$(top_builddir)/util/libutil.la \
$(NULL)
appdir = $(IPA_DATA_DIR)
app_DATA = \
graceperiod-conf.ldif \
$(NULL)
EXTRA_DIST = \
$(app_DATA) \
$(NULL)
@@ -1,15 +0,0 @@
dn: cn=IPA Graceperiod,cn=plugins,cn=config
changetype: add
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: IPA Graceperiod
nsslapd-pluginpath: libipa_graceperiod
nsslapd-plugininitfunc: ipagraceperiod_init
nsslapd-plugintype: object
nsslapd-pluginenabled: on
nsslapd-pluginid: ipagraceperiod_version
nsslapd-pluginversion: 1.0
nsslapd-pluginvendor: Red Hat, Inc.
nsslapd-plugindescription: IPA Graceperiod plugin
nsslapd-plugin-depends-on-type: database
@@ -1,507 +0,0 @@
/** BEGIN COPYRIGHT BLOCK
* 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/>.
*
* Additional permission under GPLv3 section 7:
*
* In the following paragraph, "GPL" means the GNU General Public
* License, version 3 or any later version, and "Non-GPL Code" means
* code that is governed neither by the GPL nor a license
* compatible with the GPL.
*
* You may link the code of this Program with Non-GPL Code and convey
* linked combinations including the two, provided that such Non-GPL
* Code only links to the code of this Program through those well
* defined interfaces identified in the file named EXCEPTION found in
* the source code files (the "Approved Interfaces"). The files of
* Non-GPL Code may instantiate templates or use macros or inline
* functions from the Approved Interfaces without causing the resulting
* work to be covered by the GPL. Only the copyright holders of this
* Program may make changes or additions to the list of Approved
* Interfaces.
*
* Copyright (C) 2022 Red Hat, Inc.
* All rights reserved.
* END COPYRIGHT BLOCK **/
/**
* IPA Graceperiod plug-in
*
* Limit LDAP operations to password changes while in the grace period.
*
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include "slapi-plugin.h"
#include "nspr.h"
#include <krb5.h>
#include "util.h"
#include "ipa_pwd.h"
#define IPAGRACEPERIOD_PLUGIN_NAME "ipa-graceperiod-plugin"
#define IPAGRACEPERIOD_PLUGIN_VERSION 0x00010000
#define IPA_PLUGIN_NAME IPAGRACEPERIOD_PLUGIN_NAME
#define IPAGRACEPERIOD_FEATURE_DESC "IPA Graceperiod"
#define IPAGRACEPERIOD_PLUGIN_DESC "IPA Graceperiod plugin"
#define IPAGRACEPERIOD_PREOP_DESC "IPA Graceperiod preop plugin"
static Slapi_PluginDesc pdesc = {
IPAGRACEPERIOD_FEATURE_DESC,
"Red Hat, Inc.",
"1.0",
IPAGRACEPERIOD_PLUGIN_DESC
};
struct ipa_context {
bool disable_last_success;
bool disable_lockout;
};
static void *_PluginID = NULL;
static int g_plugin_started = 0;
#if 0
static struct ipa_context *global_ipactx = NULL;
#endif
static char *ipa_global_policy = NULL;
int ipagraceperiod_getpolicy(Slapi_Entry *target_entry, Slapi_Entry **policy_entry,
Slapi_ValueSet** values, char **actual_type_name,
const char **policy_dn, int *attr_free_flags,
char **errstr);
int ipagraceperiod_version(void);
static void *getPluginID(void);
static void setPluginID(void *pluginID);
#define GENERALIZED_TIME_LENGTH 15
/**
*
* management functions
*
*/
int ipagraceperiod_init(Slapi_PBlock * pb);
static int ipagraceperiod_start(Slapi_PBlock * pb);
static int ipagraceperiod_close(Slapi_PBlock * pb);
static int ipagraceperiod_preop_init(Slapi_PBlock * pb);
static int ipagraceperiod_get_global_config(void);
/**
*
* the ops (where the real work is done)
*
*/
static int ipagraceperiod_preop(Slapi_PBlock *pb);
/**
*
* Get the plug-in version
*
*/
int ipagraceperiod_version(void)
{
return IPAGRACEPERIOD_PLUGIN_VERSION;
}
/**
* Plugin identity mgmt
*/
static void setPluginID(void *pluginID)
{
_PluginID = pluginID;
}
static void *getPluginID(void)
{
return _PluginID;
}
static int
ipagraceperiod_get_global_config(void)
{
char *dn = NULL;
char *basedn = NULL;
char *realm = NULL;
Slapi_DN *sdn;
Slapi_Entry *config_entry = NULL;
krb5_context krbctx = NULL;
krb5_error_code krberr;
int ret;
/* Get cn=config so we can get the default naming context */
sdn = slapi_sdn_new_dn_byref("cn=config");
ret = slapi_search_internal_get_entry(sdn, NULL, &config_entry,
getPluginID());
slapi_sdn_free(&sdn);
if (ret) {
goto done;
}
basedn = slapi_entry_attr_get_charptr(config_entry,
"nsslapd-defaultnamingcontext");
slapi_entry_free(config_entry);
config_entry = NULL;
if (!basedn) {
goto done;
}
krberr = krb5_init_context(&krbctx);
if (krberr) {
LOG_FATAL("krb5_init_context failed (%d)\n", krberr);
/* Yes, we failed, but it is because /etc/krb5.conf doesn't exist
* or is misconfigured. Start up in a degraded mode.
*/
} else {
krberr = krb5_get_default_realm(krbctx, &realm);
if (krberr) {
LOG_FATAL("Failed to get default realm (%d)\n", krberr);
} else {
ipa_global_policy =
slapi_ch_smprintf("cn=global_policy,cn=%s,cn=kerberos,%s",
realm, basedn);
if (!ipa_global_policy) {
LOG_OOM();
ret = LDAP_OPERATIONS_ERROR;
goto done;
}
}
}
ret = 0;
done:
if (config_entry)
slapi_entry_free(config_entry);
free(realm);
krb5_free_context(krbctx);
free(dn);
free(basedn);
return ret;
}
int ipagraceperiod_getpolicy(Slapi_Entry *target_entry, Slapi_Entry **policy_entry,
Slapi_ValueSet** values, char **actual_type_name,
const char **policy_dn, int *attr_free_flags,
char **errstr)
{
int ldrc = 0;
int type_name_disposition = 0;
Slapi_DN *pdn = NULL;
/* Only continue if there is a password policy */
ldrc = slapi_vattr_values_get(target_entry, "krbPwdPolicyReference",
values,
&type_name_disposition, actual_type_name,
SLAPI_VIRTUALATTRS_REQUEST_POINTERS,
attr_free_flags);
if (ldrc == 0) {
Slapi_Value *sv = NULL;
if (values != NULL) {
slapi_valueset_first_value(*values, &sv);
*policy_dn = slapi_value_get_string(sv);
}
} else {
*policy_dn = ipa_global_policy;
}
if (*policy_dn == NULL) {
LOG_TRACE("No kerberos password policy\n");
return LDAP_SUCCESS;
} else {
pdn = slapi_sdn_new_dn_byref(*policy_dn);
ldrc = slapi_search_internal_get_entry(pdn, NULL, policy_entry,
getPluginID());
slapi_sdn_free(&pdn);
if (ldrc != LDAP_SUCCESS) {
LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", *policy_dn, ldrc);
*errstr = "Failed to retrieve account policy.";
return LDAP_OPERATIONS_ERROR;
}
}
return LDAP_SUCCESS;
}
int
ipagraceperiod_init(Slapi_PBlock *pb)
{
int status = EOK;
char *plugin_identity = NULL;
LOG_TRACE("--in-->\n");
slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
PR_ASSERT(plugin_identity);
setPluginID(plugin_identity);
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
SLAPI_PLUGIN_VERSION_01) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
(void *) ipagraceperiod_start) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
(void *) ipagraceperiod_close) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
(void *) &pdesc) != 0 ||
slapi_register_plugin("preoperation",
1,
"ipagraceperiod_init",
ipagraceperiod_preop_init,
IPAGRACEPERIOD_PREOP_DESC,
NULL,
plugin_identity
)
) {
LOG_FATAL("failed to register plugin\n");
status = EFAIL;
}
LOG_TRACE("<--out--\n");
return status;
}
static int
ipagraceperiod_preop_init(Slapi_PBlock *pb)
{
int status = EOK;
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
SLAPI_PLUGIN_VERSION_01) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
(void *) &pdesc) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN,
(void *) ipagraceperiod_preop) != 0) {
status = EFAIL;
}
return status;
}
static int
ipagraceperiod_start(Slapi_PBlock * pb)
{
LOG_TRACE("--in-->\n");
/* Check if we're already started */
if (g_plugin_started) {
goto done;
}
g_plugin_started = 1;
ipagraceperiod_get_global_config();
LOG("ready for service\n");
done:
LOG_TRACE("<--out--\n");
return EOK;
}
static int
ipagraceperiod_close(Slapi_PBlock * pb)
{
LOG_TRACE( "--in-->\n");
slapi_ch_free_string(&ipa_global_policy);
LOG_TRACE("<--out--\n");
return EOK;
}
/*
* In the pre-op stage the bind hasn't occurred yet. It is here that
* we do the lockout enforcement.
*/
static int ipagraceperiod_preop(Slapi_PBlock *pb)
{
char *dn = NULL;
const char *policy_dn = NULL;
Slapi_Entry *target_entry = NULL;
Slapi_Entry *policy_entry = NULL;
Slapi_Value *objectclass = NULL;
Slapi_DN *sdn = NULL;
char *errstr = NULL;
int ldrc = 0;
int rc = 0;
int ret = LDAP_SUCCESS;
char *actual_type_name = NULL;
int attr_free_flags = 0;
Slapi_ValueSet *values = NULL;
long grace_limit = 0;
int grace_user_time;
char *pwd_expiration = NULL;
int pwresponse_requested = 0;
Slapi_PBlock *pbtm = NULL;
Slapi_Mods *smods = NULL;
LOG_TRACE("--in-->\n");
/* Just bail if we aren't ready to service requests yet. */
if (!g_plugin_started) {
goto done;
}
if (slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn) != 0) {
LOG_FATAL("Error retrieving target DN\n");
ret = LDAP_OPERATIONS_ERROR;
goto done;
}
/* Client is anonymously bound */
if (dn == NULL) {
LOG_TRACE("anonymous bind\n");
goto done;
}
/* Get the entry */
sdn = slapi_sdn_new_dn_byref(dn);
if (sdn == NULL) {
LOG_OOM();
errstr = "Out of memory.\n";
ret = LDAP_OPERATIONS_ERROR;
goto done;
}
ldrc = slapi_search_internal_get_entry(sdn, NULL, &target_entry,
getPluginID());
if (ldrc != LDAP_SUCCESS) {
LOG_TRACE("Failed to retrieve entry \"%s\": %d\n", dn, ldrc);
goto done;
}
/* Only deal with users and sysaccount entries */
objectclass = slapi_value_new_string("posixAccount");
if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
LOG_TRACE("Not a posix user\n");
slapi_value_free(&objectclass);
objectclass = slapi_value_new_string("simplesecurityobject");
if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
LOG_TRACE("Not a sysaccount user\n");
slapi_value_free(&objectclass);
goto done;
}
}
slapi_value_free(&objectclass);
pwd_expiration = slapi_entry_attr_get_charptr(target_entry, "krbPasswordExpiration");
if (pwd_expiration == NULL) {
/* No expiration means nothing to do */
LOG_TRACE("No krbPasswordExpiration for %s, nothing to do\n", dn);
goto done;
}
ldrc = ipagraceperiod_getpolicy(target_entry, &policy_entry,
&values, &actual_type_name,
&policy_dn, &attr_free_flags,
&errstr);
if (ldrc != LDAP_SUCCESS || policy_dn == NULL) {
goto done;
}
slapi_pblock_get(pb, SLAPI_PWPOLICY, &pwresponse_requested);
/* This returns 0 if the attribute doesn't exist, so no grace but
* report that logins are not allowed.
*/
grace_limit = slapi_entry_attr_get_int(policy_entry, "passwordGraceLimit");
/* -1 means disable grace limit */
if (grace_limit == -1) {
LOG_TRACE("grace limit disabled, skipping\n");
goto done;
} else if (grace_limit < -1) {
LOG_FATAL("Invalid passwordGraceLimit value %d\n", grace_limit);
return LDAP_OPERATIONS_ERROR;
}
grace_user_time = slapi_entry_attr_get_int(target_entry, "passwordGraceUserTime");
if ((grace_limit > 0) && (grace_user_time < grace_limit)) {
char graceUserTime[16] = {0};
grace_user_time++;
sprintf(graceUserTime, "%d", grace_user_time);
smods = slapi_mods_new();
slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
"passwordGraceUserTime", graceUserTime);
pbtm = slapi_pblock_new();
slapi_modify_internal_set_pb(pbtm,
slapi_entry_get_dn_const(target_entry),
slapi_mods_get_ldapmods_byref(smods),
NULL, NULL, getPluginID(), 0);
slapi_modify_internal_pb(pbtm);
slapi_pblock_get(pbtm, SLAPI_PLUGIN_INTOP_RESULT, &rc);
if (rc != LDAP_SUCCESS) {
LOG_TRACE("WARNING: modify error %d on entry '%s'\n",
rc, slapi_entry_get_dn_const(target_entry));
}
if (pwresponse_requested) {
slapi_pwpolicy_make_response_control(pb, -1, grace_limit - grace_user_time , -1);
}
} else if ((grace_limit > 0) && (grace_user_time >= grace_limit)) {
LOG_TRACE("%s password is expired and out of grace limit\n", dn);
errstr = "Password is expired.\n";
ret = LDAP_INVALID_CREDENTIALS;
if (pwresponse_requested) {
slapi_pwpolicy_make_response_control(pb, -1, 0, LDAP_PWPOLICY_PWDEXPIRED);
}
goto done;
}
slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
done:
slapi_pblock_destroy(pbtm);
slapi_mods_free(&smods);
slapi_entry_free(target_entry);
slapi_entry_free(policy_entry);
if (values != NULL) {
slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
}
if (sdn) slapi_sdn_free(&sdn);
LOG("preop returning %d: %s\n", ret, errstr ? errstr : "success\n");
if (ret) {
slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
}
LOG_TRACE("<--out--\n");
return (ret == 0 ? EOK : EFAIL);
}
@@ -96,14 +96,6 @@ void *ipapwd_get_plugin_id(void)
return ipapwd_plugin_id;
}
static bool is_nthash_allowed(const char *service_name, const char *bind_dn)
{
#define CIFS_PRINCIPAL_PREFIX "krbprincipalname=cifs/"
return (0 == strncmp("cifs/", service_name, 5)) ||
(0 == strncmp(CIFS_PRINCIPAL_PREFIX, bind_dn,
sizeof(CIFS_PRINCIPAL_PREFIX) - 1));
}
static void filter_keys(struct ipapwd_krbcfg *krbcfg,
struct ipapwd_keyset *kset,
bool allow_nthash)
@@ -1236,7 +1228,7 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
int kvno;
char *svcname;
bool allowed_access = false;
bool nthash_allowed = false;
bool is_nthash_allowed = false;
struct berval *bvp = NULL;
LDAPControl new_ctrl;
@@ -1313,8 +1305,8 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
/* Only allow generating arcfour-hmac keys for cifs/.. services
* unless the enctype is allowed by the IPA configuration for use
* by the all principals */
nthash_allowed = is_nthash_allowed(serviceName, bindDN);
filter_keys(krbcfg, kset, nthash_allowed);
is_nthash_allowed = (0 == strncmp("cifs/", serviceName, 5));
filter_keys(krbcfg, kset, is_nthash_allowed);
/* check if we have any left */
if (kset->num_keys == 0) {
@@ -1623,7 +1615,7 @@ static int ipapwd_getkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
struct berval *bvp = NULL;
LDAPControl new_ctrl;
bool wantold = false;
bool nthash_allowed = false;
bool is_nthash_allowed = false;
/* Get Bind DN */
slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn);
@@ -1720,8 +1712,8 @@ static int ipapwd_getkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
/* Only allow generating arcfour-hmac keys for cifs/.. services
* unless the enctype is allowed by the IPA configuration for use
* by the all principals */
nthash_allowed = is_nthash_allowed(service_name, bind_dn);
filter_enctypes(krbcfg, kenctypes, &num_kenctypes, nthash_allowed);
is_nthash_allowed = (0 == strncmp("cifs/", service_name, 5));
filter_enctypes(krbcfg, kenctypes, &num_kenctypes, is_nthash_allowed);
/* check if we have any left */
if (num_kenctypes == 0 && kenctypes != NULL) {
@@ -1071,7 +1071,6 @@ static int ipapwd_post_modadd(Slapi_PBlock *pb)
struct ipapwd_krbcfg *krbcfg = NULL;
char *principal = NULL;
Slapi_Value *ipahost;
Slapi_Value *zero;
LOG_TRACE("=>\n");
@@ -1168,13 +1167,6 @@ static int ipapwd_post_modadd(Slapi_PBlock *pb)
}
slapi_value_free(&ipahost);
}
zero = slapi_value_new_string("0");
if (!slapi_entry_attr_has_syntax_value(pwdop->pwdata.target,
"passwordgraceusertime", zero)) {
/* Clear the passwordgraceusertime from the user entry */
slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "passwordgraceusertime", "0");
}
slapi_value_free(&zero);
ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
if (ret)
+6
View File
@@ -23,6 +23,12 @@ help:
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: workshop-remote workshop-pull
workshop-remote:
git remote add -f freeipa-workshop https://github.com/freeipa/freeipa-workshop.git
workshop-pull:
git pull -s subtree freeipa-workshop master
.PHONY: lint
lint:
$(MAKE) html SPHINXOPTS="$(SPHINXOPTS) -E -a"
-4
View File
@@ -1,4 +0,0 @@
section ul.simple li {
list-style: disc;
margin-left: 1em;
}
+2 -7
View File
@@ -40,8 +40,7 @@ extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'sphinxcontrib.plantuml',
'm2r2',
'm2r',
]
# Add any paths that contain templates here, relative to this directory.
@@ -72,11 +71,7 @@ html_theme = 'default'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_css_files = [
'css/ipa.css',
]
# html_static_path = ['_static']
# -- Options for sources -----------------------------------------------------
-499
View File
@@ -1,499 +0,0 @@
# FreeIPA and an external identity provider integration
FreeIPA provides an integrated identity management solution for POSIX-alike
environments. Its implementation has been based on a number of assumptions about
the usage and the representation of users and groups in a generalized POSIX
environment. Users and groups consumed by the applications running in POSIX
environment in several common ways:
- application processes run under some user identity;
- application processes create or access files attributed to specific users and
groups;
- a set of groups a user belongs to is fixed at the login time and does not
change until that login session is completed;
- an authentication flow of a user is directly tied with the login session that
is instantiated as a result of authentication.
Consumption patterns described above equate presence of POSIX user and group IDs
with ability to run application processes under these identities. Interfacing
with the applications was typically done inside shell sessions initiated by the
users represented by the POSIX identities. With the move to web- and
mobile-oriented user interfaces, the POSIX user consumption patterns have become
less prominent. Applications consumed through interfaces that aren't expressed
through POSIX environments as in the past. POSIX identities, instead, relegated
to be a support mechanism for running isolated applications. This is especially
visible in Android application model or containerized environments.
Application-level identities are not necessarily the same as the system level
users anymore.
Usage shift does not, however, dictate an exclusion between the two models in
enterprise environments. The same users need to access both operating
system-level applications and be able to authenticate as application-level
identities. This is typically achieved by forming a single sign-on environment
where a user would be authenticated directly once and then the fact of
authentication is consumed by other services for a certain amount of time,
regardless of how the applications that represent these services are operating.
In this document discussion of 'application-level identities' really means
resource owner identities visible through OAuth 2.0 Authorization Framework.
This level of abstraction allows discussion of authentication and access to
resources regardless of internal details of a specific application.
There are two major use cases to be considered:
- FreeIPA serves as a backend to provide identities to an identity provider
(IdP) to authenticate and authorize access to OAuth 2.0 clients. This IdP
would be called 'an integrated IdP' to FreeIPA. A subset of user properties
would be stored in IdP itself, another part retained in FreeIPA.
- FreeIPA communicates with an external IdP to perform identity verification and
ask for an access grant to itself. Authentication and authorization of the
identity is delegated to the external IdP and the user information in FreeIPA
is used as an anchor to map external IdP identity to a system-level user
identity.
The scope of this document is to address the second use case.
## Use of an external IdP to verify external identities for FreeIPA
OAuth 2.0 authorization framework concerns access control to resource owner
identities from OAuth clients. OAuth 2.0 authorization server arbitrates the
access by the OAuth clients. It also authenticates the resource owner identity
when OAuth client redirects a user (often a web browser) to the Authorization
Server to request the access grant.
In this document we would not look into details how OAuth 2.0 Authorization
Server would authenticate the resource owner identity. We assume this part is
implemented by the IdP.
In POSIX-like system environment access to resources often combines both
authentication and authorization steps. Two most common examples would be access
over a secure shell protocol variant and local system privilege escalation with
PAM interface. SSH protocol implementation often combines these two, allowing to
either authenticate with native SSH methods, with SSH key-pairs, GSSAPI
authentication, or delegate authentication to PAM (`keyboard-interactive`
method). Access control is then can be offloaded to PAM as well.
Locally, granting access to other resources often involves PAM stack processing
as well. `sudo` performs PAM authentication and authorization prior to applying
own access rules, for example.
FreeIPA already implements authorization part through PAM stack with the help of
SSSD suite. `pam_sss` PAM module allows using HBAC rules to grant access which
otherwise is denied. It also implements authentication pass-through mechanism,
allowing SSSD to handle a variety of authentication methods: LDAP binds,
Kerberos, etc.
When SSSD on FreeIPA-enrolled client needs to authenticate a user, it performs
mutual authentication with FreeIPA KDC. Mutual authentication relies on the fact
that each FreeIPA client is registered with the KDC in FreeIPA domain. In order
to request an access grant to a resource owner identity with an OAuth 2.0
authorization flow to FreeIPA-enrolled clients, this particular client has to be
registered as an OAuth client against an IdP that knows about the user. This is
impractical for FreeIPA deployments. What happens in many OAuth2 environments is
that instead of registering every single application system to the user's IdP, a
single client identity representing the whole 'application environment' is
registered. Once a user did log in into an application environment, an
application environment-specific access token is issued and used by the
application backend to access other resources in its own domain. In a sense,
this is similar to Kerberos protocol authentication process to obtain a ticket
granting ticket (TGT) and later request individual service tickets based on a
TGT.
To reduce authorization complexity we can view the whole FreeIPA deployment as a
single OAuth 2.0 client registered with an integrated IdP. The integrated IdP
would then handle authentication of the user identity and authorize access to
it. If that process would require, in turn, access to a federated identity
provider, the latter would not need to be known to FreeIPA OAuth 2.0 client.
Use of a single OAuth 2.0 client identity still presents an issue with multiple
FreeIPA-enrolled clients because they cannot easily share the client identity
while retaining a certain level of security.
Instead, in this design we consider IPA replicas to share OAuth 2.0 client
credentials in a way similar to how they do already share Kerberos realm master
keys: each IPA replica would be able to operate on its own using the same OAuth
2.0 client identity which is stored in a replicated IPA LDAP tree.
### OAuth 2.0 Device Authorization Grant
OAuth 2.0 Device Authorization Grant is defined in [RFC
8628](https://www.rfc-editor.org/rfc/rfc8628) and allows devices that either
lack a browser or input constrained to obtain user authorization to access
protected resources. Instead of performing the authorization flow right at the
device where OAuth authorization grant is requested, a user would perform it at
a separate device that has required rich browsing or input capabilities.
Following figure demonstrates a generic device authorization flow:
.. uml::
participant "End User at Browser"
participant "Device Client"
participant "Authorization Server"
"Device Client" -> "Authorization Server": (A) Client Identifier
"Authorization Server" -> "Device Client": (B) Device Code, User Code & Verification URI
"Device Client" -> "End User at Browser": (C) User Code & Verification URI
"End User at Browser" <-> "Authorization Server": (D) End user reviews authorization request
"Device Client" -> "Authorization Server": (E) Polling with Device Code and Client Identifier
"Authorization Server" -> "Device Client": (F) Access Token (& Optional Refresh Token)
With the help of OAuth 2.0 Device Authorization Grant, OAuth authentication
process can be integrated into Kerberos authentication protocol and would allow
to not depend on OAuth2 support in all 'traditional' applications running in
FreeIPA deployment.
Since OAuth 2.0 Device Authorization Grant is not universally supported, a
federated access can be used to organize access for users who are authenticated
against an IdP without OAuth 2.0 Device Authorization Grant support. In such
case the IdP where FreeIPA OAuth 2.0 client is registered would be called "an
integrated IdP."
The use of an integrated IdP instance allows requiring support for OAuth 2.0
Device Authorization Grant flow. Since integrated IdP would have the required
capability, it is not necessary that the same capability would be supported by
all external IdPs. It also does not require registration of the individual IPA
OAuth 2.0 clients to the external IdPs.
Setting up an integrated IdP with FreeIPA is beyond scope of this document.
### High-level authentication overview
External IdP integration with FreeIPA is designed with the following
assumptions. Identities for users authenticated through the external IdPs stored
in FreeIPA as user accounts. They have no passwords associated and instead are
forced to authenticate to external IdPs over Kerberos-based authentication flow.
The user accounts for externally-authenticated users to be created in advance;
this can be achieved manually or automatically. The way how they created is out
of scope for this document.
With the following preconditions:
- FreeIPA is registered as an OAuth 2.0 client with an external IdP
- user account is associated with the external IdP
- user account entry contains mapping to some resource owner identity in the
external IdP
- user authentication type for this user includes possibility to authenticate
against IdP
a general authentication workflow for a user registered in External IdP would
involve following steps:
- the user performs a prompt-based authentication to the IPA-enrolled system
- upon login, a prompt is shown that guides the user to use a separate device
to login to a specified URI and verify a device code shown at a prompt
- once logged into a specified URI, the user would be asked to confirm the
login intent
- An empty response is entered to the original prompt following the login and
confirmation at a specified URI
- A backend process behind the login would perform the validation of the
response
- Once the response is validated, a Kerberos ticket is issued for this login
attempt
- successful Kerberos authentication leads to an authorization step which is
performed using standard IPA facilities (HBAC rules, group membership, etc)
- if both authentication and authorization are successful, user is logged into
the system
Upon successful login, the user with External IdP identity would have an initial
Kerberos ticket granting ticket in the login session's credentials cache. This
ticket is further can be used to perform required authentication to other
IPA-provided services during its validity time.
### OpenID Connect Client-initiated backchannel authentication
An alternative to OAuth 2.0 Device Authorization Grant Flow would be to use
CIBA, OpenID Connect Client-initiated backchannel authentication flow, as
defined in
https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html.
CIBA flow is not yet supported in Keycloak. For the initial implementation of
the OAuth 2.0 authorization in FreeIPA, we would skip implementation of CIBA flow.
### OAuth 2.0 access token exchange
Another method to authorize and verify resource owner identity is to exchange
already existing OAuth 2.0 access token obtained by a different OAuth 2.0
client. This is most useful if the latter client is capable to initiate OAuth
2.0 authorization flow using a web browser.
For the initial implementation of the OAuth 2.0 authorization in FreeIPA, we
would skip this method.
### Authentication flow for OAuth2 proxying over Kerberos protocol
MIT Kerberos implements a mechanism for one-time password (OTP)
pre-authentication, as described in [RFC
6560](https://tools.ietf.org/html/rfc6560). The implementation in MIT Kerberos
allows a KDC to ask an external RADIUS server for the authentication decision
for a specific Kerberos principal. MIT Kerberos client, upon receiving a
pre-authentication mechanism response from the KDC, interacts with a user by
asking individual questions for OTP factors and further communicating back with
KDC.
FreeIPA implements a shim RADIUS proxy, called `ipa-otpd`, which listens on a
UNIX domain socket configured by default for KDC. If OTP pre-authentication
method is allowed for the requested Kerberos principal, KDC queries a RADIUS
server.
`ipa-otpd` implements two authentication flows:
- TOTP/HOTP token authentication, performed against FreeIPA LDAP server as an
LDAP BIND operation;
- proxying RADIUS request to a remote RADIUS server
In either flow, `ipa-otpd` responds to a KDC request with a RADIUS packet
constructed out of the result of authentication. KDC then performs the remaining
communication as defined in the RFC 6560.
This approach can be used to implement other authentication flows that can fit
into a RADIUS exchange with `Accept-Request` and `Accept-Response` messages. An
example of this approach is an Azure AD multi-factor authentication (MFA)
extension to Microsoft's RADIUS server, NPS. The detailed flow is described
[Azure AD Multi-factor authentication how-to
guide](https://docs.microsoft.com/en-us/azure/active-directory/authentication/howto-mfa-nps-extension).
Together with MIT Kerberos developers during a prototype investigation it was
decided to not extend existing OTP pre-authentication mechanism to add support
for external IdPs support but rather implement a separate Kerberos
pre-authentication mechanism based on similar ideas.
Original design for `idp` pre-authentication mechanism used RADIUS attribute
`State` to pass through the state of OAuth 2.0 flow. In RADIUS packets, size of
RADIUS attributes is limited up to 254 bytes. For many IdPs, the state can be
larger than 253 bytes. MIT Kerberos `krad` library supports concatenating
multiple appearances of the same attribute in the packet when retrieving the
value. `State` attribute semantics are defined in [RFC
2865](https://tools.ietf.org/html/rfc2865), where `State` attribute can only be
present at most once. Therefore, was decided to utilize `Proxy-State` attribute
instead. `Proxy-State` allows multiple appearances in a single RADIUS packet.
Additionally, a bug was found in `krad` library implementation which prevented
to use RADIUS attribute values larger than 127 bytes. As of now, the fix for MIT
Kerberos is expected to be released with MIT Kerberos 1.20. Those fixes include
commits from upstream pull requests
[krb5#1229](https://github.com/krb5/krb5/pull/1229) and
[krb5#1230](https://github.com/krb5/krb5/pull/1230). They were backported to
RHEL 8.7, RHEL 9.1, and Fedora 34-37 releases ahead of MIT Kerberos 1.20
release.
Finally, as with `otp` pre-authentication mechanism, use of `idp` method
requires a FAST channel. Any valid Kerberos ticket can be used to form the FAST
channel. SSSD would automatically use host principal's keytab to generate one.
Anonymous PKINIT can also be used. The following sequence of `kinit` commands
can be used to prepare for use of `idp` pre-authentication mechanism:
```
kinit -n -c $TMPDIR/anonymous.ccache
kinit -T $TMPDIR/anonymous.ccache idpuser
```
The new pre-authentication mechanism was released in [SSSD 2.7.0
release](https://sssd.io/release-notes/sssd-2.7.0.html).
The `idp` pre-authentication mechanism flow can be described with the following
sequence diagram:
.. uml::
:scale: 50%
participant Operator
participant "Kerberos client"
box "FreeIPA server" #LightBlue
participant "Kerberos KDC"
participant "ipa-otpd"
participant "oidc_child"
end box
box "External IdP" #LightYellow
participant "Authorization Server"
participant "Device code portal"
end box
Operator -> "Kerberos client": kinit -Tsome-ccache idp-user
"Kerberos client" -> "Kerberos KDC" : AP-REQ
"Kerberos KDC" -> "ipa-otpd": Access-Request
"ipa-otpd" -> "oidc_child": Provide Device auth state
"oidc_child" -> "Authorization Server": Initiate OAuth 2.0 Device authorization grant flow
"Authorization Server" -> "oidc_child": device_code: ..., user_code: XXXXX, verification_uri: ....
"oidc_child" -> "ipa-otpd": Device auth state: device_code: ...., user_code: XXXXX, verification_uri: ....
"ipa-otpd" -> "Kerberos KDC": Access-Challenge with idp state
"Kerberos KDC" -> "Kerberos client": idp state returned
"Kerberos client" -> Operator: Please visit https://... and enter device code XXXXX
Operator -> "Device code portal": visit https://... in browser and authorize access
"Device code portal" -> Operator: Now you can close this window and continue
"Operator" -> "Kerberos client": <ENTER>
"Kerberos client" -> "Kerberos KDC": idp state returned
"Kerberos KDC" -> "ipa-otpd": Access-Request with idp state as Proxy-State attribute
"ipa-otpd" -> "oidc_child": Request access token with idp state provided
"oidc_child" -> "Authorization Server": request access token
"Authorization Server" -> "oidc_child": access token YYYY given
"oidc_child" -> "Authorization Server": request userinfo using access token YYYY
"Authorization Server" -> "oidc_child": userinfo details
"oidc_child" -> "ipa-otpd": user information
"ipa-otpd" -> "Kerberos KDC": Access-Response or Access-Reject
"Kerberos KDC" -> "Kerberos client": AP-REP with a ticket or an error
"Kerberos client" -> Operator: kinit: success or error message
- Kerberos client advertises 'idp' pre-authentication mechanism in the initial
request to KDC
- KDC will look up Kerberos principal to initiate processing of AP-REQ request
- IPA KDB driver is expected to set JSON metadata named `idp` for the Kerberos
principal which is allowed to use IdP method
- KDC side of the 'idp' pre-authentication method will notice `idp` metadata and
will request default RADIUS end-point to authenticate the Kerberos principal.
Default RADIUS end-point on FreeIPA KDC points to `ipa-otpd` daemon over UNIX
domain socket, activated with the help of systemd.
- Upon receiving RADIUS request for Kerberos principal authentication,
`ipa-otpd` will discover that the user entry is associated with a particular
IdP.
- `ipa-otpd` will collect details for the OAuth 2.0 client associated with the
IdP and will launch a helper `oidc_child`, provided by SSSD, to communicate
with the IdP
- `oidc_child` will perform a request to initiate OAuth 2.0 Device
Authorization Grant flow against the IdP
- Device authorization end-point of the IdP will return an initial information
about the transaction state
- `oidc_child` will relay this information to `ipa-otpd`
- `ipa-otpd` will re-pack this information in a `Access-Challenge` RADIUS packet
with a number of `Proxy-State` attributes representing the transaction state
and return it to the KDC.
- KDC side of the pre-authentication method will re-pack the transaction state
into a JSON metadata to send it as a part of the KDC response to the Kerberos
client
- Kerberos client will receive a KDC response and will allow pre-authentication
methods to handle it one by one. `idp` method will be triggered because the
response contains `idp` method response.
- `idp` pre-authentication method will display the message to instruct a user to
visit an IdP-specific URL and enter provided device code. It then waits for
the user to press `<ENTER>` to continue.
- Once user completed authentication and authorization flow as defined by the
IdP, `<ENTER>` is pressed to allow Kerberos client to complete the TGT
acquisition.
- `idp` pre-authentication method sends back the same metadata it received from
the KDC side.
- Upon receiving the transaction state metadata, KDC side of the `idp`
pre-authentication method will perform another request to the default RADIUS
end-point, sending `Proxy-State` attribute with the state as a part of the
RADIUS package
- Upon receiving the transaction state, `ipa-otpd` will launch `oidc_child`
again to verify completion of the authorization step.
- `oidc_child` will perform verification of the authorization step and would
obtain an OAuth 2.0 token to access `userinfo` OAuth 2.0 end-point
- `oidc_child` will retrieve information about the resource owner identity from
the `userinfo` OAuth 2.0 end-point using scopes defined for this IdP.
- The user information is then returned to `ipa-otpd` daemon which performs
comparison of the identity subject against the one recorded in the user entry
in LDAP.
- If the comparison is successful, `Access-Response` RADIUS packet is returned,
allowing KDC to issue Kerberos ticket.
## Authentication to IPA web UI
At this point, no direct login to Web UI would be available for users
authenticated against an external IdP. They can obtain Kerberos ticket first and
then login to Web UI.
In the future, we may implement OAuth 2.0 native flow to redirect to an IdP-provided
Authorization Server and rely on OAuth 2.0 authorization flow with PKCE. There
are several unsolved issues at the moment that prevent this to be done.
### Stable callback URI
OAuth 2.0 authorization flows rely on HTTP redirects handled by the resource
owner's browser to complete the authorization. Once Authorization Server has
done its job, it would redirect the resource owner's browser to the web resource
define as a callback URI in the OAuth 2.0 client definition. This callback is
not expected to change dynamically and is set as a part of the OAuth 2.0 client
registration.
The IPA servers provide Web UI access on their own individual URIs. There is
already a precedent to provide a stable URI for ACME operations with
`ipa-ca.$DOMAIN` host. Each IPA server's HTTP certificate includes
`ipa-ca.$DOMAIN` SAN dNSName. A similar extension could be added to provide a
stable `ipa-idp.$DOMAIN` stable URI.
### OAuth 2.0 proxy app
Adding a stable `ipa-idp.$DOMAIN` stable URI would need to be complemented by a
web application that would respond at that URI. This application would
essentially need to implement a specialized OAuth 2.0 proxy flow to allow a web
application in IPA domain to initiate OAuth 2.0-based login on behalf of that
application.
Such OAuth 2.0 proxy would be hosted on IPA servers and would have access to the
definition of IdPs associated with the specific users. A user, effectively,
would be redirected to the OAuth 2.0 proxy, then redirected to their IdP for the
authorization, then logged in into the OAuth 2.0 proxy and, finally, redirected
back to the original web app.
An OAuth 2.0 proxy app would need to mutually authenticate the original web app.
If it were to use OAuth 2.0 token issued by IPA deployment itself, it would, in
fact, be an integrated IdP. It is a tempting option but it would require
additional infrastructure overhead on IPA side.
On the other hand, each host enrolled into IPA domain already has means to
authenticate any other Kerberos service with the help of Kerberos
infrastructure. Such OAuth 2.0 proxy might be able to accept HTTPS connections
authenticated by the Kerberos ticket of the requester service. Once OAuth 2.0
authorization of the resource owner (user) is done through the OAuth 2.0 proxy
app, following outcomes are possible:
- OAuth 2.0 proxy app returns a state that can describe a user from the external
IdP in IPA Kerberos point of view and the requester service then uses S4U2Self
to acquire a Kerberos ticket to itself on the resource owner's identity
behalf;
- OAuth 2.0 proxy app returns a limited access token that the requester service
then passes into `idp` pre-authentication method through AP-REQ to turn it
into a normal Kerberos ticket of the resource owner's identity.
Initial implementation does not include support for this approach.
## Individual tasks
### Manage IdP references in IPA
Implement a method to manage IdP references in IPA. It can be done similarly to
how RADIUS proxy links are managed but with more complex data structures
specific for OAuth2.
Topic commands:
```
idp-add Add new external IdP server.
idp-del Delete an external IdP server.
idp-find Search for external IdP servers.
idp-mod Modify an external IdP server.
idp-show Display information about an external IdP server.
```
For more details about IPA API and Web UI implementation please refer to
[idp-api].
### Extend supported user authentication methods in IPA
In order to recognize new authentication method to perform OAuth 2.0 Device
Authorization Grant flow, IPA needs to be extended to support the new method:
- in IPA API, to allow managing the method in `ipa user-mod --user-auth-type`
and similar commands;
- in IPA KDB driver, to recognize that the user has IdP authentication method
and trigger OTP pre-authentication response;
- in IPA KDB driver, to associate a new authentication indicator with the IdP
authentication method.
Both the authentication method and the authentication indicator are called
`idp`. This would allow to distinguish it from the regular RADIUS or OTP
authentication indicators.
### Extend `ipa-otpd` daemon to recognize IdP references
`ipa-otpd` supports two methods at the moment:
- native IPA OTP authentication
- RADIUS proxy authentication
`ipa-otpd` itself does not implement any OAuth 2.0 calls. Instead, configuration
of the OAuth 2.0 client and IdP reference passed to the `oidc_child` process
provided by SSSD in `sssd-idp` package. `oidc_child` uses `curl` and `cjose`
libraries to implement OAuth 2.0 communication.
`ipa-otpd` retrieves IdP references associated with the user being authenticated
and calls out to the `oidc_child` process to verify the user identity against an
associated IdP.
[idp-api]: idp-api.html
-403
View File
@@ -1,403 +0,0 @@
# IPA and an external identity provider integration - idp objects
IPA needs to store and manage IdP references. As a new IPA object, IdP reference needs:
* creation of an LDAP object class and LDAP attribute types used to store IdP
information
* creation of a new IPA API managing the IdP references
* definition of access control lists protecting access to the IdP references
* upgrade and backward compatibility.
## LDAP Schema
### New attribute types
The external IdP reference object needs to contain the following information:
* name (string)
* authorization endpoint (URI mapped as a case-sensitive string),
* device authorization endpoint (URI mapped as a case-sensitive string), for instance https://oauth2.googleapis.com/device/code
* token endpoint (URI mapped as a case-sensitive string), for instance https://oauth2.googleapis.com/token
* userinfo endpoint (URI mapped as a case-sensitive string)
* JWKS endpoint (URI mapped as a case-sensitive string)
* OIDC URL (URI mapped as a case-sensitive string)
* `client_id` (case-sensitive string)
* `client_secret` (optional, may be an empty string, must be protected as a password)
* scope (case sensitive string)
* user subject attribute (case-sensitive string)
Additionally, an idp object needs to be referenced from a user object, thus a link attribute has to be defined. This translates into the following attribute type definitions:
```text
attributeTypes: (2.16.840.1.113730.3.8.23.15 NAME 'ipaIdpDevAuthEndpoint' DESC 'Identity Provider Device Authorization Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.16 NAME 'ipaIdpAuthEndpoint' DESC 'Identity Provider Authorization Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.17 NAME 'ipaIdpTokenEndpoint' DESC 'Identity Provider Token Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.18 NAME 'ipaIdpClientId' DESC 'Identity Provider Client Identifier' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.19 NAME 'ipaIdpClientSecret' DESC 'Identity Provider Client Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.20 NAME 'ipaIdpScope' DESC 'Identity Provider Scope' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.21 NAME 'ipaIdpConfigLink' DESC 'Corresponding Identity Provider Configuration link' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: (2.16.840.1.113730.3.8.23.22 NAME 'ipaIdpSub' DESC 'Identity Provider User Subject' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.23 NAME 'ipaIdpIssuerURL' DESC 'Identity Provider OIDC URL' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.24 NAME 'ipaIdpUserInfoEndpoint' DESC 'Identity Provider UserInfo Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.25 NAME 'ipaIdpKeysEndpoint' DESC 'Identity Provider JWKS Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
```
### New objectclasses for idp
In addition to IdP configuration object, a special object class is added to be able to store IdP configuration link in a user object:
```text
objectClasses: (2.16.840.1.113730.3.8.24.6 NAME 'ipaIdP' SUP top STRUCTURAL DESC 'Identity Provider Configuration' MUST ( cn ) MAY ( ipaIdpDevAuthEndpoint $ ipaIdpAuthEndpoint $ ipaIdpTokenEndpoint $ ipaIdpUserInfoEndpoint $ ipaIdpKeysEndpoint $ ipaIdpClientId $ description $ ipaIdpClientSecret $ ipaIdpScope $ ipaIdpIssuerURL $ ipaIdpSub ) X-ORIGIN 'IPA v4.9' )
objectClasses: (2.16.840.1.113730.3.8.24.7 NAME 'ipaIdpUser' SUP top AUXILIARY DESC 'User from an external Identity Provider ' MAY ( ipaIdpConfigLink $ ipaIdpSub ) X-ORIGIN 'IPA v4.9' )
```
In the current implementation, a user can be connected to a single IdP
reference. This is similar to RADIUS proxy references already used in IPA. For
both RADIUS and IdP cases IPA needs to know a particular user identity within
the context of the referred resource (IdP or RADIUS server). Without associated
IdP user information authentication of the IPA user will not be triggered.
### LDAP indices
Indices need to be defined for all the attributes that can be used in
`ipa idp-find` command.
- The `cn` attribute is already indexed.
- The `ipaidpdevauthendpoint`, `ipaidpauthendpoint`, `idaidptokenendpoint`, and `idpidpscope` attributes
need indices (equality and substring indices).
## IdP reference API
### Command Line Interface
```
idp-add Add new external IdP server.
idp-del Delete an external IdP server.
idp-find Search for external IdP servers.
idp-mod Modify an external IdP server.
idp-show Display information about an external IdP server.
```
Following common options defined for the external IdP server object:
| Option | Description |
|:--------------------- | :--------------------- |
| `--dev-auth-uri`=URI | Device authorization endpoint |
| `--auth-uri`=URI | General OAuth 2.0 authorization endpoint |
| `--token-uri`=URI | Token endpoint |
| `--userinfo-uri`=URI | User information endpoint |
| `--keys-uri`=URI | JWKS endpoint |
| `--issuer-url`=URI | The Identity Provider OIDC URL |
| `--client-id`=STR | The client identifier issued by the IdP during registration |
| `--secret` | The client secret, asked interactively |
| `--scope`=STR | OAuth 2.0 scopes of the access request |
| `--idp-user-id`=STR | Attribute holding user identity in User info |
`--scope` option expects space-separated list of OAuth 2.0 scopes. Since the
value includes space, it has to be enclosed in quotes:
```
ipa idp-mod foo --scope="openid email"
```
Not all common options are required at the same time in the context of different commands.
`ipa idp-add` command adds three more options:
| Option | Description |
|:--------------------- | :--------------------- |
| `--provider`=STR | One of [google, github, microsoft, okta, keycloak] |
| `--org`=STR | IdP-specific organization (tenant or realm) ID |
| `--base-url`=URI | IdP-specific base URL (e.g. `idp-host.$DOMAIN:$PORT/prefix`)|
In order to ease the creation of idp, IPA pre-populates a set of
templates for well-known IdPs so that the user does not need to provide the
individual endpoint details.
For instance, for `google` provider, adding a new IdP would look like:
```
ipa idp-add MyGoogleIdP --provider google \
--client-id nZ8JDrV8Hklf3JumewRl2ke3ovPZn5Ho \
```
This call would be equivalent to the following:
```
ipa idp-add MyGoogleIdP \
--dev-auth-uri https://oauth2.googleapis.com/device/code \
--token-uri https://oauth2.googleapis.com/token \
--client-id nZ8JDrV8Hklf3JumewRl2ke3ovPZn5Ho \
--scope "profile email"
```
As a consequence, `--provider` option and URI-specific options `--auth-uri`,
`--dev-auth-uri`, `--keys-uri`, and `--token-uri` are mutually exclusive.
`ipa idp-add` command does not currently use `--issuer-url` option URL
to dynamically look up other URIs through a well-known OIDC endpoints. Some of
the IdPs do not provide well-known OIDC endpoints URL at all. This option can
be used in `ipa idp-mod` to set `ipaIdpIssuerURL` LDAP attribute. If this
attribute is set, it will be passed to SSSD's `oidc_child` helper during actual
authorization processing.
The `scope` is optional, and the `client_id` is mandatory.
The `client_secret` must not be displayed by `ipa idp-find` or `ipa idp-show`
commands unless permission 'System: Read External IdP client secret' is
assigned to the authenticated user running the command.
### Pre-populated IdP templates
List of pre-populated IdP types is currently limited by the following provider
"names":
* google
* github
* microsoft
* okta
* keycloak
Some IdP providers support parametrized URIs which include organization or a
realm name, or specific base URL, or both.
One notable omission in the pre-populated IdP types above is Gitlab.
FreeIPA only supports IdPs that implement OAuth 2.0 Device authorization
grant flow as defined by the [RFC 8628](https://www.rfc-editor.org/rfc/rfc8628).
If required IdP cannot be made to support Device authorization grant flow, it
is recommended to use OAuth 2.0 federation within an IdP that supports this
method. Gitlab does not support OAuth 2.0 Device authorization grant flow and
thus is not supported directly.
SSSD 2.7.0 implements Kerberos pre-authentication method `idp` (registered as a
pre-authentication type 152, PA-REDHAT-IDP-OAUTH2). It relies on IPA KDB driver
to provide a metadata in the Kerberos principal entry to specify use of IdP.
KDC side of the pre-authentication method 'idp' then communicates with IPA
`ipa-otpd` daemon which reads external IdP reference object associated with the
user and spawns `oidc_child` helper from SSSD to complete authorization.
`oidc_child` helper performs OAuth 2.0 device authorization grant flow. Since
it only supports a single authorization method, not all endpoints are required.
In order to authenticate users associated with external IdPs to access IPA Web
UI, a general OAuth 2.0 authorization endpoint is required. Thus, this endpoint
information is stored in the object but not otherwise used by the Kerberos
flow.
#### Google IdPs
Choosing `--provider=google` would expand to use the following options:
| Option | Value |
|:--------------------- | :--------------------- |
| `--auth-uri`=URI | `https://accounts.google.com/o/oauth2/auth` |
| `--dev-auth-uri`=URI | `https://oauth2.googleapis.com/device/code` |
| `--token-uri`=URI | `https://oauth2.googleapis.com/token` |
| `--userinfo-uri`=URI | `https://openidconnect.googleapis.com/v1/userinfo` |
| `--keys-uri`=URI | `https://www.googleapis.com/oauth2/v3/certs` |
| `--scope`=STR | `openid email` |
| `--idp-user-id`=STR | `email` |
#### Github IdPs
Choosing `--provider=github` would expand to use the following options:
| Option | Value |
|:--------------------- | :--------------------- |
| `--auth-uri`=URI | `https://github.com/login/oauth/authorize` |
| `--dev-auth-uri`=URI | `https://github.com/login/device/code` |
| `--token-uri`=URI | `https://github.com/login/oauth/access_token` |
| `--userinfo-uri`=URI | `https://openidconnect.googleapis.com/v1/userinfo` |
| `--keys-uri`=URI | `https://api.github.com/user` |
| `--scope`=STR | `user` |
| `--idp-user-id`=STR | `login` |
Please note that Github explicitly states that a user login is not unique and
can be reused after a user account was deleted. The configuration above aims
for an easy setup for testing. If production deployment with Github IdP would
be required, it is recommended to change `--idp-user-id` to a more unique subject
like `id`. Unfortunately, Github UI does not give an easy way to discover a
user ID. Other IdPs also lack an easy way to resolve these internal identifiers
when not authorized by the user themselves.
For Github, user's ID can be looked up without authentication through the Users
API. Assuming we have `curl` and `jq` utilities available, a request to
discover an ID of a Github user named `test` would look like:
```
$ curl --silent \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/users/test | jq .id
383316
```
#### Microsoft IdPs
Microsoft Azure IdPs allow parametrization based on the Azure tenant ID
which can be specified with `--org` option to `ipa idp-add`. If support for
live.com IdP is required, specify organization ID `common`, e.g.
```
ipa idp-add LiveCom --provider microsoft --org common --client-id some-client-id
```
Choosing `--provider=microsoft` would expand to use the following options. A string
`${ipaidporg}` will be replaced by the value of `--org` option.
| Option | Value |
|:--------------------- | :--------------------- |
| `--auth-uri`=URI | `https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/authorize` |
| `--dev-auth-uri`=URI | `https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/devicecode`|
| `--token-uri`=URI | `https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/token` |
| `--userinfo-uri`=URI | `https://graph.microsoft.com/oidc/userinfo` |
| `--keys-uri`=URI | `https://login.microsoftonline.com/common/discovery/v2.0/keys` |
| `--scope`=STR | `openid email` |
| `--idp-user-id`=STR | `email` |
#### Okta IdPs
Upon registration of a new organization in Okta, a new base URL is associated
with it. It can be specified with `--base-url` option to `ipa idp-add`:
```
ipa idp-add MyOkta --provider okta --base-url dev-12345.okta.com --client-id some-client-id
```
Choosing `--provider=okta` would expand to use the following options. A string
`${ipaidpbaseurl}` will be replaced by the value of `--base-url` option.
| Option | Value |
|:--------------------- | :--------------------- |
| `--auth-uri`=URI | `https://${ipaidpbaseurl}/oauth2/v1/authorize` |
| `--dev-auth-uri`=URI | `https://${ipaidpbaseurl}/oauth2/v1/device/authorize` |
| `--token-uri`=URI | `https://${ipaidpbaseurl}/oauth2/v1/token` |
| `--userinfo-uri`=URI | `https://${ipaidpbaseurl}/oauth2/v1/userinfo` |
| `--scope`=STR | `openid email` |
| `--idp-user-id`=STR | `email` |
#### Keycloak IdPs
Keycloak allows defining multiple realms (organizations). Since it is often a
part of a custom deployment, both base URL and realm ID are required and
can be specified with `--base-url` and `--org` options to `ipa idp-add`:
```
ipa idp-add MySSO --provider keycloak \
--org master --base-url keycloak.$DOMAIN:$PORT/prefix \
--client-id some-client-id
```
Quarkus version of the Keycloak 17 or later has removed `/auth/` part of the URI. If
your deployment is using non-Quarkus distribution of the Keycloak, `--base-url`
would need to include `/auth/` component as well.
Choosing `--provider=keycloak` would expand to use the following options. A string
`${ipaidpbaseurl}` will be replaced by the value of `--base-url` option. A string
`${ipaidporg}` will be replaced by the value of `--org` option.
| Option | Value |
|:--------------------- | :--------------------- |
| `--auth-uri`=URI | `https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/openid-connect/auth` |
| `--dev-auth-uri`=URI | `https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/openid-connect/auth/device` |
| `--token-uri`=URI | `https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/openid-connect/token` |
| `--userinfo-uri`=URI | `https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/openid-connect/userinfo` |
| `--scope`=STR | `openid email` |
| `--idp-user-id`=STR | `email` |
#### User-specific options
Similar to RADIUS proxy support, external IdP authentication only triggered
when the following three conditions are true:
* user entry contains external IdP reference
* user entry has external IdP user name (subject) set
* either global user authentication type or per-user authentication type includes `idp` method
Therefore, two additional attributes available for User object in IPA API:
| Option | Description |
|:--------------------- | :--------------------- |
| `--idp`=STR | External IdP object reference |
| `--idp-user-id`=STR | External IdP's user name (subject) |
External IdP user name format is specific to individual IdPs. Most OAuth 2.0
IdPs provide `email` scope to retrieve email associated with the OAuth 2.0
resource owner (user) subject. Pre-populated IdP templates described above
include `email` scope by default. If external IdP references were created with
the help of these templates, user's email can be set to `--idp-user-id` to match
the resource owner subject. Please read Security section for details on when
this is inappropriate.
Below is an example how to associate a user account with a specific IdP reference:
```
ipa user-mod test --user-auth-type=idp --idp google --idp-user-id test@example.test
```
### WebUI
The IdP server objects need to be accessible in the WebUI. A new tab for
IdP will be created in the "Authentication" section, similar to
the existing _RADIUS servers_ tab. It will contain a table with the
list of IdP objects, enabling to add or delete an object.
Each object must be clickable in order to be edited.
In the _settings_ page for IdP objects, all the properties must be editable
but the client secret must not be displayed unless the authenticated user permitted
with the help of the `System: Read External IdP client secret` permission.
In order to change the client secret, the admin will click on
_Actions:Reset secret_.
Web UI for user entry attributes have to be extended to include both external
IdP reference and IdP user subject.
## Access control
Specific permissions need to be created to protect access to the IdP objects:
* System: Add External IdP
* System: Read External IdP (does not provide access to the client secret)
* System: Modify External IdP
* System: Delete External IdP
* System: Read External IdP client secret
Specific Privileges:
* External IdP Administrator: all the permissions related to external idp
except _System: Read External IdP client secret_
Any user who is a member of the admins group will be able to manage the
IdP server objects.
## Security
IPA objects representing external IdP references are OAuth 2.0 clients. The
client ID and client secret details can be used to impersonate OAuth 2.0
client. For public OAuth 2.0 client access to Client ID is enough. It is not
considered secure as it is typically a part of the HTTP redirect in OAuth 2.0
authorization flows.
Client secret has to be protected as it is typically used for private (trusted)
OAuth 2.0 clients.
Kerberos pre-authentication method `idp` relies on the user subject
(`ipaIdpSub` LDAP attribute) defined in the LDAP object entry to match the
Kerberos principal and the OAuth 2.0 resource owner. When `openid` OAuth 2.0
scope is used, this typically maps to `sub` value. Since there are no ways to
pull this value for all users in advance, pre-populated IdP templates set OAuth
2.0 scopes to include `email` and then use `email` to map IdP subject where possible.
There are some well-known IdPs which allow reuse of user accounts and emails, this
applies to both Github and Gitlab. Since Gitlab does not support OAuth 2.0
Device authorization grant flow, it is not an issue in itself for this project. However,
for Github it is known that user accounts can be recycled after their removal. In
this case we would recommend to use internal Github identifier instead.
## Upgrade and backward compatibility
As the new object class and attribute types are added in the LDAP schema, there
is no need for a specific upgrade task.
Upon normal upgrade, the new schema is applied and replicated to the other
servers, even if they are installed with a version which does not define the
new object class / attribute types.
In a mixed topology containing old and new servers, the new API is provided
only by the new servers.
-3
View File
@@ -13,11 +13,8 @@ FreeIPA design documentation
krb-ticket-policy.md
extdom-plugin-protocol.md
expiring-password-notification.md
ldap_grace_period.md
libpwquality.md
membermanager.md
hidden-replicas.md
disable-stale-users.md
subordinate-ids.md
external-idp/external-idp.md
external-idp/idp-api.md
-1
View File
@@ -18,7 +18,6 @@ Indicators and corresponding mechanisms are listed below:
| otp | Two factor authentication (password + OTP) |
| pkinit | PKINIT |
| hardened | Hardened Password (by SPAKE or FAST) |
| idp | External Identity Provider |
Hardened password means a password authentication with either SPAKE or FAST armoring enabled.
Although it is possible to assign separate indicators to SPAKE and FAST, when both SPAKE and FAST are used,
-96
View File
@@ -1,96 +0,0 @@
# LDAP Grace Period
## Overview
IPA previously allowed LDAP authentications past expiration to prevent a chicken-and-egg problem. Because all administratively-set passwords are expired a user must be given an opportunity to authenticate in order to reset their password. This allowed for unlimited authentications.
## Use Cases
Many IPA users have asked for a mechanism to deny LDAP authentications once expired.
## How to Use
A new option is added to password policies, --gracelimit. This specifies the number of allowed BINDs past expiration.
To set the global policy:
ipa pwpolicy-mod --gracelimit 3
This will allow 3 additional logins past expiration.
Setting to -1 disables the grace limit check.
Setting to 0 will do a grace limit check but will always fail because no further logins are allowed. The distinction between 0 and -1 is that with 0 a password policy control is returned.
## Design
An expired draft around LDAP password policies describes two methods for limiting account access past expiration, https://tools.ietf.org/id/draft-behera-ldap-password-policy-10.html
The RFE provides two options (which can be use simultaneously).
1. Limit the number of authentications past expiration
2. Limit by time past the expiration
This implements the first part only. 389-ds already implements this so we can reuse existing schema, using their alternative attribute names passwordGraceLimit (system) and passwordGraceUserTime (user).
A 389-ds plugin will be registered as a bind preop function which will compare the policy grace period with the user's grace period
The password policy plugin will have a new attribute added, passwordGraceLimit, to store the policy maximum.
The basic flow is:
- Register preop BIND plugin
- On BIND, get the DN
- Get the password policy for that DN
- If the policy grace limit == -1 then exit
- Compare the policy grace limit to the user's passwordGraceUserTime
- If the user's grace limit exceeds policy, fail
- If the grace limit == 0, fail
- Set a password policy control with the number of remaining logins
- Set the time in the policy to -1 to indicate no value
On successful password reset (by anyone) reset the user's passwordGraceUserTime to 0.
The default value on install/upgrade will be -1 to retail existing behavior.
The per-user attempts will not be replicated.
## Implementation
N/A
## Feature Management
### UI
The new option is not visible in the UI yet.
### CLI
Overview of the CLI commands. Example:
| Command | Options |
| --- | ----- |
| pwpolicy-mod | --gracelimit=<-1 to MAXINT> |
### Configuration
The plugin will be registered on new installations.
## Upgrade
The plugin will be registered on upgrades.
## Test plan
1. Set a grace limit on a custom policy, ldapsearch a few times, verify that the limit is set to what is expected
2. Keep going until authentication fails
3. Reset password, verify that authentication can happen
4. With two servers verify that an authentication one one server doesn't trigger a replication event to the other.
## Troubleshooting and debugging
$ ldapsearch -LLL -x -D 'uid=tuser,cn=users,cn=accounts,dc=example,dc=test' -w password -e ppolicy -b uid=tuser,cn=users,cn=accounts,dc=example,dc=test dn
# PasswordExpired control
ldap_bind: Success (0) (Password expired, 3 grace logins remain)
dn: uid=tuser,cn=users,cn=accounts,dc=example,dc=test
+76 -167
View File
@@ -1,8 +1,12 @@
# Central management of subordinate user and group ids
## OUTDATED
**The design document does not reflect new implementation yet!**
Subordinate ids are a Linux Kernel feature to grant a user additional
user and group id ranges. Amongst others the feature can be used
by container runtime engines to implement rootless containers.
by container runtime engies to implement rootless containers.
Traditionally subordinate id ranges are configured in ``/etc/subuid``
and ``/etc/subgid``.
@@ -64,7 +68,6 @@ and don't auto-map or auto-assign subordinate ids by default. Instead
we give the admin several options to assign them manually, semi-manual,
or automatically.
### Revision 1 limitation
The first revision of the feature is deliberately limited and
@@ -73,13 +76,14 @@ basic use cases. Some restrictions may be lifted in the future.
* subuid and subgids cannot be set independently. They are always set
to the same value.
* counts are hard-coded to value 65536.
* once assigned subids cannot be removed.
* counts are hard-coded to value 65536
* once assigned subids cannot be removed
* IPA does not support multiple subordinate id ranges, yet. Contrary to
``/etc/subuid``, users are limited to one set of subordinate ids. The
limitation is implemented with a unique index on owner reference and
can be lifted in the future.
* subids are auto-assigned from 389-DS DNA plugin (dnaInterval).
* subids are auto-assigned. Auto-assignment is currently emulated
until 389-DS has been extended to support DNA with step interval.
* subids are allocated from hard-coded range
``[2147483648..4294901767]`` (``2^31`` to ``2^32-1-65536``), which
is the upper 2.1 billion uids of ``uid_t`` (``uint32_t``). The range
@@ -87,29 +91,6 @@ basic use cases. Some restrictions may be lifted in the future.
* Active Directory support is out of scope and may be provided in the
future.
Finally, it is important to note that new installations come with a new
idrange for subordinate ids. The new ``$REALM_subid_range`` entry uses
range type ``ipa-ad-trust`` instead of range type ``ipa-local-subid``
for backwards compatibility with older SSSD clients, see [SSSD #5571](https://github.com/SSSD/sssd/issues/5571).
```text
dn: cn=TESTREALM.TEST_subid_range,cn=ranges,cn=etc,dc=testrealm,dc=test
objectClass: top
objectClass: ipaIDrange
objectClass: ipaTrustedADDomainRange
cn: TESTREALM.TEST_subid_range
ipaBaseID: 2147483648
ipaIDRangeSize: 2147352576
ipaBaseRID: 2147283648
ipaNTTrustedDomainSID: S-1-5-21-738065-838566-1351619967
ipaRangeType: ipa-ad-trust
```
The additional idrange entry is defined as: ``cn=$REALM_subid_range,cn=ranges,cn=etc,dc=ipa,dc=test``,
which is visible with ``ipa idrange-find``.
### Subid assignment example
```
@@ -263,51 +244,11 @@ objectClasses: (
)
```
Finally, setting ``ipaUserDefaultSubordinateId`` to TRUE will cause
new user entries to gain subordinate id by default:
```text
attributeTypes: (
2.16.840.1.113730.3.8.23.14
NAME 'ipaUserDefaultSubordinateId'
DESC 'Enable adding user entries with subordinate id'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE
X-ORIGIN 'IPA v4.9'
)
```
### cn=subids,cn=accounts,$SUFFIX
Subordinate ids and ACIs are stored in the new subtree
Subordiante ids and ACIs are stored in the new subtree
``cn=subids,cn=accounts,$SUFFIX``.
The following is an example of a subordinate id entry:
```text
dn: ipauniqueid=8a7c0808-375b-4643-9582-e9c851a4b45d,cn=subids,cn=accounts,dc=
testrealm,dc=test
ipaOwner: uid=asmith-auto,cn=users,cn=accounts,dc=testrealm,dc=test
ipaUniqueID: 8a7c0808-375b-4643-9582-e9c851a4b45d
description: auto-assigned subid
objectClass: ipasubordinateidentry
objectClass: ipasubordinateuid
objectClass: ipasubordinateid
objectClass: ipasubordinategid
objectClass: top
ipaSubUidCount: 65536
ipaSubGidCount: 65536
ipaSubUidNumber: 2147745792
ipaSubGidNumber: 2147745792
```
where as can be seen, it shows the link to the ipa user ``asmith-auto`` throught
the ipaOwner attribute:
```text
ipaOwner: uid=asmith-auto,cn=users,cn=accounts,dc=testrealm,dc=test
```
### Index, integrity, memberOf
The attributes ``ipaSubUidNumber`` and ``ipaSubGidNumber`` are index
@@ -327,8 +268,9 @@ Subordinate id auto-assignment requires an extension of 389-DS'
plug-in. The DNA plug-in is responsible for safely assigning unique
numeric ids across all replicas.
A new option ``dnaInterval`` will tell the DNA plug-in to use the value of
entry attributes as interval size.
Currently the DNA plug-in only supports a step size of ``1``. A new
option ``dnaStepAttr`` (name is tentative) will tell the DNA plug-in
to use the value of entry attributes as step size.
## IPA plugins and commands
@@ -356,30 +298,30 @@ Topic commands:
```
```text
$ ipa subid-generate --owner testuser
$ ipa subid-generate --owner testuser9
-----------------------------------------------------------
Added subordinate id "2cf2c0db-aa9d-45d8-acbd-118aba0c1db3"
Added subordinate id "aa28f132-457c-488b-82e1-d123727e4f81"
-----------------------------------------------------------
Unique ID: 2cf2c0db-aa9d-45d8-acbd-118aba0c1db3
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
Description: auto-assigned subid
Owner: testuser
SubUID range start: 2147680256
Owner: testuser9
SubUID range start: 3922132992
SubUID range size: 65536
SubGID range start: 2147680256
SubGID range start: 3922132992
SubGID range size: 65536
```
```text
$ ipa subid-find --owner testuser
$ ipa subid-find --owner testuser9
------------------------
1 subordinate id matched
------------------------
Unique ID: 2cf2c0db-aa9d-45d8-acbd-118aba0c1db3
Owner: testuser
SubUID range start: 2147680256
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
Owner: testuser9
SubUID range start: 3922132992
SubUID range size: 65536
SubGID range start: 2147680256
SubGID range start: 3922132992
SubGID range size: 65536
----------------------------
Number of entries returned 1
@@ -392,22 +334,22 @@ $ ipa -vv subid-stats
ipa: INFO: Response: {
"error": null,
"id": 0,
"principal": "admin@TESTREALM.TEST",
"principal": "admin@IPASUBID.TEST",
"result": {
"result": {
"assigned_subids": 1,
"assigned_subids": 20,
"baseid": 2147483648,
"dna_remaining": 65527,
"dna_remaining": 4293394434,
"rangesize": 2147352576,
"remaining_subids": 0
"remaining_subids": 65512
},
"summary": "0 remaining subordinate id ranges"
"summary": "65532 remaining subordinate id ranges"
},
"version": "4.9.9"
"version": "4.10.0.dev"
}
---------------------------------
0 remaining subordinate id ranges
---------------------------------
-------------------------------------
65532 remaining subordinate id ranges
-------------------------------------
```
## Permissions, Privileges, Roles
@@ -476,8 +418,8 @@ $ ipa role-add-member "Subordinate ID Selfservice User" --groups=ipausers
This allows members of ``ipausers`` to request subordinate ids with
the ``subid-generate`` command or the *Auto assign subordinate ids*
action in the web UI. The command picks the name of the current user
principal automatically.
action in the web UI (**TODO** not implemented yet). The command picks
the name of the current user principal automatically.
```shell
$ ipa subid-generate
@@ -511,7 +453,7 @@ Options:
-q, --quiet output only errors
--log-file=FILE log to the given file
# /usr/libexec/ipa/ipa-subids --group ipausers
# # /usr/libexec/ipa/ipa-subids --group ipausers
Processing user 'testsubordinated1' (1/15)
Processing user 'testsubordinated2' (2/15)
Processing user 'testsubordinated3' (3/15)
@@ -533,65 +475,30 @@ The ipa-subids command was successful
### Find and match users by any subordinate id
The ``user-find`` command is now able to search for users matching a
particular subordinate id when using ``--in-subids`` option:
The ``user-find`` command search by start value of subordinate uid and
gid range. The new command ``user-match-subid`` can be used to find a
user by any subordinate id in their range.
```text
$ ipa user-find --in-subids=8a7c0808-375b-4643-9582-e9c851a4b45d
--------------
1 user matched
--------------
User login: asmith-auto
First name: asmith
Last name: auto
Home directory: /home/asmith-auto
Login shell: /bin/sh
Principal name: asmith-auto@TESTREALM.TEST
Principal alias: asmith-auto@TESTREALM.TEST
Email address: asmith-auto@testrealm.test
UID: 1242200006
GID: 1242200006
Account disabled: False
----------------------------
Number of entries returned 1
----------------------------
```
and search for users not matching a particular subordinate id when
using ``--not-in-subids`` option:
```text
$ ipa user-find --not-in-subids=8a7c0808-375b-4643-9582-e9c851a4b45d
--------------
1 user matched
--------------
User login: bjones-auto
First name: bjones
Last name: auto
Home directory: /home/bjones-auto
Login shell: /bin/sh
Principal name: bjones-auto@TESTREALM.TEST
Principal alias: bjones-auto@TESTREALM.TEST
Email address: bjones-auto@testrealm.test
UID: 1242200007
GID: 1242200007
Account disabled: False
```
Additionally, the new command ``subid-match`` can be used to find a user
by any subordinate id in their range when using the ``--subuid`` option:
```text
$ ipa subid-match --subuid=2147745792
$ ipa subid-match --subuid=2147549183
------------------------
1 subordinate id matched
------------------------
Unique ID: 8a7c0808-375b-4643-9582-e9c851a4b45d
Owner: asmith-auto
SubUID range start: 2147745792
Name: asmith-auto
Owner: asmith
SubUID range start: 2147483648
SubUID range size: 65536
SubGID range start: 2147745792
SubGID range start: 2147483648
SubGID range size: 65536
----------------------------
Number of entries returned 1
----------------------------
$ ipa user-match-subid --subuid=2147549184
Name: bjones-auto
Owner: bjones
SubUID range start: 2147549184
SubUID range size: 65536
SubGID range start: 2147549184
SubGID range size: 65536
----------------------------
Number of entries returned 1
@@ -646,18 +553,6 @@ ipaSubUidCount: 65536
ipaSubGidCount: 65536
```
### Configure subid managed at IPA level
Starting in FreeIPA 4.10.0 release, ipa-client-install provides a new option that
allows to configure the client subid managed at IPA level. This way, ipa-client-install
can configure the sssd profile and customize /etc/nsswitch.conf in such a way the subid
database relies on IPA instead of both of the local files /etc/subuid and /etc/subgid.
It is important to note that the default behavior remains unchanged and ``--subid`` option
must be provided to the client, server and replica installers in order to have SSSD setup
as a datasource for subid in /etc/nsswitch.conf
## Implementation details
* ``ipaSubordinateId`` object class does not subclass the other two
@@ -691,13 +586,27 @@ as a datasource for subid in /etc/nsswitch.conf
### TODO
* enable configuration for ``dnaStepAttr``
* remove ``fake_dna_plugin`` hack from ``baseuser`` plug-in.
* add custom range type for idranges and teach AD trust, sidgen, and
range overlap check code to deal with new range type.
* ipa subid-stats should include assigned_subids value as output, since
the remaning_subids value is always seen as 0, its better to display
assigned_subids as output for #ipa subid-stats which will be more useful
([RFE#2063176](https://bugzilla.redhat.com/show_bug.cgi?id=2063176))
* user-del --preserve and user-undel. Preserving a user with
``ipa user-del --preserve`` is possible however it deletes the
associated subid, meaning that ``ipa user-undel <ipauser>``
is not able to restore the subid of the deleted user ([RFE#2063168](https://bugzilla.redhat.com/show_bug.cgi?id=2063168)).
#### user-del --preserve
Preserving a user with ``ipa user-del --preserve`` currently fails with
an ObjectclassViolation error (err=65). The problem is caused by
configuration of referential integrity postoperation plug-in. The
plug-in excludes the subtree
``nsslapd-pluginexcludeentryscope: cn=provisioning,$SUFFX``. Preserved
users are moved into the staging area of the provisioning subtree.
Since the ``ipaOwner`` DN target is now out of scope, the plug-in
attempts to delete references. However ``ipaOwner`` is a required
attribute, which triggers the objectclass violation.
Possible solutions
* Don't preserve subid entries
* Implement preserve feature for subid entries and move subids of
preserved users into
``cn=deleted subids,cn=accounts,cn=provisioning,$SUFFIX`` subtree.
* Change ``nsslapd-pluginexcludeentryscope`` setting
+4 -11
View File
@@ -1,11 +1,8 @@
wheel
## m2r is not compatible with Sphinx 3.x yet
sphinx >2.1,<3.0
sphinx > 3.0
# convert markdown to rest
# m2r2 fork is active in development and works with
# newer sphinx versions
m2r2
## convert markdown to rest
m2r
## ipa dependencies
dnspython
@@ -29,7 +26,3 @@ lxml
## No sufficient PyPI packages
# dogtag-pki
# Sequence diagrams generator
sphinxcontrib-plantuml
pillow
+1 -7
View File
@@ -6,6 +6,7 @@ FreeIPA workshop
:caption: Workshop:
workshop/workshop.rst
workshop/building.rst
workshop/1-server-install.rst
workshop/2-client-install.rst
workshop/3-user-management.rst
@@ -17,11 +18,4 @@ FreeIPA workshop
workshop/9-selinux-user-map.rst
workshop/10-ssh-key-management.rst
workshop/11-kerberos-ticket-policy.rst
workshop/12-external-idp-support.rst
.. toctree::
:includehidden:
workshop/troubleshooting.rst
workshop/building.rst
workshop/facilitators.rst
+7 -68
View File
@@ -8,8 +8,6 @@ Unit 11: Kerberos ticket policy
In this module you will explore how to manage properties of Kerberos
authentication and authorization in FreeIPA.
**Note:** To complete this module, FreeIPA-4.8.4 or later is needed.
Kerberos ticket policies
------------------------
@@ -99,7 +97,6 @@ radius RADIUS
otp FreeIPA two factor authentication (password + OTP)
pkinit PKINIT, smart-card or certificate authentication
hardened Hardened Password (by SPAKE or FAST)
idp External Identity Provider
========================= ========================
**Hardened** authentication indicator is set by FreeIPA KDC when a Kerberos
@@ -129,7 +126,7 @@ pre-authenticate the initial ticket granting ticket exchange using chosen
mechanism. This fact can further be used to differentiate the issued ticket life
time and renewal age.
With FreeIPA 4.8.4 or later, Kerberos ticket policy allows an administrator to
With FreeIPA 4.8.3 or later, Kerberos ticket policy allows an administrator to
set different life time and renewal age for ticket granting tickets obtained
with different pre-authentication methods. Each policy setting may include
authentication indicator to say that the life time or renewal age applies to
@@ -256,70 +253,12 @@ For example, an application might decide to only allow access to a specialized
resource to people who used smart-card authentication initially, even if the
application itself only supports Kerberos authentication.
At the moment, there is only one known application that implements
authentication indicator-based authorization. Since version 2.6.0, SSSD provides
a PAM module ``pam_sss_gss`` which allows to authenticate users with GSSAPI
(Kerberos ticket) and optionally check authentication indicator that was used to
obtain this ticket. More information can be found in the man page for
``pam_sss_gss` and for ``sssd.conf`` where options starting with ``pam_gssapi_``
prefix are documented. This feature was also backported to SSSD 2.4.2 and 2.5.0.
For example, setting the following in SSSD configuration would allow GSSAPI
authentication to ``sudo`` and ``sudo -i`` only if the Kerberos ticket was
obtained with the use of a smartcard or certificate-based authentcation::
[pam]
pam_gssapi_services = sudo, sudo-i
pam_gssapi_indicators_map = sudo:pkinit, sudo-i:pkinit
The actual enforcement requires use of ``pam_sss_gss`` module in the PAM stack.
Fedora and RHEL distributions provide ``authselect`` tool to handle PAM and NSS
configuration. ``authselect`` was extended to allow use of ``pam_sss_gss`` as
SSSD feature ``with-gssapi``::
[root@client ~]# authselect enable-feature with-gssapi
Make sure that SSSD service is configured and enabled. See SSSD documentation for more information.
- with-gssapi is selected, make sure that GSSAPI authenticaiton is enabled in SSSD
- set pam_gssapi_services to a list of allowed services in /etc/sssd/sssd.conf
- see additional information in pam_sss_gss(8)
Once this change made and SSSD configuration updated to allow PAM services to
use GSSAPI authentication, it will be possible to use Kerberos ticket to
authenticate over a chosen PAM service. A session below demonstrates it::
[root@client ~]# vim /etc/sssd/sssd.conf
[root@client ~]# systemctl restart sssd
[root@client ~]# id testuser
uid=167200003(testuser) gid=167200003(testuser) groups=167200003(testuser)
[root@client ~]# ssh testuser@`hostname`
(testuser@client.ipa.test) password:
Last login: Thu Mar 24 13:54:21 2022 from 192.168.122.141
-sh-5.1$ klist
Ticket cache: KCM:167200003:41683
Default principal: testuser@IPA.TEST
Valid starting Expires Service principal
03/25/22 13:47:55 03/26/22 13:04:47 krbtgt/IPA.TEST@IPA.TEST
-sh-5.1$ sudo -l
Matching Defaults entries for testuser on client:
!visiblepw, always_set_home, match_group_by_gid,
always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME
HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS
LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT
LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER
LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET
XAUTHORITY",
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin
User testuser may run the following commands on dc:
(ALL) ALL
FreeIPA also provides a check for an authentication indicator at KDC side. This
means that a lack of a specific authentication indicator in TGT can result in
denying an issuance of a requested service ticket. A consequence is that an
application will never see any user with a ticket that does not contain a
specified authentication indicator.
At the moment, there are no known applications that implement authentication
indicator-based authorization. Instead, FreeIPA provides a check for an
authentication indicator at KDC side. This means that a lack of a specific
authentication indicator in TGT can result in denying an issuance of a requested
service ticket. A consequence is that an application will never see any user
with a ticket that does not contain a specified authentication indicator.
In order to enable the check, add authentication indicator to a service using
``ipa service-mod`` command. We can create a new service and associate
-772
View File
@@ -1,772 +0,0 @@
Unit 12: Authentication against external Identity Providers
===========================================================
**Prerequisites:**
- `Unit 11: Kerberos ticket policy <11-kerberos-ticket-policy.rst>`_
In this module you will explore how to manage use external OAuth 2.0 servers to
authorize issuance of Kerberos tickets in FreeIPA.
**Note:** To complete this module, FreeIPA-4.10 or later is needed.
Authentication using external Identity Providers
------------------------------------------------
It is possible to let FreeIPA to delegate authentication and authorization
process of issuing Kerberos tickets to an external entity. FreeIPA has been
supporting RADIUS server proxying for some time. This is exposed over
Kerberos with the help of 'otp' pre-authentication mechanism.
Configuration of the RADIUS proxy authentication is done in two steps: first,
create a RADIUS proxy object in FreeIPA and then associate the user account with
this RADIUS proxy object.
There is no specific requirement as to how RADIUS proxy actually authenticates
the user. It is left outside the FreeIPA scope. The connection to the RADIUS
server becomes critical and is important to protect.
This approach has been extended to allow FreeIPA to contact an OAuth 2.0
Authorization Server instead of RADIUS server. OAuth 2.0 authorization framework
is a modern way to delegate authorization decisions between loosely coupled
parties. It heavily relies on the ability to use HTTP redirects to guide a
user's browser to hop between OAuth 2.0 parties and reach the one that logs user
in and the one that authorizes the access.
Traditionally, it was hard to integrate with OAuth 2.0-enabled systems in POSIX
environment because there is no way to run a browser session from within the
shell or console. Most of OAuth 2.0 identity providers (IdP) heavily rely on
JavaScript and other modern browser features to provide an enhanced user
experience. Emulating the login pages as part of line- and packet-oriented SSH
protocol or console login script is not possible.
OAuth 2.0 Device Authorization Grant is defined in
[RFC 8628](https://www.rfc-editor.org/rfc/rfc8628) and allows devices that either
lack a browser or input constrained to obtain user authorization to access
protected resources. Instead of performing the authorization flow right at the
device where OAuth authorization grant is requested, a user would perform it at
a separate device that has required rich browsing or input capabilities.
Following figure demonstrates a generic device authorization flow:
.. uml::
participant "End User at Browser"
participant "Device Client"
participant "Authorization Server"
"Device Client" -> "Authorization Server": (A) Client Identifier
"Authorization Server" -> "Device Client": (B) Device Code, User Code & Verification URI
"Device Client" -> "End User at Browser": (C) User Code & Verification URI
"End User at Browser" <-> "Authorization Server": (D) End user reviews authorization request
"Device Client" -> "Authorization Server": (E) Polling with Device Code and Client Identifier
"Authorization Server" -> "Device Client": (F) Access Token (& Optional Refresh Token)
FreeIPA implements a variation of this flow and hides it behind Kerberos KDC. A
special pre-authentication method in MIT Kerberos, ``idp`` is implemented in
SSSD 2.7.0 to facilitate the process outlined above.
Device authorization grant flow decouples the process into several steps:
- the device initiates OAuth 2.0 flow and receives a response from the
Authorization Server that contains a special code and a link to a website user
needs to visit to authorize the device;
- user opens this website somewhere else (mobile, desktop, etc) where a proper
browser is available;
- user is asked to enter the special code;
- if needed, user would be asked login into an OAuth 2.0-based IdP;
- once logged in, IdP would ask user if they authorize this device to access
certain user information;
- once the access request is granted, user comes back to the device's prompt and
confirms it;
- the device at this point would poll OAuth 2.0 Authorization Server on whether
it is allowed to access user information already.
Set up external IdP integration in FreeIPA
------------------------------------------
In order to perform OAuth 2.0 device authorization grant flow against an IdP, an
OAuth 2.0 client has to be registered with the IdP and a capability to allow the
device authorization grant has to be given to it. Not all IdPs support this
feature. Out of the known public ones, following IdPs do support device
authorization grant flow:
* Microsoft Identity Platform, including Azure AD
* Google
* Github
* Keycloak, including Red Hat SSO
* Okta
Many OAuth 2.0 platforms do not support device authorization grant flow and
cannot be directly enabled to operate with FreeIPA. However, one can always
chain (federate) IdPs. It means that, for example, one can deploy Keycloak
locally to allow users to sign in with identities from a different IdP. In that
case the local Keycloak instance would need to be registered as an OAuth 2.0
client with the remote IdP. Local Keycloak instance would then be registered
with IPA.
Setting up IdP references (OAuth 2.0 clients) in IPA can be done with ``ipa
idp-add`` command. The command accepts an option to specify a pre-defined
template for one of the known IdPs. If none of the pre-defined templates is
suitable, individual parameters can also be added::
ipa help idp-add
Usage: ipa [global-options] idp-add NAME [options]
Add a new Identity Provider server.
Options:
-h, --help show this help message and exit
--auth-uri=STR OAuth 2.0 authorization endpoint
--dev-auth-uri=STR Device authorization endpoint
--token-uri=STR Token endpoint
--userinfo-uri=STR User information endpoint
--keys-uri=STR JWKS endpoint
--issuer-url=STR The Identity Provider OIDC URL
--client-id=STR OAuth 2.0 client identifier
--secret OAuth 2.0 client secret
--scope=STR OAuth 2.0 scope. Multiple scopes separated by space
--idp-user-id=STR Attribute for user identity in OAuth 2.0 userinfo
--setattr=STR Set an attribute to a name/value pair. Format is
attr=value. For multi-valued attributes, the command
replaces the values already present.
--addattr=STR Add an attribute/value pair. Format is attr=value. The
attribute must be part of the schema.
--provider=['google', 'github', 'microsoft', 'okta', 'keycloak']
Choose a pre-defined template to use
--organization=STR Organization ID or Realm name for IdP provider
templates
--base-url=STR Base URL for IdP provider templates
--all Retrieve and print all attributes from the server.
Affects command output.
--raw Print entries as stored on the server. Only affects
output format.
In this part we would use Keycloak IdP to integrate with IPA. Next section shows
how to set up Keycloak on a host enrolled into IPA domain. All shell scripts
below assume execution under ``root`` privileges.
Set up Keycloak IdP on enrolled IPA client
------------------------------------------
In this section, we set up `Keycloak <https://www.keycloak.org>`_ IdP on IPA
client and use it to authenticate IPA users. User database in Keycloak would be
different from the one in IPA, one would need to keep user accounts duplicated
in both places but this would simplify our configuration. We also would use
automation provided by the Keycloak to set up OAuth 2.0 clients and user
accounts.
First, we would download keycloak and unpack it into ``/opt/keycloak-<VERSION>`` as ``root``::
[root@client ~]# dnf -y install java-11-openjdk-headless openssl
#### download keycloak ####
[root@client ~]# export KEYCLOAK_VERSION=18.0.0
[root@client ~]# wget https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-${KEYCLOAK_VERSION}.tar.gz
[root@client ~]# tar zxf keycloak-${KEYCLOAK_VERSION}.tar.gz -C /opt
#### add keycloak system user/group and folder ####
[root@client ~]# groupadd keycloak
[root@client ~]# useradd -r -g keycloak -d /opt/keycloak-${KEYCLOAK_VERSION} keycloak
[root@client ~]# chown -R keycloak:keycloak /opt/keycloak-${KEYCLOAK_VERSION}
[root@client ~]# chmod o+x /opt/keycloak-${KEYCLOAK_VERSION}/bin/
[root@client ~]# restorecon -R /opt/keycloak-${KEYCLOAK_VERSION}
Next step would be to prepare a TLS certificate to be used to protect HTTPS
connections in Keycloak. Since our system is already enrolled into IPA, we can
rely on two features:
* Enrolled IPA client has Kerberos host principal registered with keytab in ``/etc/krb5.keytab``
* Enrolled IPA client host Kerberos principal can manage Kerberos services on the same host
This means we can create ``HTTP/client...`` Kerberos service right from the IPA
client and use ``certmonger`` to issue TLS certificate for it. Certmonger would
automatically renew the certificate. The following sequence of commands
demonstrates how to achieve this, run as root::
########## setup TLS certificate using IPA CA ###############################
[root@client ~]# kinit -k
[root@client ~]# ipa service-add HTTP/$(hostname)
[root@client ~]# ipa-getcert request -K HTTP/$(hostname) -D $(hostname) \
-o keycloak -O keycloak \
-m 0600 -M 0644 \
-k /etc/pki/tls/private/keycloak.key \
-f /etc/pki/tls/certs/keycloak.crt \
-w
[root@client ~]# keytool -import \
-keystore /etc/pki/tls/private/keycloak.store \
-file /etc/ipa/ca.crt \
-alias ipa_ca \
-trustcacerts -storepass Secret123 -noprompt
[root@client ~]# chown keycloak:keycloak /etc/pki/tls/private/keycloak.store
The private key for this certificate is stored in
``/etc/pki/tls/private/keycloak.key``, only accessible to the keycloak user.
Public part of the certificate is stored in ``/etc/pki/tls/certs/keycloak.crt``
and has permissions 0644.
We also import IPA CA's chain to a Java keystore that would be used by Keycloak,
stored at ``/etc/pki/tls/private/keycloak.store``.
Finally, we need to set up ``systemd`` service to run Keycloak::
# Setup keycloak service and config files
[root@client ~]# cat > /etc/sysconfig/keycloak <<EOF
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=Secret123
#KC_LOG_LEVEL=debug
KC_HOSTNAME=$(hostname):8443
KC_HTTPS_CERTIFICATE_FILE=/etc/pki/tls/certs/keycloak.crt
KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/pki/tls/private/keycloak.key
KC_HTTPS_TRUST_STORE_FILE=/etc/pki/tls/private/keycloak.store
KC_HTTPS_TRUST_STORE_PASSWORD=Secret123
KC_HTTP_RELATIVE_PATH=/auth
EOF
[root@client ~]# cat > /etc/systemd/system/keycloak.service <<EOF
[Unit]
Description=Keycloak Server
After=network.target
[Service]
Type=idle
EnvironmentFile=/etc/sysconfig/keycloak
User=keycloak
Group=keycloak
ExecStart=/opt/keycloak-${KEYCLOAK_VERSION}/bin/kc.sh start
TimeoutStartSec=600
TimeoutStopSec=600
[Install]
WantedBy=multi-user.target
EOF
[root@client ~]# systemctl daemon-reload
When ``systemd`` service is prepared, Keycloak needs to be initialized::
[root@client ~]# su - keycloak -c '''
export KEYCLOAK_ADMIN=admin
export KEYCLOAK_ADMIN_PASSWORD=Secret123
export KC_HOSTNAME=$(hostname):8443
export KC_HTTPS_CERTIFICATE_FILE=/etc/pki/tls/certs/keycloak.crt
export KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/pki/tls/private/keycloak.key
export KC_HTTPS_TRUST_STORE_FILE=/etc/pki/tls/private/keycloak.store
export KC_HTTPS_TRUST_STORE_PASSWORD=Secret123
export KC_HTTP_RELATIVE_PATH=/auth
/opt/keycloak-${KEYCLOAK_VERSION}/bin/kc.sh --verbose build
'''
and can be started with the standard ``systemctl`` tool::
[root@client ~]# systemctl start keycloak
[root@client ~]# systemctl status --lines 3 --no-pager keycloak
● keycloak.service - Keycloak Server
Loaded: loaded (/etc/systemd/system/keycloak.service; disabled; vendor preset: disabled)
Active: active (running) since Fri 2022-05-06 10:43:06 UTC; 9min ago
Main PID: 27170 (java)
Tasks: 37 (limit: 2318)
Memory: 297.1M
CPU: 25.560s
CGroup: /system.slice/keycloak.service
└─27170 java -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -D…
May 06 10:43:28 client.ipademo.local kc.sh[27170]: 2022-05-06 10:43:28,411 INFO [io.quarkus] (main) Keycloak 18.0.0 on …0.0:8443
May 06 10:43:28 client.ipademo.local kc.sh[27170]: 2022-05-06 10:43:28,412 INFO [io.quarkus] (main) Profile prod activated.
May 06 10:43:28 client.ipademo.local kc.sh[27170]: 2022-05-06 10:43:28,412 INFO [io.quarkus] (main) Installed features: [agroal…
Hint: Some lines were ellipsized, use -l to show in full.
Now we can use it for setting up users and OAuth 2.0 clients. There are two
handy scripts, ``kcadm.sh`` and ``kcreg.sh`` that allow to perform all
operations without visiting the Keycloak Web UI.
With ``kcadm.sh`` we login as admin and create user ``testuser1`` and set a password::
[root@client ~]# /opt/keycloak-18.0.0/bin/kcadm.sh config truststore \
--trustpass Secret123 \
/etc/pki/tls/private/keycloak.store
[root@client ~]# /opt/keycloak-18.0.0/bin/kcadm.sh config credentials \
--server https://$(hostname):8443/auth/ \
--realm master --user admin --password Secret123
Logging into https://client.ipademo.local:8443/auth/ as user admin of realm master
[root@client ~]# /opt/keycloak-18.0.0/bin/kcadm.sh create users \
-r master \
-s username=testuser1 -s enabled=true -s email=testuser1@ipademo.local
Created new user with id 'd319b32a-4cea-43c5-8ef8-19b2b8418d0a'
[root@client ~]# /opt/keycloak-18.0.0/bin/kcadm.sh set-password \
-r master \
--username testuser1 --new-password Secret123
With ``kcreg.sh`` we can create OAuth 2.0 client using a pre-defined template
that will include all parameters we need to allow OAuth 2.0 Device Authorization
Grant flow::
[root@client ~]# /opt/keycloak-18.0.0/bin/kcreg.sh config credentials \
--server https://$(hostname):8443/auth \
--realm master --user admin --password Secret123
[root@client ~]# cat >ipa_client.json <<EOF
{
"enabled" : true,
"redirectUris" : [ "https://ipa-ca.$(hostname -d)/ipa/idp/*" ],
"webOrigins" : [ "https://ipa-ca.$(hostname -d)" ],
"protocol" : "openid-connect",
"publicClient" : true,
"attributes" : {
"oauth2.device.authorization.grant.enabled" : "true",
"oauth2.device.polling.interval": "5"
}
}
EOF
[root@client ~]# /opt/keycloak-18.0.0/bin/kcreg.sh create \
-f ipa_client.json \
-s clientId=ipa_oidc_client
At this point, we have a Keycloak instance with a default ``master`` realm
(organization) and base URL ``https://$(hostname):8443/auth/``. In this realm we
have created ``testuser1`` user with a simple password. We also created OAuth
2.0 client ``ipa_oidc_client`` that is allowed to utilize OAuth 2.0 device
authorization grant flow. This client has no client secret ("public OAuth 2.0
client") associated. Confidential clients can also support device authorization
grant flows.
The client details include information about the redirect URIs. These are required
to specify for public OAuth 2.0 clients, but they aren't used for OAuth 2.0
device authorization grant flow.
Two attributes specified in the OAuth 2.0 client definition for Keycloak:
- ``oauth2.device.authorization.grant.enabled``, set to ``true``, allows OAuth 2.0
device authorization grant processing,
- ``oauth2.device.polling.interval``, set to 5, defines the polling interval for
the client to 5 seconds.
Keycloak 17.0.0 and 18.0.0 releases have a bug that sets default polling
interval to 600 seconds. This makes impossible actual polling process as the
lifespan of the device code is also set to 600 seconds. Keycloak's
[pull request 11893](https://github.com/keycloak/keycloak/pull/11893) needs
to be merged to fix the default settings.
Add IdP reference to IPA
------------------------
The following command adds IdP reference named ``keycloak`` as IPA administrator::
[root@client ~]# kinit admin
..
[root@client ~]# echo -e "Secret123\nSecret123" | \
[root@client ~]# ipa idp-add keycloak --provider keycloak \
--org master \
--base-url https://client.ipademo.local:8443/auth \
--client-id ipa_oidc_client \
--secret
-----------------------------------------
Added Identity Provider server "keycloak"
-----------------------------------------
Identity Provider server name: keycloak
Authorization URI: https://client.ipademo.local:8443/auth/realms/master/protocol/openid-connect/auth
Device authorization URI: https://client.ipademo.local:8443/auth/realms/master/protocol/openid-connect/auth/device
Token URI: https://client.ipademo.local:8443/auth/realms/master/protocol/openid-connect/token
User info URI: https://client.ipademo.local:8443/auth/realms/master/protocol/openid-connect/userinfo
Client identifier: ipa_oidc_client
Secret: U2VjcmV0MTIz
Scope: openid email
External IdP user identifier attribute: email
The name for the IdP reference is only used to associate an IdP with users in
IPA. Option ``--provider keycloak`` allows us to fill-in pre-defined template
for Keycloak or Red Hat SSO IdPs. The template expects both Keycloak's realm
(``--org`` option) and a base URL (``--base-url`` option) because Keycloak is
typically deployed as a part of a larger solution. These options may not be
needed for other pre-defined templates like Google or Github.
Associate IdP reference with IPA user
-------------------------------------
While we have added ``testuser1`` to Keycloak instance, this user needs to exist
in IPA to be visible to all enrolled systems. Currently there is no good
solution to integrate between IPA and Keycloak to allow automatically propagate
changes between the two. For the purpose of this workshop we would create users
manually -- we already did that for Keycloak.
Create a user ``testuser1`` in IPA::
[root@client ~]# ipa user-add testuser1 --first Test --last User1
----------------------
Added user "testuser1"
----------------------
User login: testuser1
First name: Test
Last name: User1
Full name: Test User1
Display name: Test User1
Initials: TU
Home directory: /home/testuser1
GECOS: Test User1
Login shell: /bin/sh
Principal name: testuser1@ipademo.local
Principal alias: testuser1@ipademo.local
Email address: testuser1@ipademo.local
UID: 35000003
GID: 35000003
Password: False
Member of groups: ipausers
Kerberos keys available: False
Once user is added, associate it with ``keycloak`` IdP reference we just
created. In order to allow user to login via IdP we need few conditions to be
satisfied:
* IdP reference defined for this IdP in IPA
* IdP reference associated with the user (``--idp`` option to ``ipa user-add``
or ``ipa user-mod``)
* IdP identity for the user is set in the user entry (``--idp-user-id`` option
to ``ipa user-add`` or ``ipa user-mod``)
* finally, user should be allowed to use ``idp`` user authentication method
(``--user-auth-type=idp`` option to ``ipa user-add`` or ``ipa user-mod`` or
``idp`` method set globally)
We can set these options to ``testuser1`` with ``ipa user-mod`` command::
[root@client ~]# ipa user-mod testuser1 --idp keycloak \
--idp-user-id testuser1@ipademo.local \
--user-auth-type=idp
-------------------------
Modified user "testuser1"
-------------------------
User login: testuser1
First name: Test
Last name: User1
Home directory: /home/testuser1
Login shell: /bin/sh
Principal name: testuser1@ipademo.local
Principal alias: testuser1@ipademo.local
Email address: testuser1@ipademo.local
UID: 35000003
GID: 35000003
User authentication types: idp
External IdP configuration: keycloak
External IdP user identifier: testuser1@ipademo.local
Account disabled: False
Password: False
Member of groups: ipausers
Kerberos keys available: False
As can be seen in the output, the account for ``testuser1`` has no password and
no Kerberos keys. It will not be able to authenticate to IPA services without
IdP's help.
Access IPA resources as an IdP user
-----------------------------------
There are two ways to trigger authentication and authorization of ``testuser1``
via our Keycloak IdP instance:
* obtain Kerberos ticket with ``kinit`` tool
* login to the target system via SSH or on the console
In order to obtain initial Kerberos ticket, we need to use ``kinit`` tool. SSSD
2.7.0 provides a special package ``sssd-idp`` which implements Kerberos
pre-authentication method ``idp``. When this package is installed, MIT Kerberos
configuration on the host is updated to automatically allow use of ``idp``
method. However, ``idp`` method requires use of FAST channel in order to provide
a secure connection between the Kerberos client and KDC. This is similar to
``otp`` pre-authentication method FreeIPA already provided for several years.
When IPA is deployed with integrated CA, IPA also provides a way to obtain a
special ticket, called Anonymous PKINIT, to use as a FAST channel factor.
Let's use Anonymous PKINIT to obtain a ticket and store it in the file
``./fast.ccache``. Then we can enable FAST channel with the use of ``-T`` option
for ``kinit`` tool::
[root@client ~]# kinit -n -c ./fast.ccache
[root@client ~]# kinit -T ./fast.ccache testuser1
Authenticate at https://client.ipademo.local:8443/auth/realms/master/device?user_code=YHMQ-XKTL and press ENTER.:
The prompt indicates that ``idp`` method was chosen between the KDC and the
Kerberos client. When KDC received the initial ticket granting ticket request,
IPA database driver (KDB) performed an LDAP lookup of the Kerberos principal and
found out that ``testuser1@IPADEMO.LOCAL`` Kerberos principal has ``idp`` user
authentication type. This, in turn, activated KDC side of the ``idp``
pre-authentication method and led to a request to ``ipa-otpd`` daemon. Finally,
``ipa-otpd`` daemon asked ``oidc_child`` to request a device code authorization
grant from the IdP associated with the ``testuser1@IPADEMO.LOCAL`` principal.
The grant flow resulted in IdP returning a code and a message which was
propagated back to the Kerberos client and displayed by the client side of the
``idp`` pre-authentication method.
At this point we need to visit the page and authorize access to the information.
Once it is done, we complete the process by pressing ``<ENTER>`` key. If
authorization was granted, KDC will issue a Kerberos ticket to and it will be
stored in the credentials cache::
[root@client ~]# klist
Ticket cache: KCM:0:58420
Default principal: testuser1@IPADEMO.LOCAL
Valid starting Expires Service principal
05/09/22 07:48:23 05/10/22 07:03:07 krbtgt/IPADEMO.LOCAL@IPADEMO.LOCAL
Similar process happens when ``pam_sss`` PAM module is used, for example, to
authenticate and authorize access to PAM services. Applications which use PAM to
authenticate and authorize remote access can also benefit from the flow. For
example, SSH daemon can be configured with ``keyboard-interactive`` method which
will allow PAM authentication and authorization. As part of it, PAM messages
will be relayed to the SSH client and SSH client's user input will be sent back
to PAM::
$ ssh testuser1@client.ipademo.local
(testuser1@client.ipademo.local) Authenticate at https://client.ipademo.local:8443/auth/realms/master/device?user_code=XYFL-ROYR and press ENTER.
Last login: Mon May 9 07:48:25 2022 from 10.0.190.227
-sh-5.1$ klist
Ticket cache: KCM:7800003:58420
Default principal: testuser1@IPADEMO.LOCAL
Valid starting Expires Service principal
05/09/22 07:49:38 05/10/22 07:49:24 krbtgt/IPADEMO.LOCAL@IPADEMO.LOCAL
-sh-5.1$
Once initial Kerberos ticket is available, it can be used to perform normal IPA
operations:
- access IPA API with command line tool ``ipa`` or through a Web UI in a browser
- login to other systems with GSSAPI authentication
- access PAM services which use ``pam_sss_gss`` module in their PAM stack definitions
Direct authentication to Web UI with the help of OAuth 2.0 client is not implemented yet.
Troubleshooting IdP integration
-------------------------------
Communication with an IdP server happens on IPA server when KDC calls out to
``ipa-otpd`` daemon and ``ipa-otpd`` daemon launches ``oidc_child`` helper.
Journal logs for ``ipa-otpd`` can be checked with the ``journalctl`` tool.
``ipa-otpd`` processes start on demand and content from all sessions can be
captured with the following command::
[root@master #] journalctl -u 'ipa-otpd@*'
The output would look similar to the following real world example::
May 02 18:51:28 dc.ipa.test systemd[1]: Started ipa-otpd service (PID 1473660/UID 0).
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: LDAP: ldapi://%2Frun%2Fslapd-IPA-TEST.socket
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: request received
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: user query start
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: user query end: uid=ab,cn=users,cn=accounts,dc=ipa,dc=test
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: idp query start: cn=github,cn=idp,dc=ipa,dc=test
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: idp query end: github
May 02 18:51:28 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: oauth2 start: Get device code
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: Received: [{"device_code":"f071833afe966eaf596d83646f55250cfdb57418","expires_in":899,"interval":5}
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: oauth2 {"verification_uri": "https://github.com/login/device", "user_code": "ECD3-4310"}
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: ]
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: sent: 0 data: 200
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: ..sent: 200 data: 200
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: ab@IPA.TEST: response sent: Access-Challenge
May 02 18:51:29 dc.ipa.test ipa-otpd[1636136]: Socket closed, shutting down...
First part of the output until ``idp query start`` is similar to RADIUS proxy
operation. Unlike RADIUS proxy, in the case of IdP communication, ``ipa-otpd``
first receives an initial state from the ``oidc_child`` process and sends it
back to the KDC within a RADIUS packet with ``Access-Challenge`` message.
The state is then transferred to the Kerberos client and results in a message
that instructs to visit the verification URI and enter the code. Some IdPs also
return a complete message to show, like in the case of Keycloak in our examples
above.
Once the Kerberos client returns, another ``ipa-otpd`` call is performed,
this time to request an access token::
May 02 18:51:50 dc.ipa.test systemd[1]: Started ipa-otpd service (PID 1473661/UID 0).
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: LDAP: ldapi://%2Frun%2Fslapd-IPA-TEST.socket
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: request received
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: user query start
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: user query end: uid=ab,cn=users,cn=accounts,dc=ipa,dc=test
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: idp query start: cn=github,cn=idp,dc=ipa,dc=test
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: idp query end: github
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: oauth2 start: Get access token
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: Received: [abbra]
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: sent: 0 data: 20
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: ..sent: 20 data: 20
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: ab@IPA.TEST: response sent: Access-Accept
May 02 18:51:50 dc.ipa.test ipa-otpd[1636149]: Socket closed, shutting down...
An access token request followed by the request to obtain a user information.
The resource owner's subject then compared with the value set in the LDAP entry
for this Kerberos principal with the help of ``--idp-user-id`` option. Subject's
field name is chosen through the same option to the IdP reference. If the check
is successful, ``ipa-otpd`` sends a RADIUS packet with ``Access-Accept``
response code.
Communication performed by ``oidc_child`` is not included into the journal logs
by default. If there are issues in accessing IdPs, a special option can be added
to ``/etc/ipa/default.conf`` to increase log level of ``oidc_child`` output. By
default, it is 0 and could be any number between 0 and 10::
[global]
oidc_child_debug_level=10
A value greater than 6 would include debug output from the ``libcurl`` utility::
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: oidc_child started.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Running with effective IDs: [0][0].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Running with real IDs [0][0].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: JSON device code: [{"device_code":"f071833afe966eaf596d83646f55250cfdb57418","expires_in":899,"interval":5}].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'user_code' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'verification_uri' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'verification_url' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'verification_uri_complete' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'message' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: device_code: [f071833afe966eaf596d83646f55250cfdb57418].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: expires_in: [899].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: interval: [5].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: POST data: [grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=some-client-id&device_code=f071833afe966eaf596d83646f55250cfdb57418].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Trying 140.82.121.3:443...
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connected to github.com (140.82.121.3) port 443 (#0)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, offering h2
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, offering http/1.1
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * successfully set certificate verify locations:
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * CAfile: /etc/pki/tls/certs/ca-bundle.crt
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * CApath: none
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS handshake, Client hello (1):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Server hello (2):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Certificate (11):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, CERT verify (15):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Finished (20):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS handshake, Finished (20):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, server accepted to use h2
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Server certificate:
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * subject: C=US; ST=California; L=San Francisco; O=GitHub, Inc.; CN=github.com
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * start date: Mar 15 00:00:00 2022 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * expire date: Mar 15 23:59:59 2023 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * subjectAltName: host "github.com" matched cert's "github.com"
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * SSL certificate verify ok.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Using HTTP2, server supports multiplexing
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connection state changed (HTTP/2 confirmed)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Using Stream ID: 1 (easy handle 0x562cd1ee96e0)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: > POST /login/oauth/access_token HTTP/2
Host: github.com
user-agent: SSSD oidc_child/0.0
accept: application/json
content-length: 139
content-type: application/x-www-form-urlencoded
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * We are completely uploaded and fine
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * old SSL session ID is stale, removing
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < HTTP/2 200
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < server: GitHub.com
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < date: Mon, 02 May 2022 18:51:50 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < content-type: application/json; charset=utf-8
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < vary: X-PJAX, X-PJAX-Container
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < permissions-policy: interest-cohort=()
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < etag: W/"some-e-tag-value"
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < cache-control: max-age=0, private, must-revalidate
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < strict-transport-security: max-age=31536000; includeSubdomains; preload
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-frame-options: deny
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-content-type-options: nosniff
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-xss-protection: 0
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < expect-ct: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors"
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < content-security-policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.git>
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < vary: Accept-Encoding, Accept, X-Requested-With
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-github-request-id: D1EA:541D:48A585:4BF8E5:62702846
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: <
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: {"access_token":"some-access-token","token_type":"bearer","scope":"user"}
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connection #0 to host github.com left intact
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: Result does not contain the 'id_token' string.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: access_token: [some-access-token].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: id_token: [(null)].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Trying 140.82.121.6:443...
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connected to api.github.com (140.82.121.6) port 443 (#0)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, offering h2
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, offering http/1.1
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * successfully set certificate verify locations:
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * CAfile: /etc/pki/tls/certs/ca-bundle.crt
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * CApath: none
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS handshake, Client hello (1):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Server hello (2):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Certificate (11):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, CERT verify (15):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Finished (20):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (OUT), TLS handshake, Finished (20):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * ALPN, server accepted to use h2
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Server certificate:
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * subject: C=US; ST=California; L=San Francisco; O=GitHub, Inc.; CN=*.github.com
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * start date: Mar 16 00:00:00 2022 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * expire date: Mar 16 23:59:59 2023 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * subjectAltName: host "api.github.com" matched cert's "*.github.com"
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * SSL certificate verify ok.
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Using HTTP2, server supports multiplexing
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connection state changed (HTTP/2 confirmed)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Server auth using Bearer with user ''
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Using Stream ID: 1 (easy handle 0x562cd1f92ae0)
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: > GET /user HTTP/2
Host: api.github.com
authorization: Bearer some-token-value
user-agent: SSSD oidc_child/0.0
accept: application/json
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * old SSL session ID is stale, removing
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < HTTP/2 200
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < server: GitHub.com
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < date: Mon, 02 May 2022 18:51:50 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < content-type: application/json; charset=utf-8
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < content-length: 1357
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < cache-control: private, max-age=60, s-maxage=60
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < vary: Accept, Authorization, Cookie, X-GitHub-OTP
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < etag: "some-e-tag-value"
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < last-modified: Mon, 14 Mar 2022 14:05:20 GMT
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-oauth-scopes: user
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-accepted-oauth-scopes:
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-oauth-client-id: some-client-id
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-github-media-type: github.v3
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-ratelimit-limit: 5000
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-ratelimit-remaining: 4996
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-ratelimit-reset: 1651520567
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-ratelimit-used: 4
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-ratelimit-resource: core
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scop>
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < access-control-allow-origin: *
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < strict-transport-security: max-age=31536000; includeSubdomains; preload
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-frame-options: deny
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-content-type-options: nosniff
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-xss-protection: 0
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < content-security-policy: default-src 'none'
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < vary: Accept-Encoding, Accept, X-Requested-With
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: < x-github-request-id: C5B8:5B48:4C0EB7:4D8AF2:62702846
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: <
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: {"login":"abbra","id":some-id,"node_id":"some-node","avatar_url":"some-avatar-url","gravatar_id":"","url":"some-user-url","html_ur
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: l":"some-url","followers_url":"some-api-url","following_url":"some-following-url","gists_url":"some-gists-url>
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: libcurl: * Connection #0 to host api.github.com left intact
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: userinfo: [{"login": "abbra", "id": some-id, "node_id": "some-node", "avatar_url": "some-avatar-rul", "gravatar_id": "", "url": "some-user-url", ">
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: User identifier: [abbra].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: User identifier: [abbra].
May 02 18:51:50 dc.ipa.test oidc_child[1636150]: oidc_child finished successful!
Don't forget to remove ``oidc_child_debug_level`` from the
``/etc/ipa/default.conf`` once troubleshooting is done. Information like above
often contains personal details of the user and should probably not stored in
the system journal.
@@ -31,5 +31,5 @@ Vagrant boxes
-------------
See ``building.rst`` for instructions on building Vagrant boxes.
At time of writing, the ``freeipa/freeipa-workshop`` box is
FreeIPA 4.9.3 / Fedora 34, for the VirtualBox and libvirt providers.
At time of writing, the ``netoarmando/freeipa-workshop`` box is
FreeIPA 4.6.90 / Fedora 28, for the VirtualBox and libvirt providers.
+15 -34
View File
@@ -3,11 +3,7 @@
Vagrant.configure(2) do |config|
# Replace this with "fedora/34-cloud-base" (or latest) for a fresh box without
# pre-installed freeipa packages, you must also uncomment shell provisioning step
# at the bottom of this file.
# config.vm.box = "fedora/34-cloud-base"
config.vm.box = "freeipa/freeipa-workshop"
config.vm.box = "netoarmando/freeipa-workshop"
config.vm.synced_folder ".", "/vagrant", disabled: true
@@ -16,21 +12,22 @@ Vagrant.configure(2) do |config|
libvirt.memory = 1024
end
config.vm.provider :virtualbox do |virtualbox|
virtualbox.memory = 1536
end
# Vagrant's "change host name" sets the short host name. Before
# we repair /etc/hosts (see below) let's reset /etc/hostname to
# the *full* host name
#
config.vm.provision "shell",
inline: "hostname --fqdn > /etc/hostname && hostname -F /etc/hostname"
# Vagrant's "change host name" capability for Fedora maps hostname
# to loopback. We must repair /etc/hosts
#
config.vm.provision "shell",
inline: "sed -ri 's/127\.0\.0\.1\s.*/127.0.0.1 localhost localhost.localdomain/' /etc/hosts"
config.vm.define "server" do |server|
server.vm.network "private_network", ip: "192.168.33.10"
server.vm.hostname = "server.ipademo.local"
config.vm.provider "virtualbox" do |v|
v.memory = 1536
end
config.vm.provider "libvirt" do |v|
v.memory = 1536
end
end
config.vm.define "replica" do |replica|
@@ -42,17 +39,7 @@ Vagrant.configure(2) do |config|
replica.vm.provision "shell",
inline: 'echo "DNS1=192.168.33.10" >> /etc/sysconfig/network-scripts/ifcfg-eth1'
replica.vm.provision "shell",
inline: 'printf "DNS=192.168.33.10\nDomains=~." >> /etc/systemd/resolved.conf'
replica.vm.provision "shell",
inline: 'systemctl restart systemd-resolved'
config.vm.provider "virtualbox" do |v|
v.memory = 1536
end
config.vm.provider "libvirt" do |v|
v.memory = 1536
end
inline: 'echo "nameserver 192.168.33.10" > /etc/resolv.conf'
end
config.vm.define "client" do |client|
@@ -64,9 +51,7 @@ Vagrant.configure(2) do |config|
client.vm.provision "shell",
inline: 'echo "DNS1=192.168.33.10" >> /etc/sysconfig/network-scripts/ifcfg-eth1'
client.vm.provision "shell",
inline: 'printf "DNS=192.168.33.10\nDomains=~." >> /etc/systemd/resolved.conf'
client.vm.provision "shell",
inline: 'systemctl restart systemd-resolved'
inline: 'echo "nameserver 192.168.33.10" > /etc/resolv.conf'
client.vm.provision "shell",
inline: 'sudo sed -i "s/^/#/g" /etc/httpd/conf.d/ssl.conf'
client.vm.provision "shell",
@@ -75,8 +60,4 @@ Vagrant.configure(2) do |config|
inline: 'systemctl -q enable oddjobd && systemctl start oddjobd'
end
# Uncomment line below when using Fedora's cloud base box.
# config.vm.provision "shell", path: "workshop-install-packages.sh"
config.vm.provision "shell", path: "workshop-ipa-customizations.sh"
end
+14
View File
@@ -0,0 +1,14 @@
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
remote_user = environ.get('REMOTE_USER')
if remote_user is not None:
yield "LOGGED IN AS: {}\n".format(remote_user).encode('utf8')
else:
yield b"NOT LOGGED IN\n"
yield b"\nREMOTE_* REQUEST VARIABLES:\n\n"
for k, v in environ.items():
if k.startswith('REMOTE_'):
yield " {}: {}\n".format(k, v).encode('utf8')
+33 -23
View File
@@ -8,45 +8,55 @@ Requirements
------------
- Install packer (http://packer.io/)
- Install Vagrant, libvirt and VirtualBox
- Clone the Fedora kickstarts repo (https://pagure.io/fedora-kickstarts)
- Clone the packer-templates repository
(https://github.com/kaorimatz/packer-templates)
Packer template
---------------
Packer template ``packer-template-fedora.json`` requires Fedora 34 kickstart file
used by Fedora to build vagrant images:
Apply the following changes to the ``fedora-28-x86_64.json`` packer
template:
- Clone the repo and checkout latest Fedora release branch::
$ git clone https://pagure.io/fedora-kickstarts.git
$ cd fedora-kickstarts
$ git checkout f34
- Install ``pykickstart`` package which provides ``ksflatten`` tool::
$ sudo dnf install pykickstart
- Generate the ``anaconda-ks.cfg`` file needed by flattening vagrant kickstart files
and putting it onto the same folder as the packer template file::
$ ksflatten -c $FEDORA_KICKSTARTS_REPO/fedora-cloud-base-vagrant.ks > $FREEIPA_REPO/doc/workshop/anaconda-ks.cfg
- Add the ``scripts/fedora/ipa.sh`` provisioner and copy (or
symlink) ``ipa.sh`` from *this* repository to ``scripts/fedora``.
This script installs the FreeIPA packages and creates other files
required for the workshop.
Building the vagrant images
Building the virtualbox image
-----------------------------
Build the images::
$ cd $FREEIPA_REPO/doc/workshop
$ BIN_PACKER build packer-template-fedora.json
$BIN_PACKER build -only=virtualbox-iso -var disk_size=4000 -var memory=1024 fedora-28-x86_64.json
Packer stores images and other data in ``/tmp`` during processing.
If you have limited space in ``/tmp`` set ``TMPDIR`` to point
somewhere else with more space.
Uploading boxes to Vagrant Cloud
Building the QEMU/libvirt image
-------------------------------
Build the image::
$BIN_PACKER build -only=qemu -var disk_size=4000 -var memory=1024 fedora-28-x86_64.json
The output box is a gzip-compressed tarball. Unfortunately, the VM
image it contains is not sparse and will waste a lot of space (and
time) when Vagrant unpacks and imports the image. Therefore we
unpack, sparsify and repack the box::
mkdir box && cd box && tar -xf ../fedora-28-x86_64-libvirt.box
virt-sparsify --in-place box.img
tar -czf ../fedora-28-x86_64-libvirt.box * && cd .. && rm -rf box
Uploading boxes to HashiCorp Atlas
----------------------------------
Vagrant by default looks for boxes in a directory called *Vagrant Cloud*.
Vagrant by default looks for boxes in a directory called *Atlas*.
Therefore is is good to make images available there, so that people
can easily download them as part of workshop preparation.
@@ -1,4 +1,5 @@
#!/bin/bash
sudo dnf install -y freeipa-server freeipa-server-dns sssd-dbus mod_lookup_identity mod_authnz_pam haveged nmap-ncat nano pamtester bash-completion
sudo systemctl enable haveged
sudo sh -c "echo 'PS1=\"[\u@\h]\\\\$ \"' >> /etc/profile"
sudo sh -c "echo 'PS1=\"[\h]\\\\$ \"' >> /etc/bashrc"
@@ -10,10 +11,10 @@ sudo rm -f /etc/httpd/conf.d/welcome.conf
sudo sh -c "cat >/usr/share/httpd/app.py" <<EOF
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
remote_user = environ.get('REMOTE_USER')
logged_in = 'REMOTE_USER' in environ
if remote_user is not None:
yield "LOGGED IN AS: {}\n".format(remote_user).encode('utf8')
if logged_in:
yield "LOGGED IN AS: {}\n".format(environ['REMOTE_USER']).encode('utf8')
else:
yield b"NOT LOGGED IN\n"
@@ -36,12 +37,3 @@ sudo sh -c "cat >/etc/httpd/conf.d/app.conf" <<EOF
</Directory>
</VirtualHost>
EOF
# Vagrant's "change host name" sets the short host name. Before
# we repair /etc/hosts (see below) let's reset /etc/hostname to
# the *full* host name
hostname --fqdn > /etc/hostname && hostname -F /etc/hostname
# Vagrant's "change host name" capability for Fedora maps hostname
# to loopback. We must repair /etc/hosts
sed -ri 's/127\.0\.0\.1\s.*/127.0.0.1 localhost localhost.localdomain/' /etc/hosts
-77
View File
@@ -1,77 +0,0 @@
{
"builders": [{
"type": "qemu",
"iso_url": "{{user `iso_url`}}",
"iso_checksum": "{{user `iso_checksum`}}",
"output_directory": "output-{{user `box_name`}}-x86_64-{{build_type}}",
"vm_name": "packer-{{user `box_name`}}-x86_64",
"disk_size": "{{user `disk_size`}}",
"headless": "{{user `headless`}}",
"http_directory": ".",
"boot_wait": "5s",
"boot_command": [
"<tab> ",
"inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/anaconda-ks.cfg ",
"biosdevname=0 ",
"net.ifnames=0 ",
"<enter>"
],
"ssh_timeout": "{{user `ssh_timeout`}}",
"ssh_username": "vagrant",
"ssh_password": "vagrant",
"shutdown_command": "sudo systemctl poweroff",
"qemuargs": [
["-m", "{{user `memory`}}"],
["-smp", "{{user `cpus`}}"]
]
}, {
"type": "virtualbox-iso",
"guest_os_type": "Fedora_64",
"iso_url": "{{user `iso_url`}}",
"iso_checksum": "{{user `iso_checksum`}}",
"output_directory": "output-{{user `box_name`}}-x86_64-{{build_type}}",
"vm_name": "packer-{{user `box_name`}}-x86_64",
"disk_size": "{{user `disk_size`}}",
"headless": "{{user `headless`}}",
"http_directory": ".",
"boot_wait": "5s",
"boot_command": [
"<tab> ",
"inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/anaconda-ks.cfg ",
"biosdevname=0 ",
"net.ifnames=0 ",
"<enter>"
],
"ssh_timeout": "{{user `ssh_timeout`}}",
"ssh_username": "vagrant",
"ssh_password": "vagrant",
"shutdown_command": "sudo systemctl poweroff",
"vboxmanage": [
["modifyvm", "{{.Name}}", "--memory", "{{user `memory`}}"],
["modifyvm", "{{.Name}}", "--cpus", "{{user `cpus`}}"]
]
}],
"provisioners": [{
"type": "shell",
"scripts": [
"workshop-install-packages.sh"
]
}],
"post-processors": [{
"type": "vagrant",
"compression_level": "{{user `compression_level`}}",
"output": "{{user `box_name`}}-x86_64-{{.Provider}}.box"
}],
"variables": {
"box_name": "fedora-34",
"iso_url": "{{user `mirror`}}/releases/34/Server/x86_64/iso/Fedora-Server-netinst-x86_64-34-1.2.iso",
"iso_checksum": "file:{{user `mirror`}}/releases/34/Server/x86_64/iso/Fedora-Server-34-1.2-x86_64-CHECKSUM",
"compression_level": "9",
"cpus": "1",
"disk_size": "40000",
"headless": "false",
"memory": "1024",
"mirror": "http://download.fedoraproject.org/pub/fedora/linux",
"ssh_timeout": "60m"
}
}
+6 -9
View File
@@ -1,8 +1,5 @@
Troubleshooting
===============
Local webserver
---------------
===============
Drop firewall::
@@ -10,10 +7,10 @@ Drop firewall::
DNS / hosts file issues
-----------------------
=======================
Flush cache
^^^^^^^^^^^
-----------
Note: some resolvers cache NX.
@@ -27,7 +24,7 @@ Windows::
Virtual network
---------------
===============
If ``Vagrant::Errors::NetworkCollision`` occurs, try deleting host
network device, e.g.::
@@ -42,7 +39,7 @@ up::
Vagrant
-------
=======
If ``vagrant up`` fails to SSH into VM, delete
``~/.vagrant.d/insecure_private_key``. See
@@ -50,7 +47,7 @@ https://stackoverflow.com/questions/28284112/.
mod_lookup_identity
-------------------
===================
To flush cache::
@@ -1,3 +0,0 @@
#!/bin/bash
sudo dnf install -y freeipa-server freeipa-server-dns sssd-dbus mod_lookup_identity mod_authnz_pam haveged nmap-ncat nano pamtester bash-completion
sudo dnf clean all
+26 -13
View File
@@ -38,7 +38,7 @@ Optional units—choose the topics that are relevant to you:
- `Unit 9: SELinux User Maps <9-selinux-user-map.rst>`_
- `Unit 10: SSH user and host key management <10-ssh-key-management.rst>`_
- `Unit 11: Kerberos ticket policy <11-kerberos-ticket-policy.rst>`_
- `Unit 12: External IdP support <12-external-idp-support.rst>`_
Editing files on VMs
--------------------
@@ -119,11 +119,14 @@ password everytime::
$ sudo gpasswd -a ${USER} libvirt
$ newgrp libvirt
On **Fedoda 28** you need to enable ``virtlogd``::
$ systemctl enable --now virtlogd.socket
Finally restart the services::
$ systemctl restart libvirtd
More information: https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/
$ systemctl restart polkit
Otherwise, you will use VirtualBox and the ``virtualbox`` provider.
VirtualBox needs to build kernel modules, and that means that you must
@@ -147,7 +150,7 @@ in the transcript below (to make sure it wasn't tampered with)::
repo_gpgcheck=1
gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc
$ sudo dnf install -y VirtualBox-6.1
$ sudo dnf install -y VirtualBox-5.2
Finally, load the kernel modules (you may need to restart your system for this to work)::
@@ -160,7 +163,7 @@ Mac OS X
Install Vagrant for Mac OS X from
https://www.vagrantup.com/downloads.html.
Install VirtualBox 6.1 for **OS X hosts** from
Install VirtualBox 5.2 for **OS X hosts** from
https://www.virtualbox.org/wiki/Downloads.
Install Git from https://git-scm.com/download/mac or via your
@@ -170,12 +173,23 @@ preferred package manager.
Debian / Ubuntu
^^^^^^^^^^^^^^^
Install Vagrant, Git and VirtualBox::
Install Vagrant and Git::
$ sudo apt-get install -y vagrant git
$ sudo apt-get install -y virtualbox-6.1
If VirtualBox 6.1 was not available in the official packages for
**Virtualbox 5.2** may be available from the system package manager,
depending your your release. Find out which version of VirtualBox is
available::
$ apt list virtualbox
Listing... done
virtualbox/bionic 5.2.10-dfsg-6 amd64
If version 5.2 is available, install it via ``apt-get``::
$ sudo apt-get install -y virtualbox
If VirtualBox 5.2 was not available in the official packages for
your release, follow the instructions at
https://www.virtualbox.org/wiki/Linux_Downloads to install it.
@@ -186,7 +200,7 @@ Windows
Install Vagrant via the ``.msi`` available from
https://www.vagrantup.com/downloads.html.
Install VirtualBox for **Windows hosts** from
Install VirtualBox 5.2 for **Windows hosts** from
https://www.virtualbox.org/wiki/Downloads.
You will also need to install an SSH client, and Git. Git for
@@ -202,19 +216,18 @@ workshop, which you will need locally.
::
$ git clone https://github.com/freeipa/freeipa.git
$ cd freeipa/doc/workshop
$ git clone https://github.com/freeipa/freeipa-workshop.git
Fetch Vagrant box
-----------------
Please fetch the Vagrant box prior to the workshop. It is > 700MB
Please fetch the Vagrant box prior to the workshop. It is > 600MB
so it may not be feasible to download it during the workshop.
::
$ vagrant box add freeipa/freeipa-workshop
$ vagrant box add netoarmando/freeipa-workshop
Add hosts file entries
+4 -21
View File
@@ -149,8 +149,8 @@
# RHEL 8.2+, F32+ has 3.58
%global nss_version 3.44.0-4
# RHEL 8.7+, F35+, adds IdP integration
%global sssd_version 2.7.0
# RHEL 8.3+, F32+
%global sssd_version 2.4.0
%define krb5_base_version %(LC_ALL=C /usr/bin/pkgconf --modversion krb5 | grep -Eo '^[^.]+\.[^.]+' || echo %krb5_version)
%global kdcproxy_version 0.4-3
@@ -323,9 +323,7 @@ BuildRequires: python3-wheel
%if %{with doc}
BuildRequires: python3-sphinx
BuildRequires: plantuml
BuildRequires: fontconfig
BuildRequires: google-noto-sans-vf-fonts
BuildRequires: python3-m2r
%endif
#
@@ -653,12 +651,7 @@ Requires: python3-sssdconfig >= %{sssd_version}
Requires: cyrus-sasl-gssapi%{?_isa}
Requires: chrony
Requires: krb5-workstation >= %{krb5_version}
# authselect: sssd profile with-subid
%if 0%{?fedora} >= 36
Requires: authselect >= 1.4.0
%else
Requires: authselect >= 1.2.5
%endif
Requires: authselect >= 0.4-2
Requires: curl
# NIS domain name config: /usr/lib/systemd/system/*-domainname.service
# All Fedora 28+ and RHEL8+ contain the service in hostname package
@@ -670,7 +663,6 @@ Requires: xmlrpc-c >= 1.27.4
Requires: jansson
%endif
Requires: sssd-ipa >= %{sssd_version}
Requires: sssd-idp >= %{sssd_version}
Requires: certmonger >= %{certmonger_version}
Requires: nss-tools >= %{nss_version}
Requires: bind-utils
@@ -703,12 +695,6 @@ Provides: %{name}-admintools = %{version}-%{release}
Conflicts: crypto-policies < 20200629-1
%endif
%if 0%{?rhel} == 9
# Conflict with crypto-policies < 20220223-1 to get upgraded AD-SUPPORT and
# AD-SUPPORT-LEGACY policy modules
Conflicts: crypto-policies < 20220223-1
%endif
%description client
IPA is an integrated solution to provide centrally managed Identity (users,
hosts, services), Authentication (SSO, 2FA), and Authorization
@@ -1067,7 +1053,6 @@ rm %{buildroot}/%{plugin_dir}/libipa_extdom_extop.la
rm %{buildroot}/%{plugin_dir}/libipa_range_check.la
rm %{buildroot}/%{plugin_dir}/libipa_otp_counter.la
rm %{buildroot}/%{plugin_dir}/libipa_otp_lasttoken.la
rm %{buildroot}/%{plugin_dir}/libipa_graceperiod.la
rm %{buildroot}/%{plugin_dir}/libtopology.la
rm %{buildroot}/%{_libdir}/krb5/plugins/kdb/ipadb.la
rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la
@@ -1251,7 +1236,6 @@ if [ $1 -gt 1 ] ; then
%{__python3} -c 'from ipaclient.install.client import configure_krb5_snippet; configure_krb5_snippet()' >>/var/log/ipaupgrade.log 2>&1
%{__python3} -c 'from ipaclient.install.client import update_ipa_nssdb; update_ipa_nssdb()' >>/var/log/ipaupgrade.log 2>&1
chmod 0600 /var/log/ipaupgrade.log
SSH_CLIENT_SYSTEM_CONF="/etc/ssh/ssh_config"
if [ -f "$SSH_CLIENT_SYSTEM_CONF" ]; then
sed -E --in-place=.orig 's/^(HostKeyAlgorithms ssh-rsa,ssh-dss)$/# disabled by ipa-client update\n# \1/' "$SSH_CLIENT_SYSTEM_CONF"
@@ -1427,7 +1411,6 @@ fi
%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
%attr(755,root,root) %{plugin_dir}/libipa_graceperiod.so
%attr(755,root,root) %{_libdir}/krb5/plugins/kdb/ipadb.so
%{_mandir}/man1/ipa-replica-conncheck.1*
%{_mandir}/man1/ipa-replica-install.1*
+1 -3
View File
@@ -7,6 +7,4 @@ systemdtmpfiles_DATA = \
CLEANFILES = $(systemdtmpfiles_DATA)
%: %.in Makefile
sed \
-e 's|@HTTPD_GROUP[@]|$(HTTPD_GROUP)|g' \
'$(srcdir)/$@.in' >$@
cp '$(srcdir)/$@.in' $@
+1 -1
View File
@@ -1,3 +1,3 @@
d /run/ipa 0711 root root
d /run/ipa/ccaches 6770 ipaapi ipaapi
a+ /run/ipa/ccaches - - - - g:@HTTPD_GROUP@:rwx
a+ /run/ipa/ccaches - - - - g:apache:rwx
@@ -220,7 +220,7 @@ def request_cert(reuse_existing, **kwargs):
sys.stderr.write(result.raw_error_output)
else:
# Write bytes directly
sys.stderr.buffer.write(result.raw_error_output)
sys.stderr.buffer.write(result.raw_error_output) #pylint: disable=no-member
sys.stderr.flush()
syslog.syslog(syslog.LOG_NOTICE,
+2 -2
View File
@@ -36,8 +36,8 @@ def run_operation(cmd):
result = ipautil.run(cmd, raiseonerr=False, env=os.environ)
# Write bytes directly
sys.stdout.buffer.write(result.raw_output)
sys.stderr.buffer.write(result.raw_error_output)
sys.stdout.buffer.write(result.raw_output) #pylint: disable=no-member
sys.stderr.buffer.write(result.raw_error_output) #pylint: disable=no-member
sys.stdout.flush()
sys.stderr.flush()
+1 -1
View File
@@ -58,4 +58,4 @@ attributeTypes: (2.16.840.1.113730.3.8.23.2 NAME 'ipaPwdMaxRepeat' EQUALITY inte
attributeTypes: (2.16.840.1.113730.3.8.23.3 NAME 'ipaPwdMaxSequence' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4')
attributeTypes: (2.16.840.1.113730.3.8.23.4 NAME 'ipaPwdDictCheck' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4')
attributeTypes: (2.16.840.1.113730.3.8.23.5 NAME 'ipaPwdUserCheck' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4')
objectClasses: (2.16.840.1.113730.3.8.24.1 NAME 'ipaPwdPolicy' DESC 'IPA Password policy object class' SUP top MAY (ipaPwdMaxRepeat $ ipaPwdMaxSequence $ ipaPwdDictCheck $ ipaPwdUserCheck $ passwordGraceLimit) X-ORIGIN 'IPA v4')
objectClasses: (2.16.840.1.113730.3.8.24.1 NAME 'ipaPwdPolicy' DESC 'IPA Password policy object class' SUP top MAY (ipaPwdMaxRepeat $ ipaPwdMaxSequence $ ipaPwdDictCheck $ ipaPwdUserCheck) X-ORIGIN 'IPA v4')
-14
View File
@@ -18,17 +18,3 @@ objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subord
objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.5 NAME 'ipaSubordinateIdEntry' DESC 'Subordinate uid and gid entry' SUP top STRUCTURAL MUST ( ipaUniqueId ) MAY ( description ) X-ORIGIN 'IPA v4.9')
# External IdP support
attributeTypes: (2.16.840.1.113730.3.8.23.15 NAME 'ipaIdpDevAuthEndpoint' DESC 'Identity Provider Device Authorization Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.16 NAME 'ipaIdpAuthEndpoint' DESC 'Identity Provider Authorization Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.17 NAME 'ipaIdpTokenEndpoint' DESC 'Identity Provider Token Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.18 NAME 'ipaIdpClientId' DESC 'Identity Provider Client Identifier' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.19 NAME 'ipaIdpClientSecret' DESC 'Identity Provider Client Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.20 NAME 'ipaIdpScope' DESC 'Identity Provider Scope' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.21 NAME 'ipaIdpConfigLink' DESC 'Corresponding Identity Provider Configuration link' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: (2.16.840.1.113730.3.8.23.22 NAME 'ipaIdpSub' DESC 'Identity Provider User Subject' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.23 NAME 'ipaIdpIssuerURL' DESC 'Identity Provider OIDC URL' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.24 NAME 'ipaIdpUserInfoEndpoint' DESC 'Identity Provider UserInfo Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
attributeTypes: (2.16.840.1.113730.3.8.23.25 NAME 'ipaIdpKeysEndpoint' DESC 'Identity Provider JWKS Endpoint' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.9' )
objectClasses: (2.16.840.1.113730.3.8.24.6 NAME 'ipaIdP' SUP top STRUCTURAL DESC 'Identity Provider Configuration' MUST ( cn ) MAY ( ipaIdpDevAuthEndpoint $ ipaIdpAuthEndpoint $ ipaIdpTokenEndpoint $ ipaIdpUserInfoEndpoint $ ipaIdpKeysEndpoint $ ipaIdpClientId $ description $ ipaIdpClientSecret $ ipaIdpScope $ ipaIdpIssuerURL $ ipaIdpSub ) X-ORIGIN 'IPA v4.9' )
objectClasses: (2.16.840.1.113730.3.8.24.7 NAME 'ipaIdpUser' SUP top AUXILIARY DESC 'User from an external Identity Provider ' MAY ( ipaIdpConfigLink $ ipaIdpSub ) X-ORIGIN 'IPA v4.9' )
-8
View File
@@ -307,11 +307,3 @@ dn: $SUFFIX
changetype: modify
add: aci
aci: (targetattr = "objectclass")(target = "ldap:///cn=certificate remove hold,cn=virtual operations,cn=etc,$SUFFIX" )(version 3.0 ; acl "permission:Certificate Remove Hold"; allow (write) groupdn = "ldap:///cn=Certificate Remove Hold,cn=permissions,cn=pbac,$SUFFIX";)
dn: cn=External IdP server Administrators,cn=privileges,cn=pbac,$SUFFIX
changetype: add
objectClass: top
objectClass: groupofnames
objectClass: nestedgroup
cn: External IdP server Administrators
description: External IdP server Administrators
+4
View File
@@ -32,6 +32,10 @@ dnaThreshold: eval($SUBID_DNA_THRESHOLD)
dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
dnaExcludeScope: cn=provisioning,$SUFFIX
dnaInterval: eval($SUBID_COUNT)
# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
# dnaIntervalAttr: ipasubuidcount
# dnaIntervalAttr: ipasubgidcount
# dnaMaxInterval: eval($SUBID_COUNT)
# Enable the DNA plugin
dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
+1 -1
View File
@@ -8,7 +8,7 @@ objectClass: top
cn: dns
ipaConfigString: DNSVersion 1
ipaDNSVersion: 2
aci: (targetattr = "*")(version 3.0; acl "Read DNS entries from a zone"; allow (read,search,compare) userattr = "parent[0,1].managedby#GROUPDN";)
aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search,compare) groupdn = "ldap:///cn=Read DNS Entries,cn=permissions,cn=pbac,$SUFFIX" or userattr = "parent[0,1].managedby#GROUPDN";)
aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
aci: (targetattr = "a6record || aaaarecord || afsdbrecord || aplrecord || arecord || certrecord || cn || cnamerecord || dhcidrecord || dlvrecord || dnamerecord || dnsclass || dnsttl || dsrecord || hinforecord || hiprecord || idnsallowdynupdate || idnsallowquery || idnsallowsyncptr || idnsallowtransfer || idnsforwarders || idnsforwardpolicy || idnsname || idnssecinlinesigning || idnssoaexpire || idnssoaminimum || idnssoamname || idnssoarefresh || idnssoaretry || idnssoarname || idnssoaserial || idnsupdatepolicy || idnszoneactive || ipseckeyrecord || keyrecord || kxrecord || locrecord || mdrecord || minforecord || mxrecord || naptrrecord || nsecrecord || nsec3paramrecord || nsrecord || nxtrecord || ptrrecord || rprecord || rrsigrecord || sigrecord || spfrecord || srvrecord || sshfprecord || tlsarecord || txtrecord || urirecord || unknownrecord ")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Update DNS entries in a zone";allow (write) userattr = "parent[0,1].managedby#GROUPDN";)
+2 -2
View File
@@ -1,4 +1,4 @@
# VERSION 17 - DO NOT REMOVE THIS LINE
# VERSION 16 - DO NOT REMOVE THIS LINE
ProxyRequests Off
@@ -11,7 +11,7 @@ ProxyRequests Off
</LocationMatch>
# matches for admin port and installer
<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries|^/kra/admin/kra/getStatus">
<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient none
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
+1
View File
@@ -81,6 +81,7 @@ pki_skip_installation=False
pki_skip_sd_verify=False
pki_sslserver_token=internal
pki_ssl_server_token=%(pki_sslserver_token)s
pki_sslserver_nickname=Server-Cert cert-pki-ca
pki_sslserver_subject_dn=cn=%(ipa_fqdn)s,%(ipa_subject_base)s
+1 -2
View File
@@ -6,8 +6,7 @@
[realms]
$REALM = {
master_key_type = $MASTER_KEY_TYPE
supported_enctypes = $SUPPORTED_ENCTYPES
master_key_type = aes256-cts
max_life = 7d
max_renewable_life = 14d
acl_file = $KRB5KDC_KADM5_ACL
+1 -4
View File
@@ -28,8 +28,6 @@ ${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:normal
${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:special
krbMaxTicketLife: 86400
krbMaxRenewableAge: 604800
krbDefaultEncSaltTypes: aes256-sha2:special
krbDefaultEncSaltTypes: aes128-sha2:special
krbDefaultEncSaltTypes: aes256-cts:special
krbDefaultEncSaltTypes: aes128-cts:special
@@ -39,7 +37,6 @@ changetype: add
objectClass: top
objectClass: nsContainer
objectClass: krbPwdPolicy
objectClass: ipaPwdPolicy
krbMinPwdLife: 3600
krbPwdMinDiffChars: 0
krbPwdMinLength: 8
@@ -48,4 +45,4 @@ krbMaxPwdLife: 7776000
krbPwdMaxFailure: 6
krbPwdFailureCountInterval: 60
krbPwdLockoutDuration: 600
passwordGraceLimit: -1
+1 -1
View File
@@ -202,7 +202,7 @@ def main():
if not (user['uid'][0] in group['member_user'] and
group['cn'][0] in user['memberof_group']):
raise errors.RequirementError(name='admins group membership')
except errors.RequirementError:
except errors.RequirementError as e:
raise ScriptError(
"Must have administrative privileges to setup AD trusts on server"
)
-5
View File
@@ -131,8 +131,6 @@ def main():
else:
print("Enabling plugin")
# https://github.com/PyCQA/pylint/issues/872
# pylint: disable=unsupported-assignment-operation
if entry is None:
ld = LDAPUpdate()
if not ld.update(files):
@@ -141,7 +139,6 @@ def main():
else:
entry['nsslapd-pluginenabled'] = ['on']
api.Backend.ldap2.update_entry(entry)
# pylint: enable=unsupported-assignment-operation
except errors.ExecutionError as lde:
print("An error occurred while talking to the server.")
print(lde)
@@ -171,9 +168,7 @@ def main():
else:
print("Disabling plugin")
# pylint: disable=unsupported-assignment-operation
entry['nsslapd-pluginenabled'] = ['off']
# pylint: enable=unsupported-assignment-operation
api.Backend.ldap2.update_entry(entry)
except errors.DatabaseError as dbe:
print("An error occurred while talking to the server.")
+6 -9
View File
@@ -71,21 +71,18 @@ def parse_options():
if len(args):
n = len(args) - 1
for cmd, args_info in commands.items():
for cmd in commands:
if cmd == args[0]:
v = commands[cmd]
err = None
if n < args_info[0]:
err = args_info[3]
elif n > args_info[1]:
if n < v[0]:
err = v[3]
elif n > v[1]:
err = "too many arguments"
else:
valid_syntax = True
if err:
parser.error(
"Invalid syntax: %s\nUsage: %s [options] %s" % (
err, cmd, args_info[2]
)
)
parser.error("Invalid syntax: %s\nUsage: %s [options] %s" % (err, cmd, v[2]))
if not valid_syntax:
cmdstr = " | ".join(commands.keys())
+5 -5
View File
@@ -184,7 +184,7 @@ class IPACustodiaTester:
pkey = JWK(**dictkeys[usage_id])
local_pubkey = json_decode(pkey.export_public())
except Exception:
raise self.error( # pylint: disable=raising-bad-type, #4772
raise self.error(
"Failed to load and parse local JWK.", fatal=True
)
else:
@@ -193,7 +193,7 @@ class IPACustodiaTester:
))
if pkey.key_id != self.host_spn:
raise self.error( # pylint: disable=raising-bad-type, #4772
raise self.error(
"KID '{}' != host service principal name '{}' "
"(usage: {})".format(pkey.key_id, self.host_spn, usage),
fatal=True
@@ -210,7 +210,7 @@ class IPACustodiaTester:
try:
host_pubkey = json_decode(find_key(self.host_spn, usage_id))
except Exception:
raise self.error( # pylint: disable=raising-bad-type, #4772
raise self.error(
"Fetching host keys {} (usage: {}) failed.".format(
self.host_spn, usage),
fatal=True
@@ -223,7 +223,7 @@ class IPACustodiaTester:
if host_pubkey != local_pubkey:
self.debug("LDAP: '{}'".format(host_pubkey))
self.debug("Local: '{}'".format(local_pubkey))
raise self.error( # pylint: disable=raising-bad-type, #4772
raise self.error(
"Host key in LDAP does not match local key.",
fatal=True
)
@@ -235,7 +235,7 @@ class IPACustodiaTester:
try:
server_pubkey = json_decode(find_key(self.server_spn, usage_id))
except Exception:
raise self.error( # pylint: disable=raising-bad-type, #4772
raise self.error(
"Fetching server keys {} (usage: {}) failed.".format(
self.server_spn, usage),
fatal=True
-2
View File
@@ -155,9 +155,7 @@ def main():
elif entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'off':
print("Enabling plugin")
# Already configured, just enable the plugin
# pylint: disable=unsupported-assignment-operation
entry['nsslapd-pluginenabled'] = ['on']
# pylint: enable=unsupported-assignment-operation
api.Backend.ldap2.update_entry(entry)
else:
print("Plugin already Enabled")
+1
View File
@@ -34,6 +34,7 @@ def main():
"File '{}' missing or not readable.\n".format(filename)
)
# pylint: disable=no-member
client = CustodiaClient(
client_service="{}@{}".format(service, env.host),
server=args.servername,
+2 -2
View File
@@ -319,7 +319,7 @@ class PortResponder(threading.Thread):
logger.debug('%d %s: Stopped listening', port, proto)
def _is_closing(self):
with self._close_lock:
with self._close_lock: # pylint: disable=not-context-manager
return self._close
def _bind_to_port(self, port, socket_type):
@@ -370,7 +370,7 @@ class PortResponder(threading.Thread):
def stop(self):
logger.debug('Stopping listening thread.')
with self._close_lock:
with self._close_lock: # pylint: disable=not-context-manager
self._close = True
+6 -9
View File
@@ -116,21 +116,18 @@ def parse_options():
if len(args):
n = len(args) - 1
for cmd, args_info in commands.items():
for cmd in commands:
if cmd == args[0]:
v = commands[cmd]
err = None
if n < args_info[0]:
err = args_info[3]
elif n > args_info[1]:
if n < v[0]:
err = v[3]
elif n > v[1]:
err = "too many arguments"
else:
valid_syntax = True
if err:
parser.error(
"Invalid syntax: %s\nUsage: %s [options] %s" % (
err, cmd, args_info[2]
)
)
parser.error("Invalid syntax: %s\nUsage: %s [options] %s" % (err, cmd, v[2]))
if not valid_syntax:
cmdstr = " | ".join(commands.keys())
-3
View File
@@ -92,9 +92,6 @@ Do not configure OpenSSH client.
\fB\-\-no\-sshd\fR
Do not configure OpenSSH server.
.TP
\fB\-\-subid\fR
Configure SSSD as data source for subid.
.TP
\fB\-\-skip\-conncheck\fR
Skip connection check to remote master
.TP
-3
View File
@@ -80,9 +80,6 @@ Do not configure OpenSSH client.
\fB\-\-no\-sshd\fR
Do not configure OpenSSH server.
.TP
\fB\-\-subid\fR
Configure SSSD as data source for subid.
.TP
\fB\-d\fR, \fB\-\-debug\fR
Enable debug logging when more verbose output is needed.
.TP
-1
View File
@@ -58,7 +58,6 @@ define([
'./topology',
'./user',
'./vault',
'./idp',
'dojo/domReady!'
],function(app_container) {
return app_container;
+5 -1
View File
@@ -184,8 +184,12 @@ return {
measurement_unit: 'seconds'
},
{
$type: 'checkbox',
$type: 'radio',
name: 'idnsallowdynupdate',
options: [
{ value: 'TRUE', label: '@i18n:true' },
{ value: 'FALSE', label: '@i18n:false' }
]
},
{
$type: 'textarea',
-4
View File
@@ -136,10 +136,6 @@ return {
{
label: '@i18n:authtype.type_hardened',
value: 'hardened'
},
{
label: '@i18n:authtype.type_idp',
value: 'idp'
}
],
tooltip: {
-296
View File
@@ -1,296 +0,0 @@
// Copyright (C) 2022 FreeIPA Contributors see COPYING for license
define([
'dojo/on',
'./ipa',
'./jquery',
'./menu',
'./phases',
'./reg',
'./text',
'./details',
'./search',
'./entity',
'./dialogs/password'
],
function(on, IPA, $, menu, phases, reg, text) {
/**
* IdP module
* @class
* @singleton
*/
var idp = IPA.idp = {};
// Templates are hardcoded in idp_add class
// and cannot be retrieved with an API call
// Structure below references template names
// and additional fields per template
idp.templates = [
{ value: 'keycloak',
label: text.get('@i18n:objects.idp.template_keycloak'),
fields: ['ipaidporg', 'ipaidpbaseurl']},
{ value: 'google',
label: text.get('@i18n:objects.idp.template_google'),
fields: []},
{ value: 'github',
label: text.get('@i18n:objects.idp.template_github'),
fields: []},
{ value: 'microsoft',
label: text.get('@i18n:objects.idp.template_microsoft'),
fields: ['ipaidporg']},
{ value: 'okta',
label: text.get('@i18n:objects.idp.template_okta'),
fields: ['ipaidporg', 'ipaidpbaseurl']}
];
var make_spec = function() {
return {
name: 'idp',
enable_test: function() {
return true;
},
facets: [
{
$type: 'search',
columns: [
'cn',
'ipaidpclientid',
'ipaidpscope',
'description'
]
},
{
$type: 'details',
sections: [
{
name: 'idpclient',
label: '@i18n:objects.idp.label_idpclient',
fields: [
'cn',
'ipaidpclientid',
{
$type: 'password',
name: 'ipaidpclientsecret',
flags: ['w_if_no_aci']
},
]
},
{
name: 'idp',
label: '@i18n:objects.idp.label_idp',
fields: [
'ipaidpscope',
'ipaidpsub',
'ipaidpauthendpoint',
'ipaidpdevauthendpoint',
'ipaidptokenendpoint',
'ipaidpuserinfoendpoint',
'ipaidpkeysendpoint',
'ipaidpissuerurl'
]
}
],
actions: [
{
$type: 'password',
dialog: {
password_name: 'ipaidpclientsecret'
}
}
],
header_actions: ['password']
}
],
adder_dialog: {
title: '@i18n:objects.idp.add',
policies: [
IPA.add_idp_policy
],
sections: [
{
name: 'idpclientsetup',
label: '@i18n:objects.idp.label_idpclient',
fields: [
'cn',
'ipaidpclientid',
{
$type: 'password',
name: 'ipaidpclientsecret'
},
{
$type: 'password',
name: 'ipaidpclientsecret_verify',
label: '@i18n:objects.idp.verify_secret',
flags: ['no_command'],
required: false,
validators: [{
$type: 'same_password',
other_field: 'ipaidpclientsecret'
}]
}
]
},
{
name: 'idpsetup',
label: '@i18n:objects.idp.label_idp',
fields: [
{
name: 'type',
label: 'Provider type',
$type: 'radio',
flags: ['no_command'],
layout: 'vertical',
default_value: 'template',
options: [
{
value: 'template',
label: 'Pre-defined IdP template',
},
{
value: 'custom',
label: 'Custom IdP definition',
}
]
},
{
label: '@i18n:idp.provider',
name: 'ipaidpprovider',
$type: 'select',
options: IPA.create_options(idp.templates)
},
{
name: 'ipaidporg',
label: '@i18n:objects.idp.ipaidporg',
metadata: '@mc-opt:idp_add:ipaidporg'
},
{
name: 'ipaidpbaseurl',
label: '@i18n:objects.idp.ipaidpbaseurl',
metadata: '@mc-opt:idp_add:ipaidpbaseurl'
},
'ipaidpscope',
'ipaidpsub',
'ipaidpauthendpoint',
'ipaidpdevauthendpoint',
'ipaidptokenendpoint',
'ipaidpuserinfoendpoint',
'ipaidpkeysendpoint'
]
},
]
},
deleter_dialog: {
title: '@i18n:objects.idp.remove'
}
};};
IPA.add_idp_policy = function() {
// Custom provider fields
// need to be hidden for a chosen template
// and show for the custom provider
var custom_fields = [
'ipaidpauthendpoint',
'ipaidpdevauthendpoint',
'ipaidptokenendpoint',
'ipaidpuserinfoendpoint',
'ipaidpkeysendpoint'
];
// Template may require an additional field
// Make it required and visible in that case
var template_fields = [
'ipaidporg',
'ipaidpbaseurl'
];
var that = IPA.facet_policy();
that.init = function() {
// Handle choice of either pre-defined or custom IdP
var type_f = that.container.fields.get_field('type');
on(type_f, 'value-change', that.on_type_change);
// Handle choice of a pre-defined IdP to show/remove additional fields
var prov_f = that.container.fields.get_field('ipaidpprovider');
on(prov_f, 'value-change', that.on_prov_change);
};
that.on_type_change = function() {
var type_f = that.container.fields.get_field('type');
var mode = type_f.get_value()[0];
var show_custom = true;
if (mode === 'template') show_custom = false;
// For custom template we show custom fields
// and mark all of them required and passed to the RPC
// If show_custom is false, the opposite happens
custom_fields.forEach(fname => {
widget_f = that.container.fields.get_field(fname);
widget_f.set_required(show_custom);
widget_f.set_enabled(show_custom);
widget_f.widget.set_visible(show_custom);
});
// For template fields we show them if custom aren't shown
template_fields.forEach(fname => {
widget_f = that.container.fields.get_field(fname);
widget_f.set_enabled(!show_custom);
widget_f.widget.set_visible(!show_custom);
});
widget_f = that.container.fields.get_field('ipaidpprovider');
widget_f.set_required(!show_custom);
widget_f.set_enabled(!show_custom);
widget_f.widget.set_visible(!show_custom);
};
that.on_prov_change = function() {
var prov_f = that.container.fields.get_field('ipaidpprovider');
var value = prov_f.get_value()[0];
// First, clear template fields from the previous provider choice
template_fields.forEach(fname => {
widget_f = that.container.fields.get_field(fname);
widget_f.widget.set_visible(false);
widget_f.set_required(false);
widget_f.set_enabled(false);
});
// Second, enable and get required template-specific fields
idp.templates.forEach(idp_v => {
if (idp_v['value'] == value) {
idp_v['fields'].forEach(fname => {
widget_f = that.container.fields.get_field(fname);
widget_f.set_required(true);
widget_f.set_enabled(true);
widget_f.widget.set_visible(true);
});
}
}
)
};
return that;
};
/**
* IdP specification object
*/
idp.spec = make_spec();
/**
* Register IdP entity
*/
idp.register = function() {
var e = reg.entity;
e.register({type: 'idp', spec: idp.spec});
};
phases.on('registration', idp.register);
return idp;
});
@@ -185,7 +185,6 @@ var nav = {};
},
{ entity: 'otptoken' },
{ entity: 'radiusproxy' },
{ entity: 'idp' },
{
entity: 'certmaprule',
facet: 'search',
+3 -3
View File
@@ -41,9 +41,9 @@
"path": "install/ui/js/freeipa"
},
"devDependencies": {
"grunt": "",
"grunt-contrib-qunit": "",
"grunt-qunit-junit": ""
"grunt": "^1.0.1",
"grunt-contrib-qunit": "^2.0.0",
"grunt-qunit-junit": "^0.3.1"
},
"dependencies": {
"dojo": "~1.16.2"
-11
View File
@@ -166,18 +166,7 @@ return {
name: 'krbauthindmaxticketlife_hardened',
acl_param: 'krbauthindmaxticketlife',
measurement_unit: 'seconds'
},
{
name: 'krbauthindmaxrenewableage_idp',
acl_param: 'krbauthindmaxrenewableage',
measurement_unit: 'seconds'
},
{
name: 'krbauthindmaxticketlife_idp',
acl_param: 'krbauthindmaxticketlife',
measurement_unit: 'seconds'
}
]
}
],
+1 -2
View File
@@ -112,8 +112,7 @@ return {
{ label: '@i18n:authtype.type_radius', value: 'radius' },
{ label: '@i18n:authtype.type_otp', value: 'otp' },
{ label: '@i18n:authtype.type_pkinit', value: 'pkinit' },
{ label: '@i18n:authtype.type_hardened', value: 'hardened' },
{ label: '@i18n:authtype.type_idp', value: 'idp' }
{ label: '@i18n:authtype.type_hardened', value: 'hardened' }
],
tooltip: {
title: '@i18n:authtype.config_tooltip',
-4
View File
@@ -146,10 +146,6 @@ return {
{
label: '@i18n:authtype.type_hardened',
value: 'hardened'
},
{
label: '@i18n:authtype.type_idp',
value: 'idp'
}
],
tooltip: {
Loaded 100 of 340 files, more files were not shown because too many files have changed in this diff. Show more