Repository: ansible-collections/community.general

Issue:
Keycloak support SSSD for User Storage Federation
### Summary

I was [Configuring a Federated SSSD Store](https://www.keycloak.org/docs/latest/server_admin/#configuring-a-federated-sssd-store) per Keycloak's documentation [SSSD and FreeIPA Identity Management Integration](https://www.keycloak.org/docs/latest/server_admin/#sssd-and-freeipa-identity-management-integration) using ansible collection [community.general](https://github.com/ansible-collections/community.general) from the main branch on Github. Currently I am able to implement this by editing [keycloak_user_federation.py](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/identity/keycloak/keycloak_user_federation.py#L768) Line 768 and adding 'sssd' to the array. However, I would not be able to use ansible's requirements.yml file for continuous deployment(s). Alot of our Red Hat customers that leverage RHSSO/Keycloak often have Red Hat IDM/IPA in their environment. It would be nice to automate this process via this collection.

### Issue Type

Feature Idea

### Component Name

[keycloak_user_federation.py](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/identity/keycloak/keycloak_user_federation.py)

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- hosts: localhost  ## assuming localhost == keycloak.example.com
  connection: local
  tasks:

  - name: Enable SSSD user storage federation
    community.general.keycloak_user_federation:
      auth_keycloak_url: http://localhost:8080/auth
      auth_realm: master
      auth_username: admin
      auth_password: password
      realm: master
      name: sssd
      state: present
      provider_id: sssd
      provider_type: org.keycloak.storage.UserStorageProvider
      #config:
      #  cachePolicy: NO_CACHE
    register: sssd_user_storage_provider
    failed_when:
      - "'No changes required to user federation' not in sssd_user_storage_provider.msg"
      #- "'has been updated' not in sssd_user_storage_provider.msg"   ## ONLY add this line when updating config.cachePolicy var
      - ('Could not create component in realm' not in sssd_user_storage_provider.msg) or
        ('HTTP Error 400{{":"}} Bad Request' in sssd_user_storage_provider.msg)
```


Current output on first run, NO CACHE POLICY
```
ok: [rhsso.jlabs.com] => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "diff": {}, 
    "end_state": {
        "config": {
            "cachePolicy": "DEFAULT", 
            "enabled": "true", 
            "priority": "0"
        }, 
        "id": "2a56db18-30f5-4662-967e-405c2895b06f", 
        "mappers": [], 
        "name": "sssd", 
        "parentId": "master", 
        "providerId": "sssd", 
        "providerType": "org.keycloak.storage.UserStorageProvider"
    }, 
    "existing": {
        "config": {
            "cachePolicy": "DEFAULT", 
            "enabled": "true", 
            "priority": "0"
        }, 
        "id": "2a56db18-30f5-4662-967e-405c2895b06f", 
        "mappers": [], 
        "name": "sssd", 
        "parentId": "master", 
        "providerId": "sssd", 
        "providerType": "org.keycloak.storage.UserStorageProvider"
    }, 
    "failed_when_result": false, 
    "invocation": {
        "module_args": {
            "auth_client_id": "admin-cli", 
            "auth_client_secret": null, 
            "auth_keycloak_url": "http://localhost:8080/auth", 
            "auth_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "auth_realm": "master", 
            "auth_username": "admin", 
            "config": null, 
            "id": null, 
            "mappers": null, 
            "name": "sssd", 
            "parent_id": null, 
            "provider_id": "sssd", 
            "provider_type": "org.keycloak.storage.UserStorageProvider", 
            "realm": "master", 
            "state": "present", 
            "token": null, 
            "validate_certs": true
        }
    }, 
    "msg": "No changes required to user federation 2a56db18-30f5-4662-967e-405c2895b06f.", 
    "proposed": {}
}
Read vars_file 'vault.yml'
META: ending play

PLAY RECAP ************************************************************************************
rhsso.jlabs.com            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

In fact this actually do enable SSSD User Store Federation dispite the output message of "No changes required"

Second run, with changing cachePolicy to NO_CACHE

```
changed: [rhsso.jlabs.com] => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "diff": {}, 
    "end_state": {
        "config": {
            "cachePolicy": "DEFAULT", 
            "enabled": "true", 
            "priority": "0"
        }, 
        "id": "2a56db18-30f5-4662-967e-405c2895b06f", 
        "mappers": [], 
        "name": "sssd", 
        "parentId": "master", 
        "providerId": "sssd", 
        "providerType": "org.keycloak.storage.UserStorageProvider"
    }, 
    "existing": {
        "config": {
            "cachePolicy": "DEFAULT", 
            "enabled": "true", 
            "priority": "0"
        }, 
        "id": "2a56db18-30f5-4662-967e-405c2895b06f", 
        "mappers": [], 
        "name": "sssd", 
        "parentId": "master", 
        "providerId": "sssd", 
        "providerType": "org.keycloak.storage.UserStorageProvider"
    }, 
    "failed_when_result": false, 
    "invocation": {
        "module_args": {
            "auth_client_id": "admin-cli", 
            "auth_client_secret": null, 
            "auth_keycloak_url": "http://localhost:8080/auth", 
            "auth_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "auth_realm": "master", 
            "auth_username": "admin", 
            "config": {
                "allowKerberosAuthentication": [
                    "false"
                ], 
                "authType": [
                    "none"
                ], 
                "batchSizeForSync": [
                    "1000"
                ], 
                "cachePolicy": [
                    "NO_CACHE"
                ], 
                "changedSyncPeriod": [
                    "-1"
                ], 
                "connectionPooling": [
                    "true"
                ], 
                "enabled": [
                    "true"
                ], 
                "fullSyncPeriod": [
                    "-1"
                ], 
                "importEnabled": [
                    "true"
                ], 
                "pagination": [
                    "true"
                ], 
                "priority": [
                    "0"
                ], 
                "searchScope": [
                    "1"
                ], 
                "startTls": [
                    "false"
                ], 
                "syncRegistrations": [
                    "false"
                ], 
                "trustEmail": [
                    "false"
                ], 
                "useKerberosForPasswordAuthentication": [
                    "false"
                ], 
                "usePasswordModifyExtendedOp": [
                    "false"
                ], 
                "useTruststoreSpi": [
                    "ldapsOnly"
                ], 
                "validatePasswordPolicy": [
                    "false"
                ]
            }, 
            "id": null, 
            "mappers": null, 
            "name": "sssd", 
            "parent_id": null, 
            "provider_id": "sssd", 
            "provider_type": "org.keycloak.storage.UserStorageProvider", 
            "realm": "master", 
            "state": "present", 
            "token": null, 
            "validate_certs": true
        }
    }, 
    "msg": "User federation 2a56db18-30f5-4662-967e-405c2895b06f has been updated", 
    "proposed": {
        "config": {
            "allowKerberosAuthentication": "false", 
            "authType": "none", 
            "batchSizeForSync": "1000", 
            "cachePolicy": "NO_CACHE", 
            "changedSyncPeriod": "-1", 
            "connectionPooling": "true", 
            "enabled": "true", 
            "fullSyncPeriod": "-1", 
            "importEnabled": "true", 
            "pagination": "true", 
            "priority": "0", 
            "searchScope": "1", 
            "startTls": "false", 
            "syncRegistrations": "false", 
            "trustEmail": "false", 
            "useKerberosForPasswordAuthentication": "false", 
            "usePasswordModifyExtendedOp": "false", 
            "useTruststoreSpi": "ldapsOnly", 
            "validatePasswordPolicy": "false"
        }
    }
}
Read vars_file 'vault.yml'
META: ending play

PLAY RECAP ************************************************************************************
rhsso.jlabs.com            : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/identity/keycloak/keycloak_user_federation.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/identity/keycloak/keycloak_user_federation.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @eikef @laurpaum @ndclt
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Just to add some more context

```bash
$ uname -a

Linux localhost 3.10.0-1160.el7.x86_64 #1 SMP Tue Aug 18 14:50:17 EDT 2020 x86_64 x86_64 x86_64 GNU/Linux
```

```bash
$cat /etc/redhat-release

Red Hat Enterprise Linux Server release 7.9 (Maipo)
```

```bash
$ yum repolist

Loaded plugins: product-id, search-disabled-repos, subscription-manager
repo id                                       repo name  
jb-eap-7.4-for-rhel-7-server-rpms/x86_64      JBoss Enterprise Application Platform 7.4
rh-sso-7.5-for-rhel-7-server-rpms/x86_64      Single Sign-On 7.5 (RHEL 7 Server) (RPMs)
rh-sso-7.2-for-rhel-7-server-rpms/x86_64      Single Sign-On 7.2 (RHEL 7 Server) (RPMs)
rhel-7-server-extras-rpms/x86_64              Red Hat Enterprise Linux 7 Server - Extras
rhel-7-server-rpms/7Server/x86_64             Red Hat Enterprise Linux 7 Server (RPMs) 
```

```bash
$ rpm -qa | grep -e jna -e sssd-dbus -e rh-sso7-libunix-dbus -e ^kernel -e rh-sso7 | sort

