Chapter 35. Using Ansible to delegate authentication for IdM users to external identity providers

You can use the idp ansible-freeipa module to associate users with external identity providers (IdP) that support the OAuth 2 device authorization flow. If an IdP reference and an associated IdP user ID exist, you can use them to enable IdP authentication for an IdM user with the user ansible-freeipa module.

Afterward, if these users authenticate with the SSSD version available in RHEL 9.1 or later, they receive RHEL Identity Management (IdM) single sign-on capabilities with Kerberos tickets after performing authentication and authorization at the external IdP.

35.1. The benefits of connecting IdM to an external IdP

As an administrator, you might want to allow users stored in an external identity source, such as a cloud services provider, to access RHEL systems joined to your Identity Management (IdM) environment. To achieve this, you can delegate the authentication and authorization process of issuing Kerberos tickets for these users to that external entity.

You can use this feature to expand IdM’s capabilities and allow users stored in external identity providers (IdPs) to access Linux systems managed by IdM.

35.2. How IdM incorporates logins via external IdPs

SSSD 2.7.0 contains the sssd-idp package, which implements the idp Kerberos pre-authentication method. This authentication method follows the OAuth 2.0 Device Authorization Grant flow to delegate authorization decisions to external IdPs:

  1. An IdM client user initiates OAuth 2.0 Device Authorization Grant flow, for example, by attempting to retrieve a Kerberos TGT with the kinit utility at the command line.
  2. A special code and website link are sent from the Authorization Server to the IdM KDC backend.
  3. The IdM client displays the link and the code to the user. In this example, the IdM client outputs the link and code on the command line.
  4. The user opens the website link in a browser, which can be on another host, a mobile phone, and so on:

    1. The user enters the special code.
    2. If necessary, the user logs in to the OAuth 2.0-based IdP.
    3. The user is prompted to authorize the client to access information.
  5. The user confirms access at the original device prompt. In this example, the user hits the Enter key at the command line.
  6. The IdM KDC backend polls the OAuth 2.0 Authorization Server for access to user information.

What is supported:

  • Logging in remotely via SSH with the keyboard-interactive authentication method enabled, which allows calling Pluggable Authentication Module (PAM) libraries.
  • Logging in locally with the console via the logind service.
  • Retrieving a Kerberos ticket-granting ticket (TGT) with the kinit utility.

What is currently not supported:

  • Logging in to the IdM WebUI directly. To log in to the IdM WebUI, you must first acquire a Kerberos ticket.
  • Logging in to Cockpit WebUI directly. To log in to the Cockpit WebUI, you must first acquire a Kerberos ticket.

35.3. Using Ansible to create a reference to an external identity provider

To connect external identity providers (IdPs) to your Identity Management (IdM) environment, create IdP references in IdM. Complete this procedure to use the idp ansible-freeipa module to configure a reference to the github external IdP.

Prerequisites

  • You have registered IdM as an OAuth application to your external IdP, and generated a client ID and client secret on the device that an IdM user will be using to authenticate to IdM. The example assumes that:

    • my_github_account_name is the github user whose account the IdM user will be using to authenticate to IdM.
    • The client ID is 2efe1acffe9e8ab869f4.
    • The client secret is 656a5228abc5f9545c85fa626aecbf69312d398c.
  • Your IdM servers are using RHEL 9.1 or later.
  • Your IdM servers are using SSSD 2.7.0 or later.
  • You have configured your Ansible control node to meet the following requirements:

    • You are using Ansible version 2.14 or later.
    • You have installed the ansible-freeipa package on the Ansible controller.
    • You are using RHEL 9.4 or later.
    • The example assumes that in the ~/MyPlaybooks/ directory, you have created an Ansible inventory file with the fully-qualified domain name (FQDN) of the IdM server.
    • The example assumes that the secret.yml Ansible vault stores your ipaadmin_password.

Procedure

  1. On your Ansible control node, create an configure-external-idp-reference.yml playbook:

    ---
    - name: Configure external IdP
      hosts: ipaserver
      become: false
      gather_facts: false
    
      tasks:
      - name: Ensure a reference to github external provider is available
        ipaidp:
          ipaadmin_password: "{{ ipaadmin_password }}"
          name: github_idp
          provider: github
          client_ID: 2efe1acffe9e8ab869f4
          secret: 656a5228abc5f9545c85fa626aecbf69312d398c
          idp_user_id: my_github_account_name
  2. Save the file.
  3. Run the Ansible playbook. Specify the playbook file, the file storing the password protecting the secret.yml file, and the inventory file:

    $ ansible-playbook --vault-password-file=password_file -v -i inventory configure-external-idp-reference.yml

Verification

  • On an IdM client, verify that the output of the ipa idp-show command shows the IdP reference you have created.

    [idmuser@idmclient ~]$ ipa idp-show github_idp

Additional resources

  • The idp ansible-freeipa upstream documentation

35.4. Using Ansible to enable an IdM user to authenticate via an external IdP

You can use the user ansible-freeipa module to enable an Identity Management (IdM) user to authenticate via an external identity provider (IdP). To do that, associate the external IdP reference you have previously created with the IdM user account. Complete this procedure to use Ansible to associate an external IdP reference named github_idp with the IdM user named idm-user-with-external-idp. As a result of the procedure, the user is able to use the my_github_account_name github identity to authenticate as idm-user-with-external-idp to IdM.

Prerequisites

  • Your IdM client and IdM servers are using RHEL 9.1 or later.
  • Your IdM client and IdM servers are using SSSD 2.7.0 or later.
  • You have created a reference to an external IdP in IdM. See Using Ansible to create a reference to an external identity provider.
  • You have configured your Ansible control node to meet the following requirements:

    • You are using Ansible version 2.14 or later.
    • You have installed the ansible-freeipa package on the Ansible controller.
    • You are using RHEL 9.4 or later.
    • The example assumes that in the ~/MyPlaybooks/ directory, you have created an Ansible inventory file with the fully-qualified domain name (FQDN) of the IdM server.
    • The example assumes that the secret.yml Ansible vault stores your ipaadmin_password.

Procedure

  1. On your Ansible control node, create an enable-user-to-authenticate-via-external-idp.yml playbook:

    ---
    - name: Ensure an IdM user uses an external IdP to authenticate to IdM
      hosts: ipaserver
      become: false
      gather_facts: false
    
      tasks:
      - name: Retrieve Github user ID
        ansible.builtin.uri:
          url: “https://api.github.com/users/my_github_account_name”
          method: GET
          headers:
            Accept: “application/vnd.github.v3+json”
        register: user_data
    
      - name: Ensure IdM user exists with an external IdP authentication
        ipauser:
          ipaadmin_password: "{{ ipaadmin_password }}"
          name: idm-user-with-external-idp
          first: Example
          last: User
          userauthtype: idp
          idp: github_idp
          idp_user_id: my_github_account_name
  2. Save the file.
  3. Run the Ansible playbook. Specify the playbook file, the file storing the password protecting the secret.yml file, and the inventory file:

    $ ansible-playbook --vault-password-file=password_file -v -i inventory enable-user-to-authenticate-via-external-idp.yml

Verification

  • Log in to an IdM client and verify that the output of the ipa user-show command for the idm-user-with-external-idp user displays references to the IdP:

    $ ipa user-show idm-user-with-external-idp
    User login: idm-user-with-external-idp
    First name: Example
    Last name: User
    Home directory: /home/idm-user-with-external-idp
    Login shell: /bin/sh
    Principal name: idm-user-with-external-idp@idm.example.com
    Principal alias: idm-user-with-external-idp@idm.example.com
    Email address: idm-user-with-external-idp@idm.example.com
    ID: 35000003
    GID: 35000003
    User authentication types: idp
    External IdP configuration: github
    External IdP user identifier: idm-user-with-external-idp@idm.example.com
    Account disabled: False
    Password: False
    Member of groups: ipausers
    Kerberos keys available: False

Additional resources

  • The idp ansible-freeipa upstream documentation

35.5. Retrieving an IdM ticket-granting ticket as an external IdP user

If you have delegated authentication for an Identity Management (IdM) user to an external identity provider (IdP), the IdM user can request a Kerberos ticket-granting ticket (TGT) by authenticating to the external IdP.

Complete this procedure to:

  1. Retrieve and store an anonymous Kerberos ticket locally.
  2. Request the TGT for the idm-user-with-external-idp user by using kinit with the -T option to enable Flexible Authentication via Secure Tunneling (FAST) channel to provide a secure connection between the Kerberos client and Kerberos Distribution Center (KDC).

Prerequisites

Procedure

  1. Use Anonymous PKINIT to obtain a Kerberos ticket and store it in a file named ./fast.ccache.

    $ kinit -n -c ./fast.ccache
  2. [Optional] View the retrieved ticket:

    $ *klist -c fast.ccache *
    Ticket cache: FILE:fast.ccache
    Default principal: WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS
    
    Valid starting       Expires              Service principal
    03/03/2024 13:36:37  03/04/2024 13:14:28  krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM
  3. Begin authenticating as the IdM user, using the -T option to enable the FAST communication channel.

    [root@client ~]# kinit -T ./fast.ccache idm-user-with-external-idp
    Authenticate at https://oauth2.idp.com:8443/auth/realms/master/device?user_code=YHMQ-XKTL and press ENTER.:
  4. In a browser, authenticate as the user at the website provided in the command output.
  5. At the command line, press the Enter key to finish the authentication process.

Verification

  • Display your Kerberos ticket information and confirm that the line config: pa_type shows 152 for pre-authentication with an external IdP.

    [root@client ~]# klist -C
    Ticket cache: KCM:0:58420
    Default principal: idm-user-with-external-idp@IDM.EXAMPLE.COM
    
    Valid starting     Expires            Service principal
    05/09/22 07:48:23  05/10/22 07:03:07  krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM
    config: fast_avail(krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM) = yes
    08/17/2022 20:22:45  08/18/2022 20:22:43  krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM
    config: pa_type(krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM) = 152

    The pa_type = 152 indicates external IdP authentication.

35.6. Logging in to an IdM client via SSH as an external IdP user

To log in to an IdM client via SSH as an external identity provider (IdP) user, begin the login process on the command linel. When prompted, perform the authentication process at the website associated with the IdP, and finish the process at the Identity Management (IdM) client.

Prerequisites

Procedure

  1. Attempt to log in to the IdM client via SSH.

    [user@client ~]$ ssh idm-user-with-external-idp@client.idm.example.com
    (idm-user-with-external-idp@client.idm.example.com) Authenticate at https://oauth2.idp.com:8443/auth/realms/main/device?user_code=XYFL-ROYR and press ENTER.
  2. In a browser, authenticate as the user at the website provided in the command output.
  3. At the command line, press the Enter key to finish the authentication process.

Verification

  • Display your Kerberos ticket information and confirm that the line config: pa_type shows 152 for pre-authentication with an external IdP.

    [idm-user-with-external-idp@client ~]$ klist -C
    Ticket cache: KCM:0:58420
    Default principal: idm-user-with-external-idp@IDM.EXAMPLE.COM
    
    Valid starting     Expires            Service principal
    05/09/22 07:48:23  05/10/22 07:03:07  krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM
    config: fast_avail(krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM) = yes
    08/17/2022 20:22:45  08/18/2022 20:22:43  krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM
    config: pa_type(krbtgt/IDM.EXAMPLE.COM@IDM.EXAMPLE.COM) = 152

35.7. The provider option in the ipaidp Ansible module

The following identity providers (IdPs) support OAuth 2.0 device authorization grant flow:

  • Microsoft Identity Platform, including Azure AD
  • Google
  • GitHub
  • Keycloak, including Red Hat Single Sign-On (SSO)
  • Okta

When using the idp ansible-freeipa module to create a reference to one of these external IdPs, you can specify the IdP type with the provider option in your ipaidp ansible-freeipa playbook task, which expands into additional options as described below:

provider: microsoft

Microsoft Azure IdPs allow parametrization based on the Azure tenant ID, which you can specify with the organization option. If you need support for the live.com IdP, specify the option organization common.

Choosing provider: microsoft expands to use the following options. The value of the organization option replaces the string ${ipaidporg} in the table.

OptionValue

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

provider: google

Choosing provider: google expands to use the following options:

OptionValue

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

provider: github

Choosing provider: github expands to use the following options:

OptionValue

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

provider: keycloak

With Keycloak, you can define multiple realms or organizations. Since it is often a part of a custom deployment, both base URL and realm ID are required, and you can specify them with the base_url and organization options in your ipaidp playbook task:

---
- name: Playbook to manage IPA idp
  hosts: ipaserver
  become: false

  tasks:
  - name: Ensure keycloak idp my-keycloak-idp is present using provider
    ipaidp:
      ipaadmin_password: "{{ ipaadmin_password }}"
      name: my-keycloak-idp
      provider: keycloak
      organization: main
      base_url: keycloak.domain.com:8443/auth
      client_id: my-keycloak-client-id

Choosing provider: keycloak expands to use the following options. The value you specify in the base_url option replaces the string ${ipaidpbaseurl} in the table, and the value you specify for the organization `option replaces the string `${ipaidporg}.

OptionValue

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

provider: okta

After registering a new organization in Okta, a new base URL is associated with it. You can specify this base URL with the base_url option in the ipaidp playbook task:

---
- name: Playbook to manage IPA idp
  hosts: ipaserver
  become: false

  tasks:
  - name: Ensure okta idp my-okta-idp is present using provider
    ipaidp:
      ipaadmin_password: "{{ ipaadmin_password }}"
      name: my-okta-idp
      provider: okta
      base_url: dev-12345.okta.com
      client_id: my-okta-client-id

Choosing provider: okta expands to use the following options. The value you specify for the base_url option replaces the string ${ipaidpbaseurl} in the table.

OptionValue

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

Additional resources