Unit 12: Authentication against external Identity Providers#
Prerequisites:
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 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:
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 reference.
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 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 totrue
, 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](keycloak/keycloak#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 reference "keycloak"
-----------------------------------------
Identity Provider reference 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 toipa user-add
oripa user-mod
)IdP identity for the user is set in the user entry (
--idp-user-id
option toipa user-add
oripa user-mod
)finally, user should be allowed to use
idp
user authentication method (--user-auth-type=idp
option toipa user-add
oripa user-mod
oridp
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
toollogin 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 browserlogin 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.