Repository: ansible-collections/community.general

Issue:
ipa module utils can not handle HA FreeIPA with Python3
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
module_utils/ipa.py does not handle multiple cookie objects in http request if ansible is used with Python3.6 and 

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
ipa_hostgroup ipa identity

##### ANSIBLE VERSION
```bash
ansible 2.9.9
  config file = /home/OWNUSER/ansible/ansible.cfg
  configured module search path = ['/home/OWNUSER/ansible/OWNFOLDER/module']
  ansible python module location = /var/lib/ansible-py/lib64/python3.6/site-packages/ansible
  executable location = /var/lib/ansible-py/bin/ansible
  python version = 3.6.8 (default, Apr  2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
Using /home/OWNUSER/ansible/ansible.cfg as config file
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```bash
ANSIBLE_PIPELINING(/home/OWNUSER/ansible/ansible.cfg) = True
ANSIBLE_SSH_RETRIES(/home/OWNUSER/ansible/ansible.cfg) = 3
COLLECTIONS_PATHS(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/galaxy_collections']
DEFAULT_ACTION_PLUGIN_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/action']
DEFAULT_BECOME(/home/OWNUSER/ansible/ansible.cfg) = True
DEFAULT_BECOME_METHOD(/home/OWNUSER/ansible/ansible.cfg) = sudo
DEFAULT_BECOME_USER(/home/OWNUSER/ansible/ansible.cfg) = root
DEFAULT_CALLBACK_PLUGIN_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/callback']
DEFAULT_CALLBACK_WHITELIST(/home/OWNUSER/ansible/ansible.cfg) = ['timer', 'profile_tasks', 'rocketchat']
DEFAULT_FORCE_HANDLERS(/home/OWNUSER/ansible/ansible.cfg) = True
DEFAULT_FORKS(/home/OWNUSER/ansible/ansible.cfg) = 100
DEFAULT_GATHERING(/home/OWNUSER/ansible/ansible.cfg) = explicit
DEFAULT_HOST_LIST(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/inventories/erfi.yml']
DEFAULT_INVENTORY_PLUGIN_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/inventory']
DEFAULT_LOOKUP_PLUGIN_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/lookup']
DEFAULT_MODULE_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/module']
DEFAULT_MODULE_UTILS_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/erfiansible/module_utils']
DEFAULT_PRIVATE_KEY_FILE(/home/OWNUSER/ansible/ansible.cfg) = /Users/otp00234228/.ssh/ansible_rsa
DEFAULT_REMOTE_USER(/home/OWNUSER/ansible/ansible.cfg) = ansible
DEFAULT_ROLES_PATH(/home/OWNUSER/ansible/ansible.cfg) = ['/home/OWNUSER/ansible/galaxy_roles', '/home/OWNUSER/ansible/roles', '/home/OWNUSER/ansible/roles/kubernetes']
DEFAULT_STRATEGY(/home/OWNUSER/ansible/ansible.cfg) = linear
DEFAULT_TIMEOUT(/home/OWNUSER/ansible/ansible.cfg) = 40
DEFAULT_VERBOSITY(/home/OWNUSER/ansible/ansible.cfg) = 3
HOST_KEY_CHECKING(/home/OWNUSER/ansible/ansible.cfg) = False
INVENTORY_ENABLED(/home/OWNUSER/ansible/ansible.cfg) = ['erfi', 'script']
INVENTORY_UNPARSED_IS_FAILED(/home/OWNUSER/ansible/ansible.cfg) = True
SHOW_CUSTOM_STATS(/home/OWNUSER/ansible/ansible.cfg) = True
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->


##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
Setup multi node freeipa with a Load balancer in front of them.
Example config snippet for HAProxy:
```
frontend https
bind   10.0.0.100:443  tfo  ssl  crt /etc/pki/tls/certs/haproxy/example.com.pem
mode                       http
option                     forwardfor except 127.0.0.0/8
option                     http-keep-alive
timeout http-request       10s
timeout http-keep-alive    10s
http-response set-header   Strict-Transport-Security max-age=1576800
http-response set-header   X-Frame-Options sameorigin
http-response set-header   X-Forwarded-Proto https
http-response set-header   Cache-Control no-cache,\ max-age=600
http-response set-header   X-XSS-Protection 1
http-response set-header   X-Content-Type-Options nosniff
capture request header     x-request-id len 128
capture request header     x-transaction-id len 64
capture response header    x-request-id len 128
capture response header    x-transaction-id len 64
option  httplog
acl is_root path -i /
acl host_ipa hdr_dom(host) -i ipa
redirect code 301 location https://ipa.example.com/ipa/ui if is_root host_ipa
use_backend ipa_https if host_ipa


backend ipa_https
mode   http
balance  roundrobin
cookie   SERVERID  insert  indirect  nocache  secure  httponly
http-request set-header SERVERID %{+Q}[req.cook(SERVERID)]
http-request add-header  X-Forwarded-Proto https
option  http-keep-alive
http-reuse  always
timeout  http-request 10s
timeout  http-keep-alive 10s
redirect  scheme https   if !{ ssl_fc }
cookie SERVERID insert indirect nocache httponly secure
acl hdr_ipa01 req.cook(SERVERID) -m sub 10.0.0.101
acl hdr_ipa02 req.cook(SERVERID) -m sub 10.0.0.102
http-request replace-header Referer ^https://.*\.example\.com(.*)$ https://ipa01\.\example\.com\1 if hdr_ipa01
http-request replace-header Referer ^https://.*\.example\.com(.*)$ https://ipa02\.example\.com\1 if hdr_ipa02
acl hdr_set_cookie_dom_ipa01 res.hdr(Set-cookie) -m sub Domain=ipa01.example.com
http-response replace-header Set-Cookie Domain=ipa01.example.com(.*) Domain=ipa.example.com\1 if hdr_set_cookie_dom_ipa02
acl hdr_set_cookie_dom_ipa02 res.hdr(Set-cookie) -m sub Domain=ipa02.example.com
http-response replace-header Set-Cookie Domain=ipa01.example.com(.*) Domain=ipa.example.com\1 if hdr_set_cookie_dom_ipa02
server   10.0.0.101  10.0.0.101:443  check  cookie 10.0.0.101  ssl  check  verify none
server   10.0.0.102  10.0.0.102:443  check  cookie 10.0.0.102  ssl  check  verify none
```
<!--- Paste example playbooks or commands between quotes below -->
```yaml

- name: Manage IPA Hostgroup
  hosts: all
  tasks:
  - name: Add hostgroup
    become: no
    run_once: true
    ipa_hostgroup:
      name: "{{ hostgroup }}"
      ipa_host: "{{ ipa_server }}"
      ipa_pass: "{{ ipa_password }}"
      ipa_user: "{{ ipa_username }}"
      state: present
    delegate_to: localhost

```

##### EXPECTED RESULTS
Success


##### ACTUAL RESULTS
```
fatal: [localhost -> localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "cn": "test",
            "description": null,
            "host": null,
            "hostgroup": null,
            "ipa_host": "ipa.example.com",
            "ipa_pass": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "ipa_port": 443,
            "ipa_prot": "https",
            "ipa_timeout": 10,
            "ipa_user": "user",
            "name": "test",
            "state": "present",
            "validate_certs": true
        }
    },
    "msg": "hostgroup_find: HTTP Error 401: Unauthorized"
}
```


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

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Akasurde @Nosmoht @fxfitz
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/module_utils/ipa.py b/plugins/module_utils/ipa.py
--- a/plugins/module_utils/ipa.py
+++ b/plugins/module_utils/ipa.py
@@ -83,7 +83,7 @@ def login(self, username, password):
                 if status_code not in [200, 201, 204]:
                     self._fail('login', info['msg'])
 
-                self.headers = {'Cookie': resp.info().get('Set-Cookie')}
+                self.headers = {'Cookie': info.get('set-cookie')}
             except Exception as e:
                 self._fail('login', to_native(e))
         if not self.headers:


Test patch: