diff --git a/README-permission.md b/README-permission.md
index c1cc539ad1c431b7f038f131a2112f2d1a2ea92b..950e6ff06809cea4e441556a3a1414b91c55196e 100644
--- a/README-permission.md
+++ b/README-permission.md
@@ -161,6 +161,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The permission name string. | yes
 `right` \| `ipapermright` | Rights to grant. It can be a list of one or more of `read`, `search`, `compare`, `write`, `add`, `delete`, and `all` default: `all` | no
 `attrs` | All attributes to which the permission applies. | no
diff --git a/plugins/modules/ipapermission.py b/plugins/modules/ipapermission.py
index 2dc6ab1cea590e791aec8669ddb63e6b818da73c..657d934ff7ff1f8f922c806e8f5aed7d77a79273 100644
--- a/plugins/modules/ipapermission.py
+++ b/plugins/modules/ipapermission.py
@@ -371,6 +371,10 @@ def main():
                     for _member, _member_change in check_members.items():
                         if _member_change is not None:
                             _res_list = res_find[_member]
+                            # if running in a client context, data may be
+                            # returned as a tuple instead of a list.
+                            if isinstance(_res_list, tuple):
+                                _res_list = list(_res_list)
                             _new_set = set(_res_list + _member_change)
                             if _new_set != set(_res_list):
                                 member_attrs[_member] = list(_new_set)
diff --git a/tests/permission/test_permission.yml b/tests/permission/test_permission.yml
index b4c04fcdf2e47ba2ff42dab3e015caaff4453e46..d7edc102c0def2e0c99d69ac4a95e022b067e128 100644
--- a/tests/permission/test_permission.yml
+++ b/tests/permission/test_permission.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test permission
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -9,6 +9,7 @@
   - name: Ensure testing groups are present.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ item }}"
       state: present
     with_items:
@@ -20,6 +21,7 @@
   - name: Ensure permission perm-test-1 is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - perm-test-1
       - perm-test-bindtype-test
@@ -31,6 +33,7 @@
   - name: Ensure permission perm-test-1 is present
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       object_type: host
       memberof: rbacgroup1
@@ -42,6 +45,7 @@
   - name: Ensure permission perm-test-1 is present again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       object_type: host
       memberof: rbacgroup1
@@ -53,6 +57,7 @@
   - name: Ensure permission perm-test-1 has an extra filter '(cn=*.internal.*)'
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       filter: '(cn=*.internal.*)'
       action: member
@@ -62,6 +67,7 @@
   - name: Ensure permission perm-test-1 has an extra filter '(cn=*.internal.*)', again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       filter: '(cn=*.internal.*)'
       action: member
@@ -71,6 +77,7 @@
   - name: Ensure permission perm-test-1 `right` has `write`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -80,6 +87,7 @@
   - name: Ensure permission perm-test-1 `right` has `write`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -89,6 +97,7 @@
   - name: Ensure permission perm-test-1 `right` has no `write`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -99,6 +108,7 @@
   - name: Ensure permission perm-test-1 `right` has no `write`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -109,6 +119,7 @@
   - name: Ensure permission perm-test-1 `memberof` has `rbackgroup2`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup2
       action: member
@@ -118,6 +129,7 @@
   - name: Ensure permission perm-test-1 `memberof` has `rbackgroup2`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup2
       action: member
@@ -127,6 +139,7 @@
   - name: Ensure permission perm-test-1 `memberof` item `rbackgroup1` is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup1
       action: member
@@ -137,6 +150,7 @@
   - name: Ensure permission perm-test-1 `memberof` item `rbackgroup1` is absent, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup1
       action: member
@@ -147,6 +161,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -156,6 +171,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -165,6 +181,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense and displayname
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -175,6 +192,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense and displayname again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -185,6 +203,7 @@
   - name: Ensure attr gecos is present in permission perm-test-1
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -195,6 +214,7 @@
   - name: Ensure attr gecos is present in permission perm-test-1 again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -205,6 +225,7 @@
   - name: Ensure attr gecos is absent in permission perm-test-1
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -216,6 +237,7 @@
   - name: Ensure attr gecos is absent in permission perm-test-1 again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -227,6 +249,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries"
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -238,6 +261,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries" again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -249,6 +273,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries"
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -261,6 +286,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries" again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -273,6 +299,7 @@
   - name: Ensure permission perm-test-1 has rawfilter '(objectclass=ipagroup)'
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       action: member
@@ -282,6 +309,7 @@
   - name: Ensure permission perm-test-1 has rawfilter '(objectclass=ipagroup)', again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       action: member
@@ -291,6 +319,7 @@
   - name: Ensure filter and rawfilter cannot be used together.
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       filter: '(cn=*.internal.*)'
@@ -301,6 +330,7 @@
   - name: Rename permission perm-test-1 to perm-test-renamed
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rename: perm-test-renamed
       state: renamed
@@ -310,6 +340,7 @@
   - name: Ensure permission perm-test-1 is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       state: absent
     register: result
@@ -318,6 +349,7 @@
   - name: Ensure permission perm-test-renamed is present
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-renamed
       object_type: host
       right: all
@@ -327,6 +359,7 @@
   - name: Ensure permission with bindtype 'self' is present, if IPA version >= 4.8.7
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-bindtype-test
       bindtype: self
       object_type: host
@@ -338,6 +371,7 @@
   - name: Fail to set permission perm-test-renamed bindtype to 'self', if IPA version < 4.8.7
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-bindtype-test
       bindtype: self
       object_type: host
@@ -351,6 +385,7 @@
   - name: Ensure testing permissions are absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - perm-test-1
       - perm-test-bindtype-test
@@ -360,6 +395,7 @@
   - name: Ensure testing groups are absent.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ item }}"
       state: absent
     with_items:
diff --git a/tests/permission/test_permission_client_context.yml b/tests/permission/test_permission_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b351c08651f2edbcdb4ed707d2c8b67d610229c9
--- /dev/null
+++ b/tests/permission/test_permission_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test permission
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test permission using client context, in client host.
+  import_playbook: test_permission.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test permission using client context, in server host.
+  import_playbook: test_permission.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']