jna-3.5.2-8.el7.x86_64
kernel-3.10.0-1160.el7.x86_64
kernel-tools-3.10.0-1160.el7.x86_64
kernel-tools-libs-3.10.0-1160.el7.x86_64
rh-sso7-freemarker-2.3.26-1.incubating_redhat_1.1.jbcs.el7.noarch
rh-sso7-javapackages-tools-3.4.1-5.15.3.jbcs.el7.noarch
rh-sso7-keycloak-15.0.2-2.redhat_00001.1.el7sso.noarch
rh-sso7-keycloak-server-15.0.2-2.redhat_00001.1.el7sso.noarch
rh-sso7-libunix-dbus-java-0.8.0-2.jbcs.el7.x86_64
rh-sso7-liquibase-core-3.4.1-2.redhat_2.1.jbcs.el7.noarch
rh-sso7-python-javapackages-3.4.1-5.15.3.jbcs.el7.noarch
rh-sso7-runtime-1-4.el7sso.x86_64
rh-sso7-twitter4j-core-4.0.4-1.redhat_3.1.jbcs.el7.noarch
rh-sso7-zxing-3.2.1-1.redhat_4.1.jbcs.el7.noarch
rh-sso7-zxing-core-3.2.1-1.redhat_4.1.jbcs.el7.noarch
rh-sso7-zxing-javase-3.2.1-1.redhat_4.1.jbcs.el7.noarch
sssd-dbus-1.16.5-10.el7_9.10.x86_64
```

```bash
$ cat /etc/pam.d/keycloak 

  auth    required   pam_sss.so
  account required   pam_sss.so
```

```bash
$cat /etc/sssd/sssd.conf 

[domain/jlabs.com]

cache_credentials = True
krb5_store_password_if_offline = True
ipa_domain = jlabs.com
id_provider = ipa
auth_provider = ipa
access_provider = ipa
ipa_hostname = rhsso.jlabs.com
chpass_provider = ipa
dyndns_update = True
ipa_server = _srv_, idm.jlabs.com
dyndns_iface = enp0s3
ldap_tls_cacert = /etc/ipa/ca.crt
ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber
[sssd]
services = nss, sudo, pam, ssh, ifp

domains = jlabs.com
[nss]
homedir_substring = /home

[pam]

[sudo]

[autofs]

[ssh]

[pac]

[ifp]
allowed_uids = root
user_attributes = +mail, +telephoneNumber, +givenname, +sn

[secrets]

[session_recording]
```

Resolving patch:
diff --git a/plugins/modules/identity/keycloak/keycloak_user_federation.py b/plugins/modules/identity/keycloak/keycloak_user_federation.py
--- a/plugins/modules/identity/keycloak/keycloak_user_federation.py
+++ b/plugins/modules/identity/keycloak/keycloak_user_federation.py
@@ -64,6 +64,7 @@
         choices:
             - ldap
             - kerberos
+            - sssd
 
     provider_type:
         description:
@@ -83,9 +84,10 @@
     config:
         description:
             - Dict specifying the configuration options for the provider; the contents differ depending on
-              the value of I(provider_id). Examples are given below for C(ldap) and C(kerberos). It is easiest
-              to obtain valid config values by dumping an already-existing user federation configuration
-              through check-mode in the I(existing) field.
+              the value of I(provider_id). Examples are given below for C(ldap), C(kerberos) and C(sssd).
+              It is easiest to obtain valid config values by dumping an already-existing user federation
+              configuration through check-mode in the I(existing) field.
+            - The value C(sssd) has been supported since community.general 4.2.0.
         type: dict
         suboptions:
             enabled:
@@ -531,6 +533,22 @@
         allowPasswordAuthentication: false
         updateProfileFirstLogin: false
 
+  - name: Create sssd user federation
+    community.general.keycloak_user_federation:
+      auth_keycloak_url: https://keycloak.example.com/auth
+      auth_realm: master
+      auth_username: admin
+      auth_password: password
+      realm: my-realm
+      name: my-sssd
+      state: present
+      provider_id: sssd
+      provider_type: org.keycloak.storage.UserStorageProvider
+      config:
+        priority: 0
+        enabled: true
+        cachePolicy: DEFAULT
+
   - name: Delete user federation
     community.general.keycloak_user_federation:
       auth_keycloak_url: https://keycloak.example.com/auth
@@ -765,7 +783,7 @@ def main():
         realm=dict(type='str', default='master'),
         id=dict(type='str'),
         name=dict(type='str'),
-        provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos']),
+        provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos', 'sssd']),
         provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
         parent_id=dict(type='str', aliases=['parentId']),
         mappers=dict(type='list', elements='dict', options=mapper_spec),
@@ -843,8 +861,8 @@ def main():
 
     # special handling of mappers list to allow change detection
     if module.params.get('mappers') is not None:
-        if module.params['provider_id'] == 'kerberos':
-            module.fail_json(msg='Cannot configure mappers for Kerberos federations.')
+        if module.params['provider_id'] in ['kerberos', 'sssd']:
+            module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
         for change in module.params['mappers']:
             change = dict((k, v) for k, v in change.items() if change[k] is not None)
             if change.get('id') is None and change.get('name') is None:


Test patch: