Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9277430447 | ||
|
|
252955c104 | ||
|
|
61a64aef6d | ||
|
|
dabea80f3a | ||
|
|
553b84c60a | ||
|
|
70d23b225d | ||
|
|
60739ce483 | ||
|
|
fe048d83cb | ||
|
|
cfca49c469 | ||
|
|
d062dc9da8 | ||
|
|
deaaaaf149 | ||
|
|
4b8b032ed5 | ||
|
|
f325539318 | ||
|
|
c6bc8fd4c8 | ||
|
|
23d56bb952 | ||
|
|
8abc0a22a8 | ||
|
|
3488276649 | ||
|
|
6147f877a5 | ||
|
|
faeb656c77 | ||
|
|
e6cc41094b | ||
|
|
9b0fbdc37b | ||
|
|
d2b296454c | ||
|
|
8b2edd5b4e | ||
|
|
62bafcc53d | ||
|
|
c002864621 | ||
|
|
58ddcffc41 | ||
|
|
352b9dfb49 | ||
|
|
0e8350e0dd | ||
|
|
ab0e67d1f5 | ||
|
|
87fe3fbba6 | ||
|
|
6b3ab98b90 | ||
|
|
4fcbf2ded2 | ||
|
|
6c6fc7db61 | ||
|
|
35c720cab0 | ||
|
|
1c6bdf9759 | ||
|
|
300f1301bb | ||
|
|
ed1447ab61 | ||
|
|
a2baae42f8 | ||
|
|
84c88b69fe | ||
|
|
7e596fd16c | ||
|
|
0193498f68 | ||
|
|
e10f3385d0 | ||
|
|
74b2fd06d9 | ||
|
|
c03a8c3c06 | ||
|
|
b39f9336fa | ||
|
|
b979dd91f1 | ||
|
|
9ae6ef549f | ||
|
|
d39e232e9e | ||
|
|
979163bff2 | ||
|
|
40a257f1e6 | ||
|
|
5f9e0d3ff3 | ||
|
|
d49aa7103b | ||
|
|
d0eab8fe76 | ||
|
|
660c3dc249 | ||
|
|
51a4e42dd7 | ||
|
|
673478b1cf | ||
|
|
bf8e2bb99f | ||
|
|
b77015b7a3 | ||
|
|
2136bd5d00 | ||
|
|
1df7b82ac1 | ||
|
|
8d81338cb9 | ||
|
|
42afcc95be | ||
|
|
7f814d9f54 | ||
|
|
f11b7b3bf5 | ||
|
|
ea0275f611 | ||
|
|
c2434c4e52 | ||
|
|
137e62cc2f | ||
|
|
1e882144bb | ||
|
|
59cf9017a0 | ||
|
|
b3093d9c39 | ||
|
|
5ea1866f1b | ||
|
|
ffd8f14af2 | ||
|
|
68c20846cf | ||
|
|
7ddef72fbb | ||
|
|
de918aea19 | ||
|
|
5877c4e17a | ||
|
|
d37d1f717e | ||
|
|
0cdbe00a72 | ||
|
|
029c4fc6e6 | ||
|
|
8a28154a32 | ||
|
|
2ffa2b4254 | ||
|
|
5ba5143f9e | ||
|
|
ee6472cee2 | ||
|
|
91d083c36e | ||
|
|
710314a794 | ||
|
|
de1f4467fb | ||
|
|
c3bd6908fa | ||
|
|
9cd48d1854 | ||
|
|
b6b5f6073b | ||
|
|
09481117b5 | ||
|
|
98eb661fd8 | ||
|
|
85b2c8191b | ||
|
|
12785a3657 | ||
|
|
c46ea21ed3 | ||
|
|
3e8a355dd4 | ||
|
|
bd8748f6b7 | ||
|
|
83551693b3 | ||
|
|
984190eea0 | ||
|
|
895e99b684 | ||
|
|
2e70535f74 | ||
|
|
3baae8d1bd | ||
|
|
3e54c43624 | ||
|
|
cedca75f4f | ||
|
|
3a4238ba96 | ||
|
|
b413a327f3 | ||
|
|
1e2cf55150 | ||
|
|
b58ec49da9 | ||
|
|
6202a7d85b | ||
|
|
c5b4657869 | ||
|
|
bfb233185a | ||
|
|
fd99e4d47a | ||
|
|
1f17ade67f | ||
|
|
3ea0e1bd8d | ||
|
|
a960adc6c2 | ||
|
|
03cd914381 | ||
|
|
2db2c6cb32 | ||
|
|
bb515f41b3 | ||
|
|
322d08921a | ||
|
|
13e5720d18 | ||
|
|
76c2c08fdb | ||
|
|
08f2db7855 | ||
|
|
afba414770 | ||
|
|
054376c1a0 | ||
|
|
d3b384b5ca | ||
|
|
ccf9334da9 | ||
|
|
0ebf09e061 | ||
|
|
91ff7b87d9 | ||
|
|
4da897c3dc | ||
|
|
bffde84c81 | ||
|
|
9ca818b179 | ||
|
|
dfa1ceac6f | ||
|
|
3c77949ab4 | ||
|
|
04c4032370 | ||
|
|
6fd75de5a7 | ||
|
|
106d011e5f | ||
|
|
40ee6a47ac | ||
|
|
b5fc2eeff9 | ||
|
|
7f8b4f0368 | ||
|
|
ee39de46a1 | ||
|
|
49d9147e38 | ||
|
|
b016683552 | ||
|
|
a51900819b | ||
|
|
42f41ff637 | ||
|
|
57de18e914 | ||
|
|
7ac8e9696e | ||
|
|
ab9e7dac41 | ||
|
|
7ad500e5d3 | ||
|
|
84381001d2 | ||
|
|
6b70e3c49a | ||
|
|
d8174b0ca6 | ||
|
|
f2731107db | ||
|
|
186ebe311b | ||
|
|
837702199c | ||
|
|
b36bcf4ea5 | ||
|
|
cc2348aedb | ||
|
|
86b98b86f6 | ||
|
|
0d9eb3d515 | ||
|
|
9b6d0bb124 | ||
|
|
896d0f3516 | ||
|
|
0d034d7fd4 | ||
|
|
10d32d43e4 | ||
|
|
85ce7acb73 | ||
|
|
7c5540bb47 | ||
|
|
edb216849e | ||
|
|
294ae35a61 | ||
|
|
6d70421f57 | ||
|
|
653a7fe028 | ||
|
|
9bae549227 | ||
|
|
cd8e9ce173 | ||
|
|
ace0bbfdc8 | ||
|
|
d8a7f15e32 | ||
|
|
ef43ea03ef | ||
|
|
0edf915efb | ||
|
|
e11cf7f489 | ||
|
|
419d7fd6e5 | ||
|
|
cd735099e8 | ||
|
|
b9c42fed9b | ||
|
|
6ff7491172 | ||
|
|
878859f4a2 | ||
|
|
edbd8f692a | ||
|
|
1efdda078e | ||
|
|
5444da016e | ||
|
|
1d19b860d4 | ||
|
|
ce112e68bd | ||
|
|
cbd9ac6ab0 | ||
|
|
465f1669a6 | ||
|
|
4c54e9d6dd | ||
|
|
8b22ee018c | ||
|
|
ba7ec71ba9 | ||
|
|
7d93bda31c | ||
|
|
669f3d7116 | ||
|
|
69f5f319d1 | ||
|
|
739d356695 | ||
|
|
e99870f7d0 | ||
|
|
dcdc31b6f9 | ||
|
|
da9be70f70 | ||
|
|
cf9c4cc7da | ||
|
|
a0eb02cfb6 | ||
|
|
8042bdc90c |
No files matched your search
+10
-1
@@ -1,5 +1,14 @@
|
||||
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
|
||||
@@ -57,6 +57,12 @@ 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/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# .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
|
||||
@@ -10,6 +10,6 @@ ipaserver == @VERSION@
|
||||
ipatests == @VERSION@
|
||||
|
||||
# keep pylint version in sync with current Fedora release
|
||||
# F34 has 2.6
|
||||
# F36 has 2.12.2
|
||||
# https://pagure.io/freeipa/issue/8818 added pylint 2.8 support
|
||||
pylint < 2.9
|
||||
pylint ~= 2.12.2
|
||||
@@ -180,6 +180,16 @@ 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
|
||||
@@ -231,9 +241,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")(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 || 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";)
|
||||
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")(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 || 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";)
|
||||
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
|
||||
|
||||
@@ -136,9 +136,8 @@ output: Entry('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: automember_default_group_remove/1
|
||||
args: 0,5,3
|
||||
args: 0,4,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?')
|
||||
@@ -146,10 +145,9 @@ 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,6,3
|
||||
args: 0,5,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?')
|
||||
@@ -1100,7 +1098,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'disabled'])
|
||||
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: 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')
|
||||
@@ -2479,7 +2477,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'])
|
||||
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: Str('l?', cli_name='locality')
|
||||
option: Str('macaddress*')
|
||||
option: Flag('no_members', autofill=True, default=False)
|
||||
@@ -2615,7 +2613,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'])
|
||||
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: Str('l?', autofill=False, cli_name='locality')
|
||||
option: Str('macaddress*', autofill=False)
|
||||
option: Str('man_by_host*', cli_name='man_by_hosts')
|
||||
@@ -2655,7 +2653,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'])
|
||||
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: Principal('krbprincipalname*', autofill=False)
|
||||
option: Str('l?', autofill=False, cli_name='locality')
|
||||
option: Str('macaddress*', autofill=False)
|
||||
@@ -3053,6 +3051,96 @@ 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')
|
||||
@@ -3238,16 +3326,18 @@ output: Output('result', type=[<type 'bool'>])
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: krbtpolicy_mod/1
|
||||
args: 1,17,3
|
||||
args: 1,19,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')
|
||||
@@ -3968,7 +4058,7 @@ output: Entry('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: pwpolicy_add/1
|
||||
args: 1,18,3
|
||||
args: 1,19,3
|
||||
arg: Str('cn', cli_name='group')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -3985,6 +4075,7 @@ 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?')
|
||||
@@ -4000,7 +4091,7 @@ output: Output('result', type=[<type 'dict'>])
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: ListOfPrimaryKeys('value')
|
||||
command: pwpolicy_find/1
|
||||
args: 1,20,4
|
||||
args: 1,21,4
|
||||
arg: Str('criteria?')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Str('cn?', autofill=False, cli_name='group')
|
||||
@@ -4017,6 +4108,7 @@ 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)
|
||||
@@ -4027,7 +4119,7 @@ output: ListOfEntries('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: Output('truncated', type=[<type 'bool'>])
|
||||
command: pwpolicy_mod/1
|
||||
args: 1,20,3
|
||||
args: 1,21,3
|
||||
arg: Str('cn?', cli_name='group')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -4045,6 +4137,7 @@ 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')
|
||||
@@ -4590,7 +4683,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'])
|
||||
option: StrEnum('krbprincipalauthind*', cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: Flag('no_members', autofill=True, default=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||
option: Str('setattr*', cli_name='setattr')
|
||||
@@ -4726,7 +4819,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'])
|
||||
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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)
|
||||
@@ -4750,7 +4843,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'])
|
||||
option: StrEnum('krbprincipalauthind*', autofill=False, cli_name='auth_ind', values=[u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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)
|
||||
@@ -4978,7 +5071,7 @@ output: Entry('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: stageuser_add/1
|
||||
args: 1,45,3
|
||||
args: 1,47,3
|
||||
arg: Str('uid', cli_name='login')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -4995,10 +5088,12 @@ 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'])
|
||||
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: DateTime('krbpasswordexpiration?', cli_name='password_expiration')
|
||||
option: DateTime('krbprincipalexpiration?', cli_name='principal_expiration')
|
||||
option: Principal('krbprincipalname*', autofill=True, cli_name='principal')
|
||||
@@ -5084,7 +5179,7 @@ output: Output('result', type=[<type 'dict'>])
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: ListOfPrimaryKeys('value')
|
||||
command: stageuser_find/1
|
||||
args: 1,60,4
|
||||
args: 1,62,4
|
||||
arg: Str('criteria?')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Str('carlicense*', autofill=False)
|
||||
@@ -5105,13 +5200,15 @@ 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'])
|
||||
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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')
|
||||
@@ -5151,7 +5248,7 @@ output: ListOfEntries('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: Output('truncated', type=[<type 'bool'>])
|
||||
command: stageuser_mod/1
|
||||
args: 1,51,3
|
||||
args: 1,53,3
|
||||
arg: Str('uid', cli_name='login')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -5168,6 +5265,8 @@ 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')
|
||||
@@ -5175,7 +5274,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'])
|
||||
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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')
|
||||
@@ -6158,7 +6257,7 @@ output: Entry('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: user_add/1
|
||||
args: 1,46,3
|
||||
args: 1,48,3
|
||||
arg: Str('uid', cli_name='login')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -6174,10 +6273,12 @@ 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'])
|
||||
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
option: DateTime('krbpasswordexpiration?', cli_name='password_expiration')
|
||||
option: DateTime('krbprincipalexpiration?', cli_name='principal_expiration')
|
||||
option: Principal('krbprincipalname*', autofill=True, cli_name='principal')
|
||||
@@ -6280,7 +6381,7 @@ output: Output('result', type=[<type 'bool'>])
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: user_find/1
|
||||
args: 1,63,4
|
||||
args: 1,65,4
|
||||
arg: Str('criteria?')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Str('carlicense*', autofill=False)
|
||||
@@ -6301,13 +6402,15 @@ 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'])
|
||||
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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')
|
||||
@@ -6350,7 +6453,7 @@ output: ListOfEntries('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: Output('truncated', type=[<type 'bool'>])
|
||||
command: user_mod/1
|
||||
args: 1,52,3
|
||||
args: 1,54,3
|
||||
arg: Str('uid', cli_name='login')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -6367,6 +6470,8 @@ 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')
|
||||
@@ -6374,7 +6479,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'])
|
||||
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'idp'])
|
||||
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')
|
||||
@@ -6550,7 +6655,7 @@ output: Output('completed', type=[<type 'int'>])
|
||||
output: Output('failed', type=[<type 'dict'>])
|
||||
output: Entry('result')
|
||||
command: vault_archive_internal/1
|
||||
args: 1,9,3
|
||||
args: 1,10,3
|
||||
arg: Str('cn', cli_name='name')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Bytes('nonce')
|
||||
@@ -6561,6 +6666,7 @@ 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')
|
||||
@@ -6651,7 +6757,7 @@ output: Output('completed', type=[<type 'int'>])
|
||||
output: Output('failed', type=[<type 'dict'>])
|
||||
output: Entry('result')
|
||||
command: vault_retrieve_internal/1
|
||||
args: 1,7,3
|
||||
args: 1,8,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)
|
||||
@@ -6660,6 +6766,7 @@ 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')
|
||||
@@ -7023,6 +7130,12 @@ 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
|
||||
@@ -7329,6 +7442,7 @@ 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
|
||||
|
||||
@@ -31,6 +31,7 @@ Developers:
|
||||
Benjamin Drung
|
||||
Brian Cook
|
||||
Brian J. Murrell
|
||||
Brian Turek
|
||||
Carl George
|
||||
Cédric Jeanneret
|
||||
Changmin Teng
|
||||
@@ -80,6 +81,7 @@ Developers:
|
||||
Jayesh Garg
|
||||
Jeremy Frasier
|
||||
Jérôme Fenal
|
||||
jh23453
|
||||
Jim Meyering
|
||||
Jochen Kellner
|
||||
JoeDrane
|
||||
@@ -88,6 +90,7 @@ Developers:
|
||||
John Morris
|
||||
Jr Aquino
|
||||
Julian Gethmann
|
||||
Julien Rische
|
||||
Justin Stephenson
|
||||
Kaleemullah Siddiqui
|
||||
Karl MacMillan
|
||||
@@ -110,6 +113,7 @@ Developers:
|
||||
Martin Košek
|
||||
Martin Nagy
|
||||
Matt Rogers
|
||||
Matthew Davis
|
||||
Michael Simacek
|
||||
Michal Polovka
|
||||
Michal Reznik
|
||||
|
||||
+3
-4
@@ -21,7 +21,7 @@
|
||||
########################################################
|
||||
define(IPA_VERSION_MAJOR, 4)
|
||||
define(IPA_VERSION_MINOR, 9)
|
||||
define(IPA_VERSION_RELEASE, 8)
|
||||
define(IPA_VERSION_RELEASE, 10)
|
||||
|
||||
########################################################
|
||||
# For 'pre' releases the version will be #
|
||||
@@ -86,9 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
|
||||
# #
|
||||
########################################################
|
||||
define(IPA_API_VERSION_MAJOR, 2)
|
||||
# Last change: add enable_sid to config
|
||||
define(IPA_API_VERSION_MINOR, 245)
|
||||
|
||||
# Last change: add graceperiodlimit
|
||||
define(IPA_API_VERSION_MINOR, 248)
|
||||
|
||||
########################################################
|
||||
# Following values are auto-generated from values above
|
||||
|
||||
+9
-18
@@ -743,8 +743,7 @@ jsonrpc_request(const char *ipaserver, const json_t *json, curl_buffer *response
|
||||
|
||||
json_str = json_dumps(json, 0);
|
||||
if (!json_str) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("json_dumps() failed\n"));
|
||||
fprintf(stderr, _("json_dumps() failed\n"));
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -758,8 +757,7 @@ jsonrpc_request(const char *ipaserver, const json_t *json, curl_buffer *response
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
{
|
||||
if (debug)
|
||||
fprintf(stderr, _("JSON-RPC call failed: %s\n"), curl_easy_strerror(res));
|
||||
fprintf(stderr, _("JSON-RPC call failed: %s\n"), curl_easy_strerror(res));
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -769,8 +767,7 @@ 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) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("JSON-RPC call failed with status code: %li\n"), resp_code);
|
||||
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"));
|
||||
@@ -848,8 +845,7 @@ jsonrpc_parse_response(const char *payload, json_t** j_result_obj, bool quiet) {
|
||||
|
||||
j_root = json_loads(payload, 0, &j_error);
|
||||
if (!j_root) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("Parsing JSON-RPC response failed: %s\n"), j_error.text);
|
||||
fprintf(stderr, _("Parsing JSON-RPC response failed: %s\n"), j_error.text);
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -864,8 +860,7 @@ 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) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("Parsing JSON-RPC response failed: no 'result' value found.\n"));
|
||||
fprintf(stderr, _("Parsing JSON-RPC response failed: no 'result' value found.\n"));
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -897,8 +892,7 @@ jsonrpc_parse_join_response(const char *payload, join_info *join_i, bool quiet)
|
||||
&tmp_hostdn,
|
||||
"krbprincipalname", &tmp_princ,
|
||||
"krblastpwdchange", &tmp_pwdch) != 0) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
|
||||
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -941,8 +935,7 @@ join_krb5_jsonrpc(const char *ipaserver, const char *hostname, char **hostdn, co
|
||||
"nshardwareplatform", uinfo.machine);
|
||||
|
||||
if (!json_req) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
|
||||
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
@@ -990,8 +983,7 @@ 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) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
|
||||
fprintf(stderr, _("Extracting the data from the JSON-RPC response failed: %s\n"), j_error.text);
|
||||
|
||||
rval = 20;
|
||||
goto cleanup;
|
||||
@@ -1021,8 +1013,7 @@ jsonrpc_unenroll_host(const char *ipaserver, const char *host, bool quiet) {
|
||||
host);
|
||||
|
||||
if (!json_req) {
|
||||
if (debug)
|
||||
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
|
||||
fprintf(stderr, _("json_pack_ex() failed: %s\n"), j_error.text);
|
||||
|
||||
rval = 17;
|
||||
goto cleanup;
|
||||
|
||||
@@ -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 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.
|
||||
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.
|
||||
.TP
|
||||
.B debug <boolean>
|
||||
When True provides detailed information. Specifically this set the global log level to "debug". Default is False.
|
||||
@@ -119,6 +119,9 @@ 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
|
||||
@@ -160,6 +163,9 @@ 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
|
||||
@@ -247,12 +253,20 @@ 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 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:
|
||||
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:
|
||||
.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)
|
||||
@@ -89,6 +89,9 @@ 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
|
||||
|
||||
@@ -24,14 +24,12 @@ ipa\-client\-automount [\fIOPTION\fR]... <location>
|
||||
.SH "DESCRIPTION"
|
||||
Configures automount for IPA.
|
||||
|
||||
The automount configuration consists of three files:
|
||||
The automount configuration consists of two 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.
|
||||
@@ -42,9 +40,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
|
||||
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.
|
||||
SSSD is configured to manage the automount maps.
|
||||
.TP
|
||||
The nsswitch automount service is configured to use either sss or ldap and files depending on whether SSSD is configured or not.
|
||||
The nss automount service is configured to use sss and files.
|
||||
.TP
|
||||
NFSv4 is also configured. The rpc.gssd and rpc.idmapd are started on clients to support Kerberos\-secured mounts.
|
||||
.SH "OPTIONS"
|
||||
@@ -54,9 +52,6 @@ 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
|
||||
@@ -71,21 +66,12 @@ Restore the automount configuration files.
|
||||
|
||||
.SH "FILES"
|
||||
.TP
|
||||
Files that will be always be configured:
|
||||
Files that will 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
|
||||
|
||||
|
||||
@@ -136,6 +136,9 @@ 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
|
||||
|
||||
@@ -64,6 +64,9 @@ 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
|
||||
|
||||
|
||||
+17
-3
@@ -101,9 +101,13 @@ dnl ---------------------------------------------------------------------------
|
||||
|
||||
SAVE_CPPFLAGS=$CPPFLAGS
|
||||
CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
|
||||
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"
|
||||
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"
|
||||
LDAP_CFLAGS=""
|
||||
AC_SUBST(LDAP_LIBS)
|
||||
AC_SUBST(LDAP_CFLAGS)
|
||||
@@ -289,6 +293,9 @@ 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 ---------------------------------------------------------------------------
|
||||
@@ -384,6 +391,7 @@ 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"
|
||||
@@ -391,6 +399,7 @@ 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"
|
||||
@@ -398,6 +407,10 @@ 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])
|
||||
@@ -633,6 +646,7 @@ 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
|
||||
|
||||
@@ -19,6 +19,7 @@ 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,
|
||||
@@ -145,7 +146,11 @@ def ldap2replica_zone_keys_sync(ldapkeydb, localhsm):
|
||||
|
||||
|
||||
# IPA framework initialization
|
||||
standard_logging_setup(debug=True)
|
||||
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")
|
||||
ipalib.api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
|
||||
ipalib.api.finalize()
|
||||
|
||||
|
||||
@@ -16,14 +16,31 @@ 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
|
||||
standard_logging_setup(debug=True)
|
||||
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")
|
||||
api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
|
||||
api.finalize()
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ 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
|
||||
@@ -675,7 +676,11 @@ def cleanup_ldap_zone(ldap, dns_dn, zone_name):
|
||||
|
||||
|
||||
# IPA framework initialization
|
||||
standard_logging_setup(debug=True)
|
||||
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")
|
||||
ipalib.api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True)
|
||||
ipalib.api.finalize()
|
||||
|
||||
|
||||
@@ -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,6 +41,12 @@ 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
|
||||
@@ -84,6 +90,12 @@ 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
|
||||
|
||||
@@ -199,6 +199,7 @@ static const struct {
|
||||
{ "otp", IPADB_USER_AUTH_OTP },
|
||||
{ "pkinit", IPADB_USER_AUTH_PKINIT },
|
||||
{ "hardened", IPADB_USER_AUTH_HARDENED },
|
||||
{ "idp", IPADB_USER_AUTH_IDP },
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -221,6 +222,10 @@ 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);
|
||||
}
|
||||
@@ -792,8 +797,47 @@ 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 != 8) && \
|
||||
(KRB5_KDB_DAL_MAJOR_VERSION != 9)
|
||||
#error unsupported DAL major version
|
||||
#endif
|
||||
@@ -89,6 +89,7 @@ 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 {
|
||||
@@ -96,6 +97,7 @@ 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,
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jitter(krb5_deltat baseline, krb5_deltat *lifetime_out)
|
||||
return;
|
||||
}
|
||||
|
||||
*lifetime_out = baseline - offset % JITTER_WINDOW_SECONDS;
|
||||
*lifetime_out = baseline - abs(offset) % JITTER_WINDOW_SECONDS;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
@@ -89,8 +89,9 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
|
||||
|
||||
ua = ied->user_auth;
|
||||
|
||||
/* If no mechanisms are set, allow every auth method */
|
||||
if (ua == IPADB_USER_AUTH_NONE) {
|
||||
/* 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)) {
|
||||
jitter(ONE_DAY_SECONDS, lifetime_out);
|
||||
kerr = 0;
|
||||
goto done;
|
||||
@@ -133,6 +134,15 @@ 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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +162,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(ONE_DAY_SECONDS, lifetime_out);
|
||||
jitter(client->max_life, lifetime_out);
|
||||
}
|
||||
|
||||
if (pol_limits->max_renewable_life != 0) {
|
||||
|
||||
+54
-311
@@ -982,11 +982,11 @@ static krb5_error_code ipadb_get_pac_attrs_blob(TALLOC_CTX *mem_ctx,
|
||||
|
||||
#endif
|
||||
|
||||
static 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_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,7 +1148,8 @@ static 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));
|
||||
@@ -1180,7 +1181,7 @@ done:
|
||||
return kerr;
|
||||
}
|
||||
|
||||
static bool is_cross_realm_krbtgt(krb5_const_principal princ)
|
||||
bool ipadb_is_cross_realm_krbtgt(krb5_const_principal princ)
|
||||
{
|
||||
if ((princ->length != 2) ||
|
||||
(princ->data[0].length != 6) ||
|
||||
@@ -1696,7 +1697,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_POLICY;
|
||||
return KRB5KDC_ERR_TGT_REVOKED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1708,7 +1709,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_POLICY;
|
||||
return KRB5KDC_ERR_TGT_REVOKED;
|
||||
}
|
||||
|
||||
ied = (struct ipadb_e_data *)client_actual->e_data;
|
||||
@@ -1742,7 +1743,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_POLICY;
|
||||
kerr = KRB5KDC_ERR_TGT_REVOKED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -2004,22 +2005,43 @@ 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) {
|
||||
/* 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2202,17 +2224,17 @@ done:
|
||||
return kerr;
|
||||
}
|
||||
|
||||
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_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;
|
||||
@@ -2242,7 +2264,7 @@ static 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 (is_cross_realm_krbtgt(krbtgt->princ)) {
|
||||
if (ipadb_is_cross_realm_krbtgt(krbtgt->princ)) {
|
||||
/* krbtgt from a trusted realm */
|
||||
is_cross_realm = true;
|
||||
|
||||
@@ -2403,104 +2425,6 @@ 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)
|
||||
{
|
||||
@@ -2630,187 +2554,6 @@ 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)
|
||||
{
|
||||
|
||||
@@ -55,4 +55,22 @@ 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 *_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);
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ static char *std_principal_attrs[] = {
|
||||
IPA_KRB_AUTHZ_DATA_ATTR,
|
||||
IPA_USER_AUTH_TYPE,
|
||||
"ipatokenRadiusConfigLink",
|
||||
"ipaIdpConfigLink",
|
||||
"krbAuthIndMaxTicketLife",
|
||||
"krbAuthIndMaxRenewableAge",
|
||||
"ipaNTSecurityIdentifier",
|
||||
@@ -316,8 +317,31 @@ static void ipadb_validate_radius(struct ipadb_context *ipactx,
|
||||
"ipatokenRadiusConfigLink");
|
||||
if (vals == NULL || vals[0] == NULL)
|
||||
*ua &= ~IPADB_USER_AUTH_RADIUS;
|
||||
else
|
||||
*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;
|
||||
|
||||
if (vals != NULL)
|
||||
ldap_value_free_len(vals);
|
||||
@@ -355,6 +379,7 @@ 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;
|
||||
}
|
||||
@@ -537,6 +562,8 @@ 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",
|
||||
@@ -547,6 +574,8 @@ 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},
|
||||
};
|
||||
|
||||
@@ -556,7 +585,13 @@ static void ipadb_parse_authind_policies(krb5_context kcontext,
|
||||
}
|
||||
|
||||
for (size_t i = 0; life_authind_map[i].attribute != NULL; i++) {
|
||||
if (ua & life_authind_map[i].flag) {
|
||||
/* 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)) {
|
||||
|
||||
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
||||
life_authind_map[i].attribute,
|
||||
&result);
|
||||
@@ -583,6 +618,7 @@ 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;
|
||||
@@ -788,17 +824,18 @@ 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 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)) {
|
||||
/* 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)) {
|
||||
/* This is the same behavior as ENOENT below. */
|
||||
ipa_krb5_free_key_data(res_key_data, result);
|
||||
break;
|
||||
@@ -960,6 +997,11 @@ 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) {
|
||||
@@ -1251,6 +1293,8 @@ 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);
|
||||
@@ -1278,6 +1322,15 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ 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
|
||||
ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c \
|
||||
oauth2.c
|
||||
|
||||
%.socket: %.socket.in
|
||||
@sed -e 's|@krb5rundir[@]|$(krb5rundir)|g' \
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
|
||||
#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__)
|
||||
@@ -36,11 +43,28 @@
|
||||
|
||||
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 {
|
||||
@@ -48,6 +72,9 @@ struct otpd_queue_item {
|
||||
char *uid;
|
||||
char *ipatokenRadiusUserName;
|
||||
char *ipatokenRadiusConfigLink;
|
||||
char *ipaidpSub;
|
||||
char *ipaidpConfigLink;
|
||||
char **ipauserauthtypes;
|
||||
char *other;
|
||||
} user;
|
||||
|
||||
@@ -58,6 +85,28 @@ 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;
|
||||
};
|
||||
|
||||
@@ -98,6 +147,10 @@ struct otpd_context {
|
||||
struct otpd_queue requests;
|
||||
struct otpd_queue responses;
|
||||
} bind;
|
||||
|
||||
struct {
|
||||
struct otpd_queue states;
|
||||
} oauth2_state;
|
||||
};
|
||||
|
||||
extern struct otpd_context ctx;
|
||||
@@ -143,8 +196,13 @@ 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);
|
||||
@@ -242,7 +242,8 @@ int main(int argc, char **argv)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ctx.vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL);
|
||||
ctx.vctx = verto_default(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL
|
||||
| VERTO_EV_TYPE_CHILD);
|
||||
if (ctx.vctx == NULL) {
|
||||
otpd_log_err(ENOMEM, "Unable to initialize event loop");
|
||||
goto error;
|
||||
@@ -342,4 +343,3 @@ error:
|
||||
krb5_free_context(ctx.kctx);
|
||||
return ctx.exitstatus;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,614 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -24,8 +24,11 @@
|
||||
* 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
|
||||
@@ -65,6 +68,69 @@ 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)
|
||||
@@ -112,6 +178,21 @@ 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) {
|
||||
@@ -122,6 +203,101 @@ 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)
|
||||
|
||||
+162
-14
@@ -31,6 +31,7 @@
|
||||
#define _GNU_SOURCE 1 /* for asprintf() */
|
||||
#include "internal.h"
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DEFAULT_TIMEOUT 15
|
||||
#define DEFAULT_RETRIES 3
|
||||
@@ -39,6 +40,9 @@ static char *user[] = {
|
||||
"uid",
|
||||
"ipatokenRadiusUserName",
|
||||
"ipatokenRadiusConfigLink",
|
||||
"ipaidpSub",
|
||||
"ipaidpConfigLink",
|
||||
"ipauserauthtype",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -51,6 +55,37 @@ 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)
|
||||
{
|
||||
@@ -76,6 +111,7 @@ 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)
|
||||
@@ -86,9 +122,20 @@ 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,
|
||||
@@ -98,6 +145,7 @@ 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;
|
||||
@@ -107,7 +155,6 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
}
|
||||
|
||||
if (i == LDAP_SUCCESS) {
|
||||
item->sent++;
|
||||
push = &ctx.query.responses;
|
||||
}
|
||||
|
||||
@@ -115,6 +162,78 @@ 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)
|
||||
{
|
||||
@@ -126,6 +245,7 @@ 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);
|
||||
|
||||
@@ -150,16 +270,19 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
goto egress;
|
||||
|
||||
err = NULL;
|
||||
switch (item->sent) {
|
||||
case 1:
|
||||
switch (item->ldap_query) {
|
||||
case LDAP_QUERY_USER:
|
||||
err = otpd_parse_user(ldp, entry, item);
|
||||
break;
|
||||
case 2:
|
||||
case LDAP_QUERY_RADIUS:
|
||||
err = otpd_parse_radius(ldp, entry, item);
|
||||
break;
|
||||
case 3:
|
||||
case LDAP_QUERY_RADIUS_USERMAP:
|
||||
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;
|
||||
@@ -181,14 +304,14 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
|
||||
item->msgid = -1;
|
||||
|
||||
switch (item->sent) {
|
||||
case 1:
|
||||
switch (item->ldap_query) {
|
||||
case LDAP_QUERY_USER:
|
||||
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 2:
|
||||
case LDAP_QUERY_RADIUS:
|
||||
otpd_log_req(item->req, "radius query end: %s",
|
||||
item->error == NULL
|
||||
? item->radius.ipatokenRadiusServer
|
||||
@@ -197,22 +320,47 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
item->radius.ipatokenRadiusSecret == NULL)
|
||||
goto egress;
|
||||
break;
|
||||
case 3:
|
||||
case LDAP_QUERY_RADIUS_USERMAP:
|
||||
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;
|
||||
}
|
||||
|
||||
if (item->error != NULL)
|
||||
goto egress;
|
||||
|
||||
if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) {
|
||||
/* 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 (item->sent == 2 &&
|
||||
} 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) {
|
||||
|
||||
push = &ctx.query.requests;
|
||||
event = ctx.query.io;
|
||||
goto egress;
|
||||
} else if (item->ldap_query == LDAP_QUERY_RADIUS &&
|
||||
item->radius.ipatokenUserMapAttribute != NULL &&
|
||||
item->user.ipatokenRadiusUserName == NULL) {
|
||||
push = &ctx.query.requests;
|
||||
|
||||
@@ -46,6 +46,8 @@ 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;
|
||||
|
||||
@@ -54,9 +56,34 @@ 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);
|
||||
|
||||
@@ -166,6 +166,7 @@ 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) {
|
||||
@@ -191,6 +192,7 @@ 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)));
|
||||
|
||||
@@ -59,7 +59,7 @@ def main():
|
||||
rsp.DecodePacket(buf)
|
||||
pkt.VerifyReply(rsp)
|
||||
|
||||
proc.terminate() # pylint: disable=E1101
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+117
-40
@@ -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_UTF16, CH_UNIX,
|
||||
if (!convert_string_talloc(tmp_ctx, CH_UTF16LE, CH_UNIX,
|
||||
iopw.current.array[0].AuthInfo.clear.password,
|
||||
iopw.current.array[0].AuthInfo.clear.size,
|
||||
&trustpw, &converted_size)) {
|
||||
@@ -3581,12 +3581,14 @@ 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) {
|
||||
@@ -3613,17 +3615,15 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
|
||||
}
|
||||
|
||||
len = ldap_count_values_len(usernames);
|
||||
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;
|
||||
}
|
||||
/* 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,6 +3644,31 @@ 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;
|
||||
@@ -3712,41 +3737,93 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state,
|
||||
}
|
||||
|
||||
|
||||
/* Force machine accounts to be workstation trust type */
|
||||
pdb_set_acct_ctrl(sampass, machine_account ? ACB_WSTRUST : ACB_NORMAL,
|
||||
PDB_SET);
|
||||
/* 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);
|
||||
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!retval) {
|
||||
DEBUG(5, ("Failed to read NT hash form LDAP response.\n"));
|
||||
}
|
||||
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 (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"));
|
||||
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 {
|
||||
DEBUG(5, ("Failed to read NT hash form LDAP response.\n"));
|
||||
}
|
||||
/* FIXME: */
|
||||
if (!pdb_set_pass_last_set_time(sampass, (time_t) 1, PDB_SET)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ SUBDIRS = \
|
||||
ipa-cldap \
|
||||
ipa-dns \
|
||||
ipa-enrollment \
|
||||
ipa-graceperiod \
|
||||
ipa-lockout \
|
||||
ipa-modrdn \
|
||||
ipa-otp-counter \
|
||||
|
||||
@@ -287,6 +287,7 @@ done:
|
||||
ipa_cldap_respond(ctx, req, &reply);
|
||||
|
||||
ipa_cldap_free_kvps(&req->kvps);
|
||||
free(reply.bv_val);
|
||||
free(req);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@
|
||||
#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)
|
||||
@@ -886,8 +894,8 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx,
|
||||
}
|
||||
|
||||
if (request_type == REQ_FULL_WITH_GROUPS) {
|
||||
ret = sss_nss_getorigbyname_timeout(pwd.pw_name, get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbyusername_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");
|
||||
@@ -965,8 +973,8 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx,
|
||||
}
|
||||
|
||||
if (request_type == REQ_FULL_WITH_GROUPS) {
|
||||
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbygroupname_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");
|
||||
@@ -1115,8 +1123,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
|
||||
}
|
||||
|
||||
if (request_type == REQ_FULL_WITH_GROUPS) {
|
||||
ret = sss_nss_getorigbyname_timeout(pwd.pw_name, get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbyusername_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");
|
||||
@@ -1152,8 +1160,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
|
||||
}
|
||||
|
||||
if (request_type == REQ_FULL_WITH_GROUPS) {
|
||||
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbygroupname_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");
|
||||
@@ -1266,9 +1274,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_getorigbyname_timeout(pwd.pw_name,
|
||||
get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbyusername_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");
|
||||
@@ -1357,8 +1365,8 @@ static int handle_groupname_request(struct ipa_extdom_ctx *ctx,
|
||||
}
|
||||
|
||||
if (request_type == REQ_FULL_WITH_GROUPS) {
|
||||
ret = sss_nss_getorigbyname_timeout(grp.gr_name, get_timeout(ctx),
|
||||
&kv_list, &id_type);
|
||||
ret = sss_nss_getorigbygroupname_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) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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)
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
@@ -0,0 +1,507 @@
|
||||
/** 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,6 +96,14 @@ 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)
|
||||
@@ -1228,7 +1236,7 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
|
||||
int kvno;
|
||||
char *svcname;
|
||||
bool allowed_access = false;
|
||||
bool is_nthash_allowed = false;
|
||||
bool nthash_allowed = false;
|
||||
struct berval *bvp = NULL;
|
||||
LDAPControl new_ctrl;
|
||||
|
||||
@@ -1305,8 +1313,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 */
|
||||
is_nthash_allowed = (0 == strncmp("cifs/", serviceName, 5));
|
||||
filter_keys(krbcfg, kset, is_nthash_allowed);
|
||||
nthash_allowed = is_nthash_allowed(serviceName, bindDN);
|
||||
filter_keys(krbcfg, kset, nthash_allowed);
|
||||
|
||||
/* check if we have any left */
|
||||
if (kset->num_keys == 0) {
|
||||
@@ -1615,7 +1623,7 @@ static int ipapwd_getkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
|
||||
struct berval *bvp = NULL;
|
||||
LDAPControl new_ctrl;
|
||||
bool wantold = false;
|
||||
bool is_nthash_allowed = false;
|
||||
bool nthash_allowed = false;
|
||||
|
||||
/* Get Bind DN */
|
||||
slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn);
|
||||
@@ -1712,8 +1720,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 */
|
||||
is_nthash_allowed = (0 == strncmp("cifs/", service_name, 5));
|
||||
filter_enctypes(krbcfg, kenctypes, &num_kenctypes, is_nthash_allowed);
|
||||
nthash_allowed = is_nthash_allowed(service_name, bind_dn);
|
||||
filter_enctypes(krbcfg, kenctypes, &num_kenctypes, nthash_allowed);
|
||||
|
||||
/* check if we have any left */
|
||||
if (num_kenctypes == 0 && kenctypes != NULL) {
|
||||
|
||||
@@ -1071,6 +1071,7 @@ 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");
|
||||
|
||||
@@ -1167,6 +1168,13 @@ 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)
|
||||
|
||||
@@ -23,12 +23,6 @@ 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"
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
section ul.simple li {
|
||||
list-style: disc;
|
||||
margin-left: 1em;
|
||||
}
|
||||
+7
-2
@@ -40,7 +40,8 @@ extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'm2r',
|
||||
'sphinxcontrib.plantuml',
|
||||
'm2r2',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
@@ -71,7 +72,11 @@ 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_static_path = ['_static']
|
||||
|
||||
html_css_files = [
|
||||
'css/ipa.css',
|
||||
]
|
||||
|
||||
# -- Options for sources -----------------------------------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
# 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
|
||||
@@ -0,0 +1,403 @@
|
||||
# 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.
|
||||
@@ -13,8 +13,11 @@ 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
|
||||
@@ -18,6 +18,7 @@ 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,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# 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
|
||||
+167
-76
@@ -1,12 +1,8 @@
|
||||
# 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 engies to implement rootless containers.
|
||||
by container runtime engines to implement rootless containers.
|
||||
Traditionally subordinate id ranges are configured in ``/etc/subuid``
|
||||
and ``/etc/subgid``.
|
||||
|
||||
@@ -68,6 +64,7 @@ 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
|
||||
@@ -76,14 +73,13 @@ 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. Auto-assignment is currently emulated
|
||||
until 389-DS has been extended to support DNA with step interval.
|
||||
* subids are auto-assigned from 389-DS DNA plugin (dnaInterval).
|
||||
* 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
|
||||
@@ -91,6 +87,29 @@ 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
|
||||
|
||||
```
|
||||
@@ -244,11 +263,51 @@ 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
|
||||
|
||||
Subordiante ids and ACIs are stored in the new subtree
|
||||
Subordinate 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
|
||||
@@ -268,9 +327,8 @@ 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.
|
||||
|
||||
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.
|
||||
A new option ``dnaInterval`` will tell the DNA plug-in to use the value of
|
||||
entry attributes as interval size.
|
||||
|
||||
|
||||
## IPA plugins and commands
|
||||
@@ -298,30 +356,30 @@ Topic commands:
|
||||
```
|
||||
|
||||
```text
|
||||
$ ipa subid-generate --owner testuser9
|
||||
$ ipa subid-generate --owner testuser
|
||||
-----------------------------------------------------------
|
||||
Added subordinate id "aa28f132-457c-488b-82e1-d123727e4f81"
|
||||
Added subordinate id "2cf2c0db-aa9d-45d8-acbd-118aba0c1db3"
|
||||
-----------------------------------------------------------
|
||||
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
|
||||
Unique ID: 2cf2c0db-aa9d-45d8-acbd-118aba0c1db3
|
||||
Description: auto-assigned subid
|
||||
Owner: testuser9
|
||||
SubUID range start: 3922132992
|
||||
Owner: testuser
|
||||
SubUID range start: 2147680256
|
||||
SubUID range size: 65536
|
||||
SubGID range start: 3922132992
|
||||
SubGID range start: 2147680256
|
||||
SubGID range size: 65536
|
||||
```
|
||||
|
||||
|
||||
```text
|
||||
$ ipa subid-find --owner testuser9
|
||||
$ ipa subid-find --owner testuser
|
||||
------------------------
|
||||
1 subordinate id matched
|
||||
------------------------
|
||||
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
|
||||
Owner: testuser9
|
||||
SubUID range start: 3922132992
|
||||
Unique ID: 2cf2c0db-aa9d-45d8-acbd-118aba0c1db3
|
||||
Owner: testuser
|
||||
SubUID range start: 2147680256
|
||||
SubUID range size: 65536
|
||||
SubGID range start: 3922132992
|
||||
SubGID range start: 2147680256
|
||||
SubGID range size: 65536
|
||||
----------------------------
|
||||
Number of entries returned 1
|
||||
@@ -334,22 +392,22 @@ $ ipa -vv subid-stats
|
||||
ipa: INFO: Response: {
|
||||
"error": null,
|
||||
"id": 0,
|
||||
"principal": "admin@IPASUBID.TEST",
|
||||
"principal": "admin@TESTREALM.TEST",
|
||||
"result": {
|
||||
"result": {
|
||||
"assigned_subids": 20,
|
||||
"assigned_subids": 1,
|
||||
"baseid": 2147483648,
|
||||
"dna_remaining": 4293394434,
|
||||
"dna_remaining": 65527,
|
||||
"rangesize": 2147352576,
|
||||
"remaining_subids": 65512
|
||||
"remaining_subids": 0
|
||||
},
|
||||
"summary": "65532 remaining subordinate id ranges"
|
||||
"summary": "0 remaining subordinate id ranges"
|
||||
},
|
||||
"version": "4.10.0.dev"
|
||||
"version": "4.9.9"
|
||||
}
|
||||
-------------------------------------
|
||||
65532 remaining subordinate id ranges
|
||||
-------------------------------------
|
||||
---------------------------------
|
||||
0 remaining subordinate id ranges
|
||||
---------------------------------
|
||||
```
|
||||
|
||||
## Permissions, Privileges, Roles
|
||||
@@ -418,8 +476,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 (**TODO** not implemented yet). The command picks
|
||||
the name of the current user principal automatically.
|
||||
action in the web UI. The command picks the name of the current user
|
||||
principal automatically.
|
||||
|
||||
```shell
|
||||
$ ipa subid-generate
|
||||
@@ -453,7 +511,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)
|
||||
@@ -475,30 +533,65 @@ The ipa-subids command was successful
|
||||
|
||||
### Find and match users by any subordinate id
|
||||
|
||||
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.
|
||||
The ``user-find`` command is now able to search for users matching a
|
||||
particular subordinate id when using ``--in-subids`` option:
|
||||
|
||||
```text
|
||||
$ ipa subid-match --subuid=2147549183
|
||||
------------------------
|
||||
1 subordinate id matched
|
||||
------------------------
|
||||
Name: asmith-auto
|
||||
Owner: asmith
|
||||
SubUID range start: 2147483648
|
||||
SubUID range size: 65536
|
||||
SubGID range start: 2147483648
|
||||
SubGID range size: 65536
|
||||
$ 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
|
||||
----------------------------
|
||||
$ ipa user-match-subid --subuid=2147549184
|
||||
Name: bjones-auto
|
||||
Owner: bjones
|
||||
SubUID range start: 2147549184
|
||||
```
|
||||
|
||||
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
|
||||
------------------------
|
||||
1 subordinate id matched
|
||||
------------------------
|
||||
Unique ID: 8a7c0808-375b-4643-9582-e9c851a4b45d
|
||||
Owner: asmith-auto
|
||||
SubUID range start: 2147745792
|
||||
SubUID range size: 65536
|
||||
SubGID range start: 2147549184
|
||||
SubGID range start: 2147745792
|
||||
SubGID range size: 65536
|
||||
----------------------------
|
||||
Number of entries returned 1
|
||||
@@ -553,6 +646,18 @@ 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
|
||||
@@ -586,27 +691,13 @@ ipaSubGidCount: 65536
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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
|
||||
* 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)).
|
||||
+11
-4
@@ -1,8 +1,11 @@
|
||||
## m2r is not compatible with Sphinx 3.x yet
|
||||
sphinx >2.1,<3.0
|
||||
wheel
|
||||
|
||||
## convert markdown to rest
|
||||
m2r
|
||||
sphinx > 3.0
|
||||
|
||||
# convert markdown to rest
|
||||
# m2r2 fork is active in development and works with
|
||||
# newer sphinx versions
|
||||
m2r2
|
||||
|
||||
## ipa dependencies
|
||||
dnspython
|
||||
@@ -26,3 +29,7 @@ lxml
|
||||
|
||||
## No sufficient PyPI packages
|
||||
# dogtag-pki
|
||||
|
||||
# Sequence diagrams generator
|
||||
sphinxcontrib-plantuml
|
||||
pillow
|
||||
+7
-1
@@ -6,7 +6,6 @@ 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
|
||||
@@ -18,4 +17,11 @@ 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
|
||||
@@ -8,6 +8,8 @@ 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
|
||||
------------------------
|
||||
|
||||
@@ -97,6 +99,7 @@ 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
|
||||
@@ -126,7 +129,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.3 or later, Kerberos ticket policy allows an administrator to
|
||||
With FreeIPA 4.8.4 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
|
||||
@@ -253,12 +256,70 @@ 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 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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,772 @@
|
||||
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.
|
||||
Vendored
+34
-15
@@ -3,7 +3,11 @@
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
|
||||
config.vm.box = "netoarmando/freeipa-workshop"
|
||||
# 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.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
@@ -12,22 +16,21 @@ Vagrant.configure(2) do |config|
|
||||
libvirt.memory = 1024
|
||||
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.provider :virtualbox do |virtualbox|
|
||||
virtualbox.memory = 1536
|
||||
end
|
||||
|
||||
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|
|
||||
@@ -39,7 +42,17 @@ 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: 'echo "nameserver 192.168.33.10" > /etc/resolv.conf'
|
||||
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
|
||||
|
||||
end
|
||||
|
||||
config.vm.define "client" do |client|
|
||||
@@ -51,7 +64,9 @@ 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: 'echo "nameserver 192.168.33.10" > /etc/resolv.conf'
|
||||
inline: 'printf "DNS=192.168.33.10\nDomains=~." >> /etc/systemd/resolved.conf'
|
||||
client.vm.provision "shell",
|
||||
inline: 'systemctl restart systemd-resolved'
|
||||
client.vm.provision "shell",
|
||||
inline: 'sudo sed -i "s/^/#/g" /etc/httpd/conf.d/ssl.conf'
|
||||
client.vm.provision "shell",
|
||||
@@ -60,4 +75,8 @@ 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
|
||||
@@ -1,14 +0,0 @@
|
||||
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')
|
||||
+23
-33
@@ -8,55 +8,45 @@ Requirements
|
||||
------------
|
||||
|
||||
- Install packer (http://packer.io/)
|
||||
- Clone the packer-templates repository
|
||||
(https://github.com/kaorimatz/packer-templates)
|
||||
- Install Vagrant, libvirt and VirtualBox
|
||||
- Clone the Fedora kickstarts repo (https://pagure.io/fedora-kickstarts)
|
||||
|
||||
|
||||
Packer template
|
||||
---------------
|
||||
|
||||
Apply the following changes to the ``fedora-28-x86_64.json`` packer
|
||||
template:
|
||||
Packer template ``packer-template-fedora.json`` requires Fedora 34 kickstart file
|
||||
used by Fedora to build vagrant images:
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
||||
Building the virtualbox image
|
||||
Building the vagrant images
|
||||
-----------------------------
|
||||
|
||||
Build the images::
|
||||
|
||||
$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.
|
||||
$ cd $FREEIPA_REPO/doc/workshop
|
||||
$ BIN_PACKER build packer-template-fedora.json
|
||||
|
||||
|
||||
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
|
||||
Uploading boxes to Vagrant Cloud
|
||||
----------------------------------
|
||||
|
||||
Vagrant by default looks for boxes in a directory called *Atlas*.
|
||||
Vagrant by default looks for boxes in a directory called *Vagrant Cloud*.
|
||||
Therefore is is good to make images available there, so that people
|
||||
can easily download them as part of workshop preparation.
|
||||
|
||||
|
||||
@@ -31,5 +31,5 @@ Vagrant boxes
|
||||
-------------
|
||||
|
||||
See ``building.rst`` for instructions on building Vagrant boxes.
|
||||
At time of writing, the ``netoarmando/freeipa-workshop`` box is
|
||||
FreeIPA 4.6.90 / Fedora 28, for the VirtualBox and libvirt providers.
|
||||
At time of writing, the ``freeipa/freeipa-workshop`` box is
|
||||
FreeIPA 4.9.3 / Fedora 34, for the VirtualBox and libvirt providers.
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
Local webserver
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
Local webserver
|
||||
---------------
|
||||
|
||||
Drop firewall::
|
||||
|
||||
sudo firewall-cmd --add-service=http
|
||||
|
||||
|
||||
DNS / hosts file issues
|
||||
=======================
|
||||
-----------------------
|
||||
|
||||
Flush cache
|
||||
-----------
|
||||
^^^^^^^^^^^
|
||||
|
||||
Note: some resolvers cache NX.
|
||||
|
||||
@@ -24,7 +27,7 @@ Windows::
|
||||
|
||||
|
||||
Virtual network
|
||||
===============
|
||||
---------------
|
||||
|
||||
If ``Vagrant::Errors::NetworkCollision`` occurs, try deleting host
|
||||
network device, e.g.::
|
||||
@@ -39,7 +42,7 @@ up::
|
||||
|
||||
|
||||
Vagrant
|
||||
=======
|
||||
-------
|
||||
|
||||
If ``vagrant up`` fails to SSH into VM, delete
|
||||
``~/.vagrant.d/insecure_private_key``. See
|
||||
@@ -47,7 +50,7 @@ https://stackoverflow.com/questions/28284112/.
|
||||
|
||||
|
||||
mod_lookup_identity
|
||||
===================
|
||||
-------------------
|
||||
|
||||
To flush cache::
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/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
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/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"
|
||||
@@ -11,10 +10,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')])
|
||||
logged_in = 'REMOTE_USER' in environ
|
||||
remote_user = environ.get('REMOTE_USER')
|
||||
|
||||
if logged_in:
|
||||
yield "LOGGED IN AS: {}\n".format(environ['REMOTE_USER']).encode('utf8')
|
||||
if remote_user is not None:
|
||||
yield "LOGGED IN AS: {}\n".format(remote_user).encode('utf8')
|
||||
else:
|
||||
yield b"NOT LOGGED IN\n"
|
||||
|
||||
@@ -37,3 +36,12 @@ 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
|
||||
+13
-26
@@ -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,14 +119,11 @@ 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
|
||||
$ systemctl restart polkit
|
||||
|
||||
More information: https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/
|
||||
|
||||
Otherwise, you will use VirtualBox and the ``virtualbox`` provider.
|
||||
VirtualBox needs to build kernel modules, and that means that you must
|
||||
@@ -150,7 +147,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-5.2
|
||||
$ sudo dnf install -y VirtualBox-6.1
|
||||
|
||||
Finally, load the kernel modules (you may need to restart your system for this to work)::
|
||||
|
||||
@@ -163,7 +160,7 @@ Mac OS X
|
||||
Install Vagrant for Mac OS X from
|
||||
https://www.vagrantup.com/downloads.html.
|
||||
|
||||
Install VirtualBox 5.2 for **OS X hosts** from
|
||||
Install VirtualBox 6.1 for **OS X hosts** from
|
||||
https://www.virtualbox.org/wiki/Downloads.
|
||||
|
||||
Install Git from https://git-scm.com/download/mac or via your
|
||||
@@ -173,23 +170,12 @@ preferred package manager.
|
||||
Debian / Ubuntu
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Install Vagrant and Git::
|
||||
Install Vagrant, Git and VirtualBox::
|
||||
|
||||
$ sudo apt-get install -y vagrant git
|
||||
$ sudo apt-get install -y virtualbox-6.1
|
||||
|
||||
**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
|
||||
If VirtualBox 6.1 was not available in the official packages for
|
||||
your release, follow the instructions at
|
||||
https://www.virtualbox.org/wiki/Linux_Downloads to install it.
|
||||
|
||||
@@ -200,7 +186,7 @@ Windows
|
||||
Install Vagrant via the ``.msi`` available from
|
||||
https://www.vagrantup.com/downloads.html.
|
||||
|
||||
Install VirtualBox 5.2 for **Windows hosts** from
|
||||
Install VirtualBox for **Windows hosts** from
|
||||
https://www.virtualbox.org/wiki/Downloads.
|
||||
|
||||
You will also need to install an SSH client, and Git. Git for
|
||||
@@ -216,18 +202,19 @@ workshop, which you will need locally.
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/freeipa/freeipa-workshop.git
|
||||
$ git clone https://github.com/freeipa/freeipa.git
|
||||
$ cd freeipa/doc/workshop
|
||||
|
||||
|
||||
Fetch Vagrant box
|
||||
-----------------
|
||||
|
||||
Please fetch the Vagrant box prior to the workshop. It is > 600MB
|
||||
Please fetch the Vagrant box prior to the workshop. It is > 700MB
|
||||
so it may not be feasible to download it during the workshop.
|
||||
|
||||
::
|
||||
|
||||
$ vagrant box add netoarmando/freeipa-workshop
|
||||
$ vagrant box add freeipa/freeipa-workshop
|
||||
|
||||
|
||||
Add hosts file entries
|
||||
|
||||
+26
-8
@@ -149,8 +149,8 @@
|
||||
# RHEL 8.2+, F32+ has 3.58
|
||||
%global nss_version 3.44.0-4
|
||||
|
||||
# RHEL 8.3+, F32+
|
||||
%global sssd_version 2.4.0
|
||||
# RHEL 8.7+, F35+, adds IdP integration
|
||||
%global sssd_version 2.7.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,7 +323,9 @@ BuildRequires: python3-wheel
|
||||
|
||||
%if %{with doc}
|
||||
BuildRequires: python3-sphinx
|
||||
BuildRequires: python3-m2r
|
||||
BuildRequires: plantuml
|
||||
BuildRequires: fontconfig
|
||||
BuildRequires: google-noto-sans-vf-fonts
|
||||
%endif
|
||||
|
||||
#
|
||||
@@ -651,7 +653,12 @@ Requires: python3-sssdconfig >= %{sssd_version}
|
||||
Requires: cyrus-sasl-gssapi%{?_isa}
|
||||
Requires: chrony
|
||||
Requires: krb5-workstation >= %{krb5_version}
|
||||
Requires: authselect >= 0.4-2
|
||||
# authselect: sssd profile with-subid
|
||||
%if 0%{?fedora} >= 36
|
||||
Requires: authselect >= 1.4.0
|
||||
%else
|
||||
Requires: authselect >= 1.2.5
|
||||
%endif
|
||||
Requires: curl
|
||||
# NIS domain name config: /usr/lib/systemd/system/*-domainname.service
|
||||
# All Fedora 28+ and RHEL8+ contain the service in hostname package
|
||||
@@ -663,6 +670,7 @@ 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
|
||||
@@ -695,6 +703,12 @@ 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
|
||||
@@ -1053,6 +1067,7 @@ 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
|
||||
@@ -1236,6 +1251,7 @@ 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"
|
||||
@@ -1411,6 +1427,7 @@ 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*
|
||||
@@ -1508,10 +1525,11 @@ fi
|
||||
%dir %{_sysconfdir}/ipa/html
|
||||
%config(noreplace) %{_sysconfdir}/ipa/html/ssbrowser.html
|
||||
%config(noreplace) %{_sysconfdir}/ipa/html/unauthorized.html
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-kdc-proxy.conf
|
||||
%ghost %attr(0640,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf
|
||||
%dir %{_sysconfdir}/ipa/http
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/httpd/ipa-rewrite.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/httpd/ipa.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/httpd/ipa-kdc-proxy.conf
|
||||
%ghost %attr(0640,root,root) %config(noreplace) %{_sysconfdir}/ipa/httpd/ipa-pki-proxy.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/kdcproxy/ipa-kdc-proxy.conf
|
||||
%ghost %attr(0644,root,root) %config(noreplace) %{_usr}/share/ipa/html/ca.crt
|
||||
%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-ext.conf
|
||||
|
||||
@@ -7,4 +7,6 @@ systemdtmpfiles_DATA = \
|
||||
CLEANFILES = $(systemdtmpfiles_DATA)
|
||||
|
||||
%: %.in Makefile
|
||||
cp '$(srcdir)/$@.in' $@
|
||||
sed \
|
||||
-e 's|@HTTPD_GROUP[@]|$(HTTPD_GROUP)|g' \
|
||||
'$(srcdir)/$@.in' >$@
|
||||
@@ -1,3 +1,3 @@
|
||||
d /run/ipa 0711 root root
|
||||
d /run/ipa/ccaches 6770 ipaapi ipaapi
|
||||
a+ /run/ipa/ccaches - - - - g:apache:rwx
|
||||
a+ /run/ipa/ccaches - - - - g:@HTTPD_GROUP@: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) #pylint: disable=no-member
|
||||
sys.stderr.buffer.write(result.raw_error_output)
|
||||
sys.stderr.flush()
|
||||
|
||||
syslog.syslog(syslog.LOG_NOTICE,
|
||||
|
||||
@@ -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) #pylint: disable=no-member
|
||||
sys.stderr.buffer.write(result.raw_error_output) #pylint: disable=no-member
|
||||
sys.stdout.buffer.write(result.raw_output)
|
||||
sys.stderr.buffer.write(result.raw_error_output)
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
@@ -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) 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')
|
||||
@@ -18,3 +18,17 @@ 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' )
|
||||
@@ -101,6 +101,7 @@ dist_app_DATA = \
|
||||
ipa-kdc-proxy.conf.template \
|
||||
ipa-pki-proxy.conf.template \
|
||||
ipa-rewrite.conf.template \
|
||||
ipa-vhost.conf.template \
|
||||
ipaca_default.ini \
|
||||
ipaca_customize.ini \
|
||||
ipaca_softhsm2.ini \
|
||||
|
||||
@@ -307,3 +307,11 @@ 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
|
||||
@@ -32,10 +32,6 @@ 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
|
||||
|
||||
@@ -8,7 +8,7 @@ objectClass: top
|
||||
cn: dns
|
||||
ipaConfigString: DNSVersion 1
|
||||
ipaDNSVersion: 2
|
||||
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: (targetattr = "*")(version 3.0; acl "Read DNS entries from a zone"; allow (read,search,compare) 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";)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# VERSION 16 - DO NOT REMOVE THIS LINE
|
||||
# VERSION 17 - 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">
|
||||
<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">
|
||||
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
|
||||
SSLVerifyClient none
|
||||
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# VERSION 32 - DO NOT REMOVE THIS LINE
|
||||
#
|
||||
# This file may be overwritten on upgrades.
|
||||
#
|
||||
|
||||
# FIXME: WSGISocketPrefix is a server-scope directive. The mod_wsgi package
|
||||
# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
|
||||
WSGISocketPrefix $WSGI_PREFIX_DIR
|
||||
|
||||
# Configure mod_wsgi handler for /ipa
|
||||
WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \
|
||||
user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 \
|
||||
lang=C.UTF-8 locale=C.UTF-8
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName $FQDN
|
||||
ServerAlias ipa-ca.$DOMAIN
|
||||
|
||||
# Logging
|
||||
CustomLog /var/log/httpd/ipa-access.log combined
|
||||
ErrorLog /var/log/httpd/ipa-error.log
|
||||
|
||||
IncludeOptional $HTTPD_IPA_PKI_PROXY_CONF
|
||||
Include $HTTPD_IPA_REWRITE_CONF
|
||||
Include $HTTPD_IPA_CONF
|
||||
</VirtualHost>
|
||||
|
||||
# the below section is only need if you have above 3 lines and want SSL
|
||||
<VirtualHost *:443>
|
||||
ServerName $FQDN
|
||||
ServerAlias ipa-ca.$DOMAIN
|
||||
|
||||
# Logging
|
||||
CustomLog /var/log/httpd/ipa-access.log combined
|
||||
ErrorLog /var/log/httpd/ipa-error.log
|
||||
|
||||
# SSL
|
||||
SSLEngine on
|
||||
SSLCertificateFile $HTTPD_CERT_FILE
|
||||
SSLCertificateKeyFile $HTTPD_KEY_FILE
|
||||
SSLCACertificateFile $IPA_CA_CRT
|
||||
SSLVerifyDepth $MOD_SSL_VERIFY_DEPTH
|
||||
|
||||
# FreeIPA
|
||||
IncludeOptional $HTTPD_IPA_KDCPROXY_CONF_SYMLINK
|
||||
IncludeOptional $HTTPD_IPA_PKI_PROXY_CONF
|
||||
Include $HTTPD_IPA_REWRITE_CONF
|
||||
Include $HTTPD_IPA_CONF
|
||||
</VirtualHost>
|
||||
@@ -35,19 +35,11 @@ AddOutputFilterByType DEFLATE text/html text/plain text/xml \
|
||||
application/javascript application/json text/css \
|
||||
application/x-font-ttf
|
||||
|
||||
# FIXME: WSGISocketPrefix is a server-scope directive. The mod_wsgi package
|
||||
# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
|
||||
WSGISocketPrefix $WSGI_PREFIX_DIR
|
||||
|
||||
# Configure mod_wsgi handler for /ipa
|
||||
WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \
|
||||
user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 \
|
||||
lang=C.UTF-8 locale=C.UTF-8
|
||||
WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py process-group=ipa \
|
||||
application-group=%{GLOBAL}
|
||||
WSGIScriptReloading Off
|
||||
|
||||
|
||||
# Turn off mod_msgi handler for errors, config, crl:
|
||||
<Location "/ipa/errors">
|
||||
SetHandler None
|
||||
|
||||
@@ -81,7 +81,6 @@ 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
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
[realms]
|
||||
$REALM = {
|
||||
master_key_type = aes256-cts
|
||||
master_key_type = $MASTER_KEY_TYPE
|
||||
supported_enctypes = $SUPPORTED_ENCTYPES
|
||||
max_life = 7d
|
||||
max_renewable_life = 14d
|
||||
acl_file = $KRB5KDC_KADM5_ACL
|
||||
|
||||
@@ -28,6 +28,8 @@ ${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
|
||||
|
||||
@@ -37,6 +39,7 @@ changetype: add
|
||||
objectClass: top
|
||||
objectClass: nsContainer
|
||||
objectClass: krbPwdPolicy
|
||||
objectClass: ipaPwdPolicy
|
||||
krbMinPwdLife: 3600
|
||||
krbPwdMinDiffChars: 0
|
||||
krbPwdMinLength: 8
|
||||
@@ -45,4 +48,4 @@ krbMaxPwdLife: 7776000
|
||||
krbPwdMaxFailure: 6
|
||||
krbPwdFailureCountInterval: 60
|
||||
krbPwdLockoutDuration: 600
|
||||
|
||||
passwordGraceLimit: -1
|
||||
@@ -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 as e:
|
||||
except errors.RequirementError:
|
||||
raise ScriptError(
|
||||
"Must have administrative privileges to setup AD trusts on server"
|
||||
)
|
||||
|
||||
@@ -131,6 +131,8 @@ 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):
|
||||
@@ -139,6 +141,7 @@ 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)
|
||||
@@ -168,7 +171,9 @@ 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.")
|
||||
|
||||
@@ -71,18 +71,21 @@ def parse_options():
|
||||
|
||||
if len(args):
|
||||
n = len(args) - 1
|
||||
for cmd in commands:
|
||||
for cmd, args_info in commands.items():
|
||||
if cmd == args[0]:
|
||||
v = commands[cmd]
|
||||
err = None
|
||||
if n < v[0]:
|
||||
err = v[3]
|
||||
elif n > v[1]:
|
||||
if n < args_info[0]:
|
||||
err = args_info[3]
|
||||
elif n > args_info[1]:
|
||||
err = "too many arguments"
|
||||
else:
|
||||
valid_syntax = True
|
||||
if err:
|
||||
parser.error("Invalid syntax: %s\nUsage: %s [options] %s" % (err, cmd, v[2]))
|
||||
parser.error(
|
||||
"Invalid syntax: %s\nUsage: %s [options] %s" % (
|
||||
err, cmd, args_info[2]
|
||||
)
|
||||
)
|
||||
|
||||
if not valid_syntax:
|
||||
cmdstr = " | ".join(commands.keys())
|
||||
|
||||
@@ -184,7 +184,7 @@ class IPACustodiaTester:
|
||||
pkey = JWK(**dictkeys[usage_id])
|
||||
local_pubkey = json_decode(pkey.export_public())
|
||||
except Exception:
|
||||
raise self.error(
|
||||
raise self.error( # pylint: disable=raising-bad-type, #4772
|
||||
"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(
|
||||
raise self.error( # pylint: disable=raising-bad-type, #4772
|
||||
"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(
|
||||
raise self.error( # pylint: disable=raising-bad-type, #4772
|
||||
"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(
|
||||
raise self.error( # pylint: disable=raising-bad-type, #4772
|
||||
"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(
|
||||
raise self.error( # pylint: disable=raising-bad-type, #4772
|
||||
"Fetching server keys {} (usage: {}) failed.".format(
|
||||
self.server_spn, usage),
|
||||
fatal=True
|
||||
|
||||
@@ -155,7 +155,9 @@ 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")
|
||||
|
||||
@@ -34,7 +34,6 @@ 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,
|
||||
|
||||
@@ -319,7 +319,7 @@ class PortResponder(threading.Thread):
|
||||
logger.debug('%d %s: Stopped listening', port, proto)
|
||||
|
||||
def _is_closing(self):
|
||||
with self._close_lock: # pylint: disable=not-context-manager
|
||||
with self._close_lock:
|
||||
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: # pylint: disable=not-context-manager
|
||||
with self._close_lock:
|
||||
self._close = True
|
||||
|
||||
|
||||
|
||||
@@ -116,18 +116,21 @@ def parse_options():
|
||||
|
||||
if len(args):
|
||||
n = len(args) - 1
|
||||
for cmd in commands:
|
||||
for cmd, args_info in commands.items():
|
||||
if cmd == args[0]:
|
||||
v = commands[cmd]
|
||||
err = None
|
||||
if n < v[0]:
|
||||
err = v[3]
|
||||
elif n > v[1]:
|
||||
if n < args_info[0]:
|
||||
err = args_info[3]
|
||||
elif n > args_info[1]:
|
||||
err = "too many arguments"
|
||||
else:
|
||||
valid_syntax = True
|
||||
if err:
|
||||
parser.error("Invalid syntax: %s\nUsage: %s [options] %s" % (err, cmd, v[2]))
|
||||
parser.error(
|
||||
"Invalid syntax: %s\nUsage: %s [options] %s" % (
|
||||
err, cmd, args_info[2]
|
||||
)
|
||||
)
|
||||
|
||||
if not valid_syntax:
|
||||
cmdstr = " | ".join(commands.keys())
|
||||
|
||||
@@ -92,6 +92,9 @@ 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
|
||||
|
||||
@@ -80,6 +80,9 @@ 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
|
||||
|
||||
@@ -58,6 +58,7 @@ define([
|
||||
'./topology',
|
||||
'./user',
|
||||
'./vault',
|
||||
'./idp',
|
||||
'dojo/domReady!'
|
||||
],function(app_container) {
|
||||
return app_container;
|
||||
|
||||
@@ -184,12 +184,8 @@ return {
|
||||
measurement_unit: 'seconds'
|
||||
},
|
||||
{
|
||||
$type: 'radio',
|
||||
$type: 'checkbox',
|
||||
name: 'idnsallowdynupdate',
|
||||
options: [
|
||||
{ value: 'TRUE', label: '@i18n:true' },
|
||||
{ value: 'FALSE', label: '@i18n:false' }
|
||||
]
|
||||
},
|
||||
{
|
||||
$type: 'textarea',
|
||||
|
||||
@@ -136,6 +136,10 @@ return {
|
||||
{
|
||||
label: '@i18n:authtype.type_hardened',
|
||||
value: 'hardened'
|
||||
},
|
||||
{
|
||||
label: '@i18n:authtype.type_idp',
|
||||
value: 'idp'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
// 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,6 +185,7 @@ var nav = {};
|
||||
},
|
||||
{ entity: 'otptoken' },
|
||||
{ entity: 'radiusproxy' },
|
||||
{ entity: 'idp' },
|
||||
{
|
||||
entity: 'certmaprule',
|
||||
facet: 'search',
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
"path": "install/ui/js/freeipa"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-contrib-qunit": "^2.0.0",
|
||||
"grunt-qunit-junit": "^0.3.1"
|
||||
"grunt": "",
|
||||
"grunt-contrib-qunit": "",
|
||||
"grunt-qunit-junit": ""
|
||||
},
|
||||
"dependencies": {
|
||||
"dojo": "~1.16.2"
|
||||
|
||||
Loaded 100 of 344 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user